From a2770f36b6c03f00978c0f266531028094eaff44 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Wed, 14 Feb 2024 10:40:52 -0500 Subject: [PATCH 01/40] Squashed 'evm_arithmetization/' changes from d547f8d5..016b000e 016b000e FMT 155b6741 Merge remote-tracking branch 'upstream/main' into tmp-cancun 710225c9 Simulate jumpdest data with the interpreter (#1489) 3ec1bfdd Update `starky` and leverage it as dependency for `plonky2_evm` (#1503) b6fec06c Fix nightly build (ahash issue) (#1524) 605af817 Add blob_basefee to challenger transcript (#1522) ce220255 Merge pull request #1521 from 0xPolygonZero/cancun-no-ff ac656507 Merge branch 'main' into feat/cancun b600142c Cleanup `alloc` / `std` imports for plonky2 (#1518) 6b39fc90 Remove risk of panics in interpreter (#1519) d2598bde Revert "Remove StarkProofWithMetadata (#1497)" (#1502) 1a08e783 Fix no-std tests and add corresponding jobs in the CI (#1501) 06444eaa Switch permutation argument for logUp in `starky` (#1496) fb8452de Merge pull request #1500 from topos-protocol/ci 246c2b62 Fix workflow 8f919133 Fix nightly version 212f29cf Add missing constraints mentioned by auditors (#1499) af0259c5 Remove StarkProofWithMetadata (#1497) f3f7433c Some cleanup (#1498) 63579636 Reorganize lookup / ctl modules (#1495) e502a0df Make CTLs more generic (#1493) 1dc22b76 Fix BaseSumGenerator and BaseSplitGenerator Ids (#1494) f76245e2 Cleanup imports (#1492) a9060e61 Add LA audit report 36e62a13 Use usize::BITS and wrapping_shr in reverse_index_bits_in_place_small (#1478) 14bb5bdb Use web_time crate instead of std::time (#1481) eff7cc0f Fix circuit sizes (#1484) ca2e56e2 Fix bugs in jumpdest analysis (#1474) c0700065 Improve `BIGNUM` operations (#1482) acc59c35 Speed-up `bn254` pairing operation (#1476) ae0907ff Merge pull request #1462 from 0xPolygonZero/pg/evm-licensing 8cb80e09 Improve `blake2f` call (#1477) 319fc6a2 Improve SHA2 precompile (#1480) 8e1969db Fix interpreter jumps (#1471) b8a16b39 Fix typos (#1479) 265d46a9 chore: update ci workflow (#1475) 39a2d62d Fix touched_addresses removal (#1473) 10fc9660 Remove some more CPU cycles (#1472) 990eb34d Remove some CPU cycles (#1469) 30b47998 Fix simulation for jumpdest analysis (#1467) 5c1ec524 proofreading (#1466) c4319dce fix: make add_generators public (#1463) bb48cabd Add math rendering with Katex (#1459) 70483050 Fix fill_gaps (#1465) c2a73ad8 Fix clippy (#1464) 219365d6 Packed rlp prover inputs (#1460) ccd4ff87 Update Cargo.toml de2709d8 Update README.md 7f5fae84 Add files via upload b119e96f Merge pull request #1461 from 0xPolygonZero/eth_trie_utils_bug_fix ab05d181 Bumped `eth_trie_utils` f80ebe77 Remove full memory channel (#1450) a78a29a6 Merge pull request #1423 from topos-protocol/jumpdest_nd b05e84dd Interpreter GenerationInputs (#1455) 3b1ed824 Merge pull request #1447 from topos-protocol/plonky2_doc cda30847 Apply review ac9f704f Fix comment f9c3ad66 Update empty_txn_list fdedf3e3 Merge remote-tracking branch 'public/main' into jumpdest_nd bead1d60 Adress review comments 233ddd4e Constrain syscall/exceptions filter to be boolean (#1458) 6ef0a3c7 Apply suggestions from code review 606732a8 Free up some CPU cycles (#1457) 99a1eb5c Missing review comments ae4a720a Address comments 92aaa404 Apply suggestions from code review 0bf9cd2f Use current context in ecrecover (#1456) c329b368 chore(evm,field,plonky2):fix typos (#1454) 85524cfa Intra doc link 33def084 Merge branch 'main' into plonky2_doc aedfe5df Implement CTL bundling (#1439) c8430dac Add Boolean constraints for `ArithmeticStark` (#1453) 54a13135 Improve some calls to `%mstore_rlp` (#1452) 1715573c Fix problems after address bundling 95c83add Merge pull request #1399 from topos-protocol/refactor_encode_funcs 5b71eb4e Address review comments 3c699be7 Merge remote-tracking branch 'public/main' into refactor_encode_funcs 22e267c3 Address bundling bd02117c Fix `after_mpt_delete_extension_branch` (#1449) f4be34dc Some more dcbfef6d chore: fix typos (#1451) 82804e42 Add some more + module doc 80917cbe Merge remote-tracking branch 'public/main' into refactor_encode_funcs 7fc6b86d Minor 77f51095 Adress reviewer comments cb19f219 Add crate-level documentation (#1444) 47b42856 Remove unused macro 2dacbfe2 Address bundling (#1426) 3e61f06a Remove gas check in sys_stop (#1448) ed2e1bc7 Add comment 3c8b150f Rustdoc 20db596e Add some more explicit doc on plonky2 crate 18a14bf2 Remove assertion 897ba585 Remove assertion in packed verif 1c994737 Address comments f4713c44 Apply suggestions from code review 247d655b Minor 8f1efa15 Fix minor error ab4508fc Add packed verification f46cf4ef Prevent some lints from being allowed (#1443) 6cf4df7d Add initial constraint z polynomial (#1440) ef07eabf Pacify latest clippy (#1442) 1a95f7aa Clippy 9c573a07 Restore simple_transfer and Clippy a85f9872 Fix bug in jumpdest proof generation and check that jumpdest addr < code_len 48b9769e Remove duplicated label a8340496 Rebase to main ae3003a9 Add alternative method to prove txs without pre-loaded table circuits (#1438) c3d707c1 Constrain partial_channel (#1436) dfcf276d Refactor encode_empty_node and encode_branch_node cb3f91a0 add Debug trait to PartitionWitness to enable trace information output (#1437) 24ae0d9d Clippy 3e78865d Remove aborts for invalid jumps and Rebase 0ae56db0 Reabse to main 11d668f5 Remove aborts for invalid jumps 4e569484 Improve proof generation c4025063 Clippy aaa38b33 Fix fmt 08982498 Remove U256::as_u8 in comment 5acabad7 Eliminate nested simulations 0bec6278 Apply suggestions from code review ed260980 Fix jumpdest analisys test ff3dc2e5 Refactor run_next_jumpdest_table_proof 9e39d88a Rebase to main 6ababc96 Remove aborts for invalid jumps 7eff4e27 Constrain first offset of a segment (#1397) 829ae64f Improve proof generation a291d92c Merge pull request #1392 from 0xPolygonZero/dp-from_values-take-ref 7cb04884 Minor cleanup (#1435) 096c7456 Constrain new top to loaded value in MLOAD_GENERAL (#1434) 18e08f4f Filter range checks (#1433) f67ee258 Add exceptions handling to the interpreter (#1393) 536cd1c8 Regenerate tries upon Kernel failure during `hash_final_tries` (#1424) ee91b67c Merge pull request #1432 from 0xPolygonZero/discord_badge 0b56ab75 Added a Discord badge to `README.md` f8f6b07a Change context to current context for BN precompiles (#1428) 68b9f0ad Add ERC721 test (#1425) a64311cf Add aborting signal (#1429) 77f1cd34 Clippy 5a0c1ad8 Fix fmt ad8c2df8 Remove U256::as_u8 in comment 81f13f3f Eliminate nested simulations fdd7ee46 fix: make `from_noncanonical_biguint` work for zero (#1427) 2c5347c4 Apply suggestions from code review 71dff6e9 Constrain MSTORE_32BYTES new offset limbs (#1415) 746e1344 Fix jumpdest analisys test f76ab777 Refactor run_next_jumpdest_table_proof 3e8ad086 Rebase to main bc1a3c48 Merge `push` and `prover_input` flags (#1417) 51ff8c5b Merge pull request #1420 from dzizazda/main 837434cf Fix a minor typo in evm/spec/cpulogic.tex 4e4e61c2 typo fix 00ed16fc minor typo fix 724437d0 typo fix 942e43ab typo fix bfcfcdb4 Add `Checkpoint` heights (#1418) 5607faf3 Check that limbs after the length are 0 (#1419) 3195c205 Merge MSTORE_32BYTES and MLOAD_32BYTES columns (#1414) 56e83956 Merge pull request #1416 from AdventureSeeker987/main 43ecf1df chore: fix some comment typos 7efd147e Use mstore_32bytes to optimize decode_int_given_len (#1413) edfc86c3 Remove is_keccak_sponge (#1410) 170ce5f2 Preinitialize all code segments (#1409) a90aa40b Implement MPT preinitialization (#1406) 4ba7718e Optimize asserts (#1411) 47e24306 Remove GenerationOutputs (#1408) 46b6aa10 Implement degree 2 filters (#1404) 2d36559d Make some functions const (#1407) 7ac6bf2c Implement `PublicValues` retrieval from public inputs (#1405) 6c3e3c0e Use logUp for CTLs (#1398) cb2a22a5 Update stack op cost (#1402) d28ba240 Pacify clippy (#1403) d682769b Fix set_context constraints (#1401) 2d0df393 Merge pull request #1391 from succinctlabs/chris/recursion 32d00967 Fix kernel codehash discrepancy (#1400) 5572da30 Remove intermediary block bloom filters (#1395) 30c944f7 Remove bootstrapping (#1390) e68195fc chore: Remove TODOs about `from_values` taking a reference 37918ccc Revert "chore: from_values takes ref" 471ff68d Optimize `num_bytes` and `hex_prefix_rlp` (#1384) 7cc123e0 chore: from_values takes ref b598e6ff VerifierCircuitData Clone,PartialEq,Eq 64cc1000 Move stack_len_bounds_aux to general columns (#1360) 96f3faf2 Changes in interpreter and implement interpreter version for add11 (#1359) 6dd2e313 Add upgradeability to `AllRecursiveCircuits` and output verifier data (#1387) 3440ba94 Remove extra rows in BytePackingStark (#1388) 2a6065b4 comment ab70bc53 Fix run_syscall in interpreter. (#1351) 2039e18f Fix genesis block number in `prove_block` (#1382) acd3b1ad Merge pull request #1383 from topos-protocol/mpt_specs 110a8eff Update evm/spec/mpts.tex ee450d6d Remove redundant sect about MPT 36e31c53 Address comment 08e0afe4 Fix typo in evm/spec/mpts.tex c7951fcc Update evm/spec/mpts.tex 12b522cb Update evm/spec/mpts.tex 98bed16a Update evm/spec/mpts.tex 0c0818c7 Update evm/spec/mpts.tex c6178a45 Update evm/spec/mpts.tex a3b5e13e Update evm/spec/mpts.tex 3af316f3 Add MPT specs d4b05f37 Add specs for stack handling (#1381) a7364586 Backporting gas handling to the specs (#1379) 2d5a84a1 Add specs for the CPU table (#1375) bec1073c Explain difference between simple opcodes and syscalls (#1378) 48e2b24b Add range check constraints for the looked table (#1380) 8d473168 Add specs for BytePackingStark (#1373) fe311c7f Check is_kernel_mode when halting (#1369) 98b5e5be Initialize blockhashes (#1370) 06933b1d Starting the specs for the CPU logic (#1377) 1f65a8a8 Add exceptions to specs (#1372) 0c4d9a8e CTL and range-check documentation (#1368) f1be8409 Update README.md (#1371) 7a50292a Update README.md f2b75fa5 Update README.md edeb2c76 Merge pull request #1367 from 0xPolygonZero/avm-readme 29762c85 Update README.md d98305b3 Create README.md 398b15c1 wip 79c6de14 Update Keccak-f specs. (#1365) b44fc0d6 Add specs for KeccakSponge (#1366) b9328815 Reduce visibility (#1364) ddecf8bd Update specs for Logic and Arithmetic Tables (#1363) f879d925 Add doc for privileged instructions (#1355) 2c951de4 Update Memory in specs (#1362) 24aa9668 Revert "Make gas fit in 2 limbs (#1261)" (#1361) 40d3c6dd Merge pull request #1294 from matthiasgoergens/matthias/make-clippy-happy eda7fd65 Constrain is_keccak_sponge (#1357) 4dc42c51 Merge public values inside prove_aggreg (#1358) 0e63e661 Implement out of gas exception (#1328) 01f229a8 Add push constraints (#1352) a0876d73 Refactor JUMPDEST analysis (#1347) 6f2b3349 Fix parsing of non-legacy receipts (#1356) f7d5e7c4 Fix MSTORE_32BYTES in interpreter (#1354) 6d751b13 Remove values of last memory channel (#1291) 75ae0eb5 Merge pull request #1346 from 0xPolygonZero/dp-unwrap-hunting 88fcc329 Reduce visibility for a bunch of structs and methods in EVM crate (#1289) 5800e6ad Add run_syscall and tests for sload and sstore (#1344) cc0cdd07 Remove unnecessary code duplication (#1349) 3810bd1a Charge gas for native instructions in interpreter (#1348) 5c41dc4d Range-check keccak sponge inputs to bytes (#1342) d2b5882a Root out some unwraps ec41b754 Fix ranges in AllRecursiveCircuits initialization for log_opcode aggregation test (#1345) 01bbf1a0 Constrain clock (#1343) 954d1a77 Remove logic for multiple txns at once (#1341) 5d5628b5 Move empty_check inside final iteration 605932d1 restore `no-std` support (#1335) e41435e9 Add memory checks for prover_input, as well as range_checks for prover_input, syscalls/exceptions (#1168) 1d8a8416 Implement EIP-6780 (new SELFDESTRUCT) (#1327) fa93454c Add withdrawals (#1322) 19178072 Remove `len` column in `KeccakSpongeStark` (#1334) 099994ab Add test for ERC20 transfer (#1331) d8f6de25 Merge pull request #1329 from shuoer86/main d941539b Fix typos in comments a0ea26f4 Fix typos in comments d2b549af Constrain uninitialized memory to 0 (#1318) f71f227d Add test for selfdestruct (#1321) 1e8ed78f Merge pull request #1320 from topos-protocol/fix-combine-keccak-jumpdest 85b38bec Fix merging of jumpdest and keccak_general. 41362075 Combine JUMPDEST and KECCAK_GENERAL flags. (#1259) 20501d9b Add context constraints (#1260) 3ca16620 Merge pull request #1317 from topos-protocol/more_memcpy_bytes 6332900d Combine PUSH0 and PC flags. (#1256) 0d97b93a Add some documentation in EVM crate (#1295) afd357f4 More of memcpy_bytes 0f299d4c Merge pull request #1316 from topos-protocol/memcpy_tiny af4935cd Merge NOT and POP flags. (#1257) 4b40bc03 Remerge context flags (#1292) c96a8700 Move empty check inside final iteration 0258ad4a Merge pull request #1314 from topos-protocol/refactor_wcopy 6f52b76d Review 7447959f Fix kexit_info in test a67c057e Merge pull request #1313 from topos-protocol/blobbasefee d4d0868a Merge pull request #1312 from topos-protocol/mcopy 666a155d Remove new_stack_top_channel from StackBehavior (#1296) 530a1d90 Review 15a9e992 Fix test on interpreter side 75fdd342 Fix calldatacopy ade5b8c3 Fix a9e47afc Refactor codecopy 4f6ed3d4 Implement BLOBBASEFEE a1b178f6 Refactor memcpy 3feb04d2 Refactor wcopy syscalls 3aeec83a Add missing constraints for DUP/SWAP (#1310) e74045ad Clippy 8d91d26a Implement MCOPY 44af80f2 Merge pull request #1309 from topos-protocol/fix_padding_in_wcopy c1c1ab6d Fix wcopy and extcodecopy for ranges over code limit ed5ec3ca Merge pull request #1307 from topos-protocol/fmt b31f7061 Apply rustfmt with latest nightly b212fff7 Merge pull request #1306 from topos-protocol/fix_empty_last_chunk 8326db60 refactor: remove usage of unstable `generic_const_exprs` in starky (#1300) 60811d08 Also for memset 83054b0f Handle empty case for memcpy_bytes 8af189b9 Merge pull request #1305 from topos-protocol/memset 385ab3c6 Remove redundant d185d30e Speed-up memset and fix it to write 0 values 0300a322 Merge pull request #1304 from topos-protocol/memcpy_bytes 4140eb54 Fix 503a31b6 Reviews e2b66206 Merge pull request #1302 from topos-protocol/remove_kernel_memory_zeroing 9607a41b Merge pull request #1303 from topos-protocol/amortize_receipt_reset 595dfa6b Merge pull request #1301 from topos-protocol/submod_kernel ba61d15c Add macro for copying sequences of bytes 6d2586ef Amortize bloom reset 40de5059 Remove outdated code 07ffe4f5 Combine stack macros for fewer operations 6ca9970a Alter stack to remove SWAPs for SUBMOD 29005dc5 Use SUBMOD in Kernel 05006deb Pad according to RATE rather than WIDTH (#1299) 49976ea2 Check gas in sys_stop (#1297) 817e3e78 Combine DUP and SWAP (#1254) 29fdd3e3 minor: use explicit builder.assert_zero for readability (#1293) c9391be0 Update check_ctls with extra looking values (#1290) f9242702 Make clippy happy d89966b0 Merge pull request #1288 from 0xPolygonZero/git_dep_org_update a02a2ecb Updated `mir-protocol` --> `0xPolygonZero` 1d604319 Store top of the stack in memory channel 0 (#1215) 762e6f07 Fix hash node case in `mpt_delete_branch` (#1278) d7990ee1 Add journal entry for logs (#1286) 9fd0425f Fix journal order in `sys_selfdestruct` (#1287) 49ca63ee Fix sys_blockhash (#1285) 2aeecc3d Fix failed receipt. (#1284) b4203c3d Make sure success is 0 in contract failure (#1283) 41a29f06 Remove some dead_code in EVM crate (#1281) 8a5eed9d Fix shift constraint (#1280) e58d7795 Remove reg_preimage columns in KeccakStark (#1279) 0de6f949 Remove extra SHL/SHR CTL. (#1270) 51eb7c0b Merge pull request #1276 from topos-protocol/fix_empty_to_encoding 571dc14f Fix encoding for empty recipient 3ac0c4ae Fix genesis state trie root when calling `prove_root` (#1271) cd36e96c Derive clone for txn RLP structs (#1264) 0f19cd0d Make gas fit in 2 limbs (#1261) 4e2cba56 Merge pull request #1262 from topos-protocol/fix_rc_doc 8afd06cf Fix description of Range-Check columns in STARK modules 30005957 Optimize lookup builder (#1258) 1ff6d4a2 Merge pull request #1235 from topos-protocol/new-logup f49fbc8e Transactions trie support (#1232) acc659da Add type 1 and 2 txn for RLP encoding support (#1255) 916ce0dd Merge pull request #1228 from topos-protocol/constrain-genesis-state 5694af79 Merge remote-tracking branch 'mir-plonky2/main' into constrain-genesis-state 75c0e47a Apply comments. 03a95581 Handle additional panics (#1250) 72241ca7 Connect block_gas_used (#1253) a24cd4f3 Merge pull request #1251 from topos-protocol/fix-observe-challenges 043d12c2 Fix observe_block_metadata 8c78271f Add `random` value to block metadata and fix `sys_prevrandao` (#1207) bbc6fe76 Merge branch 'main' into 'new-logup' 3983969c Use function for genesis block connection. 9d0101d6 Merge branch 'main' into 'constrain-genesis-state' 0abc3b92 Apply comments (#1248) d6be2b98 Remove `generic_const_exprs` feature from EVM crate (#1246) 70d6dd97 Merge branch 'main' into new-logup f438d45f Merge branch 'main' into 'new-logup'. 6618cfad Remove SEQUENCE_LEN in BytePackingStark (#1241) 1b7207ee Merge pull request #1244 from topos-protocol/block_metadata_doc 459e9b3d Merge pull request #1245 from topos-protocol/indexing_tables d8874c83 Update ranges indices c468465c Merge pull request #1243 from tamirhemo/main edd3f383 Add some doc for BlockMetadata / ExtraBlockData 8a19f436 Merge pull request #1242 from topos-protocol/fix_multi_row_ctl 8839285f add trait bound a44379b5 fmt and clippy 7d7f01da refactor prove method 4eb6a3b5 Fix eval_table ca441872 Merge branch 'main' into new-logup b60a3d4b Merge pull request #1239 from topos-protocol/mload_mstore_with_packing 053553d4 Reuse new packing instructions for MLOAD and MSTORE 696377ba Merge pull request #1231 from topos-protocol/error_vs_panic b5c28bd6 Rename utility methods for U256 conversion f07351fc Merge pull request #1238 from topos-protocol/cleanup f3ea95ca Merge branch 'main' into error_vs_panic c4be838a Typo d1c00767 Cleanup lookup_test module and reduce module visibility 15064b3a Merge pull request #1229 from topos-protocol/next_row_ctls 1a4caaa0 Move next row logic inside Column ffa71787 Merge remote-tracking branch 'mir/main' into new-logup a9b7b5a6 Revert "Remove where clauses: [(); CpuStark::::COLUMNS]" 8903aec1 Change padding rule for CPU (#1234) 865f185b Merge branch 'main' of github.com:mir-protocol/plonky2 into new-logup 1afcafad Merge pull request #1236 from topos-protocol/ci 5db6abf0 Update clippy in CI 4f0330ad Update clippy in CI ec9e6196 Fix range 66f935a7 Remove where clauses: [(); CpuStark::::COLUMNS] 91000591 Merge branch 'main' of github.com:mir-protocol/plonky2 into new-logup 9697c906 Clippy 7dc2a774 Cleanup c5af894e Add assert with char(F). Cleanup. Fix recursive challenges. 9ab8a118 Remove one helper function 17f661f9 Fix BytePacking range-check. Fix lookup challenges c9c0f8b7 Use CTL challenges for logUP + change comments + add assert f65ad58a Implement logUp d4a8026b Combine mstore_general and mload_general into one flag (#1188) 27d9113f Merge branch 'main' into next_row_ctls 0b5ac312 Merge pull request #1203 from topos-protocol/constrain_nv_stack_len 19220b21 Remove redundant Keccak sponge cols (#1233) 06bc73f7 Combine arithmetic flags on the CPU side (#1187) 61a1c246 Fix CTLs c27fc96a Merge branch 'main' into next_row_ctls f944a08b Fix self_balance_gas_cost and basic_smart_contract. (#1227) 7ebbb47f Swap ordering in stack macro (#1230) 5a1b05ac Remove risks of panic 9508b490 Move byte packing / unpacking to a distinct table (#1212) 3c4f938f Make next row available to CTLs 4d7d9ffa Constrain genesis block's state trie. d1c395ef Merge pull request #1202 from mir-protocol/keccak-preimage 3571f097 Merge pull request #1224 from mir-protocol/latest-nightly 9a8a769d more clippy suggestions 55d05147 clippy suggestions a4e6c6ae clippy suggestions 7415810f clippy suggestions 8af3b0fe clippy suggestions ed8bcf9d clippy suggestions 1dd77d6d fmt e947a624 suppress incorrect Clippy error 967f7b12 latest nightly in CI and rust-toolchain 65917f5f Merge pull request #1222 from mir-protocol/internal_crate_path_stablization 2f1ed951 Merge pull request #1220 from mir-protocol/latest_nightly_fix 90ea0318 Merge pull request #1223 from succinctlabs/uma/change-witness-visibility faa70e07 Merge pull request #1219 from succinctlabs/uma/add-mock-feature-flag a184f09b Made visibilities outside of crate to allow for forking partial witness gen outside of crate 1be1ca4d clippy a6433071 Fixes 5936c67f Now refers to sub-crates using paths (and removed `patch` section) 180c2094 Merge pull request #1208 from topos-protocol/blockhash_opcode 71b2ece1 Merge pull request #1216 from topos-protocol/checkpoint_lengths 0b7c4082 Merge pull request #1218 from topos-protocol/keccak_col 05e9fc0b Apply Nick's comment d0379e94 Apply Nick's comment 4716fe7d Also included clippy fixes introduced by new nightly 6d3d2cb2 Now builds on the latest nightly 5a3c8b26 clippy 0ca796e1 Removed mock feature flag and added mock_build 170f7d83 Fix Clippy 9a06fc9b Fix memop reads, from_prover_inputs and cleanup. ddf2b817 Clippy 1c01d682 Fix overflow check and test. Remove [..8] when using h256_limbs. c30b1834 Change h256_ulimbs 4e0fe74a Apply comments 42f70380 Add blockhash sys opcode 4782519d remove spurious 18d31412 Added mock feature flag and test 258b075f Remove filter column for KeccakStark e6ca4606 Merge pull request #1214 from jtguibas/jtguibas/serde-target 5690b951 Merge pull request #1217 from topos-protocol/cleanup_duplicates fa9aae1f Remove duplicate code 6207f446 Merge pull request #1206 from topos-protocol/missing-public-value-links 8dcb29e5 Display actual trace lengths instead of number of ops 800603d6 feat: serde for targets a7096546 Merge pull request #1209 from topos-protocol/receipts-all-types d4b71c56 Replace genesis state trie check with TODO 6bd17e29 Apply comments dd3b61a3 Merge pull request #1211 from mir-protocol/comment-fix ac89c7cd Fix comment in `proof.rs` bf21b278 Apply comments 9ba2b895 Implement receipts of types 1 and 2 b0764436 Add missing links between public values 8beba569 Constrain next row's stack length ea03e418 Keccak STARK: constraint preimage to equal A on first round 760f09a8 Merge pull request #1201 from shuklaayush/fix/keccak-stark-reg-preimage 301aedf0 fix: constrain higher bits of reg_preimage a0b2b489 Merge pull request #1200 from topos-protocol/fix_empty_txn_list 71967147 Update range from ReceiptTrie PR 6e7fcc9e Merge pull request #1199 from jtguibas/john/make-generate-partial-witness-pub d3f33bae make generate partial_witness pub 62f271a8 Merge pull request #1198 from mir-protocol/public_values_serde 975fd451 Made `PublicValues` serializable 86fb6aa0 Merge pull request #1097 from topos-protocol/receipts_and_logs 6a2e2423 Clippy caae038c Cleanup 5b962f3c Change receipts_trie in basic_smart_contract and self_balance_gas_cost ad9796cb Fix tests and address comments 925cdd53 Cleanup c0b4f155 Implement receipts and logs 44115de7 Merge pull request #1174 from topos-protocol/merge-context-flags a881c70e Merge pull request #1191 from mir-protocol/eth_trie_utils_patch 18ca89f0 Patched plonky2 to use a patch for eth_trie_utils 4e5f6e7e Apply comment 10bbda03 Remove unnecessary changes in the Operation enum c3cb2278 Combine get_context and set_context into one flag 74212a29 Merge pull request #1192 from topos-protocol/misc_constraints f6f9fa31 Merge pull request #1190 from topos-protocol/mpt-remove-cow 06e20f87 Apply comment a94d9282 Merge pull request #1194 from topos-protocol/block_basefee 8476fdcd Refactor 9a450068 Update BlockBaseFee to fit in 2 limbs c138f2d6 Merge pull request #1193 from topos-protocol/observe_pv 68bb4967 Update tests to have a blockgaslimit fitting u32s 976d7521 Observe public values 0b78c43f Remove filtering in membus 91e8d52d Reduce overconstraining in decode module b711e527 Combine a few constraints d96c6491 Merge pull request #1165 from topos-protocol/ci-test d70d67fa Remove copy on write for mpt_insert and mpt_delete 1997bf24 Implement inverse from Fermat little theorem (#1176) eb7bb461 Merge pull request #1189 from topos-protocol/remove_is_bootstrap_kernel_flag 49d92cb8 Remove is_bootstrap_kernel column 683501cc Merge pull request #1183 from topos-protocol/remove_is_cpu_cycle_flag 815a02ab Remove is_cpu_cycle 89e62e55 Use Keccak config in simple tests 7b07229b Add guidance for external contributors to README.md 830fdf53 Merge pull request #1184 from topos-protocol/combine_jump_flags 12f379f9 Combine jump flags 470788d0 Merge pull request #1185 from topos-protocol/combine_simple_logic_flags 7cdb6baf Merge pull request #1177 from topos-protocol/alloc 7829dccf Combine EQ and ISZERO flags dc7e0aa7 Merge pull request #1181 from topos-protocol/combine_logic_flags 654f7cac Comment e10eaad0 Combine all logic flags together 437f57a8 Fix logic CTL 5100e032 Revert changes in cyclic_subgroup_unknown_order 8541a04b Apply Nicholas comment 56ebda49 Address review 12a687d3 Reduce reallocations ee9ce4c5 Combine AND and OR flags in CpuStark 6f98fd76 Merge pull request #1147 from metacraft-labs/gate_make_public_parameter 5f4b15af Connect SHL/SHR operations to the Arithmetic table (#1166) df07ae09 Write trie roots to memory before kernel bootstrapping (#1172) c9eed2bb Connect public values in aggregation circuit (#1169) 397ee266 Merge pull request #1171 from topos-protocol/exception-flag 017e6217 Set exception flag to 1. e6407089 Error instead of panicking for missing preprocessed circuits (#1159) b2626fdc Merge pull request #1162 from topos-protocol/cleanup_attributes 9eeb69f0 Merge pull request #1105 from topos-protocol/poseidon_warning bf1ed783 Merge pull request #1161 from topos-protocol/fix_recursive_ctl c9bd32d5 Fix trait import. (#1163) 9f8c1522 Remove unused attributes 4a762e33 Merge pull request #1160 from topos-protocol/keccak_general 5b9e8d85 Merge branch 'main' into poseidon_warning bd3834c4 Silence Poseidon warnings for ARM targets 5316f890 Clippy 8365608b Convert to u32 instead of u64 c93f9d5f Fix endianness in benefiary limbs bca3e09b Reuse set_public_value_targets f01098a7 Constrain keccak general 9e0719e6 Better document constraints on addcy carries (#1139) d8e314bc Merge pull request #1155 from 0xmozak/matthias/generalise-transpose 8c6e8d63 Merge pull request #1158 from mir-protocol/jacqui/gas-check-spec c52ed29e Gas handling brain dump eebf7eb0 Merge pull request #1157 from mir-protocol/update-versions b414b8e9 fmt f574effe make imports conditional on config 84321955 update versions in cross-crate references 3a556029 update versions for crates.io updates 7537193d Generalise transpose 5b8740a7 Merge pull request #1026 from topos-protocol/memory-ctl-verifier-bus 3b21b87d Merge pull request #1151 from mir-protocol/jacqui/dead-memtable-cols 7a882d0a Clippy 6253a68e Change public values into public inputs 59b73c84 Apply comments 1590c1d0 Fix indices in CTL functions f97deab8 Remove non-passing debug assert 06037f81 Fix the memory CTL and implement the verifier memory bus b3f00d4a Merge pull request #1146 from topos-protocol/overlap-cpu-syscalls 831fe862 Cut 5 Columns From The Memory Table With This One Weird Trick! bfd6b988 Merge pull request #1148 from topos-protocol/lookup_check ee9cd80c Change arg to non-mutable reference 0276446e Add additional lookup unit tests dc70902f Remove always true condition bc246780 Fix name in ID 6ca3f1a9 Make GateRef value public bfa7ab36 Merge pull request #1111 from topos-protocol/lookup_serial 16227f90 Merge syscall and exceptions constraints. 0f52c889 Merge pull request #1145 from mir-protocol/npwardberkeley-patch-1 535fb7d8 Update prover.rs e047676e Merge pull request #1114 from onsen-egg/onsen-egg/lookup-opt a67cfdcb Precompute RE poly evals for challenges 03d90f30 Faster multiplicity counting for lookup tables 167518ed Merge pull request #1143 from succinctlabs/build_issue_on_mac_M2 6a772879 Fix negative quotient issue (#1140) 25678f46 Merge pull request #1144 from mir-protocol/build-in-subdirectories f3e87ec4 CI: build in subdirectories 8a86e195 fix: add itertools/use_std feature flag for [std] 2d8c02bf Merge pull request #1138 from 0xmozak/bing/dep-serde-rc 7ba051f4 Fix failing byte constraint (#1135) 152e3959 Merge pull request #1137 from topos-protocol/fix-kernel-panic e28b484a deps(serde): use rc 1af1afcf Change current context in bignum_modmul cf278eac Merge pull request #1136 from topos-protocol/div_by_zero f116c855 Fix risk of division by zero b27389df Merge pull request #1134 from topos-protocol/avx_tests 04657d24 Fix import 1d6ca589 Add LUT hash to remove CircuitBuilder overhead 4893a860 Merge pull request #1116 from topos-protocol/recursive_ranges 00579850 Merge pull request #1132 from mir-protocol/dependabot/cargo/itertools-0.11.0 8b35fefb Rename cd to common_data for consistency Cf review cbb3da15 Reduce number of lookup accesses b32345cd Update lookup serialization c8020126 Provide methods for ProverOnlyCircuitData serialization 544aff27 Also provide CommonCircuitData in serialization of gates and generators 47781e47 Add CommonCircuitData to gates deserialization method b43d6c1d Add CommonCircuitData to generators deserialization method d684ee2d Switch Field type of generators to be F: RichField + Extendable 5d513207 Update itertools requirement from 0.10.3 to 0.11.0 4400757f Merge pull request #1128 from mir-protocol/dependabot/cargo/hex-literal-0.4.1 dc170915 Merge pull request #1131 from mir-protocol/dependabot/cargo/criterion-0.5.1 c202f4bc Merge pull request #1129 from mir-protocol/dependabot/cargo/ahash-0.8.3 0f284ca6 Merge pull request #1130 from mir-protocol/dependabot/cargo/hashbrown-0.14.0 63b8ceba Merge pull request #1124 from 0xmozak/matthias/remove_unused_deps e3f12709 Merge pull request #1123 from 0xmozak/matthias/fix-readme b0c5ddc0 Update criterion requirement from 0.4.0 to 0.5.1 fc70f36c Update hashbrown requirement from 0.12.3 to 0.14.0 0d9208a6 Update ahash requirement from 0.7.6 to 0.8.3 413f589e Update hex-literal requirement from 0.3.4 to 0.4.1 cc45ac9a Merge pull request #1125 from 0xmozak/matthias/fix-resolver-warning 1f561771 Merge pull request #1126 from 0xmozak/matthias/add-dependabot 7437fe2b Fill modulus in cpu row for Fp254 operations. (#1122) 9e748a47 Enable github's Dependabot 6c2f76d5 Fix resolver warning 4b0fc861 Remove unused dependency `blake2` from `evm` crate f6b2e742 Fix spaces and wording in README ee5d1aa6 Merge pull request #1033 from 0x0ece/transpose 398f86af Merge pull request #1092 from matthiasgoergens/matthias/move_to_field 2d7a94de formatting 94f880b6 Merge pull request #1104 from topos-protocol/serializer dca50adf Merge pull request #1119 from mir-protocol/jacqui/topos-protocol/stack_len_bounds_aux_error 6b493d6f Remove redundant case (error in kernel mode) 1664ab44 Merge pull request #1112 from topos-protocol/fix-generate-jump 7aa5ed3b Merge pull request #1117 from topos-protocol/fix_set_context b9b227c8 Merge pull request #1118 from mir-protocol/revert-1109-new-clippy-fixes 14c40115 Revert "clippy fixes" f08afec6 Merge pull request #1109 from mir-protocol/new-clippy-fixes 5bff02a1 Fix generate_set_context 0a59aa6e Remove need for matching start ranges 325cd2f7 Compute stack_len_bounds_aux correctly in generate_error 224064bf Fix jump operation generation c982826e Add feature "rc" to serde crate import 3870524a Merge pull request #1113 from topos-protocol/ci 6bd575d1 Fix nightly version in CI dbb23587 Merge pull request #964 from topos-protocol/lookup 96fbecd9 ignoring where appropriate (for izip), fixing elsewhere b0568a79 remove useless vec 0fec1124 update itertools 08a6e66d fix 5b08ac58 fix 3c776a8d clippy fixes 4df4d865 No default implementation 91c55d15 Add wrapper types for Lookup and LookupTable 43512371 Review c0fc349c Fix lookup serialization and update with latest serialization changes 7e80b42a Serialize Lookup gates and generators 35abffd5 Implement lookups with logarithmic derivatives in Plonk 6122dccb Move operations to Field 56a127eb Make Buffer available in no-std d960bfe2 Make serializer work with slices instead of Vec 3de92d9e Merge pull request #1102 from mir-protocol/modexp-memory-context-change 605ea47f reset Cargo.toml 897e2e99 fix 54cf74ac addressed comments 975e9a49 fmt 8eeca9be undo dummy change 2fa43121 dummy change to get tests to rerun :P 9bb6da04 fmt afd4bd04 cleanup bc53ddc5 fix 264192aa modexp uses current_general 39d2237d Merge pull request #1101 from mir-protocol/blake_fix ee452fc0 Merge pull request #1099 from mir-protocol/blake_fix_fix ef8ea64d Minor 7559bb2f Minor 83ee5fd6 Minor c8ff80ca Fix blake2 fix 7ca56768 fix 246eb8d8 blake fix 23bc390a Merge pull request #1095 from mir-protocol/jacqui/push0-opcode 3eb41edb William comments 564864ea Remove parts of the copy-on-write logic (#1096) cedeff52 PUSH0 9cc35360 Merge pull request #1082 from mir-protocol/jacqui/simplify-stack-bounds 8ded9e84 Minor: William comment ec07255f Fix halt loop (#1094) 01efa013 Fix account touch in calls (#1093) ba705703 Use current context for pairing memory (#1091) 9838a367 Check call depth in create (#1089) e51c4d0d Set returndata size to 0 in some create errors (#1088) d37c5455 Increment call depth in precompiles (#1087) 56e7ad00 Fix LOG* gas (#1086) 68b15ea5 Fix CALLDATALOAD for large offsets (#1085) f852984e Implement PREVRANDAO as if it was DIFFICULTY (#1084) 6920992e Simplify stack bounds constraints 01175419 Merge pull request #1071 from mir-protocol/jacqui/bad-opcode-witness-generation ae290dbf William PR comments 0f7e1c0b Call stack depth (#1081) 0f874317 Minor fix to REVERT (#1080) 90bb4741 RIPEMD doesn't get untouched (#1079) fd48e5d1 Contract creation fixes (#1078) 77f0d8b5 Don't revert state in CREATE in case of OOF or nonce overflow (#1077) 63a6e706 Fill BLOCKHASH and PREVRANDAO syscalls with dummy code (#1076) 0e23606e Revert #1074 (#1075) 5a13b62d Don't overwrite existing account (#1074) 2cf31f5f Prevent shift ops from panicking (#1073) d3387172 Commit missing file c773476c Minor docs 55b29cac Remove bootloader.asm (#1072) 3ecf5309 Minor bugfixes 448bc719 Lints 1d804e46 Fix stack after precompiles (#1061) 7ab0bba5 Merge branch 'main' into jacqui/bad-opcode-witness-generation b7220428 Error handling 973624f1 Minor fixes to RETURN and RETURNDATACOPY (#1060) 720faa67 Fix create OOG because of code deposit cost (#1062) fbf6591b Warm precompiles earlier (#1065) f605d912 Propagate static flag (#1066) 73079796 Fix pairing invalid input (#1067) 49bbe4e0 Fix arithmetic stark padding (#1069) 0d819cf8 Implement EVM `BYTE` operation (#1059) 8153dc78 Remove `-C prefer-dynamic=y` from CI build. a492d3e1 Fix revert gas bug 7dfdacf2 Fix return and revert gas (#1058) 42f33017 Fix ecrecover edge case (#1057) c0abefda Fix DUP in call gas e6a7b8c5 Add contract creation flag (#1056) 30b97b29 Fix DelegateCall bug 9727eaf1 Fix extcodehash when account is empty (#1055) 08a061bc Implement LOG* gas and remove panic (#1054) 354664c8 Fix ecmul (#1053) 6e303601 Support for type-2 transactions (#1052) 9b0092ab Support for type-1 transactions (#1051) 15dec6fa Encode `to` as B160. (#1011) beefc91d Pop checkpoint in the right place 5a7c176c Fix issues related to CREATE2 collisions (#1050) e720090e Merge pull request #1041 from mir-protocol/storage_addr_h160_to_h256 d57b62ff Perform jumpdest analysis whenever entering a new context (#1049) 971bfba6 EIP-2681: Limit account nonce to 2^64-1 (#1048) 8faea881 Don't add an event for account creation for pre-existing account (#1047) 29fac4ca Check balance in create (#1046) 1616c0ba Fix extcodecopy 3a9e5cc0 More fixes to contract creation (#1045) 49979df9 Fixed failing test a294c7e2 Some fixes to contract creation (#1044) 84c15606 Minor fixes to returndata and create (#1043) 10e6c768 `TrieInputs` now uses `H256` for storage account addresses ce6ac9f8 Merge pull request #1038 from mir-protocol/tests-memory-context-fix c36ed15e Merge pull request #941 from mir-protocol/bls-fp2 6292d8d7 redundant d3986e6b merge successful 59ae7103 merge 244d5e9b Add refund journal event and checkpoint after access address event (#1040) bde7fb50 Various fixes to checkpoint logic (#1039) e5b0fce6 revert testing changes 3a77c5a0 fix 14f92f7b Cargo.toml change for testing b116929f Delete touched recipient in EOA -> EOA (#1037) 6ebee38e fix d05db497 Don't touch contract address in DELEGATECALL or CALLCODE (#1036) bfd6834d Journal of state changes + state reversion (#1028) 74ba3032 MPT deletion (#1025) 202985b2 Fix CALL gas (#1030) 944d4a24 SSTORE refund (#1018) f1cc284d Optimize transpose c134b597 Cross-table lookup for arithmetic stark (#905) 779456c2 Merge pull request #1029 from mir-protocol/precompile-memory-context-change 6f4f00c6 Merge pull request #1027 from mir-protocol/memory-refactor 2c5f6fd6 Fix compile time problems and generic hash implementation (#1024) 76fb3160 Merge branch 'memory-refactor' into precompile-memory-context-change 0d9e3216 fix (mstore_unpacking returns offset) 97aedd11 Merge branch 'memory-refactor' into precompile-memory-context-change 6e7fa6da fix 675d6440 Merge branch 'memory-refactor' into precompile-memory-context-change 57bcb451 use mstore_unpacking and mload_packing 6669f73a use mstore_unpacking and mload_packing af12368a addressed comments & cleanup 98a75774 cleanup 5dc043aa Merge pull request #1012 from honeywest/transpose a4a4fbb3 fmt 057b650f fix a076da75 fix 24159886 precompile memory context change d9694d95 fix 446c3b71 fix b566dbd7 refactor memory/core.asm to make code more reusable 46c7903b Merge pull request #1023 from topos-network/clippy 92c2378c Fix clippy f11921c9 Fix doubly_encode_rlp_scalar in the 0 case. (#1022) 653a6b15 Remove `generic_const_exprs` dependency from field crate. (#1020) 4380395e Merge pull request #1017 from mir-protocol/expmod-fix 91067e58 expmod edge case fix b159c9e7 Merge pull request #1013 from topos-network/overflow-check f0df03f6 Merge pull request #1009 from mir-protocol/expmod_precompile 099e7946 fixes 17a7c57d Change add_or_fault macro 1f39c555 Address overflow-related TODOs in ASM code, using a macro add_or_fault. This is related to https://github.com/mir-protocol/plonky2/pull/930/files/a4ea0965d79561c345e2f77836c07949c7e0bc69 40515dc6 Merge pull request #1014 from toposware/bootstrap_constraint f3de2afc remove test file cae5a2cf fix 050c2e65 fix: calculate gas properly ab8ebdfb Merge pull request #1016 from mir-protocol/remove-proof-challenges-serialization 8d738729 Merge pull request #1015 from mir-protocol/clippy-fix ade5b433 fix 30e58ad2 remove ProofChallenges serialization 8358b85d remove unneeded mut 08e6c352 addressed comments 037c2f5e addressed comments 841c5829 Fix todo in kernel bootstrapping debe65f9 addressed comments 1d8f71f8 optimize transpose_in_place_square_small code ae21ef8f Merge pull request #997 from mir-protocol/pairing-test 84f17699 comments f9aad433 neutral input 397d5953 fix 031fe6ed Merge branch 'main' into expmod_precompile 9e4056e2 cleanup badbf010 store and unpack at end 998cd8ab addressed comments a638ebe0 fix 7ede443e Merge pull request #1006 from mir-protocol/blake_precompile dc076df5 addressed comments 96742f29 addressed comments e40b9edb addressed comments 9b18b3ae fix unit 16928fd0 peculiar... b37e049a fmt f6a49e88 fair naming 4a42ddb2 on stack 57113905 redundant 2aa83d9a Merge branch 'pairing-test' of github.com:mir-protocol/plonky2 into pairing-test ae4b5091 neutral name 503cb8a9 random inp 4ad8520e SNARKV precompile (#1010) b28e3e0d minor ab721fa3 SSTORE gas (#1007) efd5a81b Merge pull request #980 from mir-protocol/serialize_common_circuit_data 13c653bc mul works 6599c90a abstraction 78a368cf fix 8df0c743 remove build_without_randomizing (no longer needed) b640bf63 serialize ProofChallenges 537debdc return bool 89122a3d it works 479e919c fmt 6dc094a8 test 4c235e5a Merge branch 'main' of github.com:mir-protocol/plonky2 into pairing-test b4e06271 fix d31c60a0 clean up d928a70b clean 1e9db292 fixes 67a3edb2 Precompiles exist (#1008) 0d98e4b8 formatting e642b824 move serialization to separate example dc91554d expmod precompile 3b7ad771 cleanup 46d9cee0 charge gas! 9460acc1 rename blake2b ce033410 fix 6a239c4f fix a41cf018 fixed blake tests 11a03c5e Merge pull request #1005 from mir-protocol/precompile-fixes 621c63de clippy fix c083cc63 fix 29a8367b fmt 2d98dd3c commented out unused functions 5dc44916 Merge branch 'main' into blake_precompile 454e0add fixed blake2_f, and testing 137a9966 Merge pull request #998 from mir-protocol/even-smaller-bignum-modexp-test f225ea49 add comment 93398472 Merge branch 'precompile-fixes' into blake_precompile 1eba893e mload_packing macro ffc5d6d6 Merge branch 'main' into precompile-fixes 43f4d2b8 clean more 14ee46c8 cleanup 0f662ed0 fixes b7e93511 New contract hook (#1002) a4b714e6 EIP-3541: Reject new contract code starting with the 0xEF byte (#1003) 472face2 EIP-3860: Limit and meter initcode (#999) f3f3641f rename 975a35c3 fmt f718f857 cleanup 8d50806b fix b9f1c1c5 cleanup 858c59a2 cleanup 45c0d894 cleanup 905c5eb5 deterministic build function 146e6605 don't serialize challenges 66763c7d cleanup 993ed149 seralizing 34a03545 Serialize impls, and use in Fibonacci example ea82d680 Merge pull request #981 from toposware/serialization 86acc15f blake precompile progress b288ff5f Merge branch 'precompile-fixes' into blake_precompile d5060ecd precompile optimizations 26204461 it works 9f0c2f47 blake precompile progress dd58b9b5 dont panic 26d99a9b memory compress 0c55aa04 clean 50752246 clean ff0695d7 renumber memory f1bbf66c it works c01b2bf2 minor 5f564b67 initial work on blake precompile 44a623d4 initialize out in asm 4e0be664 Merge branch 'main' of github.com:mir-protocol/plonky2 into pairing-test b35d2524 Merge pull request #1001 from mir-protocol/eth_trie_utils_0_6_0_bump db93bada Bumped `eth_trie_utils` to `0.6.0` 1a0a6300 EIP170 (#1000) 723f197d Cleanup 099c4b97 msg 82bca7fa error b661a709 twisted check 2a9c5cfd Add serialization check in square_root example bf02a3e8 Make generators public 5de5bfb5 Move serialization files into dedicated module 0e465c1c Customize range specification for AllRecursiveCircuits f71139d9 Add serialisation support for gates, generators, and various structs f7f5fb4e Change display for GoldilocksField 6edd5891 Gas and more for `CREATE(2)` (#995) a8e5613b EOA to precompiles logic (#993) df4a6f01 fix for full modexp test 6d84b988 fmt cb23bfca check for special cases and align with yellow paper bbe64674 tests passing 3628021a fmt d6584dcb restructure tate test 3e437a0c oops remove more debug stuff bf5dc256 undo debug commenting 0df18d5e tests de94ac25 missing file 3444e810 even smaller bignum modexp test, and fixes 21a1a98a reorg ca3a7f8a Merge branch 'main' of github.com:mir-protocol/plonky2 into pairing-test 9b54ee43 refactor c0ced26f Merge pull request #992 from mir-protocol/smaller-bignum-modexp-test 0e082432 reorg ada250f3 Merge branch 'main' into smaller-bignum-modexp-test d8fef87a Only print warning or errors from the log. (#996) 2e16ab04 Replace %stack calls with equivalent opcodes. (#994) 3a3ff87a fmt 7dda0eff works 1f077628 new api c9b09936 compiles d112c716 fmt a704b152 even less thorough :P a4f60a04 less thorough bignum modexp test 049a258b Merge pull request #991 from mir-protocol/disable_couple_tests 8562abe0 Disable a couple tests 690fd100 Merge pull request #990 from mir-protocol/mpt_failure_labels 67593f16 Labels for failed MPT read/insert e6864e98 Merge pull request #985 from toposware/kernel_serial 5dfac715 Fix generic const expressions warning (#984) 9037ceb0 Merge pull request #986 from mir-protocol/incremental_release eb7468e7 Incremental release builds df5a90cc Provide methods for serializing Kernel 6b2503f7 Merge pull request #970 from toposware/env 3cc39fa4 wip 9d60191d Implement returndatasize/returndatacopy for interpreter 191ca102 comment 0b85c8bb getting there 0b9ef768 nl 2106ae07 Merge branch 'bls-fp2' of github.com:mir-protocol/plonky2 into bls-fp2 a5c6b14e Merge branch 'main' of github.com:mir-protocol/plonky2 into bls-fp2 9b9cd735 Update evm/src/extension_tower.rs 6946eaca Implement codesize/codecopy for interpreter 0f3285c3 Implement gasprice on the interpreter e9cc5632 Impl caller/address/origin opcodes for interpreter b721236e Precompiles interface (#983) b896f9b2 Merge pull request #974 from toposware/stack_bound c8637635 Remove dummy_yield_constr 4946c3d5 Merge branch 'main' into stack_bound 5fce67d1 Merge pull request #978 from toposware/stack_constraints 32a6bdf1 Merge pull request #971 from toposware/keccak_sponge_is_final_block 58f4568e Merge pull request #982 from toposware/sys_chainid 92d94dc6 Use Block chain id for sys_chainid ba844a24 Change shl/shr behavior as well as BASIC_TERNARY_OP 475b2ba0 Fix copy_returndata_to_mem (#976) c7e60073 Check if context is static for state-changing opcodes (#973) 142be4e1 Implement rest of *CALL opcodes (#972) b202196b switch f2650418 Merge branch 'main' of github.com:mir-protocol/plonky2 into bls-fp2 1e57ef96 Remove unnecessary constraint 29726f92 Apply review f424bd36 Merge pull request #966 from toposware/interpreter 938e3bd5 Set stack_len_bounds_aux properly 18d27d2f Remove is_final_block column in KeccakSpongeStark 011ea8e4 Fix from review 5b1fd5f2 CALL gas (#969) d79d2c49 Merge branch 'main' of github.com:mir-protocol/plonky2 into bls-fp2 8130a8a6 Merge pull request #950 from toposware/keccak_sponge 0529fa06 Change endianness within generate_keccak_general 3da8efa6 Implement sar in interpreter b943ddb0 Implement signextend in interpreter 4db00441 Implement sgt in interpreter ac2ccc1e Implement slt in interpreter 232832e3 Implement smod in interpreter 18d317b9 Implement sdiv in interpreter. 0e5e28f6 Merge pull request #968 from toposware/block_interpreter c0fae77c Merge pull request #951 from toposware/prove_block 6124e4d6 Fix BlockCircuitData proofs 0146f48a Cleanup 524b39e2 Reactivate CTL for keccak sponge d1379ac1 Fix hash output writing to memory 5f6098ff Add test for keccakf_u8s 99b0d009 Implement KeccakSpongeStark constraints 2fae2fbc Impl gaslimit opcode for interpreter 9e6f284b Impl chain_id opcode for interpreter ae8ee27e Impl coinbase opcode for interpreter e3572f1d Impl basefee opcode for interpreter 86bd055b Impl difficulty opcode for interpreter 60fed608 Impl number opcode for interpreter a17c6231 Impl timestamp opcode for interpreter 042c0042 Merge pull request #965 from mir-protocol/fix_run_constructor 31e134f0 Delete %set_new_ctx_parent_ctx 1a9f0104 Fix call logic (#963) ab692252 Minor fixes to context creation (#961) 7a65b1d4 Merge pull request #967 from toposware/fix_decode 310107f2 Fix decode constraint cfc54f95 Fix run_constructor d1c9277d Merge pull request #962 from mir-protocol/range-check-example d6bb5d5d range check example af3fa142 Implement sys_return and sys_revert (#959) f24c3537 Update README.md c11f4f41 Merge pull request #960 from mir-protocol/readme-updates fb24b200 README updates: examples and external tutorial 5ac12de9 Fix sys_exp (#958) 923722b1 Fix copy opcodes when offset is large (#957) d59fa59a Merge pull request #925 from mir-protocol/bignum-modexp 6a4e9ab6 fix 90f7ba9a addressed final comments 9690b60b Merge pull request #956 from mir-protocol/doubly_encode_storage_values e70e4fca Doubly RLP-encode storage values 889911e8 redundancy 93dd25a1 fmt 33dc8eae better names 251d7e34 systematize names 4e48fc43 all Stacks 0e3b86de frob 26da6dc7 rev stack d52c15e8 Merge branch 'main' of github.com:mir-protocol/plonky2 into bls-fp2 6fa59d20 Fix MSTORE8 (#955) f9217272 Fix signed syscalls stack (#954) 3b607bde Merge branch 'main' of github.com:mir-protocol/plonky2 into bls-fp2 a061c3cf Merge pull request #952 from mir-protocol/extra_where_clauses 209c1ff1 Remove extra conditions e2f33dd3 Merge pull request #873 from toposware/hashconfig 9ee47ab7 Move HashConfig into GenericConfig associated types 46d5e62a Copy txn data to calldata (#935) 8e04cbfe Self-destruct list (#948) a926dcad Transaction validity checks (#949) f1a99e69 Add patch section to workspace config file e857c020 Make hash functions generic 2ca00a9a Selfdestruct gas and set (#947) 786a71d6 Merge pull request #946 from mir-protocol/selfBalanceGasCost 31cd0f64 Remove dbg 9ae69a7c Add an integration test for the `selfBalanceGasCost` case 56bf0892 Charge gas for extcodecopy (#942) f71d3642 Merge pull request #945 from mir-protocol/remove_CONSUME_GAS 9480cbed Signed operations as syscalls (#933) 2d87c5d6 Remove `CONSUME_GAS` 834522a6 Merge pull request #939 from mir-protocol/termination_fixes be0cccdf Merge pull request #938 from mir-protocol/rework_create_create2 39fdad8c Feedback a0d04ca3 Fix Wcopy when size=0 (#944) 9f1a5f97 Charge gas for keccak (#943) cdaabfe9 Merge branch 'main' into bignum-modexp b667c074 Merge pull request #940 from mir-protocol/eth_trie_utils_bump 3c7bc883 Removed a type alias 0c87a57f addressed comment e4f2e864 fix 1a348eed check for x < m bce25720 documentation 1e5677c4 comments a6ccd350 cleanup fb73e889 uncommented c18377d1 Merge branch 'main' into bignum-modexp 91fb4fc0 fix modexp test 823b06ac fp2 works 3b95e013 bls method 5783a374 Merge branch 'main' of github.com:mir-protocol/plonky2 into bls-fp2 7b93b81a Merge pull request #931 from mir-protocol/fp381-opcodes cf5a4edc prover input minor improvements 60ad9e03 Bumped `eth_trie_utils` to `0.5.0` c3a5fd86 merge 1f14ae98 skeleton 911dfedd Rework CREATE, CREATE2 syscalls b4eb8373 A few fixes for terminal instructions ce22d945 Access lists (#937) f9fa38d3 Fix new account insert key 7028b6ba comment 7ff2122e Merge branch 'main' of github.com:mir-protocol/plonky2 into fp381-opcodes 1ce47ceb Merge pull request #906 from mir-protocol/fp318 0650d263 remove .scale 3f4d970f Merge branch 'main' of github.com:mir-protocol/plonky2 into fp318 c8d2769c fmt 74afec70 remove imports f4e65feb Fix bugs in `wcopy` and `update_mem_words` (#934) 15bafce5 Implement CREATE2 address generation (#936) 874805cc Merge branch 'fp318' of github.com:mir-protocol/plonky2 into fp381-opcodes 645ef664 comment 63ec13e2 Merge branch 'main' of github.com:mir-protocol/plonky2 into fp318 3425391a more comments d0b2b81e More MemoryError (#932) 1f3e3de7 clean and generalize 1627a9a0 tests pass e471818c comments 1fbe3050 Merge branch 'main' into bignum-modexp 06936c76 Implement various syscalls (#930) 84a0bcf8 cleanup 373062b2 on stack 0e8f6a2f test skeleton 392c29f4 compiles 9ea0ebd7 skeleton 1437affc fmt b847d16e redundancy 13d2ed90 merge 54b8ce74 Merge branch 'main' of github.com:mir-protocol/plonky2 into fp318 143225f4 finish d928423c cleanup d59501e6 fixes, testing, and in-progress debugging fc72ce46 fp6 works 4d83c58d frob works 2df1439d Return error instead of panic in memory operation (#928) a79271a8 Minor account code fixes (#929) caaf3b4a merge fields cf1e6a76 Merge branch 'main' of github.com:mir-protocol/plonky2 into fp318 24705e1e addressed comments ff81a565 Merge pull request #927 from mir-protocol/creation_fixes e1ae5392 Fix test afded168 Contract creation fixes 893b88c3 Implement syscalls for BALANCE and SELFBALANCE (#922) fc6487ca Merge pull request #926 from mir-protocol/fix_gas 3c4bc1d8 Fix GAS and implement storage value parsing be309a38 cleanup from comments 42d65839 addressed comments d340ff8c addressed comments 2e0b7992 addressed comments 9803581d fix 1a78f400 restored neq macro be9cbd5a fmt 902bc66a fmt 511f450a resolved conflicts 4aa212ab modexp fix e06f84dd modmul fix 76e70ac4 fixes ad85d61e fix 4cef5aaa modmul and modexp 1e019356 basic bignum b16b8261 Merge pull request #881 from mir-protocol/bignum-basic 6fe8554f Merge pull request #924 from mir-protocol/empty-stack-replacement 652b2bed allow empty stack replacement a5fad9eb addressed comments f6b9d6ee addressed comments 72b5bb0e fmt 2752456e addressed comments 5e98a5f9 adj trait 50388073 rename 25575df5 cleanup ec0f3ce7 Merge branch 'main' of github.com:mir-protocol/plonky2 into fp318 692575a2 Bump eth_trie_utils version. (#923) 2ab16344 Merge pull request #921 from mir-protocol/dlubarov_misc bdf35374 Misc b80a28db Misc f13d603a Merge pull request #920 from mir-protocol/dlubarov_misc 47fac8e3 Couple fixes & minor refactor 64c76e76 Merge branch 'main' into bignum-basic c491a989 Merge pull request #919 from mir-protocol/mem_expansion f717a40b Charge for memory expansion e8405eff Merge branch 'main' into bignum-basic de246e22 Merge pull request #918 from mir-protocol/fix_read_ext 7ed53142 Fix reads from not-found ext nodes 7853656e Merge pull request #917 from mir-protocol/fix_clobbering a05ed9fc Fix clobbering of RLP data memory c3ba7a89 Merge branch 'main' into bignum-basic 2ac4fcdf Merge pull request #915 from mir-protocol/fix_clone_account 8c692b72 Fix account cloning f514d966 Merge branch 'main' into bignum-basic 994c54ab Merge pull request #912 from mir-protocol/stack_on_panic 5720cf8a updated function name cda31b5e Merge branch 'main' into bignum-basic 9f75132f Merge pull request #889 from mir-protocol/hash-asm-optimization 38f79e49 optimizations with rep 92ee7786 Merge branch 'main' into hash-asm-optimization 69b4a21c Merge branch 'main' into bignum-basic da07a7a8 Merge pull request #914 from mir-protocol/return_post_state 373421a1 Fix tests - need to supply empty code 44c77f55 Input addresses c8d591f6 Add a `prove_with_outputs` method 95347621 div instead of shr cecbfa9b fit c59b979c addmul fix a0a23147 Merge branch 'main' into bignum-basic f518a8b4 Merge branch 'main' into hash-asm-optimization b62bc35d fixes 2d7d2ac3 Merge pull request #886 from toposware/poseidon-native f1ad3da8 fix 062eb82a cleanup e0a4bc31 cleanup fda64475 fmt fa3443a5 new testing interface, and test data bb2233cb Override from_noncanonical_u96() for Goldilocks field 10e7329a Add FFT-based specification for Poseidon MDS layer on x86 targets ee9bfb08 fix 4e736b63 fixes 534395ee fmt 73633354 test data e6027142 cleanup 54eb29e7 fix 6f6c808d more efficient divmod 202990ed Merge branch 'main' into hash-asm-optimization 2195bdd4 Merge branch 'main' of github.com:mir-protocol/plonky2 into fp318 459d2929 folder 1c71fb34 Merge branch 'main' into bignum-basic 1576a300 Merge pull request #817 from mir-protocol/non-inv e97e8188 fixed iszero and cleanup 12e6527b fixed messed up merge 2a0df523 Merge branch 'main' into hash-asm-optimization 44a0596f fmt 930ebafd Merge branch 'main' into bignum-basic 35fb1499 Merge pull request #904 from mir-protocol/optimize-blake2b 6f8a5100 interface changes b0ed6ae0 cleanup 4ef981e4 initial test data ad38f957 TODO for possible future mul optimization 06276334 carry -> carry_limb e57358bc ge -> cmp and returns 0, 1, -1 d4a485ec Log stack on panic 7fad9eb8 Merge branch 'main' into optimize-blake2b a8956b94 flip limbs 9ec97744 run_ops dd7948e7 merge 5cf8028e Merge branch 'main' into bignum-basic de6f01f4 small optimizations 424d8d22 more optimizations 29df451d optimizations 97cb5c75 bug fix 8f231bd0 optimization 265d39a5 cleanup 85411ac4 fixes 7351a166 fix 684b668b fix 63301d6b refactor sha2 compression 2236f30a more small optimizations e5f8632b small optimizations 213ba8ff optimized initial hash value generation 7c8026e8 cleanup 2020202e optimize hash generation further further df7ea93a optimize hash generation further 3a0d86e2 hash function optimization ef377c0b cleanup 4e8af821 fixes 9ad25b2a optimizations eebdd029 Merge pull request #910 from mir-protocol/optimize-asm d23e4e20 deal with and test zero-len case 4b6a5146 fix 05788a99 compiles d4c7bfd5 addressed comments 725b5a08 cleanup 4a762553 name change c4b511ba addressed comments 2000d308 addressed comments a738afce Merge branch 'non-inv' of github.com:mir-protocol/plonky2 into fp318 24e0b291 Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv 676a483c fix 476a554a OR -> ADD 87ad5714 addressed comments 04f44ef4 addressed comments 9e7dc7ca addressed comments 6f05a144 Merge branch 'main' into bignum-basic 7b2c4c61 Merge branch 'main' into optimize-blake2b ac068845 Merge pull request #909 from mir-protocol/gas_to_coinbase d5003b7c Gas fees go to coinbase 181e4409 Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv 625bdb68 skeleton 6dd99e43 Merge pull request #908 from mir-protocol/fix_call_field 84fbbbf4 Couple minor fixes 54f8dcf4 Merge branch 'main' into optimize-blake2b ce25cc84 Merge pull request #907 from toposware/wasm b3e93e91 Fix plonky2 compilation with wasm32-unknown-unknown target a96418b3 unused test 33ccf898 small optimizations fda2e190 restored blake2b_g_function and call_blake2b_g_function macros 4a378bce Merge branch 'non-inv' of github.com:mir-protocol/plonky2 into fp318 18c83e77 Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv 6d997a65 more tests f2538fff cleanup bac38f82 fix 3a019f99 fix 3662e41d fixes 1100445d cleanup 4f412182 cleanup 0fdd93b8 cleanup f46694e7 more thorough tests 2aff3e10 cleanup 1d7c28ee bug fixes c98bfb0b cleanup af3dc287 cleanup 10893fe0 addmul test: use carry efd5e6ec cleanup 5477c7dd fixes 119eae95 fix 9976a4b0 addmul initial aa605b67 flag functions used only in tests fa605d7b basic bignum 0f55956a optimized initial hash value generation 5994f4d9 cleanup 9d8d81b4 optimize hash generation further further c37d1e25 optimize hash generation further 40f90d83 hash function optimization 70475a5a cleanup 5f592e60 fixes 93abd35f optimizations eea8ab62 Merge pull request #903 from mir-protocol/misc_evm_fixes a6ac0519 Misc EVM fixes 2eed209a Merge pull request #902 from mir-protocol/debug_tries_2 e6aa62f3 Some tooling for debugging tests where the updated tries are not correct 21db4a1b Merge pull request #900 from mir-protocol/sys_gas f117d76b sys_gas f19b7553 Merge pull request #899 from mir-protocol/evm_fixes c558eedd Misc EVM fixes ec216d28 Merge pull request #898 from mir-protocol/move-out-ecdsa 77fb333a Move ecdsa to its own repo 2621d582 Merge pull request #897 from mir-protocol/move-out-u32 18733f11 Move u32 to its own repo b08e7a08 Merge pull request #896 from mir-protocol/move-out-insertion bf8780b2 Move insertion to its own repo 64296bcc Merge pull request #895 from mir-protocol/move-out-waksman b95bc90b moved waksman to outside repo 1ee39b51 fmt ab32f03b fixed multiplication bde5c557 correct mul impl 2c73d5d7 bls field arithmetic 95e5fb59 cleaner rand 6ac59f16 arithmetic skeleton 8ace54dc Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv 57ea64e3 Merge pull request #894 from mir-protocol/fix_storage_trie_lookup 1e1e75c9 Fix code that looks for an account's storage trie e8c94632 comment 1d94756e add inverse doc 5aafbaad Merge pull request #893 from mir-protocol/move_out_system_zero 801fa641 link bfaa80a3 Move system-zero to its own repo 13a8d670 loop test 2ea3e5e3 minor changes e3e5c678 Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv 7781dd36 Plonky2 to 0.1.3 db893831 Merge pull request #892 from mir-protocol/bump_plonky2 2133c7f3 Use new plonky2 52e34265 Bump plonky2 to 0.1.2 79084719 Merge pull request #891 from mir-protocol/fix_hash_or_noop e52b75b0 Fix `hash_or_noop` for general hash sizes d17f3aa4 Merge pull request #890 from mir-protocol/test_fixes 29f0692e Fix a few issues found by EVM tests 2a9d4b1a minor 5e3e40a0 more general kernel peek b89e668b minor f5b45ee4 Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv 33185476 Merge pull request #885 from mir-protocol/skip_log 745bec8d Skip log_kernel_instruction if debug logs disabled e8865130 put extract in interpreter 63f1fbfa fmt be351110 Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv 6f2d99c7 Merge pull request #884 from mir-protocol/evm-kernel-tests ea9846de format da7a8879 make hash functions take a location pointer a6ffb4b3 simplify byte extraction 4e4cfb06 function API / remove redundancy 53ab0ada remove blake storage 77a7af76 remove sha2 storage abc762f7 cleaner arithmetic 731c29c4 abstract c6cf1dc5 remove custom bce86718 simplify ripe md test e2cac0bb Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv f3946f75 Gas constraints (#880) 444da8f7 better comments 80e49caa segment virts d320fbfb update curve add with ops 787cc890 change segment 71243fd7 fix pairing code after big BN PR merge 2158c1d2 merge 9e60ee25 segment ac40bd5f Optimize `ecMul` precompile (scalar multiplication on BN254) (#852) 3332fbb4 Merge pull request #882 from mir-protocol/back_to_nightly ff80f28b Revert "Set CI to use an older version of nightly" 85b33093 Merge pull request #879 from 0x0ece/patch-1 614c4ae6 Make le_sum public 40866e77 Refactor arithmetic operation traits (#876) c6492bc5 merge fix 7b367f5c merge 83c0292b Move SHL and SHR generation to the CPU. (#878) b585b6a7 remove macro 31095e1b stack macro a061b88a naming cecad598 stack macro 361d6d72 tests and stacks cb7c638c more comments 69afed92 refactor 57146c83 miller loop test e63cc2aa Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv 9f808fc7 align 69228491 Unify generation and verification of ADD/SUB/LT/GT operations (#872) 1c73e238 fp -> fp254 61ac0eff fmt 0f030fae naming for global labels c107c505 comments e1dca870 name 962754be rand impl a950a262 add comments cd5c92b5 merge ca002aea Optimize `ecrecover` ASM (#840) 9990632f Merge pull request #870 from mir-protocol/prep_for_publish 137bc785 Prep for publishing to crates.io 81511380 TODO 6c4ef29f Add range checks to the arithmetic Stark (#866) aed617c1 Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv ea01e19c comment 5b124fb1 minor 6e8d4a57 fix 0eef28af bools 3ea8ad90 fmt 6958d46c names and comments 56be7317 comments b46af11f move consts f70243e7 better comments f0a6ec95 clean asm 136cdd05 Remove InterpolationGate trait (#868) 9c8f1166 ocd d98c69f0 better comments 0b81258a stack macros 3bdb2907 Optimized interpolation gate (#861) c9b005d2 new power works 5deb1648 refactor power 60cbdde8 clean 8ca6ba7b clean c13cf972 tate test 75c5938c rewrite w methods ec4cddb7 inv as method 7b524381 en route to ownership 17cfae66 reorg f34b35ed extra comments 94d99cca extra comments 8b670d54 meh 769c615c cleanup 530fb65b cleanup 155e973d slight refactor d2aa937a improved prover input and test api e06a2f2d duh a5c292c7 space 4d783da8 fmt d99cadeb stack macro b2f9d885 remove redundant macros and improve comments 8e62d994 fmt 922d3ebc add module and fix errors 3fcb5591 redundant macro c74a0c25 test inv from memory abab6bf1 test frob from memory 20fb2cb7 read output from memory 5f2baea0 mul test from memory 7f135fc0 reorg b44d9e2d Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv 8ae36474 Merge pull request #865 from mir-protocol/increment_nonce eb7d18da fix clippy 54676487 cleaner description d6167a63 complete description 23698b74 more comments fda4b4c1 more comments 985e8160 transmute + comments 6e215386 comments f2e40541 Increment sender nonce + buy gas 0daaa3bf org bc9c431e remove comments 9977ae03 new inverse fe91e119 frob format 37ad3407 frob format ecde3d13 frob tests 9cd1f8a1 Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv be19cb81 struct impl style arithmetic 31c5db91 rename module ccd4a38a remove make_stack b753836a Merge pull request #864 from mir-protocol/block_circuit b6f6c210 Block circuit 3a6d693f Merge pull request #863 from mir-protocol/smart_contract_test a2f4a58d log df2ba7a3 Basic smart contract test 07e02f2d Merge pull request #862 from mir-protocol/prover_inputs_error_handling a158effe Use error instead of panicking in FromStr 3fbc8bff move comment ea8cfc95 name 2a2880b7 name 800ceb60 zero name e6bcad6c Merge branch 'non-inv' of github.com:mir-protocol/plonky2 into non-inv 446a0d3f name 81861095 Update evm/src/cpu/kernel/asm/curve/bn254/field_arithmetic/inverse.asm 4f38c3a7 name 70d7fb13 cleaner inv 32f24819 Update evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/curve_add.asm 49db35d3 Merge branch 'non-inv' of github.com:mir-protocol/plonky2 into non-inv 42f98a09 Update evm/src/bn254.rs 82ce8153 \n 93a363c1 Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv 068f7485 Update evm/src/witness/util.rs 698ab6e7 Update evm/src/bn254.rs be7a489c Fix stack overflow 8ba8bb62 Merge pull request #860 from mir-protocol/agg_circuit_2 87be6097 Feedback ae212cfb Merge pull request #859 from mir-protocol/remove_older_evm_recursion_logic 14e6e7e9 Merge pull request #858 from mir-protocol/remove_ctl_defaults e4a5c2c9 Merge pull request #857 from mir-protocol/non_tight_degree_bound f4ac2d4f Fix vk 5df78441 Add aggregation circuit 76b3eb30 more fbb72e16 warning e12c6ad5 Remove some older EVM recursion logic 6655e776 Remove CTL defaults 0ca30840 Merge pull request #855 from mir-protocol/fixed_stark_recursion 5719c0b7 feedback 1ecdb96a Power of two length 2e59cecc import 40aecc8e Allow non-tight degree bound 18ce7ea5 Disable slow test on CI 595e751a Shrink STARK proofs to a constant degree 5cd86b66 names and format 2b91a1a6 simplify miller loop de494dcf remove prints 77798f88 remove loop endpoint de8637ce name 053a0206 Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv 32cda213 Merge pull request #854 from Sladuca/bool-or-gadget 403e2392 use doc comment 350b9029 add or gadget cca75c77 remove redundant definition c0744d76 TATE TEST PASSES 1f176734 better vec to fp12 9beca707 clean 84fab8d6 clean d4d80f35 rearrange 826702a7 clean f1d5c6bf tuck const e35644e9 miller test passes ef824110 miller in rust f2787a06 more clean 31ee8987 clippy b1f31caf more cleaning 89093b4d clean up 7af11f43 clean up prover code d5cec0e6 clean up code org 3c566e98 tangent and cords work bde569a2 it runs bf7da1c2 POP 41476ce4 fix cee6c653 hex a99b7d51 setup miller 6a93a6be rename e88e28a1 POWER WORKS e9e5528c space 5aab8ac0 first part works bc3adc16 debug pow 05e83526 test 7cd0dbae setup pow 32758829 refactor 950771a6 clean up inverse 95383db4 inverse edits c4e512ef Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv c2462971 inverse works 7788a29f skeleton inv d90a0559 Merge pull request #853 from mir-protocol/ctl_fixes b8e97aaa Fix logic and Keccak CTLs a503b058 fix 15ee75f2 all but inv d6c7e319 all but inv cefbe248 frob tests work c598b942 refactor 14982d48 naming d0247017 delete dead code 9063e900 streamline tests 7328b653 name 94b999c2 fix e8fe799e name d888ce8c minor a72d4faa minor 5ca2d88b aggregator 7378f380 fix bd898895 U256ify 397864fb merge 4112a240 Merge pull request #851 from mir-protocol/bn_base_in_interpreter 9d6b3b2d Ignore failing test 83a29033 Fixes f91dfe7e Use the order of the BN base field in the interpreter 8c064b86 merge a9f80d38 spacing 806b88d7 Merge pull request #831 from mir-protocol/blake 6ab65800 block_size macro f3937e99 deps fix 53004867 macro a564d735 fixes and addressed comments 24d6627a addressed comments 29143fe5 fmt 2e62ac1b cleanup f6af5240 another clippy fix 779c46c7 clippy fix fc144755 documentation 50ffb907 documentation 778aec62 rename blake -> blake2b d30a95f7 fixes 2166a407 minor memory access refactor 7663848b cleaned up hash tests 7a5a899b clippy 90726a58 fmt c0dbeb42 cleanup 0d05a4bb FIX 49504dde fixes 20169a54 debugging 245e5faa fixes galore d3a72013 fixes 5759fb7b concat 9774b74b Blake progress ebd60662 progress 772dc5c9 util file df932544 fixes and testing a38b1fb3 progress 54a2e964 progress 1367f9bc fmt dd29ec1f fixes and test infrastructure 0cfe7902 updates 5fab01b9 fix 609ed6c9 fixes d3e5feba Blake progress 6e782a1a Blake progress 0c919443 progress b40338ff progress 9a5db4b8 progress a1ea7ff9 progress 1089bbf2 blake initial efa80eda blake initial 4645cc61 Merge pull request #849 from mir-protocol/unused_deps_pass b34b3875 Removed unused deps unovered by `cargo-udeps` 2e2007ee Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv f836e4d9 Merge pull request #848 from mir-protocol/more_cyclic_recursion_changes 92974aa1 A few more cyclic recursion changes b6bc018c Simplify `JUMP`/`JUMPI` constraints and finish witness generation (#846) 1732239a Constrain memory channels in JUMPDEST (#844) 249e50eb Get/set context (#843) 29644e51 Implement `PC` instruction (#847) 64c38572 Merge pull request #841 from mir-protocol/more_timing 94b73e87 backtraces 569cd058 log level 6f841678 More timing for zkEVM proofs f58556c2 Merge pull request #845 from mir-protocol/fix_add_eth 1c78204d fix eth_to_wei 7557f320 Fix to add_eth 4435cedc Merge pull request #842 from mir-protocol/factorial f0e144bb removed confusing grammatical exclamation point from factorial example 95eeed46 Memory load/store constraints (#839) b5a06f92 Merge pull request #837 from mir-protocol/fill_memory_gaps 88f64072 Comments 4ae1d840 Merge pull request #838 from mir-protocol/transfer_test f4ab65f9 Fixes to get test_simple_transfer working 17890dd5 fix miller 8737ba9b fix tate 83328f91 fix dups dbeabb80 storefp12 macro bf9c3246 macros 03c14d03 fixed miller + conts 77ec96f6 power function complete 779a1a3f power function 51dc601a call curve add 0c183467 fmt 48149f93 inverse 97f90b22 update curve add 1c5590fb Add dummy reads to fill any large gaps in memory fields 0ced2b3e div name 57252c7f simplify original 8d60b17e , 3d6f2478 frob fix 3785e312 cord and tangent 8f154020 Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv e9c5c678 minor eae94c5a Merge pull request #836 from mir-protocol/evm_jemalloc fbb26546 Jemalloc for EVM 7d0ba54e Merge pull request #835 from mir-protocol/gen_fixes c28bab1f fmt bffdc553 fixes 1303a83f Misc witness generation fixes a3d8ecc5 Merge pull request #834 from mir-protocol/use_keccak_sponge f4fdb6a1 Fix d091910d fmt b8b2fefe Use Keccak sponge table for bootloading 4a0fbb28 Merge pull request #833 from mir-protocol/witness_gen_fixes 20183c26 Fixes & re-enabling most constraints 520bb5a8 Merge pull request #816 from mir-protocol/jacqui/witness-generation 3c548949 Merge pull request #830 from mir-protocol/more-witness-generation bfa680fc Fix recursive constraints 21719222 fmt b96c22a4 Interpreter fixes 82d0f081 clippies 72930540 Warnings 1f92d731 Misc fixes 74446659 TODO a63b73a8 Misc fixes ce786c7a Halve number of columns used by arithmetic table (#813) 4f66d58d Fixes 05ab3d79 Fixes 9bf47ef8 Fixes 3069363d Keccak fix d3aa3397 generate_keccak_general, generate_byte 25205f31 fix 231042ac Pop 027dfc14 Refactor to support PROVER_INPUT b6326c56 stubs ea0e3748 misc 19e2239d fix cb1b6cbb Generate memory ops 526dc9bb Flush out operation list 97ac5c59 Fixes 906a47a1 generate_push and misc other progress 2471f5a3 Push and arithmetic ops afb3e4b1 Misc work on witness generation 2d92b4b6 Fix warning 206f5273 Merge branch 'main' into jacqui/witness-generation 1b5a3d0f fix 709b520c miller loop 73de231e Merge pull request #827 from mir-protocol/cyclic_recursion_tweaks 3515fbdd fix ad58dcbc fix c3ae52f5 fix b23193ba use hashmap 319d9b5a Feedback 55e5d567 Trie fix 7612c39c Merge pull request #828 from mir-protocol/proof_with_pis_reference 5a153278 fix 644a8a23 Make `proof_with_pis` a reference c528da4e Cyclic recursion tweaks 5ee283b2 Merge pull request #829 from mir-protocol/ci_nightly_freeze da23fb11 Set CI to use an older version of nightly 8af4cd17 Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv c3dcdfd5 tate 87a9c002 Compiler errors + refactor d5270734 Merge pull request #824 from mir-protocol/cyclic_recursion_tweaks 964d2bc3 Fix test 40481078 Cyclic recursion tweaks 8c5d6b0a Merge pull request #822 from mir-protocol/domain_separator 7ec14029 Fix comment af1b6680 Switch to Vec 1abfb6d9 Merge pull request #823 from mir-protocol/generic_load_code 6b34f4ff alphabetical 307cbbd2 Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv 1b4acf59 Make load_code a bit more general 108cb836 Domain separator option d1d08937 Merge pull request #821 from mir-protocol/jumpdest_analysis d23cecfc Kernel code to do jumpdest analysis 08cabf2a Merge pull request #820 from mir-protocol/serialization_refactor 05f4d2be Remove conversion b0be6d7e Serialization refactor c31b0147 Implement `CALLVALUE, CALLDATALOAD, CALLDATASIZE, CALLDATACOPY` in interpreter (#819) 9a68574e frob macro 84c1954d tate 25013860 Add missing feature to field crate. (#818) 68cde336 reorg 76917580 Merge branch 'openzklib-feat/no-std-support' af2349e9 Imports 0ed92ab8 workflow_dispatch 02718177 workflow_dispatch 9a43084f Merge branch 'feat/no-std-support' of https://github.com/openzklib/plonky2 into openzklib-feat/no-std-support 7720ff37 Merge pull request #815 from mir-protocol/fri_pow_in_transcript 1732399f Remove comment e22da77b Include the FRI prover's PoW witness in the transcript d2bd64f8 Merge pull request #814 from mir-protocol/randomize_pi_wires c83dccca macros 8a7d6c65 frob_fp12 ca92057b frob_fp6 b779b825 mul_fp2 c1f7d1ce comments 3dc66a25 fp12 sq works 65d106bc chore: remove derivative dependency as non-crucial 2520bd62 chore: match hashbrown ahash dependency 77820b0f fp6 sq 205bd58f Witness generation work 812a5d46 chore: merge branch `main` 289498e8 fix: address review comments 14c2a6dd Fork Update (#3) 56da8c02 correct ops a8a852f3 sparse mul works! f6e45ea4 fix mul_fp2_fp6_sh2 a3dfea9c update fp12 69ce4f99 add total count e0ee489d fix fp12 test 7eb0c741 Randomize unused wires of PublicInputGate c854b2d8 Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv 56e291cf Remove signed operation placeholders from arithmetic table. (#812) ad645ece aggregator 784a4c08 scaling macros 5d2a9b3d sparse mul finished 1c87fbb7 EVM shift left/right operations (#801) 7126231b Merge pull request #811 from mir-protocol/test_reverse_index_bits 624dabb9 clippy c622e452 Test reverse_index_bits 692ad939 Merge pull request #810 from mir-protocol/le_sum_check 88229787 security notes cab6c18e Check that `le_sum` won't overflow 626c2583 Combine all syscalls into one flag (#802) 98b9f3a4 Merge pull request #809 from mir-protocol/update_criterion 3dcc0c25 Update criterion ce21d134 Merge pull request #804 from mir-protocol/static_kernel_in_tests 873f4583 Merge pull request #805 from mir-protocol/opcode_count_interpreter 9431fd74 chore: add missing documentation in serialization trait 7e432bd6 feat: add serialization documentation 9e33310e feat: add no-std support for insertion gate 5dfe1b41 feat: add no-std support for starky 4bc2e057 Print opcode count 47e6093e Use static `KERNEL` in tests 9f4dc346 fix: add architecture gating for inline-assembly fc3f6339 wip: start moving starky to no-std 38e467f1 chore: fix serde-cbor and run cargo-sort 703d2c3c wip: start plonky2_u32 and plonky2_ecdsa no-std impl e2cdd5a9 feat: upgrade Sampling APIs 4aaf57e9 feat: separate reading and writing to get infallible writers 7a81c5d4 feat: move to alloc for Vec/String/Box 6fd0da21 fix: remove unstable features from plonky2 11600b93 fix: do a first round of a core replacement ea7fbed3 Constrain `mem_channel.used` flag to be binary (#800) 0293a65a Fixed bad PR merge 35e8f282 Merge pull request #792 from mir-protocol/eth_trie_utils_0_3_0 0066f079 Merge pull request #796 from mir-protocol/account_code_opcodes 68107502 Merge branch 'main' into account_code_opcodes 313402de Merge pull request #797 from mir-protocol/balance 81983555 Fix + Use context=0 70b5dda3 Merge branch 'main' into account_code_opcodes 35c00fa6 Return 0 if account doesn't exist ff4210ea Return 0 if account doesn't exist 215e9a6e Merge pull request #798 from mir-protocol/use_address_macro 8736435e Updated `eth_trie_utils` to `0.4.0` 2e7da175 Bumped `eth_trie_utils` to `0.3.1` f3d4e57a Use address macro instead of opcode 7647c5b5 Use address macro instead of opcode 84151f08 Merge pull request #765 from mir-protocol/Fp12 4380a037 comment dead code 6ed65742 Balance aebeb7ac fmt ccf5cda1 spacing f90c058a minor ad067d1e comment about 107 7d78916a merge fa05a330 Merge branch 'main' of github.com:mir-protocol/plonky2 into Fp12 a8ecdc0f improve comments f4941b01 Merge pull request #782 from mir-protocol/cyclic_recursion c47f767f PR feedback fd96d30e Minor dc8a62fb Codecopy test 6bee508f Clippy 300de9d4 Merge branch 'main' into account_code_opcodes 4c0b3b60 Working with random address and code 8b1152c3 Working codesize eb192252 Merge pull request #794 from mir-protocol/fix_keccak256_word 3026533a Fix `keccak256_word` 03d7f3d1 Found bug 0d4cf5be Minor 8a60fe19 Cyclic recursion gadget doesn't move circuit builder 73e9e611 `CyclicPublicInputs` is just `VerifierOnlyCircuitData` 341e1ebe Working faeb6e0b comments 71587146 minor f6a30cba better comments 59dc9b2d clean up test 7afbddb0 Add `goal_common_data` to `CircuitBuilder` e8fd8bd1 Add type hints a0eca98c Merge branch 'main' into cyclic_recursion 258d329a Merge pull request #793 from mir-protocol/remove_config_from_common_data b97ec3bd New clippy lint 0e58efdc Remove Config from `CommonCircuitData` f28ff42f PR feedback 11c232a8 fmt cddc22e9 better comments b3ddccda cleanup 05885809 fp12 passes tests! a424916e cleanup comments 9a001487 better comments 32f1e679 fp6 test passes 2d7f3e73 new fp6 560b9b2a Finish extcodecopy 9e21a25a Merge branch 'main' into account_code_opcodes 6b4cce21 Start 7b2f515c Merge pull request #791 from mir-protocol/kernel_msize 4af2ede6 Implement DIV instruction (#790) 06e0dd64 fp6 as fn 3ccafd88 richer comments 1c2fab67 richer comments 7d4cec55 fp6 mul cad27241 finish macros 77d5c625 Minor 0a800f82 Minor fab3fe77 Minor 9982d799 Add msize 05fa0490 load/store macros b790af90 addr on stack dc59ed10 bus -> subr 71ed3c43 Fix fix interpreter 61b6b161 Fix interpreter f55e0765 Implement SUBMOD instruction (#789) 37e429c9 more comments d7fdccfc more comments 6451190d replace mul_const eb4f8fec minor 3b8b812f aggregator 0c0775da casing? 1f39053d detailed accounting dde24e6b fmt 62554456 change to 107 6fc34f6a lint 041fda13 capitalization?? 0847d988 alphabetical 9222dafe clippy 75cabedc better abstraction 574a5744 Merge pull request #788 from mir-protocol/storage_fixes a2edff46 Small storage fixes 076fe521 Merge pull request #787 from mir-protocol/mpt_storage 9639ff22 MPT storage logic c9bbd2df naming 6a6fbec9 remove fp6 test ccbf85d5 remove fp6 test since it's redundant b9a1b441 move fp6mul and add more comments cc9559d9 Merge branch 'main' of github.com:mir-protocol/plonky2 into Fp12 8a85cd30 fp12 is running d475ab93 fp6 passes randomized tests b534b221 fix fp6 subtraction---fp12 tests running! a0f7e661 fix fp6, better test function 8f9c5a78 Merge pull request #786 from mir-protocol/mpt_dirs 3cff0928 better test API c3e9827b Minor 09cee22d Better test 36656793 Fix conflict aea721e7 Merge branch 'main' into cyclic_recursion 598b91c3 Merge pull request #775 from mir-protocol/generate_dummy_proof fb94ace3 Fix conflict 1ae922dd Merge branch 'main' into generate_dummy_proof 972d8368 PR feedback f125786d More directories for MPT logic e0fe4bcb Merge pull request #785 from mir-protocol/storage_trie_inside_state_trie 7f366cda Treat storage tries as sub-tries of the state trie 34865026 Merge pull request #784 from mir-protocol/avoid_current_memory a8e30b0c Switch a few uses of current-context memory to kernel memory 3296f278 Merge pull request #781 from mir-protocol/redundant_degree_bits 2aeb8c92 Merge pull request #783 from mir-protocol/mpt_tweaks ecce5be9 MPT format tweaks 35b173ed Comments 51cea8d9 `base_case` is decreasing fce7a479 Working 861b66a3 Test passes for base proof adc8c33b Fp12 fc2ea628 Fp6 mul test passes d3e2b982 new op codes e01c91bd Merge branch 'main' of github.com:mir-protocol/plonky2 into Fp12 ec3391f9 Add Fp254 ops to the CPU table (#779) 69bdbf6b Redundant `degree_bits` f1945533 Test not working 29e0aef3 New recursion folder 66a0e772 Working in test f497fc65 Merge branch 'generate_dummy_proof' into cyclic_recursion 325bab11 test d1bad819 stuff 22d3454f Merge pull request #778 from mir-protocol/eth_trie_utils_0_2_0 06475c2b Bumped patch version cb2e69a2 Updated `eth_trie_utils` to `0.2.0` 581fcce0 Merge pull request #777 from mir-protocol/mpt_insert_7 299aabf8 Fix branch hashing bug 0b2661f8 Merge pull request #774 from mir-protocol/debug_offsets f4c0337a Interpreter feature to configure debug offsets 68a54285 Represent input columns as ranges rather than arrays (#776) e17823e7 Disallow zk for dummy proof 0013bd43 Minor 3cd337ab Comments 816e7db3 Working 0d006755 Refactor and tidy up `mul.rs` (#764) a468e466 Merge pull request #773 from mir-protocol/mpt_insert_6 caf928b1 MPT logic for inserts into extension nodes 01055a51 Merge pull request #772 from mir-protocol/mpt_insert_5 50002df8 MPT insert into leaf, overlapping keys case 8776cee4 Merge pull request #771 from mir-protocol/mpt_insert_4 cad0473e More MPT insert logic 39a72274 Merge pull request #770 from mir-protocol/mpt_insert_3 33dba3a2 Insertion optimization for leaf case 01503434 Merge pull request #769 from mir-protocol/mpt_insert_2 4a055b3a MPT insert logic, part 2 fb7ef319 Merge pull request #768 from mir-protocol/mpt_insert_1 6bb1ad94 MPT insert logic, part 1 49eecfad Merge pull request #767 from mir-protocol/fix_clippy_cast 443a0700 Clippy fix 8ee72658 Tweak MPT value storage 817156cd Begin MPT insert f83504b1 separate module + stack comments 7b1db488 Fp6 mult purely on stack 00534286 Fp12 mult + Fp6 macros 39fc7a2a Merge pull request #766 from mir-protocol/more_inc_dec_macros e6b5e365 Some more uses of %increment, %decrement d02c9bdd Fp6 mult 4690db7c Add PI 924f0dd4 Merge branch 'main' into generate_dummy_proof 4cc2fdb8 Implement Eq for CommonCircuitData 992692b0 Merge pull request #708 from mir-protocol/per_table_recursion 4ff6bbb3 Hardcode verifier data in the circuit d7bb4731 Modular operations for the EVM arithmetic unit (#755) d2dcfb58 Merge pull request #763 from mir-protocol/mpt_hash_ext f8c10403 Hash MPT extension nodes b832c6ab Update spec 40b2fec4 Merge pull request #762 from mir-protocol/mpt_fixes ed2aac3a MPT fixes 0424fe68 mload_packing 189719ff Start of `dummy_proof()` 39fc2193 PR feedback 0bc3f204 PR feedback d4f26562 Merge pull request #758 from mir-protocol/conditional_recursive_verifier 2bb63a6f PR feedback 4fe4a006 Merge pull request #761 from mir-protocol/mpt_hash_branch f2cb42bb MPT logic to hash branch nodes 47a37c5f Merge pull request #760 from mir-protocol/rework_mpt_hashing 0c9847ab Rework MPT hashing to support inlining <32 byte children 7ccc6733 Merge pull request #759 from mir-protocol/ripeFIX 7c7084a8 clean up and format 0afb9b0c fmt 9ebbc033 fix padlength issue ce0a4f44 Use `ArithmeticGate` for `select` 66c21931 Minor 52c82f0c Minor 2982f45a Add test 3260031f Select logic 1275bcca Merge pull request #757 from mir-protocol/eth_trie_utils_crates_dot_io bf57fe98 Now uses `eth_trie_utils` on `crates.io` d8bf3015 Merge pull request #756 from mir-protocol/rlp_fixes 0ccb340e RLP related fixes e515f1e1 Split circuit and witness generation dd6c5a0d Merge pull request #640 from mir-protocol/ripeMD 95128cbb done 53014b73 almost done d48f6314 Merge pull request #754 from mir-protocol/sha2_inline_consts 474ac478 Merge pull request #753 from mir-protocol/unroll_num_bytes 1475cddb rearrange c03773ba Inline some SHA2 constants 54885fef fix merge problem 66c28e95 Unroll num_bytes 6e5fe43c delete duplicates 295bd60e Merge pull request #752 from mir-protocol/hash_kernel d42250d6 merge d0caf8be Merge pull request #671 from mir-protocol/sha2_precompile 5e322415 Fill in hash_kernel 43df58ea alphabetical 9919562a clippy 99fb730a rearranging and cleanup 681b6e31 minor 9ee861fb minor fixes 6ff0b84e fix 249fc6c1 fix 416a7a86 fmt 69e33eff fix 9f49521e label name simplification 1e03c438 moved memory functions to memory ASM file (not sha2) 1f3ee6da remove prover_inputs from Interpreter fa01f83e Update evm/src/cpu/kernel/tests/sha2.rs 19b14c27 Update evm/src/cpu/kernel/tests/sha2.rs cfbc029e cleaned up test: compare as U256, not string 42320d81 fix 65b8993b addressed comments c11c5264 cleanup and comments 5d7edb33 comment 7eba4227 addressed comments 78a4b92e in %stack, treat identifiers as 1-length blocks 7eadfee5 removed parentheses d1d404b9 addressed comments 5cf8bf2b more %stack sha2 cleanup 140242c5 more %stack sha2 cleanup 5ca5a3b3 more %stack d8f2e04c more use of %stack macro to make sha2 cleaner e482bc7f addressed comments 83c959d9 opcodes to uppercase, and cleanup 2e3366d1 started on using %stack in sha2 asm fa3436b1 removed JUMPDESTs 790b32c3 fix cad56263 fix 0eab1a4b fmt e652ef92 simplification and documentation 9f923f7b cleanup f3e48dcb fmt fee0963e cleanup 4b2f1a48 split up sha2.asm file 0394fa3f cleaned up test db718900 randomized Sha2 test 5b757568 fixes 8ebf4c8f more fixes 67e19fd7 fix of message schedule b7c9f2cb fmt 8e067dcf cleanup 0150c2f0 cleanup 9f5d75fe fix f4207e75 sha2 testing infrastructure 0d14ef4e clippy 7d2b17f4 clippy 94d967f5 many fixes 0ea13882 fix b42d3780 fix dea9d064 fix 2a2152db updates 7b35433a more compression 42019264 compression deddcf6b cleanup e13841dd MESSAGE SCHEDULE WORKS 84273889 many fixes 2601109d debugging, and progress 4905f499 fixes c40074a0 fmt 54e96a9d many fixes 79e4d80d fmt b1b95e7b clippy 4624ce51 fmt bff3da1d removed duplicate macros faa1023b fix 39609409 fix d6f6fc75 fixes e4521c48 a great many fixes 05eb70f9 updates 2d34a9d2 finished sha2 pad f90cfd0f fixes and updates 4b3ce01f fixes 6a31a4b7 fixes c24af372 fixes baa4bd4e files 87e06946 first test, and fixes 268c6a11 mstore_kernel_general_u32 macro c2919032 updates f0dd1fd3 updates 92488039 h constants 89e5a040 constants 8dbb653a memory commands eb6095cd message schedule progress 615ece22 progress 92b14fe7 functions --> macros a357a34b redest, and progress 05837039 constants as macros instead of functions 94e2e984 fixes caa1aeee progress 3140c77c progress 89c79208 new padding 476e7691 ops 723b047d constants aba45e2d finished pad 0d22b3f8 fix 7045772c fixes dd2cbf60 updates 2c7b60e2 using memory 4378ff0f progress 042da0c8 starting on sha2 12695447 minor 32b8b359 move buffer_update to memory module 2f97ad44 fix arg order for memory version 24d24141 Merge pull request #751 from mir-protocol/move_constants 4d8f618f put macros in more general module 1e7c33e8 extraxt box into own module d5cf53c2 Constraints for dup/swap (#743) cb88dadd clean up macros, remove ripemd segment 0dc1a403 Merge branch 'main' of github.com:mir-protocol/plonky2 into ripeMD 5a1cf8bf Move some constants 7d7afe71 Merge pull request #750 from mir-protocol/storage_tweaks 25a448e2 Minor refactor of RLP code 3be208ed Minor a5a4098d Merge branch 'main' into per_table_recursion 66895717 PR feedback 3579f9e8 state() -> compact() 1978c2ad Merge pull request #749 from mir-protocol/storage_misc 0de392b3 Fix optimization 9f9143d6 Finish some misc storage logic 9d22e376 Add TODO 718732ee Merge pull request #748 from mir-protocol/mpt_hashing_3 9e483528 MPT hashing logic, part 3 239ecedb Merge pull request #747 from mir-protocol/mpt_hashing_2 f2f05952 MPT hashing logic, part 2 6b8a18b3 Merge pull request #746 from mir-protocol/mpt_hashing 12247047 MPT hashing logic, part 1 c5f63f88 Merge pull request #744 from mir-protocol/bootstrap_fix a7b51800 Merge pull request #745 from mir-protocol/empty_test 8b58725f Fix bootstrap channel indices 1dbd96ba Empty txn list test (disabled for now) b26a2845 Merge branch 'main' of github.com:mir-protocol/plonky2 into ripeMD faaaa0e0 Merge pull request #742 from mir-protocol/msize f6ff0784 fixes 09ba1b6f Merge pull request #740 from mir-protocol/h256_trie_roots ea135341 MSIZE 10f4a0d0 Merge pull request #741 from mir-protocol/txn_loop c721155e Main function, txn processing loop 7e684496 Few small changes related to switching to `H256` d6b3522b reverse bytes via BYTE code 8e08b218 Trie roots now use `H256` instead of `U256` abff9788 Daniel's comments 58256ce0 Merge pull request #718 from mir-protocol/fix_basesumgate_numlimbs cb620bc5 Simplify `num_ctl_zs` e1cdd824 spacing 4c9e80f2 Merge branch 'main' of github.com:mir-protocol/plonky2 into ripeMD ee54b295 Use u64 instead of usize e978425b Connect stack to memory (#735) 7202f403 spacing 8f18f815 unused macro fb3580e5 merge 71d8384a Nick's comments e3131f5f Merge pull request #739 from mir-protocol/mpt_read_2 5555085c MPT read for extension nodes bfb1f54c format fc761f7c SHR ea31a803 merge 9e02e24d Merge pull request #730 from mir-protocol/plonky2_examples e2811550 addressed comments f3e1a73e all reference tests are working! 29b2552f majorly simplify update2 9e6ba6d5 Merge pull request #738 from mir-protocol/mpt_read 9b59d02d loop thru official ripemd tests 0e48d581 Finish MPT read logic 7d7269b2 nit 20053ac4 documentation 33d97eff moved sqrt to PrimeField a0533721 cleanup and documentation 3bc1e65a fix 01b310d0 all small inputs work 4668e8c5 clippy 59acd943 fmt d239d3ff fix 880bc87b sqrt 9f5e5654 fix update2 4850c338 clean up 1de3ed82 Update comments 6cf6b56a Method to compute verifier data without proving bda96e84 working on SquareRootGenerator instead of SquareGenerator 843baf1a documentation b21883c3 fmt b271a71a square root example: use generator ecdac539 fixes to fibonacci and factorial 44a1f4c3 no need to hard-code! 0381641b addressed comments 8bd5f43c oops, included other examples 6d81968b use data.verify 38d6f98f fixes, and new examples (fibonacci and square root) 9756e06d reformat 556507a9 public input 849a8910 fmt 2e6480a9 Fibonacci example c80e9e4e Merge pull request #737 from mir-protocol/mpt_2 c7b03cfe More MPT logic 705b4a43 fix length zero input issue 85b88a1c fix padlength overflow error abcb93d0 assert as U256 36c4b480 better test API 58b6771c fancy new stack macro 39ae6426 Merge branch 'main' of github.com:mir-protocol/plonky2 into ripeMD 125ad565 Merge pull request #734 from mir-protocol/stack-manipulation-empty-lhs 26fcd9ee fmt 2b298e39 stack manipulation: allow empty LHS 243eb265 Merge pull request #733 from mir-protocol/keccak_bench 14488b2a Merge branch 'main' of github.com:mir-protocol/plonky2 into ripeMD b6d71a70 Keccak benchmark 3cceede4 format, stack macro, remove prints 41ce8e94 FIRST UNIT TEST PASSED! 24606dec full run but fails c6b62eaf fix update 8c96b8d2 Comment 0053a021 Cleaning 50230514 Working a63ed604 Add CTL verification e712986a Challenger state works bbeac10b compression test works! 4a5ddfda shr 0dfd1b64 fix stack manipulation a816f4b6 Merge pull request #732 from mir-protocol/macro_overloading dbb0503d Support macro overloading 084700a7 Memory channel for program counter (#717) c27e40e7 Merge pull request #731 from mir-protocol/mpt 37d92b55 Basic MPT logic 7342b965 test compress f45d6593 fix constants 81675e6e cargo format 3e371e54 style 7ee58355 test is running, but failing 65b393cb assembler is running 9506c647 fix macro 2c43da80 Fix 6e6c2daf Add challenger state 93795578 cargo format cfa01865 Nick's comments a25aea8e Nick's comments aa5537a9 fake test 1867e5fe Merge branch 'main' of github.com:mir-protocol/plonky2 into ripeMD 28f98074 0xdeadbeef 4783a90c tweak 94390c3c fix 98d9e6ca parse error 7a4f5e43 Merge pull request #729 from mir-protocol/generation_inputs_contract_code 8fb1e4e7 Added a mapping between code hashes and contract byte code 6cac2d79 use blocks 3da80fff Merge pull request #728 from mir-protocol/fix_prohibited_macro_names 218f6894 Fix prohibited macro names 64041e1c Merge branch 'main' of github.com:mir-protocol/plonky2 into ripeMD 3b0dda77 fix test a84d3f5d Merge pull request #727 from mir-protocol/fix_macro_vars_in_stack f876a8ab Fix macro vars in %stack directive bb968bd7 it runs (but forever...) 172bde80 everything is parsing d5f04cbf Merge branch 'main' of github.com:mir-protocol/plonky2 into ripeMD 9f1e97ed fix bugs c127f80b add to include files ee575f7c Merge pull request #723 from mir-protocol/validate_shape d7d50e9d Minor 5d4d81c2 Shape check in starky f8e0b6f6 fix 616a6b39 Validate EVM proof shape e36c17b6 test 5d25683b test running a6f72ee3 alt api for testing 76d50f33 duplicate macro, test skeleton aa862b76 delete needless d032b7fb merge 61938277 finish?? e20b76f1 Use salt_size 74ab7410 Update plonky2/src/plonk/validate_shape.rs 78682648 Update plonky2/src/fri/validate_shape.rs dbce3568 Validate the shape of each proof ce64ccdc Merge pull request #726 from mir-protocol/zkevm-spec 4d873cda zkEVM spec e151be55 ripemd storage 928e8bc0 Merge pull request #725 from mir-protocol/eth_trie_utils_lib c45785f4 change to 8 bit words 1783f971 add ripeMD segment d7d8803d Replaced `PartialTrie` definitions with `eth-trie-utils` crate 11a2099f Merge pull request #724 from mir-protocol/fix_dthroot_deg1_ext 3007b5e7 Fix DTH_ROOT for degree 1 extension e1a1b05b update skeleton ffbb6fc6 outer function 3fc7996d Merge pull request #683 from mir-protocol/call_common 9d1d179e Verify that comparison output is zero or one (#715) 67071a0c Merge pull request #721 from mir-protocol/useless_num_virtual_targets fa69f2a7 remove jumpdests / add macro 51639687 remove jumpdests / fix name 32cf13da update 7d9e8136 Python prototype of cache-oblivious FFT (#722) 145eb586 flip bytes of a u32 macro f09aec1b scale indices by 4 d8ac2ced diff name 8049a5da consume offset e86c7034 load little endian dc145501 Remove `num_virtual_targets` from `CommonCircuitData` b933e43c Merge pull request #720 from mir-protocol/stack-manipulation-block-fix a5f34d9a fix b25986ce parentheses change 0aecc2a3 Feedback 2a37aeca Move to `util` and rename b08a5772 allow offset variable e8eca780 Fix num_limbs in BaseSumGate 8647f144 Merge pull request #716 from mir-protocol/s/l1/l0 a930c1a8 s/l1/l0 e4ab93fe Merge pull request #714 from mir-protocol/stack-manipulation-blocks 11666f02 Clippy 554f62fd Merge pull request #712 from mir-protocol/comment_batch_opening cae5f487 Stack pointer + underflow/overflow checks (#710) cf80668c formatting 64e9f6f0 final error and formatting 0b9881c5 blocks in stack manipulation 9561dceb fix 3bfb994c fix errors 7ed78c22 minor errors 09f06248 Merge pull request #705 from mir-protocol/packed_len_div fdb6cafe Fill in call_common routine e127d5a4 Merge pull request #713 from mir-protocol/tweak_features 19162db5 Tweak features 8c275c49 Merge pull request #711 from proxima-one/add_flat_map_iter f1e21ffb More comment 8fd4fc43 Minor 7fbbd301 Update paper 6f98d6bc Comment batch opening aa0f0f6e add other features back aaba931e fmt e72152ee fix default features in starky & evm f496711b add flat_map_iter to maybe_rayon & feature-gate it d5fbcae3 Merge pull request #692 from mir-protocol/gate_documentation 00c43951 Add prover_data method c874fc54 Merge pull request #704 from mir-protocol/partial_trie_derive 405f24b4 Merge pull request #706 from mir-protocol/remove_jumpdests 9b259cb9 Feedback 92a0075a Merge pull request #707 from mir-protocol/par_fri_queries 11bdd501 let_chains a7609045 Clippy e6708da3 Comments a5f4730b Minor 35b22974 Recursively verify e6490fdd Add verify da03af29 Minor c320a9e8 Merge branch 'main' into per_table_recursion 1c2e94f9 Compute answers to FRI queries in parallel aaf7ace3 Remove `JUMPDEST`s 99999f16 Use `ceil_div_usize` for `PACKED_LEN` f2f29160 Merge pull request #702 from mir-protocol/keccak_sponge_table_v2 496581cf fix 46cf46cc Minor c9cfcecc Logic CTL for xor d392ec04 Feedback 0a3455ce Added a few derives to `Trie` types dc69d6af clippy fix: 'needless borrow' c80bc9f2 fmt 1ca46f76 fixes 3e388658 documentation 336046d8 cleanup for interpolation ba28919d more comment fix b93f92e6 comment fix 0ac0975d RandomAccessGate documentation df150311 clippy: remove unused 'peekable' f59431da clippy fix: 'needless borrow' 2c77247d Keccak sponge STARK ccc2a56b Added `let_chains` feature gates 4c52d375 Save columns by verifying invalid opcodes in software (#701) 97ead079 Merge pull request #703 from mir-protocol/keccak_util 8505d64e Fill in `keccakf_u32s` 013bf647 Transpose memory columns (make it an array of channel structs) (#700) 08758a3b newline 6087df5b Merge pull request #698 from proxima-one/cleaner-witness-extension f48de368 Make jumps, logic, and syscalls read from/write to memory columns (#699) 356c7cd9 fmt 8aa3ed09 cleaner witness extension 70971aee Merge pull request #697 from mir-protocol/const_num_tables e7edfdd6 Minor a1941308 Minor 05c3c4d9 First pass 2fa34712 All recursive proofs c4fc9b0a Merge conflicts 8600a5a4 Merge branch 'main' into per_table_recursion aae9e49e Merge pull request #696 from mir-protocol/public_memory a4300758 Fix test d0be79e8 Feedback 66a39996 Keccak generation tweak b829b44d Fix test aa87f2c3 Public memory e6e6099c finished hash loop 50c9638b EVM arithmetic unit: unsigned comparisons (#688) 9671c1e5 Merge pull request #669 from mir-protocol/keccak_memory 3e671155 all but blocks cf5e828c Merge pull request #695 from mir-protocol/move_assertle_gate 30cc318c Feedback a41794b5 fixes 56591711 added gate oops 06e3545b Move AssertLessThanGate to waksman crate 9e9ff987 Per table recursion a96ddeee Merge pull request #694 from mir-protocol/remove_compressed d9c210b2 Remove compressed proofs in EVM crate f1a5b7b2 Delete opcode column (#672) f0a23a7f Merge pull request #693 from mir-protocol/partial_trie 6b385359 Feedback ff228c93 Have witness generation take a partial trie instead of Merkle proofs 095140fd Use KECCAK_WIDTH_BYTES aebcdd52 Merge pull request #691 from mir-protocol/remove_keccak_rust 41120656 Fix fb34b098 Remove keccak_rust in favor of tiny-keccak c140555f Fix 74cb9074 Minor fixes 7144b1e0 Merge pull request #690 from mir-protocol/include_degree_in_circuit_digest 74849644 Include degree in circuit digest 522cac5e Keccak memory stark 54862cb7 Merge pull request #687 from mir-protocol/simpler_memory 3a73b733 Merge pull request #689 from mir-protocol/ctl_challenge_fix c38a98f9 Simpler CPU <-> memory CTL 8e220ac6 Fix for CTL challenges ca230010 add constants 881dceb1 Merge pull request #679 from mir-protocol/build_artifact_caching 9da698f0 Merge pull request #686 from mir-protocol/revert-682-ctl_prev 782d7d0e Revert "Support accessing local row in CTLs" 03b4d0a9 all but memory c3c21335 Merge pull request #685 from mir-protocol/deterministic_iteration_order cc02371e Merge pull request #682 from mir-protocol/ctl_prev 1b9c4778 Enumerate `constants_to_targets` in a deterministic order 00081890 feedback 03657643 Merge pull request #680 from qope/ECDSAPublicKeyTarget_ECDSASecretKeyTarget_public_fields ce456fb8 Merge pull request #665 from mir-protocol/trie_metadata a37dec98 Support accessing previous row in CTLs 47753e08 Testing forcing caching of local packages 776b93e1 Merge pull request #681 from mir-protocol/poly_values_check e87392bd comment 464b2329 Check each `PolynomialValues` len 21912966 Change visibility of `ECDSASecretKeyTarget` and `ECDSAPublicKeyTarget`'s fields 8a8b3f36 Merge pull request #678 from proxima-one/fix-starky-avx 3c3997b7 Added caching for GitHub Workflows 3a6bbd82 Merge pull request #677 from mir-protocol/u160_comments a0de1869 Merge pull request #676 from mir-protocol/from_biguint_change ca355026 fmt 3eadc27b add fix to evm ff961a34 fix lost evals when P::WIDTH > 0 831a6718 Tweak comments 2fd5fbbe Change `from_biguint`'s behavior with extension fields cbfc13c3 Minor paper update 5922c587 Change logic limb size to 32 bits (#674) e3d131b9 Update paper 24cc29ff Merge pull request #673 from mir-protocol/start_at_route_txn bfe86d70 Start with PC=route_txn instead of 0 dc2d4b89 Merge branch 'main' of github.com:mir-protocol/plonky2 into ripeMD b98dd478 Permission levels, jumps, traps (#653) a7618ef7 Merge branch 'licenses' 2bb2be37 Merge pull request #668 from mir-protocol/inverse_2exp_comment bf4e7ff3 Feedback c56b7c81 Expand inverse_2exp comment 232303b9 Merge pull request #666 from mir-protocol/rlp_encode 97b271bf Merge pull request #663 from mir-protocol/use_mstore_txn_field e9f38ee5 Merge pull request #667 from mir-protocol/keccak_improvement faa75178 MIT + Apache2 licenses 61819af0 Improved Keccak implementation 539152d7 RLP encoding functions 6a7e8d0f Trie related segments and metadata f52e0053 Add static 137076c9 Revert "Add static" 90c4e1b9 Add static 6ea801d9 TODOs 4c0c93ff Merge pull request #652 from mir-protocol/transaction_processing 74b1fd25 TODOs cc61c721 Core transaction processing logic 1763b6bc Merge pull request #662 from mir-protocol/stack_pruning_opt_perms cf0de26d Merge pull request #664 from mir-protocol/fix_shift_propagation 288ff639 Fix shift ordering 707e5649 Make use of `mstore_txn_field` in type 0 parsing f9efc3ae draft implementation 26574f8b swap shift orders 763d63de For permutations, find the optimal sequence of swaps 68de3ee0 Merge pull request #660 from mir-protocol/packing 48f17e48 space 46338454 fix merge conflict ac4d7820 merge 93d25859 merge 65a20bcd Merge remote-tracking branch 'proxima/log-portability' 86c1493d Merge pull request #1 from mir-protocol/log-portability-stubs 385a990c Unsuppress warnings 1e5383c6 Stub push/pop 0263116e Merge pull request #661 from proxima-one/no-rand-2 e7216f26 feature-gate rand 3d5a9174 remove explicit feature include 94def938 Merge pull request #654 from proxima-one/make-gates-public ed3ac7b2 Merge pull request #658 from mir-protocol/memory_stark_fix 5f14bef5 Merge pull request #655 from mir-protocol/more_metadata ccc4202d Packing memory operations b741458d Merge pull request #659 from mir-protocol/macro_labels 616eb618 Support macro-local labels bbcb4195 fmt bf4cf1c6 fix 9d3d3afe Merge pull request #657 from mir-protocol/more_memory 1e6cf4c4 newline 7423124e Split up memory asm and add more helper functions fae653da Missing retdest 233584e9 Merge pull request #656 from mir-protocol/min_max 7481831b Merge pull request #648 from mir-protocol/optimizer dfd715fa Fix case where a valid constant propagation a broke test f5899016 min, max macros b4d83f8d More metadata fields 90be4749 Merge branch 'main' into optimizer 9b5b77d3 Check if suggested code is actually better 8aad0b07 Feedback 64168266 Feedback 8c515b4f selectors can stay pub(crate) 243bc092 make modules public 5563176b make rest of gates public 002b568a fix cb2df9fa More commutative fns 139bec35 Merge pull request #651 from mir-protocol/asm_constants c167da8c Revert "UserspaceProgramCounter" 3b54ec39 Feedback 8bb45203 More succinct deadbeef f9513455 Update evm/src/cpu/kernel/global_metadata.rs 74fc3b4c Merge pull request #650 from mir-protocol/type_0_test 215be25c Feedback be0a5269 UserspaceProgramCounter 3f08cca1 More constants for kernel ASM b34ace4c More succinct deadbeef 94c9b1b0 Misc b737aeaf Tweak py-evm code 36187937 Test for parsing type 0 transactions 4fc7f3cd Merge pull request #649 from mir-protocol/rlp_tests 98c4a372 More binops d1c9e150 remove_swaps_commutative 63c8568b remove_ignored_values 3cf69a4e Merge pull request #647 from mir-protocol/change_literal_types 8db1ff53 Merge pull request #646 from mir-protocol/stack_labels 94183f72 fixes 61a9839f Feedback d6b5193c RLP decoding tests d639d0b3 clippy 2b9600e5 Misc a34a4c81 fix 497b26de Some simple optimization rules 7e917200 Store literals as `U256` (or `u8` for `BYTES`) bd6847e8 Allow `%stack` to work with labels 7a6d996f Move couple asm files d91e1bf3 Merge pull request #645 from mir-protocol/curve_dir 718b3c09 Move ecrecover 56d814e4 fix f49170a8 fix 86a797b1 Add a asm/curve/ directory cf637115 add u32 macros 7e0334fc fix shift direction d2141581 Merge pull request #643 from mir-protocol/interpreter_remove_stack_code 24bb6323 Implement PANIC instruction (#644) d25693d7 update dups, write out stack states 760a111a Merge pull request #642 from mir-protocol/type_0_fix 209dc26d Remove stack and code in interpreter 2bae8f92 Merge pull request #635 from mir-protocol/nondeterministic_ec_ops eb962162 Typo 16c2bee4 Increment program counter on native instructions (#641) 563de9e1 Small fix for type 0 txns 55d0edde profiling 431bb5e6 Merge pull request #621 from mir-protocol/cpu_shared_cols cc9e9fe7 Merge branch 'main' into cpu_shared_cols b2f09881 Merge branch 'main' into cpu_shared_cols bb45c8c8 Merge pull request #629 from proxima-one/maybe-rayon 87640d7e PR feedback 8ad0924b apparently i need to update rust fd0af3fa allow unused mut when feature disabled 9f2fa07e add rest of files b7fa5e81 add timing to starky, evm, and system_zero 85111b0f fix missing underscore a6931d45 fmt 585495d3 feature-gate stub TimingTree 16ddfcb9 make env_logger dev-dependency c160c403 Inter-row program counter constraints (#639) 21e64777 subroutines draft bb2ee9d5 Implement sqrt 80532158 Inverse for other fields c028afa1 Update paper ce23d437 Minor bb773e42 Merge branch 'main' into nondeterministic_ec_ops 670bed94 Merge pull request #638 from mir-protocol/interpreter_context_segments ac68ce62 Merge conflicts fbfe0ad6 Merge branch 'main' into interpreter_context_segments e8ab92b1 PR feedback a1635514 Merge pull request #627 from mir-protocol/rlp_3 3d8ac2a3 style d1cb854c terminology 6df1a669 Merge pull request #636 from mir-protocol/challenger_fixed_buffer 0ba60789 Merge branch 'main' into rlp_3 05c7dfa1 Feedback a0295f00 Minor 304299a0 Add assert to range check memory values 715c350e Implement mload/store_general e09e6c3e Merge pull request #634 from mir-protocol/fix_fixed_base_constant e48bfa83 fmt c9d610ec use maybe_rayon in starky and evm 2aad4658 Merge pull request #637 from mir-protocol/move_storage 1db5b737 Move storage asm cddc749a Fix comparison 0e5dd59d Use a fixed input buffer size in `Challenger`. 544c84b4 Transaction (RLP) parsing ee979428 Start implementing context and segments in interpreter 927cad3a Collect prover inputs 1e02fd02 Oh Clippy... 9dacbe0f Comments cafae8b8 Add `run_with_kernel` fn 19e6725c Working 0c539795 Implement prover input fns ec97f849 Modify parser bbc2ff27 Fix minor bug where `constant_affine_point` is called on zero 0afe9852 Minor e93235d0 Modify `inverse` asm 81e14bf5 fmt 529add1c switch rest of names back a281e28d add rayon shim 60227b98 Merge pull request #626 from mir-protocol/asm_fixes 47ea00d6 A few ASM fixes 90f7e8a1 Merge pull request #623 from mir-protocol/prover_input_instruction 63a86a36 Merge branch 'main' into prover_input_instruction 676e8332 Merge pull request #625 from mir-protocol/stack_manipulation c7ba4eb6 Feedback 78fb34a9 Minor 1a0d6f44 Pruning 05a1fbfb Stack manipulation macro e3f7cc21 Merge pull request #624 from mir-protocol/mload_kernel_code_u32 3dc79274 Add a `mload_kernel_code_u32` macro 71db231c Merge pull request #622 from mir-protocol/memcpy 5b1f5640 Feedback 1a5134e4 Merge pull request #620 from mir-protocol/sha3_interpreter_ecrecover b9b3c24c `PROVER_INPUT` instruction a8ce2a60 Import fix 54629a0e Merge branch 'main' into sha3_interpreter_ecrecover e7dbba8d s/sha3/keccak256 539364c8 clippy 80d32f89 fixes 6610ec44 Implement memcpy 50144a63 Enable assertions, now working 6ee2e4fc move 49a785f2 rename 3d83d63f Shared CPU columns cbdf2a66 Merge pull request #619 from mir-protocol/add_priviledged_opcodes 799d333a fix 71b9705a Merge pull request #618 from mir-protocol/asm_assertions b29de2c4 tweak 0b7e3eca PANIC returns error d53804c6 Merge branch 'main' into add_priviledged_opcodes ea0d081f Fix comment f9ec4e8e Modify ecrecover tests 15ee8917 SHA3 in asm 14a58439 SHA3 in interpreter ae7103d5 Merge pull request #611 from mir-protocol/ecrecover_kernel a22dbd18 Merge conflicts a2686779 Merge branch 'main' into ecrecover_kernel ba9aa14f PR feedback fd991a4e Merge pull request #614 from mir-protocol/evm_interpreter_memory 4aaceabd Include assertions, disabled for now 925483ed Add custom opcodes 36f1692e tweaks 563401b2 More basic ASM utility functions a9fe08a4 Merge pull request #610 from mir-protocol/feedback_591 ef842b03 Address some feedback on #591 c18d4844 Merge pull request #616 from mir-protocol/memory_u256 99745323 Store memory values as `U256`s 934bf757 Merge pull request #617 from mir-protocol/segment_enum ab5abc39 Organize segments in an enum 83643aa5 Merge pull request #612 from mir-protocol/bootstrapping_continued 134c66b3 Missing TODO 292bb4a0 Implement memory for the interpreter 48f9b7fd PR feedback 2e3ad014 Merge pull request #613 from mir-protocol/asm_rep 6d69e14a Add `%rep` syntax for repeating a block 0802d6c0 Continue work on bootstrapping 62c09461 Add `_base` suffix f4390410 Comments 0ccd5adc Redundant x-coord in lifting 7ee884b8 More tests 33a59342 Passing tests add2b42e Merge branch 'main' into ecrecover_kernel 0d628950 Merge pull request #606 from mir-protocol/jumpdest_push_data cb721543 Merge branch 'main' into ecrecover_kernel ad9e1310 Add test 905b0243 Minor fixes 522213c9 Ecrecover until hashing 8751aaec Merge pull request #609 from mir-protocol/row_wise_memory_gen 33622c1e Merge pull request #608 from mir-protocol/kernel_size_logs bfd92487 Generate most of the memory table while it's in row-wise form 4be5a25a Merge pull request #607 from mir-protocol/duplicate_macros d36eda20 Merge pull request #605 from mir-protocol/memory_misc a8852946 Have `make_kernel` log the size of each (assembled) file b4ebbe5a Start ecrecover 7a6c53e9 Working secp mul a831fab8 Working secp add a68d8ff5 Avoid duplicate macros c214c53c Merge pull request #604 from mir-protocol/get_subgroup_evals c8c3cc9a Files shuffling b1bc4819 Fix jumpdest check 678d04e6 fix d1afe812 More realistic padding rows in memory 7221c964 Use FFTs to get subgroup evaluations in `check_constraints` f0539327 Merge pull request #603 from mir-protocol/memory_simplifications 1331f992 fix 25d429af fix 934f1aeb Simplify memory table 2507985d Merge pull request #591 from mir-protocol/evm_generation 10c31b70 feedback a3deefd8 fix ef3addea Merge branch 'main' into evm_generation 61f98f36 Merge pull request #602 from mir-protocol/better_ctl_error d35d3e20 PR feedback + underflow check 29ef56eb Merge pull request #599 from mir-protocol/memory_ctl abc729f1 TODO bdae9bc3 update b935605f fmt 467f5320 addressed comments 49c208ec addressed comments 888cfe4c fix 9ad2958f fix afc5a4dc fixes 6655ee68 restored timestamp column to CTL 6b2b7452 removed debug prints 3a6f2ef2 clippy 51498eb7 fmt 58c2e721 another padding-row constraint fix 77a7ace3 updates to recursive constraints f3ef6c9b fix: ignore padding rows in constraints 83963c3a permutation pairs b467a13d fix c3e76527 updates 181a1323 fixes a98f267f initial change 8a2a0354 Merge branch 'main' into evm_generation 5e27e726 unwrap_or_else -> unwrap_or 50ebf39d Comment 3ff67e38 Minor 36c8aa34 Comments 91fcf262 Better CTL error 9902e8b7 Merge pull request #601 from therealyingtong/arithmetic_u32-canonicity 5bf545c5 arithmetic_u32: Introduce canonicity check. 2c48b117 arithmetic_u32: Introduce Self::routed_wires_per_op() method. fbffd602 arithmetic_u32::tests: Add test_canonicity check. 508d4788 arithmetic_u32::tests: Extract get_wires() from test_gate_constraint(). 58889e76 Allow constants to be passed from Rust into our assembly (#598) 457ac110 Merge pull request #596 from mir-protocol/evm_interpreter 12ca0846 PR feedback bd5c9aa8 Merge pull request #597 from mir-protocol/ec_use_macro_params a280e1c6 Merge branch 'ec_use_macro_params' into evm_interpreter a3c2e9a2 More macros 122188c8 Merge branch 'ec_use_macro_params' into evm_interpreter 4316be96 Test exp kernel function 7bf5118f Test exp kernel function 78eb7843 Merge remote-tracking branch 'origin/evm_interpreter' into evm_interpreter f8987b7e Minor 267f4162 Minor 9c4947e0 EC ops test beb8a907 Macros with arguments (#595) 3ec2d307 EVM interpreter b90be516 Merge pull request #594 from mir-protocol/elliptic_curve_asm 434615a0 PR feedback + comments e2b1e512 Minor 9747343a PR feedback 5bae732e Minor 8ffd25c1 Add zero case for mul fb8a67b0 Working ecmul 006b74f4 Merge branch 'main' into elliptic_curve_asm 9e90d7d1 Add check for zero point 726bcd45 Merge pull request #593 from mir-protocol/labels_before_assemble fd1d9fe8 Add range check 8e711d41 Minor ee80fa4a Minor 8873eaba Find labels before assembly 7364248e Fixes eed7cde3 Add moddiv for testing 8a44c557 Curve mul assembly 4d376857 Comment a5988d6c Simplify 4cdbb8c1 Minor 6db8539b Minor 683efc0d Impl double 92bb8d5f Merge branch 'main' into elliptic_curve_asm 5d74a19a Add test (won't work for a while, but to illustrate) c389abc1 other segments 28603b85 fixes e7b480de Begin work on witness generation and kernel bootstrapping f6d48f13 Serialize zero as `[0]` rather than `[]` in bytecode (#592) 797bece7 First attempt bc9e6189 Structured wrapper over CPU table row (#589) 33cabb14 Merge pull request #587 from mir-protocol/s_columns_registers f30889b7 NUM_REGISTERS -> NUM_COLUMNS 7812ad24 s/registers/columns aa423121 EVM Arithmetic Stark table (#559) dec0765f Tweak Merkle proof API and make it public (#588) e3834a53 Util for assembling EVM code to hex (#586) b228cc72 s/columns/registers 34e73db4 Memory naming tweaks (#579) e73d01a0 `packed_field` -> `packed` (#584) 3346d3f9 `field_types` -> `types` (#583) 475964a6 Move CTL config out of test (#578) 410e0334 `extension_field` -> `extension` (#581) 7b75eaa9 ASM macro support (#580) 912281de CTL: limbs (CPU) <-> bits (logic) (#577) 46df1bb6 Fix EVM dependency list (#576) 191ddf7b Exponentiation kernel function (#574) af0e3250 Merge pull request #571 from mir-protocol/evm_memory 4a7ebf05 updated in line with main changes 791b15f9 cleanup 4d69998c fix and cleanup d911eecd fixes 798b01d0 added lookup file 29fa3246 fixes d6983951 attempted fix f04f2bc3 multiple indices per timestamp f16db8c5 fmt 593d3eef fixes 94348198 addressed comments a7f6bf3b fmt de52e630 addressed comments 2d7f2b47 fmt a2c14077 fixes a860eb77 fixes 9f22cc72 allow 'unused' functions d2eb3b14 addressed comments 08be9811 timestamp fixes dace10a2 removed structs 6884b1ae addressed comment 3898ccd0 okay you win, clippy 0514cd96 addressed comments 939e6318 rename 5707baee addressed comments 10585258 removed accidentally added files 5707732b fix 398e75de fix bf58c203 updates to registers, new cross-table lookups 8155e90d fixes 7ba0652c all_stark affcd657 fmt cceb471f uncommenting ef3f0236 transpose fix 59f3a763 transition constraints, and debugging 03112f89 updated all_stark framework to include memory stark (doesn't pass yet) 31be2c8d clippy and fix 0ad54233 fmt fcf21601 memory row generation 7ed2aa63 cleanup 91dacf2e tests 40a0a252 fmt eabb10a3 lookup argument for range check 32d10a2f redoing columns 24398e2f memory 7c2cfebd memory 3aaab765 define columns for CTL closer to the constraints (#573) 2e818172 Parse and assemble kernel functions (#567) 27970003 Fix check_constraints to only look at subgroup points, vs coset points (#572) 1cc000d3 Connect logic stark to CPU (#569) 043eb2de Merge pull request #570 from mir-protocol/fix_ctl_verify b9265ccf Sanity check 29f750ed Fix CTL verification 7cbce7bf Remove redundant constraints (#568) 31435944 Merge pull request #564 from mir-protocol/ctl_linear_comb 918201d0 Fix new lints 0d8461d6 PR feedback 1e44ee36 EQ and ISZERO (#566) 49219a2b NOT stark (#565) 6a13ecf1 Remove enum and use Option for filter 499d2d07 Minor d9b5d833 Minor 1dce1849 Remove Keccak input limbs d626679c Column enum 73200269 Logic stark (#562) 1356b980 Merge pull request #558 from mir-protocol/filtered_ctl c28b0914 Merge pull request #563 from mir-protocol/keccak_round_flags_constraints e969f10b PR feedback bf375390 Keccak round flags constraints fdd6a7ca Wired CPU and Keccak 039d4efa Merge branch 'main' into filtered_ctl d256044a Merge pull request #561 from mir-protocol/keccak_input_registers 8bd6bebd INPUT_LIMBS -> NUM_INPUTS 30abe19e Fix 413a5a30 Merge branch 'main' into keccak_input_registers 5df5cc4d Merge pull request #560 from mir-protocol/keccak_stark_multi_inputs_fix aa8d69d2 Minor 2f3a280b Circuit fix 51f66d4d Fix constraint 8af99cba Progress 1cc38bb0 Add Keccak input registers 10ac355d Merge pull request #557 from mir-protocol/evm_keccak_stark 4e848c77 Merge conflicts afda9db0 Merge branch 'evm_keccak_stark' into filtered_ctl f36c012e Checks 85d84a13 moved back haha 9c6e6509 moved allow to local 05d2c69e Add constraints 2ff73863 Merge branch 'main' into filtered_ctl 315d0882 ignore silly clippy warning 47fc968b Set default to an Option 1ad8ec5f fix 67167d8e use bit operations 8b37d5d2 fix 3b9cb7a9 fmt cacc073e fix 9d118ca1 fix 901525c1 constants crate f0ed3918 clippy c39e927d added check on length of a4300fb6 fix 08bda49b fmt 82a361f4 trying to fix CTL 78b2a5eb fixed constraints, in line with generator 52afdae5 cleanup dc082139 fmt 80d5e537 fixes, cleanup, and correctness test 9fbae06b fmt 04978473 fix yay 964849d9 fix ac18c390 fmt 69aed658 fixes 60c0b4ee fix e34626e9 fix 12cb1773 fix e6d0275f fmt 2c285ca2 fixes and debugging e6880e59 included everything db6c3fd8 keccak stark 2cbf063e Merge pull request #556 from mir-protocol/slice_to_iter 4deccb09 Change partial product dcb8c37f Add linear combination of filter columns 820456fc Add TableWithColumns struct 47efff83 EVM decode (#553) ccc9c024 Change some fn to take iterators instead of slices d6006f8f Merge pull request #555 from mir-protocol/s_right_next 6f5c8e46 s/right/next db56fe39 Fix Clippy on `main` (#554) fd0f1815 Merge pull request #552 from mir-protocol/multi_table_lookups 683f3254 PR feedback e3761964 Remove collect 2ecca92b Minor 2e3a738b Implement multi-table CTLs e8fc5b57 Merge pull request #551 from mir-protocol/starkevm_recursive_verifier acb7b8bc Use `reduce_base` in `reduce_with_powers_circuit` fd7eb9e9 PR feedback and add `reduce_with_powers_circuit` fn bd738399 Clippy ffc5ce2f Fix bug. Recursive verifier test passes 1fdb4175 Recursive proof isn't correct (yet) e13bbf56 Add num_ctl_zs 4afe32a7 Merge pull request #550 from mir-protocol/stark_circuit_constraint_test c49de59c Recursive verifier test compiles but fails d47b22d2 Compiles d9b237d9 Add ctl check vars logic 8e8e4daa Start of impl 7d7851ab Merge pull request #549 from mir-protocol/evm_more_generic_structs 7cdc72f9 Detupling 12a3155b Ignore test instead of failing b3f873c6 Finish test 2bc5b24c Start 9f01840a Make evm structs more generic 2831f8f2 Merge pull request #548 from mir-protocol/remove_marking 781d1edb Remove marking c54896dc Rename starky2 -> evm (#547) 17dfa8d7 Merge pull request #541 from mir-protocol/starky_multitables f8b51743 PR feedback 917af426 Merge pull request #544 from mir-protocol/standard_gadget_suffix 1f5b0cea Merge pull request #545 from mir-protocol/rename_wire_fields 28d90d0e s/gate_index/row/ a31de48d More changes 4cd37ca8 Rename fields of `Wire` 7676f907 Missed some b606d99e Use `*_circuit` suffix for gadgets 2ffd22ac Assume every STARK in AllStark is part of a CTL db2aae51 Add default value to CTL 863d9a86 PR comments 02db6c93 Add permutation in mock STARKs for good measure aa77660b Back to const generic + arrays 8cd27939 Some renaming + cleaning a38c19f9 Fix num_permutation_zs f9e929a0 Fix bugs when no ctls e9260fe4 Merge remote-tracking branch 'origin/starky_multitables' into starky_multitables d421bd35 Lints 0c8178e2 Test works fe3811e8 Test works 17ba468e Add mock test (doesn't work) e10e1039 README.md: Update plonky2 paper link (#543) 3359ee70 Clippy c4c3533c PermutationChallenge -> GrandProductChallenge since it's also used for cross-table lookups 2b8c3de1 Finish verifier b9e921f6 CTL verification d0fb76c8 Progress on verifier 5b146d55 Change StarkOpeningSet 9de8c2c7 Open lookup polys feb00bd1 Fix `looked_table: Table` 52faa0ce Move paper 79dc28c7 Rename 44b9237c Rename 3005f7bf Cleaning 99b6ac4f Minor e744c640 Add eval for cross table lookups d4a6b354 More cleaning d659e759 Clean lookup stuff e40276ef Compiles somehow 4e18cad0 Lookup argument d56e3745 Progress 9f27849f Compiles 9096b758 Start of multi-table STARKs ed3f0d54 System Zero bit rotate and shift operations (#535) 7769c269 Do not export global allocator (#533) 90f6d07f Merge pull request #539 from proxima-one/make_recover_degree_bits_public 737e237e make recover_degree_bits public for StarkProofTarget 885cbcfd Merge pull request #540 from mir-protocol/new_clippy_lints c505c675 Fix new clippy lints b17e2c8d fmt 5b90d912 add doc explaining ordering in aad56c66 make recover_degree_bits_public 0d118d0f STARK recursion timing info (#537) dbc2d85b fmt 9cff202e Move benches to bins (#534) 76c86c55 System Zero binary bitwise operations (#529) 1558b89f Merge pull request #530 from mir-protocol/u32_crate d484b623 plonky2_u32 -> u32 b58d5d67 Move u32 stuff to plonky2_u32 crate 3c6ec875 Merge pull request #525 from mir-protocol/remove_const_gate 8faf644f Change gate method for extra constants (#528) 4fc6fdad Stop suppressing unused/dead warnings globally (#527) cc95cb5e Typo d12417c9 Comments + cleaning + fixes fae471f9 Working f81e32f8 semi-working b4d11c28 Merge pull request #524 from mir-protocol/better_selectors 60471524 PR feedback: num_selectors as fn and sorting comment e2de88d1 Add constants to the RAM gate 270ff985 Merge pull request #466 from mir-protocol/glv 9b658255 Comments e50e668f PR feedback + use only one selector when possible 283c9350 No doc-test 19cbbd4d Minor 847565a8 Comments d6b99df8 Cleaning 1d77116e Working 06fef55b u32 division (#517) c6ebd069 Not working yet 7cf32204 Rollback 185d8fae Progress 68bd0f4b Not working 2cedd1b0 Merge pull request #521 from mir-protocol/fix_inv_mod_xn 163053b8 Use truncate instead of drain 482dfe55 Vectorize constraint evaluation in Starky (#520) 744996ef Remove `remove_prefix` dbaa31d8 Back to slice 3b767ca4 `a_deg` should be degree of `a` 6a641416 Fix `inv_mod_xn` ddd51924 Move `secret_to_public` to a `ECDSASecretKey` method c472afe1 Merge branch 'main' into glv 296b21ae Not working e77383b5 Progress 7d6c0a44 Halo2 style lookup arguments in System Zero (#513) 63a30904 Start selectors deec6a78 Merge pull request #516 from mir-protocol/ecdsa_module 786c1eaf Minor 534ee7d6 Add untracked files 660d785e Merge pull request #515 from mir-protocol/fix_salt_issue 627e80bf Filter mul-add constraints (#512) cc9a43b5 Fix salt issues 2e5c2e89 Add ecdsa module 50f722d8 Merge pull request #511 from mir-protocol/gadget_curve_msm 954eaf16 PR feedback 310493c2 Faster extension field multiplication (#500) 3a68a458 Ignore large tests 5febea77 Fixes 18e341ff Comments 47523c08 Minor 90df0d9d Clippy f6525ed1 Add wide config for ECDSA in < 2^16 gates 7329dade IS_MUL -> IS_MUL_ADD (#510) 2644f5f7 System Zero subtraction operation (#508) c8d3335b ECDSA verification in 101k gates 2571862f Working GLV decomposition check 7c70c46c Working GLV with MSM 850df4df Add fixed base file 6f3ca6a0 Fixed base works ba5b1f72 Fix `set_biguint_target` 74cf1d38 Minor improvement 61af3a0d Cleaning efb074b2 Works with 2 772ff8d6 Works 6b386e75 Merge pull request #503 from mir-protocol/ecdsa_target_visibility bd7f43ad visibility 7b4ddf85 Merge pull request #502 from mir-protocol/ecdsa_secret_to_public 383b8b68 secret_to_public fn 517d26e4 Merge pull request #499 from mir-protocol/stark_permutation_checks dd4cc213 PR feedback 8c5cbbc7 Add first row Z check 17bbc6f3 Minor a31c58b6 Use ReducingFactor 150d7644 Simplification 4ea418a4 Clippy 064b3c07 Forgot to set permutation cap 6cd2fc62 Should work (does not) ed4aef0f Fill permutation todos 56e269e2 Working (not recursively) c7af6395 Restore vectorization to full Poseidon rounds on Aarch64 (#498) 85c1e1d5 Should work (does not) 5c117337 Compiles 79ba85eb Compiles f4a29a02 Merge branch 'main' into stark_permutation_checks d52fabaf First pass 6072fab0 Implement a mul-add circuit in the ALU (#495) bc368558 Rename constraint methods (#497) bedd2aa7 Rename arithmetic unit to ALU (#496) 9516e14c Merge pull request #491 from mir-protocol/fix_reduction_strategy a736aa8e Update MDS matrix and round consts in Poseidon; disable vectorization (#493) 67cb5dfd PR feedback 431faccb Change `compute_permutation_z_polys` to batch permutation checks (#492) 20fc5e2d merge fixes 74cf5da8 clippy 25555c15 fixed native GLV; fixed precompute window; other fixes 8ad193db use windowed mul in GLV 1e3743f4 fmt 0140f7a3 fixes e88564ce correct point subtraction f77192ef fmt f6f7e551 windowed mul fixes...... a89b306c fmt 12d5239b fix f67e12ee fmt ad1aa4ae fixed is_equal 3787f3be conditional add 84edb55b fmt 134a0422 is_equal function 978e2ee9 conditional add (doesn't work yet) 8bab62b8 fix 23cfe910 fix 64a09616 fmt 294a738d moved to new file, and curve random access test 5603816f fix 58492a0a fmt 67b7193e test for split nonnative, and fixes 53a2a922 windowed multiplication in circuit dc44baa5 simpler test 140f0590 fmt 5aaa5710 test for GLV gadget 5917a09c split out glv_mul function e92d4c25 fixed clippy c3126796 GLV in circuit c279c779 fixed clippy fd7abb35 GLV mul 2f4da9b4 added native GLV compose 56336e39 Fix ea9006f5 Add rate_bits c9185d92 Merge branch 'main' into fix_reduction_strategy b28cd553 Fix reduction strategy 72d13d0d Prover code for permutation argument (#485) c6f80ba5 Merge pull request #490 from mir-protocol/batchable f8dfc398 PR feedback 08e255a2 Remove `params` in `GateInstance` 661a6b44 Delete GMiMC files f4ef692a Quintic extension fields (#489) 3fd52581 Comments 2d4d2d21 Simplification 3f7cefbc Merge pull request #486 from mir-protocol/recursive_starks d0da2fe1 Remove debug info c74b0c91 Progress towards using generators 0aefe92b Merge MultiOpsGate into Gate 5e317752 Fixes 6d2c9b11 Merge branch 'main' into batchable 42d65321 PR feedback acd62f12 Changes after #481 14d8cf2c Merge branch 'main' into recursive_starks f4640bb5 Merge pull request #481 from mir-protocol/fix_hash_or_noop_merkle_proof 8d699edf Move some methods outside `impl System` (#484) 96c9a238 Merge pull request #488 from mir-protocol/ecdsa_pub_hash 14677326 Impled `Hash` for `AffinePoint` b104dfce Working 983c066b Merge pull request #487 from mir-protocol/ecdsa_more_derive c9171517 Derived more traits for ecdsa types 7820ba96 Minor 1686cb02 `verify_stark_proof` -> `recursively_verify_stark_proof` 6dca4e26 Unused 83701096 More visibility changes cff39c55 Change visibility 3db9c775 Add `set_fri_openings` 80e3c928 Clippy b0de3328 Working 2e008eac Change Merkle tree lead hashing (to change back when #481 lands) 55ca718a Test no longer ignored 7af2d058 Save allocation and add const generic bound 736b65b0 PR feedback 59d4e04b Merge pull request #480 from mir-protocol/fix_mul_sub_typo f7256a6e Other fixes 1d013b95 Fix `hash_or_noop` in Merkle proof. d22fa889 Fix one error to get another one 7c71eb66 Fix mul_add -> mul_sub typo 645d45f2 Column definitions for addition, range checks & lookups (#477) 387ce463 Merge pull request #478 from mir-protocol/from_partial_use_slice 24c20147 Recursive stark test (failing) 3aa192a7 Add witness generation for stark proofs b2c747b1 Also did the same to the circuit version cfe52ad6 Add `PrimeField`, `PrimeField64` traits (#457) adf5444f `from_partial` (non-target) now takes in a slice fe89fa94 Merge pull request #476 from mir-protocol/ecdsa_derive 8262389e Added `Debug`, `Clone`, and `Copy` to ecdsa types ff7a6548 Methods for virtual stark proofs 101b3bac Small optimization ae330ff6 Clippy ba63a37b Compiles a2dbcf2f Merge branch 'main' into recursive_starks efb13650 Split `system_zero::column_layout` into submodules (#475) 8a07d7af Merge pull request #474 from mir-protocol/match_recursive_verifier 0cc77692 Make `get_challenges` private. a10cd49b Merge pull request #473 from mir-protocol/standardize_set_method_order 415da246 Naming 02746d8a Minor f3935289 Unused d7bdc750 Further cleaning 61fcc904 Working afe89a61 Add methods debe742c Progress fcef5a57 Fibonacci recursive constraints b40827e6 `trim_to_len` helper function (#472) 0a96d33f Standardize `set_*` method parameters order a43e138f Move some FRI stuff into the FRI module (#471) 8f21fddd Add a `PolynomialValues::selector` method for convenience (#470) a51c517b Update doc 83a57271 Implement Poseidon in system_zero/permutation_unit (#459) b6a60e72 Separate methods for hashing with or without padding (#458) 659f1337 Permit small circuits in `compute_quotient_polys` (#469) 9eb9bac0 Merge pull request #468 from mir-protocol/stark_constraint_degree 511cb863 s/max_degree_bits/quotient_degree_bits fc502add Add `quotient_degree_factor` function 9c6b2394 PR feedback 1c30a5a8 Typo 431bde2c Fix number of quotient polys 0df1545f Merkle tree bugfixes + tests (#467) 01f065b8 Minor f5ddf324 Add file 978e1403 Fix degree 6b2b8b6e Use stark degree in compute_quotient 1011c302 Add test for system zero d99cabde Working 588911f1 Merge pull request #461 from mir-protocol/fix_reducing_tests 77af9fa4 Merge pull request #462 from mir-protocol/disallow_degree_1 c1d51980 Merge pull request #464 from mir-protocol/observe_fri_openings edc6b234 Merge pull request #465 from mir-protocol/change_ignored_tests 2a699ee0 Ignore `test_curve_mul` and unignore recursive tests 73dde0b7 Replace `observe_opening_set` by `observe_openings` taking a `FriOpenings` argument. c6a33220 Disallow degree `quotient_degree_factor = 1` f6c66eec Fix reducing tests dd7808e3 Merge pull request #455 from mir-protocol/start_stark_verifier 28082e97 Clippy bc5bc824 PR feedback c0cfff14 Clippy ff949f40 Works everywhere except Waksman be44edcd Minor bff763e3 Add distinction between (non-)wrapping constraints 43800ba2 Rename `PrimeField` -> `Field64` (#454) 8e07058a Remove `inner_config` param - redundant with `inner_common_data` (#453) 1e04f4f5 Comments 8ab4f855 Add `fri_challenges()` to Challenger. 984f44b2 Fix lde -> coset_lde bug 9f8696ad Fix bug f2369f4f Test pass bcbc987a Merge pull request #377 from mir-protocol/ecdsa b0738c20 Fix degree issue b0afb581 fixed clippy warnings from CI 11a1e52c ignore ecdsa circuit test 5b5084b1 clippy a471574f fix 8a56af93 TODOs 20930e00 range-check add results d68ab119 msm 30f936c4 ecdsa changes c1b8515e warning b1c8709f addressed more comments b62fa3f6 fmt 493f516f removed hashing 82e2872f updates and addressed comments edf75632 MulBigUintByBool gate 1035438d updated for changes in main 5de2b695 256-bit hashing e116ab78 fix c392606a optimizations and cleanup 2ddfb03a various cleanup 6e9318c0 u32 range check gate ddf5ee5d more efficient nonnative subtraction 8d366269 fmt 50c24dfe more efficient nonnative add and multi-add facb5661 fix 3bbedecd ECDSA merge 3ba61a4e ECDSA merge 07b71a96 ECDSA merge 440a5bd5 fmt f436c142 ECDSA merge c561333c ECDSA merge 82ce3ea8 ECDSA merge 08fa4031 ECDSA merge 9cac6d3a fmt b796c73e ECDSA gadget and test f55948a6 removed debugging prints 37562943 ECDSA rebase 92ea4b65 Constraint check working d24d26e5 Add FRI challenges 851455a2 Eval Lagrange 8993270f Progress 20a3683e Merge pull request #451 from mir-protocol/start_stark_prover dff9a409 Batch alphas in constraint consumer c73f32ef Remove initial values from Fibonacci STARK state cdea0d9f Compiles e78630ae PR feedback a74ffdb8 Remove GMiMC and Rescue hash functions (#450) b6cb72b6 Comments 1770e83c Clippy 4a268103 Working prover 3e0cb360 Added test stark ab48cca1 Merkle tree optimizations (#433) d54cc9a7 First try c0ac79e2 Beginning of STARK implementation (#413) 48379974 Jemalloc warnings in Readme (#448) 2af85ccb Make `set_proof_target` publicly accessible (#447) a6e64d1c Replace `proof_to_proof_target` (#445) 04fbb05d Swap loops in `compute_quotient_polys` (#444) 5f0eee1a Bit-order reversal optimizations (#442) 86dc4c93 Make all FFTs in-place (#439) 2e3a682b metadata d69220e2 metadata 8df9e7ec Merge pull request #436 from mir-protocol/fix_ldt_degree 094e35b0 Merge pull request #440 from mir-protocol/simplify_compute_quotient f98a6adf Bit-order reversal benchmarks (#441) 5255c04c Remove `compute_quotient` and update division tests 27ebc21f Add comments for LDT fix in verifier dcf63f53 Have hash functions take references to avoid cloning (#438) fcdcc865 Move profile defns to root workspace toml. (#437) 2bb0c4f4 Fix comment 6f65620f Add fix for recursive verifier. ec474efe Minor 2aa46e14 Optimize + test log2 functions (#434) fe0c232d Working (not yet for recursion) fe5a30ed make HashOutTarget internals public (#430) 0ff83658 timing 9f09a2aa Add Merkle tree benchmark (#429) 9ecdc4d3 note about toolchain 6c25fb97 wording ac59f2bc readme updates 3ab0a37a No longer need to store number of PP polynomials (#424) bde61144 Replace `AlgebraicConfig` with `GenericConfig` (#425) 8ec78fc0 tweak len 3fc5ff4f Remove old binaries (#423) f48d8c92 Finish making FRI generic (#422) 4e532f04 AVX2 Poseidon S-box optimizations (#421) bf30fed7 Make FRI more generic (#419) f072d09a AVX-512 packed Goldilocks (#400) a6e1f7cc Aarch64: Minor optimization to Poseidon full layers (#420) 58258938 Remove feature(asm_sym) (#418) 5caf92a3 First try 4f2ac97b consistent order 1d576f20 licensing note 3de8d36c Use single-point opening expressions (#416) 6991257d Simpler Keccak pseudo-permutation (#415) 23f0e49c Separate some circuit logic from FRI code (#414) a452da52 Merge pull request #407 from mir-protocol/challenger_outer_hash 7b03ebe1 PR feedback 0a5a2249 import cf6713e7 Remove accidental redundant struct 8d093a84 Decrease CI scratch disk space (#412) ea430535 `Square` trait (#409) 5a379f15 Rename PackedField constants ZERO -> ZEROS, ONE -> ONES (#408) 77a2fc61 Comment for KeccakPermutation df2b6e76 Move permutations to their specific files a0a42e4b Move hashes to their specific files 314a5845 Use outer hash in Challenger c126641c Split into crates (#406) 107ba3ab Fix build on 32-bit Intel (#405) 04dce92a Print timing for a regular Poseidon recursive proof (#403) d4a0a866 Packed evaluation for most gates (#395) bbbb57ca Simplify AVX2 Goldilocks (#399) 2fc1a615 Merge pull request #404 from mir-protocol/gmimc_config 514cca7e PR feedback fd03a187 Minor 2e4bea59 Multi-hash test 156fd45b Add GMiMC config 30cf4cd0 Merge pull request #398 from mir-protocol/injective_hash_conversion 0538511c Comment for why 7 bytes e9fafa51 Faster Goldilocks mul by forcing a branch c4549c4c Silence Clippy on main (#402) d594b8fc Convert chunks of 7 bytes instead of 8 7d574c86 Merge pull request #341 from mir-protocol/generic_configuration 433f3584 Fix build on AVX2 (#397) 81c6f6c7 Merge remote-tracking branch 'origin/main' into generic_configuration eb7615f7 Change gate evaluation memory layout (#390) 23a902e1 Fix nits 7a2afb51 Clippy 288a0b7c Fix merge conflicts bdbc8b69 Merge branch 'main' into generic_configuration 357eea8d Fix build on main (#396) 68e3befc Merge pull request #391 from mir-protocol/prime_field 9211bcfe Move characteristic to its own fn 6cb4f56a Merge pull request #392 from mir-protocol/remove_polynomial_file a446aa05 Merge pull request #393 from mir-protocol/remove_bits_fn 6863eea7 New clippy lints 920d5995 Replace `bits()` fn with `BITS` const 073fe7a6 New clippy lints c1698bb9 Remove polynomial.rs (+clippy lints) 1d215d5d Remove dbg fb168b5d Replace characteristic with option e6c3f354 working aed4de02 Merge pull request #389 from mir-protocol/deoptimize_tests 6a50c0fc Clippy 5061b2d1 Use rand_arr instead of rand_vec dad35ae6 Fix tests 58e1febd Update size-optimized recursion test (#388) d6a0a2e7 Run CI on optimized build (#384) bb029db2 Type tweaks for packed types (#387) 04c1ea25 Merge pull request #386 from mir-protocol/fix_recursive_fri_config 2a81ec17 Fix recursive FRI config aff71943 Minor optimizations to AVX2 multiplication (#378) 5eaa1ad5 Require a `PrimeField` to be its own `PrimeField` (#383) c6ac8e1b Merge pull request #380 from mir-protocol/variable_num_u32_ops d361dffa Merge pull request #381 from mir-protocol/obsolete_todos c2ca106a Rewrite `add_many` 817fe1e3 Remove obsolete todos 29ed0673 Variable number of U32 sub ops 93d695d3 Variable number of U32 ops 6df251e1 Remove `Singleton` type and make every `Field` a `PackedField` (#379) 982f85fd Merge pull request #349 from mir-protocol/secp256k1_curve 12defa80 remove unused test 9d8a5fc0 removed outdated comment 5aa5cc9c ignore huge tests 406092f3 clippy fixes f1dc1d44 fix b9868ec7 multiplication using projective 39300bcf fixed Secp256K1Scalar b1bbe30d Fixed tests -- thanks William! 5029f87b fixes a6ddc2ed curve_mul testing 2ec3ea86 new curve_mul 284f9a41 curve multiply; test for curve add; addressed comments 70abf3e9 addressed comments 0f49f646 removed from ProjectivePoint 7da99ad4 test fixes f6954704 fix c7fda246 fixes e4b894cb merge d6630869 msm (outside circuit) 051b79db curve_add_two_affine dfad7708 merge a4b7772c resolve 4d4605af merge fa480854 updates 86573fc6 resolve 0e6c5bb8 curve gadget changes f11fe2a9 fmt a5f21de0 fixed curve_summation tests d1ad3fdb fix: generator value 0e1f0c55 merge 2c2d36a6 merge 50db1187 Secp256K1 curve (in progress) 869a5860 Secp256K1 scalar field db464f73 merge ebce0799 initial curve_types and curve_adds f9c9cc83 fix: run all U32SubtractionGate generators fd2e2764 merge d9868de6 merge f29b591d merge 25c0614d Merge pull request #375 from mir-protocol/no_more_clippy a0b0a2d7 Move polynomial.rs to mod.rs 301edf3a Move clippy::eq_ip 915f4ecc Fix github CI b3d246a7 Minor 7097081e Add clippy to CI 2c06309c Fix all clippy lints 549ce0d8 Interleaved batched multiplicative inverse (#371) 1fed718a Merge pull request #374 from mir-protocol/arity4 3235a21d 2^12 shrinking recursion with 100 bits of security e26eb5f4 Merge branch 'main' into arity4 9cafe977 Remove specific impls of `InterpolationGate` 9bd5ac00 Merge pull request #373 from mir-protocol/mul_gate 15b41ea8 PR feedback 172fdd3d Comments fa29db1d Clean low-degree interpolation gate 5ea632f2 Fix size optimized test b7cb7e23 Minor e06ce5aa Fix proof compression test 6aaea002 Choose between high- and low-degree interpolation gate depending on the arity 8522026c Change file structure 442c8560 Under 2^12 with 27 query rounds 0d5ba7e7 Working recursive test aec88a85 First try 22f4c180 Comments 4f11713c Remove useless test 90a6ffd7 Use fold1 in mul_many 939acfed Fix mul_many 0de408c4 MulExtensionGate 2b4bb13a Remove total_constraints (#372) 9b55ff9e edition = 2021 (#370) 8772073b Update size-optimized proof test (#368) eb15837a tweak logs eb27a2d2 warnings 8b710751 Reduce constant_gate_size to 5 (#366) eb5a60be Allow one BaseSumGate to handle 64 bits (#365) 1e66cb9a Route in constants from a ConstantGate (#367) 909a5c23 Fix all lint warnings (#353) 4769efa4 rename 694b3d3d Recursion in 2^12 gates (#364) beb13af5 Merge pull request #363 from mir-protocol/reducing_ext_gate 799ff26e Avoid underflow when checking the length of `terms` 8ea6c4d3 Different implementation of `RandomAccessGate` (#360) 9aafa447 Fix stack overflows due to recursion in `Forest::find` (#358) 239c795a Address some more arithmetic gates that have unique constants (#361) 64099763 Rename z_gz -> z_gx (#359) 07d03465 Verify that non-canonical splits are OK (#357) efab3177 Have `le_sum` use arithmetic ops if it's cheaper (#362) 49e43078 Comments + test for reducing 100 extension elements 3efe2068 Minor f787c538 Simplify a54db66f Use arithmetic gate for small reductions 66719b0c Remove comments d44cb967 Merge branch 'main' into reducing_ext_gate 7185c2d7 Fix & cleanup partial products (#355) fe1e6716 256 bit salts (#352) 26a222bb Fewer wires in `PoseidonGate` (#356) ad42104e Merge pull request #354 from mir-protocol/smaller_tests 4a5123de reduced test sizes 857b74ba Bring back the base field arithmetic gate (#343) 72ef58c1 Add ReducingExtGate a48eb2f8 Merge pull request #346 from mir-protocol/partial_product_chain 21d3b127 Cargo fmt 9139d135 Minor refactor of partial product code (#351) 137c6d34 Merge pull request #281 from mir-protocol/nonnative f2ec2cad new fmt bd427cd6 fixed failing tests dd945ef5 addressed comments 9043a47e more fixes ea4f950d fixes and fmt 270521a1 addressed comments 7336aa09 fmt 3f619c70 made test_list_le random 61647968 rename db31b9f6 sub_nonnative fix 656f052b addressed nits cf3b6df0 addressed nits c861c10a nonnative neg e8380969 use map; and TODOs 1d4bb395 FFTarget uses BigUintTarget 6ab01e51 u32 arithmetic check for special cases 237a1fad addressed comments bd0164c7 fmt a3d957fa addressed comment: more tests for multiple_comparison 6705d81f nit 5dd4ed3e addressed comments 24454357 fixes to subtraction tests, and documentation 2d9f8d97 fix c664eba3 sub test 72134a3e mul test 8f8d0395 uncomment 4c5f2383 fixes to tests bbcda969 nonnative tests ee5619b8 fmt f7ce33b7 using refs in right places; and lots of fixes bfe201d9 fmt 87d81290 reduce 6232aa68 fix f639dd33 fixes to nonnative f41c8ee1 fmt 7e81f297 another fix 90178b2b many fixes 9e49c3f2 fix to test 048048ce test for list_le 166ab77e biguint_cmp test 62519eeb biguint mul test 14027911 merge 0c182c46 fix 649c2e2b tests for biguint gadget b045afbb biguint methods in fields, and biguint gadget progress 557456dd fix e8c2813c fixes and fmt b567cf9b some more BigUint arithmetic 9077c7fa BigUint arithmetic, and cleanup 72aea53d mul 956b34c2 add_many_u32 a4eac25f nonnative add reduction, and nonnative subtraction b2b7cb39 merge 97f66b58 merge cc48abff sub 18567e57 merge 3fff08aa U32 subtraction gate bdfe124b multiple comparison 6dd14eb2 comparison gate should also be <= 26959d11 range-check the bits 0ff6e6e0 fmt 7e8c021b comparison gate 912204d6 merge ebcfde1d updates 8440a0f5 merge 30843671 Start accumulator at Z(x) 32f09ac2 Remove quotients and work directly with numerators and denominators in partial products check ff943138 Apply suggestions from code review 6b294c1d fmt 34eacdad progress f71adac4 fix e48e0a4a fmt ffb544e4 initial non-native add d334a924 merge new circuit builder stuff 7054fcda initial 97111275 Use Jemalloc (#347) 168f5728 Fix rustfmt failures on main (#348) 3717ff70 Minor 067f81e2 Comments and cleaning abc706ee Fix partial product test 7cf965de All tests pass 9617c221 Increase degree bd1672cb Working 4e361726 Use partial product chain 0f06a01a Merge conflicts c4064328 Merge branch 'main' into generic_configuration 5f7992f0 Minor b2264752 Optimize combination of gate constraints in recursive circuit (#342) e9ae9a04 import 671bb9be Specialize `InterpolationGate` (#339) 75fe5686 Better fixed-base exponentiation and `exp_power_of_2` (#340) c78d7611 Unused imports 7482e7b6 Remove RichField 3d74abba [skip-ci]Clippy b83f29cc Comments and cleaning d4c8caa2 Use generic hasher in Challenger 943e1f4b Minor fb18232e Generic config 1450ffb2 Small recursion optimizations (#338) fdce382a Standard configs (#337) fb3f5e7d Shrink further with another couple layers of recursion (#335) c8e043a5 Optimize recursive Poseidon constraint evaluation (#333) e39af10a More wires for ConstantGate (#332) bae26e09 D=2 in recursion test (#336) 184f73c6 Expose optimized Poseidon routines to the Poseidon gate (ARM) (#331) bc57a561 Delete CrandallField c6f91148 PoseidonMdsGate (#330) caf95ae9 fmt eb76bc5f cargo fix 31fda351 Expose vectorized Poseidon layers for use in gate evaluation (#329) 2bc74594 Docs (minor): ARM Poseidon explanation fixes (#328) 9bbbcf78 Static asserts: check ARM Poseidon constants (#327) 06e48d0b ARM-optimized Goldilocks Poseidon (#294) 8a5419d4 Static asserts in x86 Poseidon (#325) f286925e Add .DS_Store to .gitignore (#324) 7d39074e Minor optimizations to addition (#323) bf421314 Batched eval_vanishing_poly_base (#317) f616d643 Print overall gate counts (#322) 7a8e12b8 Profile-guided optimization script (#321) 806641d1 Small optimizations (#319) db23416b Goldilocks: better constant propagation through add_with_wraparound (#320) c406f464 Faster squaring in extension fields (#318) 001c9795 AVX2: Fold the constant layer into MDS matrix multiplication (#302) 7d45c80c Have eval_unfiltered_base call the normal Poseidon methods (#316) b0b2a10d Only log timing for the final proof in recursion tests (#315) 6463e4f4 Merge pull request #314 from mir-protocol/remove_basesum_gates 1f1827da Clippy 318185d1 Use only one `BaseSumGate` bf6d0266 Merge pull request #313 from mir-protocol/arithmetic_params_order 1d2ae77e Change parameters order in `CircuitBuilder::arithmetic` 22ce2da9 Add add_const, mul_const, mul_const_add methods (#312) 0b75b24c Have split_low_high use range_check (#311) cd13a3be Update bimap 64cd2e56 2 challenges, 28 routed wires (#310) 019ccf53 Merge pull request #309 from mir-protocol/use_quadratic_extension 0af4b1f0 Merge pull request #308 from mir-protocol/borrow_new_from_config e24285c3 Separate random access generators 5f4a2442 PR feedback c7674b24 Unused imports dda14011 Forgot a random access check 5b81006e Fill random access gates to make sure all generators are run 3f0b5ab9 Keep track of the last used RAM gate a35cd98b New random access gadget 104fd08e Working RAM gate 9503bb22 Take config by reference to avoid clone 00ce9d9f Add `num_copies` to RAM gate 1a43d130 Relegate poseidon_naive to testing only. (#303) 3790b55c Delete obsolete optimizations (Crandall Poseidon on AVX2 and NEON) (#305) ff3f0891 Minor: unused import warning (#304) 0f7be8b2 Fix compilation error on Aarch64 (#301) 609028c8 Poseidon-12 in hand-rolled ASM (#276) 0f90e4fb Merge pull request #300 from mir-protocol/clean_get_challenges 5d099c5d x86 ASM tricks for scalar Goldilocks multiplication (#299) 710959f0 Comments 7f6d90ee Clean `get_challenges` 164aa094 Use `cap_height: 0` in size-optimized proof d43850e5 Merge pull request #298 from mir-protocol/remove_inferred_elmt 839110b7 `coset_index` in other places 1ced853f Add coset_index var cdb28929 Move inferred elements to a new struct ad30f4ac WIP: Remove old benchmarks (#297) 62f3b558 Typos 288a8e11 Minor comments ddac8026 Comments acadd643 Clippy ea69a873 Working 41b26e1f Precompute the Dth root of unity. (#296) 8f59381c Faster modular inverse (#292) dc600d5a Hash benchmarks (#295) c55181a4 Fix logging in tests (#293) 011429da Merge pull request #291 from mir-protocol/fix_path_compression bc95563f PR feedback 64d38605 More cleaning e531eda5 Cleaning 64ad8783 Fix path compression d2a5e679 Somewhat working 145ee8cb Merge pull request #290 from mir-protocol/trivial_random_access 7f18b21a minor fix 0f82f41b Merge pull request #289 from mir-protocol/secp256k1 88b528e3 fix d2c589e2 addressed comments 695a56c4 addressed comments b5fea8d1 addressed comments e8805a12 fix 097059e0 switch to u64 array c625aae8 cleanup and removed tests for now f79419cc add check to primitive_root_order field arithmetic test 9b098a9f Trivial random access ea3e3150 First pass 5098c2a3 Have ArithmeticExtensionGate adapt based on available wires (#287) b922def4 Better errors for insufficient (routed) wires for FRI (#288) 69678f53 removed prime field tests 5e0d2744 fixes a4c89201 fmt 1262c6af fixes 351b92f3 progress towards Secp256K1Base field a407e5e1 Fix 21480857 Give MinSize a max arity option (#284) a0c12266 Merge pull request #286 from mir-protocol/fix_buggy_split_le 242ee26b Generalize to fields with less than 64 bits cb129fb0 Refactor recursion tests (#285) 6d601c61 Overflow fixes 73f9a0be Allow zero FRI reductions (#283) 898cac17 Automatically select FRI reduction arities (#282) d9634e07 Merge pull request #279 from mir-protocol/duplicate_indices a0554cbb Merge pull request #280 from mir-protocol/custom_serializer 84d1a158 Merge branch 'duplicate_indices' into custom_serializer bce3256c PR feedback b4614991 Useless `mut` bbeb54c9 Remove pruning 9d8b32c3 fmt 734a5cef Snake case warning 01c2047a Outdated TODO dadc1b2a cargo fix fbefaa47 Unused import e3b24160 Merge branch 'duplicate_indices' into custom_serializer 3859ca20 PR comments 0839ed87 Unused import 76d3f488 Fixes 17ed6a2b Clippy c2765960 Remove useless test 5dcb85e0 Minor fb585064 (De)Serializer for CompressedProof 8f212d31 Working custom (de)serializer 04aaa172 Merge pull request #275 from mir-protocol/arithmetic_u32 094f29a1 set 0 limb targets 1af224d8 Horner's method ffd069c6 fmt 6e21528a constraints test, and fixes 28eca4bf Clippy dd689716 Cleaning 94375cdf Minor f92ce1a8 Add CompressedProof type 3d207464 combine halves separately 862eee8e fix 455bc4d5 only 3 copies :( 0811279f range check outputs, and addressed comments ac974126 Renaming a97b9a71 Add compressed FRI proof type using a HashMap 3f226632 Split up `PartitionWitness` data (#273) 932cc812 U32ArithmeticGate 3d399259 Couple tweaks for Goldilocks (#274) bd38ada0 Change case to get rid of warning a0de5648 Implement Poseidon width 8 and 12 for Goldilocks field. (#268) 31b1a0a9 Less use of ZK configs in tests (#272) 76fcc4ee Very explicit path compression (#271) 1a508d0c Merge pull request #270 from mir-protocol/poseidon_8 df9a2114 PR comments 541ad5d7 Minor 7bf25754 Minor 747c9f89 Back to width 12 f3822898 Derive challenges from other proof fields (#262) 1a55538e 8->SPONGE_WIDTH in most places 42a7ff9c Working 23b1161d Merge pull request #249 from mir-protocol/sorting_gadget 0c0a8fd8 tweaks 3b6d4cbe Merge pull request #267 from mir-protocol/sorting_gen_refactor d2dcc31a Fix witness generation performance (#269) 202967a4 Other tweaks d541e251 Add a MemoryOp to simplify MemoryOpSortGenerator 8aa43763 addressed comments (set sorted values in partial witness; no more directly setting gate inputs) e8cb2bbd Witness generation fix (#266) 2ec3b297 addressed comments 73603915 Cache FFT roots (#261) 6c4173d2 fmt 644d87e4 fixes galore 46cc2757 Delete unrolled FFT (#258) 2f8286ff Fix a few warnings (#259) 43cbb84c Have verify_merkle_proof call permute_swapped (#257) a407ed5b Merge pull request #256 from mir-protocol/fix_reduce 5f3a5e6b Add `num_bits==1,2` cases in `le_sum` 4305a95c Small fixes to the `le_sum` and `reduce` gadgets 5d824176 Merge pull request #255 from mir-protocol/better_compressed_merkle_paths 39175947 PR feedback 1369dd7c Many small optimizations to scalar Poseidon (#253) 1f42916b Comments 36e4d360 Working (de)compression for FRI proofs 471ace6d Remove loop unrolling in a few more places where it doesn't seem important (#254) 422e7295 Working path (de)compression 5e748ed7 #[inline] add_assign in CrandallField (#252) 92f5d396 Merge pull request #250 from mir-protocol/poseidon_gate 0be8650b PR feedback b8f6b3a7 Merge branch 'main' into poseidon_gate a105cae7 Merge pull request #251 from mir-protocol/jakub/fix-alignment 3d93766c test (wip) 8dd00b8d added generator 2c1c116e fixes (addressed comments) d3de2b55 Fix alignment assumptions in AVX2 Poseidon 14bbf5ae Fix AVX2 conflict 675f3283 Minor f83c587c Comments e418997d Cleanup 5488be2a Add HashGate constant type 14fd1dfa fmt a00f2536 initial memory sorting gadget 3534018f Remove hardcoded GMiMC b63d83aa Add Poseidon gadget 7be26a70 Merge pull request #240 from mir-protocol/comparison_gate 8681cdec intermediate wires 5d7f4de2 Working recursively c508fe43 Minor 49ba7ccb Working 7abf48cd addressed comments (apart from intermediate wires) b11e54d6 Semi-working e1812dd7 Slighly more user friendly Sage snippet 151d1f1d Constants for Goldilocks & binary to generate them (#247) 0acff53e fixes 7a7bf371 fmt c207a028 changes and fixes (z --> most_significant_diff) 44dc1cd4 removed z 677165fd range check of chunks a68094d3 eval_unfiltered_recusively 806f4af9 eval_unfiltered_base 8a4259cc addressed comments 1a1358c0 cleanup (using reduce_with_powers) 9fe6dab0 cleanup 2f0ba9f9 cleanup (references) 8efa5a54 fixed test (first is actually smaller than second :P) 26c3edf4 removed copies 8a726d5a fmt 4eda0e9e fix (z calculated incorrectly) 63246bc2 fix (wires were out of order) 75ff36de fmt 6807c14c fix 4484a42d test_gate_constraints 18738e59 fixes 8731f813 some tests 542bc628 added num_bits ada79f70 cleanup 93e6bc62 fixed errors 9fa05003 comparison gate b5d35b35 Merge pull request #246 from mir-protocol/goldilocks_ext c1467386 Add submodule for field extension tests 5048a3f3 Minor c9d884f7 Change tests for quartic extension 1ca19784 Add a `test_field_extension` macro d9978b58 Remove unused 80af66c8 Test quadratic extensions of Crandall and Goldilocks dc4062cd Add quadratic and quartic extensions to Goldilocks a7cd1ef4 Vectorize Poseidon constant layer with NEON (#245) b411a275 AVX2 vectorization of Poseidon S-box (#244) 2ae9e349 AVX2 vectorization of Poseidon constant layer (#243) b0f244f1 Vectorize Goldilocks with AVX2 (#241) b3008b94 Some changes to generator_indices_by_watches (#234) de1d5d0a Minor: fix a few warnings (#239) 9ef784a9 Poseidon: vectorized MDS matrix multiplication (NEON) (#231) 6465e35e Poseidon: vectorized MDS matrix multiplication (AVX2) (#229) 91f7b4e3 Replace `CrandallQuarticField` with a more generic `QuarticExtension` (#232) c76eb9dd Prepare for the switch to Poseidon (#228) 7ffeba3a Delete my old MDS code, now obsolete a8d08aa1 Vectorized FFT (#223) bdd86a30 Crandall squaring in AVX2 (#233) c0e8edb8 Non-vector Poseidon speedups (#230) ba8b40f0 Goldilocks field (#227) e50d79a3 Tweaks to CrandallField::product 3674ceb5 Lints db3171bb Conversion to/from statically-sized arrays 5d69d85d Daniel PR comments + delete throughput figures 87f5201e Style (incl. Daniel PR comments) 7ee7d8bf Crandall arithmetic in AVX2 3bc34c59 Refactor GMiMC code (#224) a2eaaceb Rework the field test code a bit (#225) 50274883 Merge pull request #217 from mir-protocol/permutation effcc967 fmt c07f99ac merge 676c244d Unused field 1818e69c addressed comments a42bec03 cargo fix 236a143a Move some Field members to a Field64 subtrait (#213) 0e247199 fixes 6f885db6 fixes 1fb7eeb0 variable-sized tests 4c3f3cda 6x6 test 4f7a587b fix for non-2x2 permutation case f01d373d made switch_bool wires routeable 7acdf976 fixed fill_switch_gates f89f4924 wip ba4b03e4 Unroll a couple loops in Poseidon code (#215) d1fea5cf witnessgenerator 10d016a9 chunk size as field 3ad03659 fixed infinite loop 34948392 removed more to_vec calls (within maps) 260d4bd1 removed to_vec calls 485d4862 fixes 4ea1df82 fixes c2439557 fix 0f6e9c5b progress ab744a7c edits and fixes d4aa4d71 fixes and new generator f7607ddd fmt fe843db5 many fixes a1d5f5b6 progress f9a47ade fixes 2d5f362c fixes 013c8bb6 progress 2ab37e68 progress a574fecc permutation progress 412ada76 permutation progress b0a855a9 progress on permutation 032e2fee Daniel comments ec0195c8 PackedField trait 92bc65a6 Native Poseidon implementation(s) (#207) 1727d2c8 Batch multiplicative inverse optimizations (#210) d51bb98d Tweaks to reflect that we're not running clippy 5513a646 Buffer reuse in eval_vanishing_poly_base (#211) c4fd0919 Save 3% on eval_unfiltered_base (#206) 21b263ee Shave off 2% by optimizing check_partial_products (#205) a71966f6 Bugfix: Crandall field addition occasionally returns incorrect results (#203) 6949d04c Field arithmetic benchmark improvements (#200) 002a0ffc Merge pull request #199 from mir-protocol/rename_connect d01d2065 Remove named connects 71f64329 Minor 69a94554 `route, assert_equal` -> `connect` cd1bd9e7 Merge pull request #195 from mir-protocol/partition_witness 8f75a8de Merge commit '717efbb' 8c496122 Optimize bit reverse transpose (#198) d4ee2a6c Merge pull request #197 from mir-protocol/remove_remaining_reverse_bits bc3eb856 Remove remaining `reverse_bits` 181ddf93 Merge two impls 2fcfa230 Pr feedback 5fba65a3 Check old value in `PartitionWitness::set_target` 717efbb8 Fix test 88e06566 Address a few more unused warnings (#196) 535c3856 Field: Default (#193) eeef54c4 Re-add Clone db0ccdd7 Unused import 1c07cb19 Renaming a61d7bc0 Typo 5264859a Minor 507577b7 Comments 1d368782 Fix tests a90ea6ec PartialWitness back to HashMap e81001b9 Clippy c53d0504 More cleaning 65847349 Cleaning c6cf5cf1 Move PartitionWitness a44bf9ff Added witness trait 98559c32 Working 40c760c8 actually randomizes 0155c422 fmt 7dea2451 addressed comments f5c5ed9c finished switch gate c2d7044f progress c1b8a4b4 visibility 1ccff4d0 progress 57dc460f Update running example 90c7a72c Remove some unused warnings (#192) af0ea25f more trials 74c2be50 First pass 94a0ad78 switch gate (in progress) 7c97751c Optimized transpose (#191) d41924da Benchmark transpose (#190) d497c108 Import 4dde4591 Merge pull request #189 from mir-protocol/remove_reverse_bits c31c06d2 FFT/LDE benches (#188) 01d745b6 Minor 291e67be Remove `reverse_bits` flag in Merkle trees f2ed563d Try PoW seeds up to p (#186) aae2c9d1 Reduce PoW bits 21bdc6b3 Temporarily remove license info e98bca6c Merge pull request #185 from mir-protocol/more_scalar_muls 1e057e89 Use scalar_mul vs converting (#184) 56122810 More scalar muls 722f9974 Use scalar_mul vs converting 69193a8d Remove `*_three` methods (#182) 81e0acfc Merge pull request #183 from mir-protocol/fix_generators_check 896988ca Put back generator check 20cf073e Merge pull request #181 from mir-protocol/push_to_8192 d9b0778e Change zip order 9a545401 Minor 5a9c5b29 Minor 3d83d9ff Minor b3664828 The mother of all arithmetic optimizations 6ba6201b Merge branch 'main' into push_to_8192 99bb86ae Comment fix e4cbee2b Disable ZK in `large_config` (#180) f3bfd666 Add a BoolTarget (#179) 8effaf76 eval_fns for PublicInputGate (#177) 9c42fef9 Little refactor (#178) 47e9f546 Merge pull request #175 from mir-protocol/some_more_arithm_opt 8aaa9401 Add comment for slope 08f0e0ff Remove unnecessary checks 630b7f22 Smaller parameters bb548fe1 More cleaning ceae6b95 Cleaning 237ef4d0 Change FRI params to get below 2^13 43641174 Comments 68af28e9 Fix tests d0aae8c2 clean 611c1767 add reducing ext gate 1c789657 better 7da4412d working 9547aa63 minor 75ad055f First try 73ab11f4 More arithmetic optimizations 71c392e9 More optim 21669be2 Some arithm optims bbfc0f8a no mut b20d6dc1 Minor optimizations (#174) 5bce9ca9 Merge pull request #173 from mir-protocol/minor_arithmetic_optim 2bfa4544 PR feedback 7271af82 Optimize `evaluate_gate_constraints_recurively` 38505b71 FRI refactor (#172) 702eab15 Add `wide_arithmetic` 08e45745 Comments 90613359 Some more arithmetic optimizations debc0e9c Merge pull request #170 from mir-protocol/merkle_cap 9c01e1d9 PR feedback 090cf797 Replace some old division code (#171) 9f004c96 Clippy e73c1d77 Cleaning / Renaming ad8428f3 12604 gates, 318637 bytes ec114784 Minor 684df1e0 Pass cap index f2c423ee save 13 gates 57f2b5b7 working f91b9b60 debug ce71b536 First pass 2cf82636 Merge pull request #166 from mir-protocol/optimize_arithmetic_ops 94123c45 Fix merge conflict 4433fd80 Merge branch 'main' into optimize_arithmetic_ops b15e36d2 PR feedback 45fdc4d1 debug_assert 12d4b9ad Merge pull request #169 from mir-protocol/ci-tests 0750f54b addressed comments 4bb525f1 triggers 4d7c6a98 fix syntax 9c591fdc test and fmt 4118c88d Merge pull request #168 from mir-protocol/derive_quotient_degree_factor b89f4d65 Update various dependencies (#163) 3eacd53b PolynomialBatchCommitment tweaks (#164) 4e86d9d7 Derive `quotient_degree_factor` 898caa2b Merge pull request #167 from mir-protocol/fix_degree_errors 05c94799 Operator precedence f08d47b7 Update comment 21f90ca8 Fix some off-by-one errors in the degrees fe360300 Merge pull request #165 from mir-protocol/partial_witness_vec c3a0b7cc PR feedback 3adabbed Fix comments 905aaa2e Unused d0cb1bec Minor bis f528835a Minor 8e6c30dc Use only one gate for `div` ff68b66b Add `div_add` 417e6055 Optimize coset in `compute_evaluation` d27dd92a Some more optimization 4b44578f More optimizations ed8dc9fd Cleaning eeb33f99 Optimize mul_ext_algebra f0f8320b First pass dd076e5c Auto resize partial witness 4f4839dc Merge branch 'main' into partial_witness_vec a02ab285 Remove a TODO cc6c3651 A couple field benchmarks (#161) 97c2b6b9 Bit of refactoring in FRI code (#162) 40f5dc6a basic test action fe46aafb Merge branch 'main' into partial_witness_vec 30bedbb1 Merge pull request #159 from mir-protocol/faster_transpose 213de80a Merge branch 'main' into partial_witness_vec cea07721 Merge pull request #158 from mir-protocol/remove_hashmap_witness_gens e97b7b07 PR feedback c328fb67 unused import 674b0a51 faster_transpose db236e48 Fix tests 7d11d0f8 Change PartialWitness to use `Vec`s a6490de7 routed_wires -> wires 99e399f6 Renaming 8531cf04 Replace HashMap by Vec in `generate_partial_witness` a255c320 Merge pull request #157 from mir-protocol/remove_reverse_limbs 7b20f342 More eval_unfiltered_recursively tweaks (#156) 6aaebfca Optimize BaseSumGate's eval_unfiltered_recursively (#155) fcc717e9 Remove useless mut df07909f Merge branch 'main' into remove_reverse_limbs 693fd4f8 Minor 83fe4d5c Update comment 54180260 Remove reversed sum from `BaseSumGate` 58204e37 unused 4eb81538 Merge pull request #154 from mir-protocol/random_access_gadget c7093cd5 addressed comments bf385c27 Batch inversion in wires_permutation_partial_products (#152) 32c8c366 added random access gadget 079baff7 Optimize some exp methods to use ExponentiationGate (#151) 8b8e4d22 Optimize witness generation a bit (#153) 79af8753 get_extension_targets 2d989198 Add a reduce_polys_base (#149) bb1c083e Merge pull request #148 from mir-protocol/random_access_gate 0ecaf408 addressed comment d19ce67c renamed element_to_compare --> claimed_element ed49bbc7 cleanup fa5a5c5e cargo fmt 51473b48 merge 1db727d9 addressed comments b1633dc4 More par_iter (#150) acc59327 random access gate 53f1acc5 random access gate f150f7ec More timing code for FRI prover (#146) 9fb780d3 Move evaluation of L_1(x) outside of loop (#147) 5c96e7b3 Trivial fixes from "cargo fix" d6211b8a Reuse a buffer of generated values (#142) 730962ce Merge pull request #145 from mir-protocol/test_eval_fns 63e78826 Imports 1229d90f Add a tree of scopes for proving times (#141) 08b018fc Merge pull request #144 from mir-protocol/optimize_small_exp_u64 f564498f Merge pull request #143 from mir-protocol/exponentiation_nits 2bbcf176 Test `eval_unfiltered_*` functions df690f92 Hardcode small exponents in `exp_u64_extension` fc9d64de Small nits for the exponentiation gate 36a1386c Small fix in arithmetic_extension_special_cases (#140) 94b85b08 Small optimization -- use mul_add instead of mul + add (#137) 50b07f2c Special cases for extension field arithmetic (#138) bb316fb1 Use batch inversion to compute the quoients for Z (#136) 018fb005 Move stuff around (#135) c02d4803 Merge pull request #134 from mir-protocol/exp_gate_config 9eda81c2 minor changes 8440a4cb fix cfda56e3 Merge branch 'main' into exp_gate_config a111fc31 fixes 95503ff7 Merge branch 'main' into exp_gate_config e46bd08f exponention gate takes only bits c5da582f Logging tweaks e382decc Import fixes 56b62f19 new exp gate takes in CircuitConfig and determines num_bits df03c22c Fix order 5f79b963 Update division calls (#132) f7e9af6f Merge pull request #133 from mir-protocol/exp_gadget fa06cc55 fix 17217f11 addressed nit 2fd9ce21 fixes to exp functions 247fa393 exponentiation gadget 6749a94c fix a1aa5d63 Merge pull request #131 from mir-protocol/exp_gate 607d0a89 fixed max_power_bits 68672c00 addressed comments a38a5e22 select_ext takes bit as extension; used in recursive eval 33ba269c fixes 3944c5e6 added eval_unfiltered_recursively 044eb597 added eval_unfiltered_base 21ec7533 fix: power bits in test now LE b5418bff added output wire 9f4cf9f3 Merge pull request #130 from mir-protocol/exp_gate_nits 54626be7 comment 1e9845af nits b66ad6c9 added debug assert 34d59305 Merge branch 'main' into exp_gate 7e5e4fc6 updated num_constraints 59bfc52f removed extraneous constraint 695e5bc9 fix? 0b8f5860 removed if statement af62688b fixes 9081ae73 Reduce noise in FRI logging (#129) 3fc02c76 Couple fixes to ReducingGate (#128) 1e5a503a Merge pull request #127 from mir-protocol/reducing_gate deea1c97 PR feedback 5fbeb874 Merge pull request #126 from mir-protocol/remove_exp_from_complement d7a9274f updated for add_gate changes da9017bc Merge branch 'main' into exp_gate b06d7bc8 cargo fmt 0c4a1bc5 fixes 8e2f33bd fix 64d8a443 cargo fmt 1c8015b9 finished tests (don't pass yet) 9932517e cargo fmt 40055dc4 basic tests 6d22ad6e initial version of exponentiation gate d509f03b Implement `eval_unfiltered_base` c0f09591 Recursive recursive verifier test 8f18089a Implement `eval_unfiltered_recursively` for `ReducingGate` 2ea35bd9 Comments 5a16d7c5 Change number of coeffs supported in `ReducingGate` depending on config c16d93ab scaling.rs -> reducing.rs ca540a84 Working `fri_combine_initial` 0526a9e1 Working ReducingGate 19140e39 Update comment f325586b Replace `exp_from_complement_bits` with simpler method 6f8053cc Forgot to exponentiate from bits in computation of `subgroup_x`. Saves 80 gates. bcf524be Have `add_gate` take a generic type instead of `GateRef` (#125) d435720d Merge pull request #123 from mir-protocol/remove_acc_in_gmimc a70e97be Fix merge issues 9a1c289f Merge branch 'main' into remove_acc_in_gmimc 47b99364 PR feedback d8b8161c Merge pull request #122 from mir-protocol/eval_unfilterd_base 981ed478 Merge pull request #121 from mir-protocol/simplify_interpolation_test a6257749 Merge pull request #120 from mir-protocol/clean_select 907f1e91 Merge pull request #124 from mir-protocol/order_bigint 0af5c3bd addressed nit 9c287aac fixed nits 3612b9f0 Merge branch 'main' into order_bigint ff055b64 cargo fmt 3425bd00 replaced some clones with refs 57da32fb fixes to use references ffc90e90 exp_biguint test b6e74b82 cargo fmt 5d301241 moved specific tests to prime_field_arithmetic 6bd197e9 Observe public inputs (#119) e87aa2c9 Renaming 05419569 Remove useless clone 1d921912 Make `exp_complement_bits` take an iterator to avoid cloning. ca3a2fcf Clippy c729a3c2 Remove all non-bits indices in the FRI verifier 22fcc3bc Pass bits around 15a64017 We need only 126 wires now 467485c3 Remove accumulator wires in GMiMCGate 3a24e8f4 Manually implement `eval_unfiltered_base` for all gates b68be576 Simplify interpolation test b65e792f - Remove useless rotation gadgets - rotate.rs -> select.rs - Added `select()` and `select_ext` - Optimize to use just one gate 1d5cd443 Merge pull request #118 from mir-protocol/avoid_rotating be2e870a PR feedback 01461ce3 Update a TODO 1322b8d0 fixes 59efe6a8 added test_arithmetic for extension fields 292a28e6 fixed tests b17dabef more fixes 3e00a581 fix: endian-ness ff564861 compiles 164bb7f5 fixes 7f92a339 cargo fmt 1dd850b0 fixes db0121d7 Update comment 6fff9363 Use `mul_many` a54a4e58 Merge branch 'main' into avoid_rotating 7c1c082a Comments 2f46ddc4 Merge pull request #117 from mir-protocol/optimize_mul_many 6cc871d3 PR feedback c5bbe9d5 fixes 5062029d fixes 59494ff8 Merge branch 'main' into optimize_mul_many 6e305f0a Change `{add|mul}_many` and `cube` d870a36d {add|mul}_three_extension 80b696a3 Avoid a clone (#114) 7d8bac71 Change FFT methods to accept references (#115) eb18c7ea Faster witness generation (#116) b8ce1d19 Public inputs (#113) b59d4979 Modify ArithmeticExtensionGate to support 32 wires 8642a10f Start of optimization 906a0c00 Merge branch 'main' into order_bigint b103c077 progress 48f5c934 route -> assert (#112) ac1872a8 FRI tweaks (#111) dff950c5 No Copy on ReducingFactor (#110) 0a59c738 Add a context for each gate evaluation (#108) 8a6d0fe0 Merge pull request #107 from mir-protocol/precomputed_reduced_evals 925c0bcb Replace rotation with exp in `compute_evaluation` 0d233505 Merge branch 'main' into precomputed_reduced_evals fbeedd47 Minor 38f4cca3 Target version 97c4cfff Fixed bug a74fce01 quick fix 8a51e6a3 started 8438d239 Tree of scopes (#106) 69dc14f8 Added PrecomputedReducedEvals struct d24ecb6d Merge pull request #105 from mir-protocol/remove_wire_partition_indices 72021a56 Simplify b72d4d12 Remove `indices` in `WirePartitions` a9e5f1e4 More routed wires for recursion (#104) 4dc6a603 Merge pull request #90 from mir-protocol/insertion_gate 8868378f Merge pull request #103 from mir-protocol/remove_frobenius_opening 661c6d00 Merge pull request #102 from mir-protocol/trim_final_poly 52cc7c79 Remove openings at the Frobenius of zeta 9baea1ae Trim final poly and check FRI arity 35c86436 Tiny cleanup d11bcd19 Optional zk (#101) 0a5d46bf Have `prove` return `Result` (#100) 9c17a00c Division related changes (#99) b9376792 Merge pull request #95 from mir-protocol/more_recursive_verifier b7d51db9 Fix merge problems 461f24a5 Merge branch 'main' into more_recursive_verifier 09741347 PR feedback d8af0a93 Merge main 6b214410 fixes 573badc9 Parallel proof-of-work search (#92) 77ce69dc Proof serialization (#93) 83a14300 Fix some warnings (#94) 4bc06dee `zs_root` -> `zs_partial_products_root` a6bc8321 Minor fe05da67 Clipp 7d41785a Comments 2e12ee8e Minor 7dec6efc Rewrite Markable to avoid Arcs 0ba59163 Minor becd0c96 Merge branch 'main' into more_recursive_verifier d3127c73 insertion gadget (test fails) c678c554 Imports e68be510 Imports ea07db14 fixed in recursive version too 220c9bc8 fixed bug oops 82d08ff2 Merge branch 'main' into insertion_gate c708d7e4 eval_unfiltered_recursively 16ae6879 cleanup 9f2f987f minor fixes 27c93f8f addressed comments 4a86e3da Optimize `get_sigma_map` 514ee0fe More realistic config 92c4bba2 Fix bug in GMiMC 7793b5a9 Identify problem in GMiMc dcdbb6be Check copy constraints in PartialWitness c24fe65f Merge pull request #86 from mir-protocol/fix_z_check 02f07150 PR feedback c3d53392 Problem in permutation argument 0a32e0fd cargo fmt bad2e646 Merge branch 'main' into insertion_gate f4c7756f removed test debugging 3a2ba05b last fix! c6a33d0e more fixes ca944d58 fixes; and in-progress test debugging f0ae72ba constraints in base field (fixes low degree test) 1ecc2346 fix e0c767f3 added intermediate wires to generator; and test (does not pass) 5c2c01b1 Circuit compiles c99d7f48 Add Merkle tree test 139430c5 Fixed GMiMC 57a39a17 basic tests, and fixes 82206fdc num_wires 1b83b4dd cargo fmt 4bac3464 generator 185117ed params cbffb854 cargo fmt 6090f6d6 changed insert_here to a wire, and fixes 7a5b04ad Merge pull request #89 from mir-protocol/insert_gadget_fix 2df81e15 switched to William's way, and len assert in test 66e4f7c3 added output constraint 43930345 fix ad24f5d4 Almost working recursive verifier bec189b5 fixes cbb0cbff Finish test setup 751e6164 cleanup and progress 662d62d8 progress on eval_unfiltered b50a9809 Start recursive verifier test 7ab21a4f Merge pull request #88 from mir-protocol/finish-verifier 9cd5f820 Add verification to scaling gadget 0512817d quotient_degree -> quotient_degree_factor 805ebb1b Working verifier fbcfbf2d insertion gate progress 2bee1c67 current progress 274ec48f Update comments 4a27a67b Constraint degree should be at least 3 85d162cd Merge branch 'main' into fix_z_check 151d1ad0 Remove addendum 50cafca7 Partial products of quotient 2841b386 Merge pull request #87 from mir-protocol/optimize_partitions 083d8413 Path halving -> Path compression b7561c31 Fix bugs 73c1733e Clippy 13f470e4 Comments b6554ba2 Replace `indices: HashMap` with `indices: Fn(T)->usize` d93cf693 Minor fc0f8a78 First try b5b2ef9f Clippy 1915ef9b Minor improvements d456efbc Minor addition to partial product test 525db6c4 Merge branch 'main' into fix_z_check a3f473a0 Comments and minor refactor in `eval_vanish*` 90bdb579 Fix product bug 50ffb1c4 Merge branch 'insertion_gate' of github.com:mir-protocol/plonky2 into insertion_gate dcde5993 Merge pull request #85 from mir-protocol/efficient_insert 3d532015 save a gate with arithmetic 6751f920 Merge branch 'main' into insertion_gate bdb6cfe9 skeleton ba06dc88 Make changes in `eval_vanish_poly` 39b22a6c addressed nits efe39f2d fixed naming (zero --> nonzero), and other fixes 519533d4 Benchmark tweaks (#83) d84b9ec8 is_zero function 51537365 fix 702a0980 cargo fmt 8f33f8e2 Merge branch 'main' into efficient_insert f4ca0df8 comments and renaming b86e60a3 Open the partial products polynomials only at `zeta` cc3c278a Some renaming 20e6d654 Add comments 95a875e2 Allow virtual targets to be routed (#84) 1858a869 Optimize products of 1 element 59410447 Add lengths to `CommonData` c83382aa Working partial products 8de59c2a cargo fmt 3959cec1 mutable borrow fix 9bacaa88 Merge branch 'main' into efficient_insert 574a3d48 FFT improvements (#81) cad7dc69 some progress 80758da0 removed duplicate functions 77d942f0 cleanup 647568fc added EqualityGenerator 6cc06b40 small change 0c9d675e fixes af9a2c05 some fixes 6c598ddc very initial attempt 6ec99d0b Merge pull request #82 from mir-protocol/blinding_related_fixes 03179e56 Couple fixes related to blinding f7c4a463 Progress b8e764bb Allow for `degree^2 < num_routed_wires` a0298a61 Added partial products 69fff573 Merge pull request #76 from mir-protocol/add_routed_wires a017e79f Merge branch 'main' into add_routed_wires b7f0352c Update comment on `reduce` 46ff0796 Merge pull request #80 from mir-protocol/blinding_factors e44c4ff6 Merge pull request #79 from mir-protocol/lower_max_constraint_degree eee3026e Move `shorten` in `find_tree` f1e3474f Simple reduce (#78) ce47eac7 fix d599664f merge bae3777b Use max filtered degree found with the tree method in `CircuitBuilder::build` 493e81d7 Merge branch 'main' into lower_max_constraint_degree 9a352193 PR feedback d5457bf4 fixed arithmetic, and added comments 2c5c073b Merge pull request #68 from mir-protocol/permutation_argument f8f28a16 Merge remote-tracking branch 'origin/permutation_argument' into permutation_argument 57c86143 Fix mistake in comment 40f53460 cargo fmt 5aa09601 minor fixes 4fb78cf7 fix 01493303 blinding 7734aed6 Performance tweaks (#77) 12e81acc Optimize the degree of the tree returned by `Tree::from_gates` to allow non-power of 2 degree. 4649c9b7 Merge pull request #75 from mir-protocol/merge_constant_sigma_commitments c2b2ef92 PR feedback 636d8bef Comments 2f06a78c Simplify exp_u64 42db0a31 Clippy fc473886 Rearrange files 8602ae15 Typo b62c2e69 Supplant ArithmeticGate with ArithmeticExtensionGate 8a119f03 Working ReducingFactorTarget beadce72 Add `ZeroOutGenerator` fd3fa739 Fix test relying on `ArithmeticGate` e195fe58 Merge branch 'main' into add_routed_wires 6652b38b Remove `ArithmeticGate` 810d1869 Minor improvements 625377b4 Merge constant and sigma polynomials 727919b1 Comment and test for `coset_ifft` 2e9d3f76 Better error message when quotient hasn't correct degree 249c8a83 calculating number of blinding gates needed 3ce91839 Modify new test 54a15c01 Fixed bug and add division test in the base field. 21e7e8fd Merge branch 'main' into permutation_argument 19e7cb39 `into_iter` -> `into_par_iter` 3400caa1 Fix recursion bench (#74) 6605ca9d Add comment for `coset_shift` fb89d637 Merge pull request #71 from mir-protocol/gate_tree 8aa9c7b8 Merge branch 'main' into gate_tree 2a38f865 PR feedback e50eeb6c Delete more outdated comments aa78d02c Delete outdated comment c7753186 Clippy 54315d17 Remove `GatePrefixes` to avoid using a `HashMap` b4258976 PR fixes 8c008ce4 Add comments 24af1f8c New heuristic 35f73a50 Clippy b0550979 Optimize evaluation of `Z_H` on coset. 31f4eee3 Fix bug with shifted `x` f215dffa Compute quotient directly 4ee70e44 Fix type errors and move copy constraints check to `Witness` ef7561fc Merge branch 'main' into permutation_argument ac117925 Delete coset [I]FFT methods (#72) 08338b6a Merge pull request #66 from mir-protocol/fix_target_partition d69f1179 Revert `num_routed_wires` to 27 ff74887a Use `with_capacity` when length is known 8796c733 Change `MulExtensionGate` to `ArithmeticExtensionGate` and change gadgets to use the new wires in this gate. 1cfffcc9 Add comment on `compute_filter` 0a75dcdb Remove prefix before calling `eval_unfiltered_*` c01e772f Simplify filter computation f050c890 Merge branch 'main' into gate_tree 747f1875 Add todo for public inputs 8ae664d9 Minor 6c864ca5 Merge branch 'main' into fix_target_partition bc90909f Add check of copy constraints after witness generation 3bc27c65 Rollback to previous semantics 1903ecac Merge pull request #69 from mir-protocol/optimize_reductions 517c75ab Add comment for `ReducingFactor` 492b0484 Optimize some polynomial operations to avoid cloning. 26e669dd 2 `collect`s -> 1 `collect` 01053ab9 Fix bug 70e98048 Merge branch 'main' into optimize_reductions 680d7a63 Add `eval_filtered` methods 5acbb674 Add prefix to constant polys aec6f217 Test with tree from all gates. cfa3d3a6 Added comments 19836001 Change tree from struct to enum da3d34a0 Working gate tree generation 5442c4dc Merge pull request #70 from mir-protocol/optimize_polynomials 15922d25 Add comment for denominator polynomial 37171505 Remove useless interpolation from `open_plonk` a4c86a6b `lagrange.rs` -> `interpolation.rs` 7d4e79f0 Clippy 621c046f Use long division when dividing by quadratic polynomial 5a8d9515 Use `interpolate2` in the FRI verifier 4f8ef2e1 Optimize some polynomial operations 9db7dce7 scale -> reduce 92e0f60c Clippy fe9cd3f7 Working commitments and verifier 100ab6ce Merge branch 'main' into optimize_reductions f27620ca First impl b2e8a449 Merge pull request #67 from mir-protocol/hardcode_plonk_polys 79e99148 Minor ad5c18b4 Comments and Clippy a71909ba Implement `compute_z` and rewrite of `compute_vanishing_polys` 1b99f827 Merge branch 'main' into permutation_argument bfd5f063 Hardcode Plonk polynomials indices and blinding flags. fbbe5398 Merge pull request #61 from mir-protocol/recursive_verifier 930e1171 Minor 5bebc746 PR feedback eaba5238 Change PoW to checking leading zeros a6acd14d Minor rewrites and optimizations 5edaab59 Renaming + Clippy e647e177 Simplify `insert` and `rotate` gadgets, and check that we don't overrotate. 8d999ab2 Rewrite `insert` gadget. 4437012d Add `num_bits` to `exp`. 6203eb00 Rewrite `MulExtensionGenerator::run_once`. 8200bdce Add `num_bits` in `split_low_high`. ab7e2381 Remove `rev` in computation of the reversed sum in `BaseSplitGenerator`. 89b25c52 PR feedback fixes 89c6a6b4 Use `repeated_frobenius` in `ExtensionTarget::frobenius`. b44a01c9 Merge main 39c793a3 Fixes from PR feedback c22bc626 Revert watch insertion 86b4b0ab Add check that all generators were run. ea6a7245 Enforce copy constraints in partial witness generation. 32255626 Cosmetic changes cfa58075 Add `insert` gadget to insert inferred leaf in FRI query rounds fa229d9a Add comments on possible optimizations 6132b2ad Use `mul_extension` in `scalar_mul_ext` since `mul_extension` now uses a single gate. 670e4838 Finish recursive verifier 88c58c32 Fix imports 19b47b52 Clippy 445ea377 Remove `Frobeniable trait` 30f23fed Merge pull request #62 from mir-protocol/fix_interpolationgenerator_deps 4ed03f4f Fix `InterpolationGenerator` dependencies. 133c75d6 Interpolation gadgets tests 5200d70c Add interpolation gadgets 4b1f368e Use Frobenius optimization in the circuit bc7f67c3 In-circuit repeated frobenius 20741cfb Implement out-of-circuit `repeated_frobenius` using hardcoded constants 1ebeab2c Implement Frobenius optimization discussed in #61 comments to avoid calling the Frobenius for every wires. 4106a47d Test `rotate` gadget 1eb37232 Add `CircuitConfig::large_config()` for tests. ccd31d17 Remove useless Generator 2cd99ff8 Change `ExtensionTarget::frobenius` to use 4 constants instead of 1. f929f946 Have rustfmt group imports (#60) bdf8417c Modify `rotate` to use binary dea6db00 Rotation gadgets 81ce0eb7 Fix bugs f8dd35b7 Use low-high in query round. cdce82e1 Small fix 6cce4c1f Add low-high split 60f3773a Try to simplify open_plonk and fri_combine_initial a bit more (#59) bb551092 Remove `mul_extension_naive` 3db6e38d Convert some `mul_extension_naive` to `mul_extension` f7e92af9 Comment out errors 89761ef2 Added in-circuit `reverse_bits` and `exp`. 9adf5bb4 Use `ExtensionAlgebra` + new `CircuitBuilder::mul_extension` 7f632766 Merge branch 'main' into recursive_verifier 72c2e19b Bit of verifier work (#54) 2b5b5f87 Merge pull request #57 from mir-protocol/quartic_quartic_algebra f9652114 Use Daniel's `fmt` e1e4bb36 Started query round 94ca809b Merge pull request #58 from mir-protocol/remove_quartic_quartic db1ef913 Remove quartic_quartic c6c71ef5 Working `fri_combine_initial` 47da1ef6 Add MLE tests for algebras c674d6c1 Remove unused imports 19a38682 Minor tweaks 070dc7c9 s/ext_ext/ext_algebra d727f84a Working test 5678c7eb Added `ExtensionAlgebra` e50d0aa6 Interpolation of two points 8cf2758b Division gadget for extension field a8da9b94 Working MulExtensionGate 6f2275bc Progress f5dfe95b Added recursive `powers` eee11751 Fill `todo!` 11698701 Added test 51c06d74 Recursive PoW 42d5b80a `BaseSum` gate 897ec3b0 `Target` version of proof structs c24ad606 Merge pull request #55 from mir-protocol/check_base_field 7334341c Attempt at simplification 9eb35c3c Remove D=1 case 60e94644 Remove unused 482b4006 Merge with main b465bcd8 Clippy + comments 2794cb9a Open wires at single point if D=1 59dfe5db Remove old tests e09a6179 Remove useless field d8822837 Working with blindings 6ee9ceac Don't check Frobenius if `D=1`. abc0ca3b Rewrite LPC code to be more PLONK-specific cb7f8c8b Draw challenge points from the extension field (#51) 845382b4 Working base field check for (non-batch) opening 15eb25bc Merge pull request #50 from mir-protocol/verifier_stub 5a5a86a4 First bit of verifier 9cf58627 Fix typos 3833d057 Merge pull request #49 from mir-protocol/mul_extension_opt d9e82881 Typo e98593aa Little circuit optimization 5a261332 Merge pull request #48 from mir-protocol/target_field_extension a11d2ed3 Fixes based on PR comments c9309eb2 Minor 4f6e9fb2 Recursive evaluation for interpolation gate. b64a5fab Addition and multiplication for ExtensionTarget 655bcd8e Minor 66d6f3c3 Remove useless Qua(d)r(a)ticFieldExtension traits 84e9573c Merge pull request #47 from mir-protocol/par_quotient 26845c59 Compute the three quotient polys in parallel c108dc6d Default degree 2^14 83ef3d1d Tweak log fb6d0d2f Merge pull request #46 from mir-protocol/bench_ldes_tweaks 741dc4c9 Tweaks to bench_ldes 0b75c9f1 Tweak readme a035a9ca Tweak readme 3619c2dd Merge pull request #42 from mir-protocol/interpolation_gate a4be5855 Fix GMiMCGate 74797455 Add test_low_degree for other gates 229784e5 Delete old FRI gate 621b097a Address most feedback d0551347 Not just quartic 6e83d956 Finish up 3311981f Minor 0ce1a4c5 Minor 110763fa Minor b535bf23 Minor 227c80c8 fmt c6fa7eb1 Minor 0c91739b [DRAFT] Interpolation gate 965c4e4d Merge pull request #45 from mir-protocol/remove-exp-self b438760f Use bits_u64 78f71672 Change `Field::exp` to using a `u64` power. 7d302aac Merge pull request #44 from mir-protocol/fri-extension-field e806d86f Remove custom `primitive_root_of_unity` in extension fields by modifying the generators. 1cbd12ed Fixes based on PR comments 8737c8d5 revert extension field order 4f6f2192 Minor fixes 96a88019 Clippy 9cd00532 Generic tests adf5c2d4 Const generics everywhere 37f6ee53 Merge branch 'main' into fri-extension-field a2cf2c03 Working FRI with field extensions cb4c420d Merge pull request #43 from mir-protocol/poly_inv_fix 1e5dfa40 Fix intermittent inv_mod_xn failure ecce373b Merge pull request #41 from mir-protocol/os_rng_to_thread_rng bd20ffa5 cargo fmt 949fb879 Switched over from OsRng --> thread_rng de0b382f Merge pull request #39 from mir-protocol/three_zeta 7ff54963 num_checks -> num_challenges 13fc0c22 Merge pull request #40 from mir-protocol/move_timed b14328c2 Move timed! and call from ListPolynomialCommitment 17b51dc1 Merge pull request #38 from mir-protocol/more_poly f45c8d95 Remove old field search code 78af8830 Old TODO a04bed28 Use num_checks zetas 7f445686 Tweaks 6d03dd06 Finish merging in old_polynomial 18d59ec9 Fix minor post-merge conflicts 51114e4e Missing import b7acdb36 Merge pull request #36 from mir-protocol/poly_port 22a625e8 trim b ec541634 Merge pull request #37 from mir-protocol/extension-field 1e45b0b1 Move Frobenius to default trait implementation. 75711f1d Merge branch 'main' into extension-field f1d81281 Added field order test 04664a54 Binary extension fields 5e86e7dc Rand + fix mul + tests e670ec3f Multiplication + Frobenius + Inverse 306fb8ef Merge pull request #34 from mir-protocol/batch-list-polycommits cdbac761 Fixes based on PR comments ce0507ba Blinding parameter can be set differently for each Merkle tree in a FRI proof. 4d5ea833 polynomial_long_division -> polynomial_division 8b309fef Tweak 44a5e0be Some cleanup related to the two polynomial APIs 5abcd85f Started extension field implementation ed805453 Minor f8f65cb9 Merge branch 'main' into batch-list-polycommits d529afcd Minor 6ca1b28f Fix bug 6f13263d Minor 1bae3a02 Batch open for PLONK ea7926bd Update PLONK prover. e44fbb86 Merge pull request #33 from mir-protocol/observe_quotient_root 89701c82 Observe root of tree containing quotient poly data 0bae47be LPC batch opening 7617018d Merge pull request #32 from mir-protocol/zero-knowledge 36dda7aa Remove useless challenger observation. 41008cf4 Move Merkle root out of opening proof. 477fe1ea Minor fixes ad6a1789 Update README 03d761ea Double blinding 5706c424 Fixes based on PR feedback 5e06c014 Cleaning 1f3f7d5b FRI reorg 6820c184 Working blinding in LPC 6dbd39de Passing test 1bf21d17 Merge branch 'main' into zero-knowledge e7c37dfa Merge pull request #31 from mir-protocol/fix-subtraction 20dae028 fix subtraction eb3011b0 More work on polynomial commitments bb8a68e1 Progress on polynomial commitment 0fa09429 FRI on coset 116f92ea Merge pull request #28 from mir-protocol/mds c464c038 Merge pull request #27 from mir-protocol/fri-reduction-arity-contd fd3e8bcd Minor fixes 6db30c6d Precomputed MDS matrix for CrandallField 5913fd71 Add methods for MDS matrices 83354972 cargo fmt 85904077 Fixes based on PR comments aabfbe82 Fix GMiMCEvalGate f624415a Clippy 79a8ccd9 Working bit-reversed version 815a2943 Merge pull request #25 from mir-protocol/fix_challenger ae771bb8 Merge pull request #26 from mir-protocol/recursive_gmimc d3ac3656 Recursive evaluation of GMiMCGate 0252d5c7 Fix Challenger's duplicate challenge bug d5da6308 Merge pull request #24 from mir-protocol/fri-reduction-arity deb981e9 More fixes 187b122c Fixes based on Daniel's PR comments. cf98d7bc Merge pull request #22 from mir-protocol/field_tweaks 460ca64b Merge pull request #21 from mir-protocol/perm_arg a8c23a35 Merge branch 'main' into fri-reduction-arity f40aba32 Cleaning and commens 1786f5e0 Merge pull request #23 from mir-protocol/montgomery-barycentric 67aa704f Working reduction arity 406df349 Merge from main 2dfdc396 More progress on arity 49c116d7 Montgomery inversion in computation of barycentric weights + clean `eval_naive` method. 110a7bc6 Fill in a few missing field methods ffaa9587 cargo fmt 87267370 Tweaks to CrandallField 53252af4 Forgot to add new file aaa0e4aa Port over some code for the permutation argument 5cf8c50a Merge pull request #15 from mir-protocol/lagrange_interp 06bb902f Barycentric formula 035d15bc Interpolants of arbitrary (point, value) lists 6c85771e Merge pull request #19 from mir-protocol/prover_use_trees a50ba9f5 More unnecessary clones 6d164adc Have the prover use the new MerkleTree API 518470a2 Merge pull request #20 from mir-protocol/beta_gamma 7ffb9cf9 Merge pull request #17 from mir-protocol/rescue_bench d18210d6 Merge pull request #16 from mir-protocol/clippy 03113e85 Merge pull request #18 from mir-protocol/no_next_access 4f9aa887 Properly use the three betas and gammas b18f152c Remove access to "next" wire & constant values 80775ead Make Rescue a bit faster af4c8734 Address some clippy warnings c6841930 Rename a couple vars 8b429ff0 Clean tests 3dcdc883 Working Merkle subtree proofs 9c50e61f cargo fmt a9e7ff80 Merge pull request #13 from mir-protocol/hash_instance c31ef237 Merge pull request #14 from mir-protocol/port_more_tests a5206f97 Better generator_order per William's comment 84a71c9c A few more tests, ported (with some adaptations) from plonky1 b7bc1bf3 Seed Challenger with a hash of the instance 13519e66 Merkle subtree proofs - WIP 4491d5ad Merge pull request #12 from mir-protocol/fri 4b75f16e Merge master d5433ffe PoW fixes based on PR comments a1023e0c Added PoW 4d31f5e3 Fixes from PR comments b49e629e Merge pull request #11 from mir-protocol/arithmetic 6b3aa02b Merge master 6b407e45 Progress on FRI 41e1b646 Basic arithmetic methods 70f4f2aa Minimal num_wires 62dccedd Fix id() to include (generic) R param 9c2b7334 Merge pull request #9 from mir-protocol/merkle_proofs_2 30b845e6 Add generator to circuit 7ff41506 Merge with merkle_proofs_2 5abd49bf Merge 7d9bb073 Switch to "overwrite mode" sponges a14ddc3b Fix constraint count b1835798 Finish up recursive Merkle proofs 93b73fb8 Recursive Merkle proofs 04f74446 Misc fixes to get tests green again f807db38 Add bench_field_mul_interleaved benchmark aa50387d Started implementing FRI 959aacca Merkle proofs 0c07fcf0 Licenses 1ab12c3d Merge pull request #7 from mir-protocol/merkle_proofs 0cd73399 Fixes based on PR feedback 9a8a7b61 Non-circuit version e8eb658f Candidate API for Merkle proof data 88a84d5b Merge pull request #6 from mir-protocol/bin_reorg 5fe8d633 Split main into multiple binaries 2f54cedb Merge pull request #5 from mir-protocol/validate_cosets 74ce3725 Avoid separate exp calls 37761a32 Simplify as per William's comment cb1c69e5 Validate that the cosets for Plonk's permutation argument are disjoint 22f7c359 Fix visibility 52400557 Comments etc 9b158103 Rename 80e87bec Minor refactor facbe117 Move some stuff into Field 78860cc3 Final fixes 572a55ed Fix to_canonical_u64 285ec165 Fix neg c25c689e More tests, ported from plonky1 4086b2b4 Arithmetic & permutation gadgets aea4eeaa Minor 8565e501 Minor 524a974d Parallelize vanishing poly computation 8302c10f Multiple vanishing polys, and multiple associated quotient polys 234fdc7c Bit of refactoring, comments, etc. cca79a99 Sponges etc 16817d12 Minor 347206d1 Add Z terms in vanishing poly 3c262a8c Fix some warnings d8ecc375 Const generics now stable, yay! 6c8dfb97 Refactor polynomial code 07718397 Fix coset [i]fft 69b98623 Batch inverse (ported from plonky1) 44eeb505 Tweaks f4212048 No more polynomial programming abstraction ba96ab4e More prover work 8c95dd11 Bit of prover work 5f92611d Bit of prover work 75b93400 Tweak APIs ca7f20bf FriConsistencyGate ea33c556 GMiMC, witness generation ec0632bf Degree-3 GMiMC gate 194c4864 Remove bad attempt at automatic degree shrinking 31a10b77 Minor 45da024a Minor 58425eb5 Misc 5d6da4f9 Degree shrinker 383812df Degree map 78d7f8c6 Don't need ConstraintPolynomialRef 9fdff8ea Gate infra 33bd3edd Minor 2571e86e Tweaks 05e98ed3 Minor 80156336 Mostly finish GMiMC gate 3ba9ef8a Const generics in GMiMC 1480876c Add some FRI params & clean up FFT a bit 13cc7631 Initial commit REVERT: d547f8d5 Simulate jumpdest data with the interpreter (#1489) REVERT: 95d3d886 Update `starky` and leverage it as dependency for `plonky2_evm` (#1503) REVERT: c9748c7f Remove risk of panics in interpreter (#1519) REVERT: 282d9dea Revert "Remove StarkProofWithMetadata (#1497)" (#1502) REVERT: 06f77e00 Switch permutation argument for logUp in `starky` (#1496) REVERT: 8d9ddc4c Add missing constraints mentioned by auditors (#1499) REVERT: 8933eb4d Remove StarkProofWithMetadata (#1497) REVERT: 7dc86071 Some cleanup (#1498) REVERT: 499ef6a4 Reorganize lookup / ctl modules (#1495) REVERT: 1935cffc Make CTLs more generic (#1493) REVERT: 9cd8e5cc Cleanup imports (#1492) REVERT: 387c2e7c Fix circuit sizes (#1484) REVERT: b7aecaef Fix bugs in jumpdest analysis (#1474) REVERT: d259858a Improve `BIGNUM` operations (#1482) REVERT: 95fcf93e Speed-up `bn254` pairing operation (#1476) REVERT: 27cd8feb Merge pull request #1462 from 0xPolygonZero/pg/evm-licensing REVERT: 7bf857b0 Improve `blake2f` call (#1477) REVERT: 5d8fa11a Improve SHA2 precompile (#1480) REVERT: 1c1abd08 Fix interpreter jumps (#1471) REVERT: 43e9f94a Fix typos (#1479) REVERT: cbe391f5 Fix touched_addresses removal (#1473) REVERT: e1f90b77 Remove some more CPU cycles (#1472) REVERT: 52caaf8b Remove some CPU cycles (#1469) REVERT: 2ec89fe2 Fix simulation for jumpdest analysis (#1467) REVERT: 9e902caa proofreading (#1466) REVERT: 14793a71 Add math rendering with Katex (#1459) REVERT: 2719bbb1 Fix fill_gaps (#1465) REVERT: 60923e04 Packed rlp prover inputs (#1460) REVERT: a71e32c2 Update Cargo.toml REVERT: 34e25f78 Update README.md REVERT: bfd6a929 Add files via upload REVERT: d9eb1133 Bumped `eth_trie_utils` REVERT: 30abae50 Remove full memory channel (#1450) REVERT: a5892806 Merge pull request #1423 from topos-protocol/jumpdest_nd REVERT: 6ec87427 Interpreter GenerationInputs (#1455) REVERT: b808cce5 Merge pull request #1447 from topos-protocol/plonky2_doc REVERT: 266f2882 Fix comment REVERT: ecebc593 Update empty_txn_list REVERT: f3497053 Merge remote-tracking branch 'public/main' into jumpdest_nd REVERT: 6ac1126a Adress review comments REVERT: 8a5cec45 Constrain syscall/exceptions filter to be boolean (#1458) REVERT: e69b6252 Apply suggestions from code review REVERT: e59b0504 Free up some CPU cycles (#1457) REVERT: 29703252 Missing review comments REVERT: af5aa341 Address comments REVERT: e8f513e5 Apply suggestions from code review REVERT: 98f9483a Use current context in ecrecover (#1456) REVERT: 1e52bf68 chore(evm,field,plonky2):fix typos (#1454) REVERT: 48f72ed4 Intra doc link REVERT: 5d473b01 Implement CTL bundling (#1439) REVERT: b71d8f17 Add Boolean constraints for `ArithmeticStark` (#1453) REVERT: f94ccf2a Improve some calls to `%mstore_rlp` (#1452) REVERT: f320ea4e Fix problems after address bundling REVERT: ae74e41a Address review comments REVERT: 09b18d8f Merge remote-tracking branch 'public/main' into refactor_encode_funcs REVERT: be5ffb93 Address bundling REVERT: 96a6af66 Fix `after_mpt_delete_extension_branch` (#1449) REVERT: b969fb26 chore: fix typos (#1451) REVERT: 1eb19ab2 Merge remote-tracking branch 'public/main' into refactor_encode_funcs REVERT: ba1ba0a5 Minor REVERT: c5ce4846 Adress reviewer comments REVERT: 56a92040 Add crate-level documentation (#1444) REVERT: 4eca3738 Remove unused macro REVERT: 50a216e5 Address bundling (#1426) REVERT: 1b5b69bf Remove gas check in sys_stop (#1448) REVERT: 0ab90567 Remove assertion REVERT: f4c2866e Remove assertion in packed verif REVERT: 3b8d2f98 Address comments REVERT: b2a3d63d Apply suggestions from code review REVERT: b19e1540 Minor REVERT: 8fc27ab0 Fix minor error REVERT: 2ae58e5a Add packed verification REVERT: 63fae0c9 Prevent some lints from being allowed (#1443) REVERT: 0a31b935 Add initial constraint z polynomial (#1440) REVERT: 1fd74a43 Pacify latest clippy (#1442) REVERT: 8e8f68dd Clippy REVERT: 21f839f3 Restore simple_transfer and Clippy REVERT: 0cb2e12a Fix bug in jumpdest proof generation and check that jumpdest addr < code_len REVERT: f15232d2 Remove duplicated label REVERT: 9906f763 Rebase to main REVERT: 5ccb96ef Add alternative method to prove txs without pre-loaded table circuits (#1438) REVERT: 82d4119a Constrain partial_channel (#1436) REVERT: 0af3fad2 Refactor encode_empty_node and encode_branch_node REVERT: 2704f3cf Clippy REVERT: 6e04e44b Remove aborts for invalid jumps and Rebase REVERT: cb1122cb Reabse to main REVERT: f86af6c4 Remove aborts for invalid jumps REVERT: c955a181 Improve proof generation REVERT: afd8ea63 Clippy REVERT: ca09aec2 Fix fmt REVERT: 4b5d5e1f Remove U256::as_u8 in comment REVERT: f07bc413 Eliminate nested simulations REVERT: dcdeb2ef Apply suggestions from code review REVERT: fb962894 Fix jumpdest analisys test REVERT: e4824b2c Refactor run_next_jumpdest_table_proof REVERT: dcf576ba Rebase to main REVERT: f645c3f4 Remove aborts for invalid jumps REVERT: 0c0f3c6d Constrain first offset of a segment (#1397) REVERT: e8065fc3 Improve proof generation REVERT: 7a7e10b8 Merge pull request #1392 from 0xPolygonZero/dp-from_values-take-ref REVERT: 01d47a7c Minor cleanup (#1435) REVERT: 0d597d5e Constrain new top to loaded value in MLOAD_GENERAL (#1434) REVERT: 15a9e2ee Filter range checks (#1433) REVERT: 6661ff5d Add exceptions handling to the interpreter (#1393) REVERT: 71af4953 Regenerate tries upon Kernel failure during `hash_final_tries` (#1424) REVERT: 11201e6d Change context to current context for BN precompiles (#1428) REVERT: 7684f0c0 Add ERC721 test (#1425) REVERT: ac385122 Add aborting signal (#1429) REVERT: ee15ef5a Clippy REVERT: 42382357 Fix fmt REVERT: 3d7fd8e4 Remove U256::as_u8 in comment REVERT: 25b435e2 Eliminate nested simulations REVERT: c376ed67 Apply suggestions from code review REVERT: a65163f5 Constrain MSTORE_32BYTES new offset limbs (#1415) REVERT: 22d2f3f7 Fix jumpdest analisys test REVERT: 4a76af28 Refactor run_next_jumpdest_table_proof REVERT: f084fc41 Rebase to main REVERT: 4149b567 Merge `push` and `prover_input` flags (#1417) REVERT: e1214045 Fix a minor typo in evm/spec/cpulogic.tex REVERT: 537517c9 typo fix REVERT: 0fbef119 minor typo fix REVERT: bf8767e0 typo fix REVERT: b9aec9cf typo fix REVERT: 36df129c Add `Checkpoint` heights (#1418) REVERT: 4f693237 Check that limbs after the length are 0 (#1419) REVERT: 10085efc Merge MSTORE_32BYTES and MLOAD_32BYTES columns (#1414) REVERT: 4480f7ea chore: fix some comment typos REVERT: ab6633fe Use mstore_32bytes to optimize decode_int_given_len (#1413) REVERT: 29ad7e8f Remove is_keccak_sponge (#1410) REVERT: 84774e7a Preinitialize all code segments (#1409) REVERT: 8fbdeb73 Implement MPT preinitialization (#1406) REVERT: 738807f8 Optimize asserts (#1411) REVERT: c5ea37d9 Remove GenerationOutputs (#1408) REVERT: 64e0266e Implement degree 2 filters (#1404) REVERT: 48b6d9bc Make some functions const (#1407) REVERT: 6ba78289 Implement `PublicValues` retrieval from public inputs (#1405) REVERT: b1f19254 Use logUp for CTLs (#1398) REVERT: d129be81 Update stack op cost (#1402) REVERT: 195d42ff Pacify clippy (#1403) REVERT: 8950925f Fix set_context constraints (#1401) REVERT: 7ff03c47 Fix kernel codehash discrepancy (#1400) REVERT: e96e0714 Remove intermediary block bloom filters (#1395) REVERT: 21165862 Remove bootstrapping (#1390) REVERT: 736a56fb chore: Remove TODOs about `from_values` taking a reference REVERT: 831f48ca Revert "chore: from_values takes ref" REVERT: f1548d18 Optimize `num_bytes` and `hex_prefix_rlp` (#1384) REVERT: bccea4a1 chore: from_values takes ref REVERT: cc8b3e6f Move stack_len_bounds_aux to general columns (#1360) REVERT: 39d1072e Changes in interpreter and implement interpreter version for add11 (#1359) REVERT: a0100fef Add upgradeability to `AllRecursiveCircuits` and output verifier data (#1387) REVERT: 91265c2b Remove extra rows in BytePackingStark (#1388) REVERT: af997d1a Fix run_syscall in interpreter. (#1351) REVERT: d795454e Fix genesis block number in `prove_block` (#1382) REVERT: 27f3f4c2 Update evm/spec/mpts.tex REVERT: 935618c3 Remove redundant sect about MPT REVERT: 7efa7ea3 Address comment REVERT: a3d71c04 Fix typo in evm/spec/mpts.tex REVERT: 26e23c7b Update evm/spec/mpts.tex REVERT: 8e08dc88 Update evm/spec/mpts.tex REVERT: 8d2ba21f Update evm/spec/mpts.tex REVERT: 9d394367 Update evm/spec/mpts.tex REVERT: 4f1472bf Update evm/spec/mpts.tex REVERT: f2e810e9 Update evm/spec/mpts.tex REVERT: 58db07ce Add MPT specs REVERT: d7d20ca7 Add specs for stack handling (#1381) REVERT: 58e2353d Backporting gas handling to the specs (#1379) REVERT: dae6b122 Add specs for the CPU table (#1375) REVERT: 5937b987 Explain difference between simple opcodes and syscalls (#1378) REVERT: e57157b2 Add range check constraints for the looked table (#1380) REVERT: 60909af8 Add specs for BytePackingStark (#1373) REVERT: b010dc06 Check is_kernel_mode when halting (#1369) REVERT: 1498547d Initialize blockhashes (#1370) REVERT: afb08b79 Starting the specs for the CPU logic (#1377) REVERT: 4ec8221e Add exceptions to specs (#1372) REVERT: 0fd60676 CTL and range-check documentation (#1368) REVERT: bbd3632e Update README.md (#1371) REVERT: d4bc6916 Update README.md REVERT: 531523b1 Update README.md REVERT: b46ae7c4 Update README.md REVERT: c39f6b26 Create README.md REVERT: ffe62aa9 Update Keccak-f specs. (#1365) REVERT: cef04a3a Add specs for KeccakSponge (#1366) REVERT: 1d5b16af Reduce visibility (#1364) REVERT: 66cd570d Update specs for Logic and Arithmetic Tables (#1363) REVERT: 1c9ca358 Add doc for privileged instructions (#1355) REVERT: b43f20db Update Memory in specs (#1362) REVERT: 5ce83beb Revert "Make gas fit in 2 limbs (#1261)" (#1361) REVERT: d7474ad9 Constrain is_keccak_sponge (#1357) REVERT: 1a6c8d3d Merge public values inside prove_aggreg (#1358) REVERT: c43208ea Implement out of gas exception (#1328) REVERT: 6b50897e Add push constraints (#1352) REVERT: 663b795d Refactor JUMPDEST analysis (#1347) REVERT: 08654b9f Fix parsing of non-legacy receipts (#1356) REVERT: 2fbb5525 Fix MSTORE_32BYTES in interpreter (#1354) REVERT: 4cd9091a Remove values of last memory channel (#1291) REVERT: a474829a Merge pull request #1346 from 0xPolygonZero/dp-unwrap-hunting REVERT: 5dbc7c1d Reduce visibility for a bunch of structs and methods in EVM crate (#1289) REVERT: d273a130 Add run_syscall and tests for sload and sstore (#1344) REVERT: dc468565 Remove unnecessary code duplication (#1349) REVERT: 1ec7e1e5 Charge gas for native instructions in interpreter (#1348) REVERT: d95c8f3b Range-check keccak sponge inputs to bytes (#1342) REVERT: 8de0387d Root out some unwraps REVERT: 6304db78 Fix ranges in AllRecursiveCircuits initialization for log_opcode aggregation test (#1345) REVERT: 4c09d2ac Constrain clock (#1343) REVERT: 924e77d3 Remove logic for multiple txns at once (#1341) REVERT: 7babb0f8 Move empty_check inside final iteration REVERT: 7322d9cd Add memory checks for prover_input, as well as range_checks for prover_input, syscalls/exceptions (#1168) REVERT: 6fa2e67c Add withdrawals (#1322) REVERT: 46bcac9d Remove `len` column in `KeccakSpongeStark` (#1334) REVERT: 10912ea1 Add test for ERC20 transfer (#1331) REVERT: 802118f6 Fix typos in comments REVERT: a29adf31 Fix typos in comments REVERT: c62ccfc9 Constrain uninitialized memory to 0 (#1318) REVERT: 6b2f4eaa Add test for selfdestruct (#1321) REVERT: e250d370 Fix merging of jumpdest and keccak_general. REVERT: 154eef75 Combine JUMPDEST and KECCAK_GENERAL flags. (#1259) REVERT: 71a2c10c Add context constraints (#1260) REVERT: 9eea871b Merge pull request #1317 from topos-protocol/more_memcpy_bytes REVERT: 5cb6594d Combine PUSH0 and PC flags. (#1256) REVERT: 5fb68d7b Add some documentation in EVM crate (#1295) REVERT: e05a2c75 More of memcpy_bytes REVERT: d92ebaf9 Merge pull request #1316 from topos-protocol/memcpy_tiny REVERT: 9fbc8d96 Merge NOT and POP flags. (#1257) REVERT: 18b5c1fc Remerge context flags (#1292) REVERT: 57045d48 Move empty check inside final iteration REVERT: b9ce0793 Merge pull request #1314 from topos-protocol/refactor_wcopy REVERT: dce75768 Review REVERT: 896c21a9 Fix kexit_info in test REVERT: 3befac1a Remove new_stack_top_channel from StackBehavior (#1296) REVERT: 5a7b0269 Fix test on interpreter side REVERT: 7ff331c6 Fix calldatacopy REVERT: f0e33cfb Fix REVERT: bb2ac6dd Refactor codecopy REVERT: c0ae9032 Refactor memcpy REVERT: 4561832d Refactor wcopy syscalls REVERT: 05f55291 Add missing constraints for DUP/SWAP (#1310) REVERT: b7e07a15 Fix wcopy and extcodecopy for ranges over code limit REVERT: 3d17b6ce Apply rustfmt with latest nightly REVERT: 4808ea08 Also for memset REVERT: 6d049631 Handle empty case for memcpy_bytes REVERT: 629c7488 Remove redundant REVERT: 26e819ca Speed-up memset and fix it to write 0 values REVERT: ef3ef738 Merge pull request #1304 from topos-protocol/memcpy_bytes REVERT: c1d7432c Fix REVERT: f8108fb5 Reviews REVERT: 8324afe2 Merge pull request #1302 from topos-protocol/remove_kernel_memory_zeroing REVERT: 96d5187c Merge pull request #1303 from topos-protocol/amortize_receipt_reset REVERT: b1c621e9 Add macro for copying sequences of bytes REVERT: 32de6d9e Amortize bloom reset REVERT: 06e89af4 Remove outdated code REVERT: 1b7d0184 Combine stack macros for fewer operations REVERT: 7d5355d4 Alter stack to remove SWAPs for SUBMOD REVERT: f77acf7b Use SUBMOD in Kernel REVERT: eacb49cd Check gas in sys_stop (#1297) REVERT: 16beb626 Combine DUP and SWAP (#1254) REVERT: 0b0ef91f minor: use explicit builder.assert_zero for readability (#1293) REVERT: d275ab4e Update check_ctls with extra looking values (#1290) REVERT: 83137d73 Updated `mir-protocol` --> `0xPolygonZero` REVERT: 3e5613c2 Store top of the stack in memory channel 0 (#1215) REVERT: 73dad32d Fix hash node case in `mpt_delete_branch` (#1278) REVERT: 584fbbbe Add journal entry for logs (#1286) REVERT: 052bcac3 Fix journal order in `sys_selfdestruct` (#1287) REVERT: 862953c0 Fix sys_blockhash (#1285) REVERT: d3c86b50 Fix failed receipt. (#1284) REVERT: ae4c0407 Make sure success is 0 in contract failure (#1283) REVERT: c7d4d996 Remove some dead_code in EVM crate (#1281) REVERT: 164eba52 Fix shift constraint (#1280) REVERT: cafcb6e9 Remove reg_preimage columns in KeccakStark (#1279) REVERT: afb5a8e7 Remove extra SHL/SHR CTL. (#1270) REVERT: c4bc7abc Fix encoding for empty recipient REVERT: ece57335 Fix genesis state trie root when calling `prove_root` (#1271) REVERT: ff17a9ee Derive clone for txn RLP structs (#1264) REVERT: 47fea2e4 Make gas fit in 2 limbs (#1261) REVERT: a0b967f6 Fix description of Range-Check columns in STARK modules REVERT: 56d4c76f Merge pull request #1235 from topos-protocol/new-logup REVERT: 666eab01 Transactions trie support (#1232) REVERT: 6551a9a7 Add type 1 and 2 txn for RLP encoding support (#1255) REVERT: 9b979aa2 Merge remote-tracking branch 'mir-plonky2/main' into constrain-genesis-state REVERT: 19b421df Apply comments. REVERT: 4acec944 Handle additional panics (#1250) REVERT: 31b0ce31 Connect block_gas_used (#1253) REVERT: 5901c53a Fix observe_block_metadata REVERT: f2d8bf4b Add `random` value to block metadata and fix `sys_prevrandao` (#1207) REVERT: bb89beee Merge branch 'main' into 'new-logup' REVERT: 61d49176 Use function for genesis block connection. REVERT: 53a9f9c8 Merge branch 'main' into 'constrain-genesis-state' REVERT: 6b0d9f33 Apply comments (#1248) REVERT: ed451f8d Remove `generic_const_exprs` feature from EVM crate (#1246) REVERT: 0415db39 Merge branch 'main' into new-logup REVERT: 2c2a04b5 Merge branch 'main' into 'new-logup'. REVERT: 2369218e Remove SEQUENCE_LEN in BytePackingStark (#1241) REVERT: f60a820e Merge pull request #1244 from topos-protocol/block_metadata_doc REVERT: a64cc378 Update ranges indices REVERT: 4d43b6fc Add some doc for BlockMetadata / ExtraBlockData REVERT: 46475ab2 Fix eval_table REVERT: 29fdf103 Merge branch 'main' into new-logup REVERT: 0e30fba1 Reuse new packing instructions for MLOAD and MSTORE REVERT: 075f8b3c Rename utility methods for U256 conversion REVERT: 4cbbf9dc Merge branch 'main' into error_vs_panic REVERT: 71c2e1fe Typo REVERT: b8546e01 Merge pull request #1229 from topos-protocol/next_row_ctls REVERT: 83895d91 Move next row logic inside Column REVERT: c4641e0f Merge remote-tracking branch 'mir/main' into new-logup REVERT: e90bf98c Revert "Remove where clauses: [(); CpuStark::::COLUMNS]" REVERT: 70871839 Change padding rule for CPU (#1234) REVERT: a7ba4d1b Fix range REVERT: 970c7074 Remove where clauses: [(); CpuStark::::COLUMNS] REVERT: 2783d106 Merge branch 'main' of github.com:mir-protocol/plonky2 into new-logup REVERT: 77aa4702 Clippy REVERT: 35a91d9d Cleanup REVERT: 28684831 Add assert with char(F). Cleanup. Fix recursive challenges. REVERT: 52ab36fa Remove one helper function REVERT: f5564f31 Fix BytePacking range-check. Fix lookup challenges REVERT: bf9a7a38 Use CTL challenges for logUP + change comments + add assert REVERT: 47a2a60f Implement logUp REVERT: 92b0f522 Combine mstore_general and mload_general into one flag (#1188) REVERT: 5fb90fc6 Merge branch 'main' into next_row_ctls REVERT: 713fbf6a Merge pull request #1203 from topos-protocol/constrain_nv_stack_len REVERT: 7724c37a Remove redundant Keccak sponge cols (#1233) REVERT: 1f1ee800 Combine arithmetic flags on the CPU side (#1187) REVERT: b4cfa5aa Fix CTLs REVERT: bd9ff419 Merge branch 'main' into next_row_ctls REVERT: b5d89c15 Fix self_balance_gas_cost and basic_smart_contract. (#1227) REVERT: 64b6b15d Swap ordering in stack macro (#1230) REVERT: f80cd1fd Remove risks of panic REVERT: 9425093c Move byte packing / unpacking to a distinct table (#1212) REVERT: c473062c Make next row available to CTLs REVERT: 25bdcf4b Constrain genesis block's state trie. REVERT: 7f725aba Merge pull request #1202 from mir-protocol/keccak-preimage REVERT: e7f1e2c1 Merge pull request #1224 from mir-protocol/latest-nightly REVERT: ef97c29c more clippy suggestions REVERT: 3b810657 clippy suggestions REVERT: c85163b6 clippy suggestions REVERT: 5f31e1ab clippy suggestions REVERT: 0f6f92c0 clippy suggestions REVERT: eb466115 clippy suggestions REVERT: f95de59c Merge pull request #1222 from mir-protocol/internal_crate_path_stablization REVERT: eab1a7b1 Merge pull request #1220 from mir-protocol/latest_nightly_fix REVERT: 1b7990a8 Merge pull request #1219 from succinctlabs/uma/add-mock-feature-flag REVERT: dd5001c7 Now refers to sub-crates using paths (and removed `patch` section) REVERT: 13f60acd Merge pull request #1208 from topos-protocol/blockhash_opcode REVERT: a2692ffe Merge pull request #1216 from topos-protocol/checkpoint_lengths REVERT: 46b38a04 Apply Nick's comment REVERT: e6ce6299 Apply Nick's comment REVERT: d0dccddf Now builds on the latest nightly REVERT: cfa95d29 Fix Clippy REVERT: daabc529 Fix memop reads, from_prover_inputs and cleanup. REVERT: 5398cccb Clippy REVERT: a1d400f7 Fix overflow check and test. Remove [..8] when using h256_limbs. REVERT: caf8d797 Change h256_ulimbs REVERT: 2a8296da Apply comments REVERT: bf1d91d9 Add blockhash sys opcode REVERT: f4184eba remove spurious REVERT: 6882854a Added mock feature flag and test REVERT: f19d9f24 Remove filter column for KeccakStark REVERT: 8f7f734f Remove duplicate code REVERT: abf6037a Merge pull request #1206 from topos-protocol/missing-public-value-links REVERT: 319669b4 Display actual trace lengths instead of number of ops REVERT: a5a52b4d Merge pull request #1209 from topos-protocol/receipts-all-types REVERT: f5e6b944 Replace genesis state trie check with TODO REVERT: 60021731 Apply comments REVERT: 932475b9 Fix comment in `proof.rs` REVERT: 2d85d1e9 Apply comments REVERT: 638589db Implement receipts of types 1 and 2 REVERT: 6044a90d Add missing links between public values REVERT: e5177d57 Constrain next row's stack length REVERT: aa119867 Keccak STARK: constraint preimage to equal A on first round REVERT: b81d0188 fix: constrain higher bits of reg_preimage REVERT: aaccebf5 Update range from ReceiptTrie PR REVERT: 84e9cbeb Made `PublicValues` serializable REVERT: f47a5fdf Clippy REVERT: 56f16b91 Cleanup REVERT: e3af7fdb Change receipts_trie in basic_smart_contract and self_balance_gas_cost REVERT: 410ffeeb Fix tests and address comments REVERT: acc10ee6 Cleanup REVERT: 49a0ae9f Implement receipts and logs REVERT: ab946891 Merge pull request #1174 from topos-protocol/merge-context-flags REVERT: 76271ebf Patched plonky2 to use a patch for eth_trie_utils REVERT: a4a6e7b9 Apply comment REVERT: 0dad7d1f Remove unnecessary changes in the Operation enum REVERT: 84d2dcd6 Combine get_context and set_context into one flag REVERT: 1a53019f Merge pull request #1192 from topos-protocol/misc_constraints REVERT: 54fe0e29 Merge pull request #1190 from topos-protocol/mpt-remove-cow REVERT: cd067ee7 Apply comment REVERT: f51e63b6 Refactor REVERT: ffc1b4fe Update BlockBaseFee to fit in 2 limbs REVERT: 1fe20fdd Update tests to have a blockgaslimit fitting u32s REVERT: 41cbf5bc Observe public values REVERT: eee10db5 Remove filtering in membus REVERT: 0f4c3750 Reduce overconstraining in decode module REVERT: d09d4889 Combine a few constraints REVERT: bc9ded57 Merge pull request #1165 from topos-protocol/ci-test REVERT: 3475f999 Remove copy on write for mpt_insert and mpt_delete REVERT: 19ff8c5d Remove is_bootstrap_kernel column REVERT: 8a85b504 Remove is_cpu_cycle REVERT: 35218f5f Use Keccak config in simple tests REVERT: 1bd9a78d Combine jump flags REVERT: e1d34f91 Merge pull request #1185 from topos-protocol/combine_simple_logic_flags REVERT: a3da1122 Merge pull request #1177 from topos-protocol/alloc REVERT: ebf1008c Combine EQ and ISZERO flags REVERT: f4015889 Comment REVERT: 51bd90dd Combine all logic flags together REVERT: fb1ea44e Fix logic CTL REVERT: 82b34cb2 Apply Nicholas comment REVERT: 55cb9ab8 Address review REVERT: 25376807 Reduce reallocations REVERT: 7788d95d Combine AND and OR flags in CpuStark REVERT: d21b62d6 Connect SHL/SHR operations to the Arithmetic table (#1166) REVERT: dd01d715 Write trie roots to memory before kernel bootstrapping (#1172) REVERT: 57351616 Connect public values in aggregation circuit (#1169) REVERT: bd3da9ba Merge pull request #1171 from topos-protocol/exception-flag REVERT: 1fff1138 Set exception flag to 1. REVERT: 5318d811 Error instead of panicking for missing preprocessed circuits (#1159) REVERT: 0eec6517 Merge pull request #1162 from topos-protocol/cleanup_attributes REVERT: 8d76f677 Merge pull request #1161 from topos-protocol/fix_recursive_ctl REVERT: 44d9599a Remove unused attributes REVERT: d7847e46 Clippy REVERT: cd4c0cce Convert to u32 instead of u64 REVERT: 7c4636e3 Fix endianness in benefiary limbs REVERT: 45f614ec Reuse set_public_value_targets REVERT: db02b341 Constrain keccak general REVERT: b4f52ccf Better document constraints on addcy carries (#1139) REVERT: a5431e34 Merge pull request #1158 from mir-protocol/jacqui/gas-check-spec REVERT: 2e3fa5e7 Gas handling brain dump REVERT: 3ab55999 update versions in cross-crate references REVERT: c7867060 update versions for crates.io updates REVERT: 2db8440b Merge pull request #1026 from topos-protocol/memory-ctl-verifier-bus REVERT: f6a3de4c Merge pull request #1151 from mir-protocol/jacqui/dead-memtable-cols REVERT: bc0bcd8d Clippy REVERT: 3f77a32c Change public values into public inputs REVERT: ba8bf7ad Apply comments REVERT: 0d2ce589 Fix indices in CTL functions REVERT: a38cce2a Remove non-passing debug assert REVERT: 4b556b42 Fix the memory CTL and implement the verifier memory bus REVERT: cb5d49ec Merge pull request #1146 from topos-protocol/overlap-cpu-syscalls REVERT: ea93829c Cut 5 Columns From The Memory Table With This One Weird Trick! REVERT: ca9999a8 Merge pull request #1111 from topos-protocol/lookup_serial REVERT: c689f34c Merge syscall and exceptions constraints. REVERT: 94d3c08c Fix negative quotient issue (#1140) REVERT: 1fce50ed Fix failing byte constraint (#1135) REVERT: 8de4e60e Merge pull request #1137 from topos-protocol/fix-kernel-panic REVERT: dfd1dc37 Change current context in bignum_modmul REVERT: 69b8ce0a Fix risk of division by zero REVERT: fb6727cd Merge pull request #1116 from topos-protocol/recursive_ranges REVERT: f3804d22 Also provide CommonCircuitData in serialization of gates and generators REVERT: f59b6679 Update itertools requirement from 0.10.3 to 0.11.0 REVERT: 1c86a1a4 Merge pull request #1128 from mir-protocol/dependabot/cargo/hex-literal-0.4.1 REVERT: 817d4291 Merge pull request #1131 from mir-protocol/dependabot/cargo/criterion-0.5.1 REVERT: 6795d31b Merge pull request #1130 from mir-protocol/dependabot/cargo/hashbrown-0.14.0 REVERT: eb3b41c4 Merge pull request #1124 from 0xmozak/matthias/remove_unused_deps REVERT: 07718b1a Update criterion requirement from 0.4.0 to 0.5.1 REVERT: a9efd7e2 Update hashbrown requirement from 0.12.3 to 0.14.0 REVERT: 7f60eca3 Update hex-literal requirement from 0.3.4 to 0.4.1 REVERT: 34dc14b2 Fill modulus in cpu row for Fp254 operations. (#1122) REVERT: 6f9160c8 Remove unused dependency `blake2` from `evm` crate REVERT: 1339a9b7 Merge pull request #1104 from topos-protocol/serializer REVERT: 13b9ef80 Merge pull request #1119 from mir-protocol/jacqui/topos-protocol/stack_len_bounds_aux_error REVERT: c5b7b64d Remove redundant case (error in kernel mode) REVERT: 06cbcd6b Merge pull request #1112 from topos-protocol/fix-generate-jump REVERT: 9cc9c4f3 Merge pull request #1117 from topos-protocol/fix_set_context REVERT: c7723b89 Revert "clippy fixes" REVERT: e43c4414 Fix generate_set_context REVERT: 944ed2de Remove need for matching start ranges REVERT: 786147e4 Compute stack_len_bounds_aux correctly in generate_error REVERT: 4fba04c9 Fix jump operation generation REVERT: 21b069fc ignoring where appropriate (for izip), fixing elsewhere REVERT: 5993db76 remove useless vec REVERT: 72adc0da Make serializer work with slices instead of Vec REVERT: 4d36135e reset Cargo.toml REVERT: e9348376 fix REVERT: d2916a77 addressed comments REVERT: 9640e185 fmt REVERT: 3c816694 undo dummy change REVERT: f22c484d dummy change to get tests to rerun :P REVERT: fa75030a fmt REVERT: d8915c95 cleanup REVERT: f41d7182 fix REVERT: 9118805c modexp uses current_general REVERT: b2c0c974 Merge pull request #1101 from mir-protocol/blake_fix REVERT: 2aa49128 Minor REVERT: efdb995f Minor REVERT: f1314d8b Minor REVERT: de8504d2 Fix blake2 fix REVERT: 22f833df fix REVERT: 1b31b01c blake fix REVERT: bfdc0600 Merge pull request #1095 from mir-protocol/jacqui/push0-opcode REVERT: 4b6012a3 William comments REVERT: d5f80cf1 Remove parts of the copy-on-write logic (#1096) REVERT: dca022b7 PUSH0 REVERT: c7265f27 Merge pull request #1082 from mir-protocol/jacqui/simplify-stack-bounds REVERT: 0f5789bd Minor: William comment REVERT: 49c802d8 Fix halt loop (#1094) REVERT: 0eb8324c Fix account touch in calls (#1093) REVERT: cd0caa5b Use current context for pairing memory (#1091) REVERT: 5caa7831 Check call depth in create (#1089) REVERT: 85c25000 Set returndata size to 0 in some create errors (#1088) REVERT: 90887143 Increment call depth in precompiles (#1087) REVERT: 9eb2fbdb Fix LOG* gas (#1086) REVERT: c1ce5d9b Fix CALLDATALOAD for large offsets (#1085) REVERT: 6927a23c Implement PREVRANDAO as if it was DIFFICULTY (#1084) REVERT: 0c0ddaab Simplify stack bounds constraints REVERT: 4c91f9f1 Merge pull request #1071 from mir-protocol/jacqui/bad-opcode-witness-generation REVERT: aa6f06fa William PR comments REVERT: 5a056898 Call stack depth (#1081) REVERT: 14b15797 Minor fix to REVERT (#1080) REVERT: a66eab6d RIPEMD doesn't get untouched (#1079) REVERT: f6cc17ab Contract creation fixes (#1078) REVERT: d041d253 Don't revert state in CREATE in case of OOF or nonce overflow (#1077) REVERT: 05358953 Fill BLOCKHASH and PREVRANDAO syscalls with dummy code (#1076) REVERT: d305321f Revert #1074 (#1075) REVERT: 3c7a3659 Don't overwrite existing account (#1074) REVERT: c94ddeff Prevent shift ops from panicking (#1073) REVERT: cf9ebd7d Commit missing file REVERT: 6e7b755f Minor docs REVERT: 6987673e Remove bootloader.asm (#1072) REVERT: bde539b8 Minor bugfixes REVERT: ad1413f4 Lints REVERT: 309e105f Fix stack after precompiles (#1061) REVERT: dcf8b636 Merge branch 'main' into jacqui/bad-opcode-witness-generation REVERT: f2d39c3a Error handling REVERT: 37174782 Minor fixes to RETURN and RETURNDATACOPY (#1060) REVERT: 5a262918 Fix create OOG because of code deposit cost (#1062) REVERT: 4cb912c9 Warm precompiles earlier (#1065) REVERT: cc5d75d1 Propagate static flag (#1066) REVERT: f0176c6c Fix pairing invalid input (#1067) REVERT: 6b307e21 Fix arithmetic stark padding (#1069) REVERT: 9db8ef1e Implement EVM `BYTE` operation (#1059) REVERT: 727fecec Fix revert gas bug REVERT: f26e93d1 Fix return and revert gas (#1058) REVERT: a9404566 Fix ecrecover edge case (#1057) REVERT: 3665f757 Fix DUP in call gas REVERT: 8de73900 Add contract creation flag (#1056) REVERT: 3665e3b6 Fix DelegateCall bug REVERT: 7aa1d99c Fix extcodehash when account is empty (#1055) REVERT: 21edf92a Implement LOG* gas and remove panic (#1054) REVERT: e58dec7b Fix ecmul (#1053) REVERT: 224db372 Support for type-2 transactions (#1052) REVERT: 45e445cb Support for type-1 transactions (#1051) REVERT: 129a02b4 Encode `to` as B160. (#1011) REVERT: faf3db7d Pop checkpoint in the right place REVERT: f09c1771 Fix issues related to CREATE2 collisions (#1050) REVERT: 8302c535 Merge pull request #1041 from mir-protocol/storage_addr_h160_to_h256 REVERT: a8f51177 Perform jumpdest analysis whenever entering a new context (#1049) REVERT: 47eefab2 EIP-2681: Limit account nonce to 2^64-1 (#1048) REVERT: f3d97a61 Don't add an event for account creation for pre-existing account (#1047) REVERT: 337d432d Check balance in create (#1046) REVERT: 1983c49d Fix extcodecopy REVERT: 45c85f33 More fixes to contract creation (#1045) REVERT: be9b80c8 Fixed failing test REVERT: 2e6ec9c6 Some fixes to contract creation (#1044) REVERT: f1fb1e72 Minor fixes to returndata and create (#1043) REVERT: bdb94999 `TrieInputs` now uses `H256` for storage account addresses REVERT: cbef5113 Merge pull request #1038 from mir-protocol/tests-memory-context-fix REVERT: 499c7a49 Merge pull request #941 from mir-protocol/bls-fp2 REVERT: 8d8939d9 redundant REVERT: 85e7b2ff merge successful REVERT: 287b869b merge REVERT: cfa53331 Add refund journal event and checkpoint after access address event (#1040) REVERT: 268b56d5 Various fixes to checkpoint logic (#1039) REVERT: a1d0ebe3 revert testing changes REVERT: d2383dc8 fix REVERT: 7ef8bb79 Cargo.toml change for testing REVERT: 95a8f7c6 Delete touched recipient in EOA -> EOA (#1037) REVERT: 6c664777 fix REVERT: 3ee2d456 Don't touch contract address in DELEGATECALL or CALLCODE (#1036) REVERT: dc41a57f Journal of state changes + state reversion (#1028) REVERT: 334ff067 MPT deletion (#1025) REVERT: 22ac671f Fix CALL gas (#1030) REVERT: c7ff512b SSTORE refund (#1018) REVERT: d28eecad Cross-table lookup for arithmetic stark (#905) REVERT: 8e27ba52 Merge pull request #1029 from mir-protocol/precompile-memory-context-change REVERT: 8eeaca0a Merge pull request #1027 from mir-protocol/memory-refactor REVERT: 04016de4 Fix compile time problems and generic hash implementation (#1024) REVERT: 0e7b8260 Merge branch 'memory-refactor' into precompile-memory-context-change REVERT: b798c813 fix (mstore_unpacking returns offset) REVERT: 3226386e Merge branch 'memory-refactor' into precompile-memory-context-change REVERT: 4599a121 fix REVERT: 0dd8ba48 Merge branch 'memory-refactor' into precompile-memory-context-change REVERT: 933a1bcc use mstore_unpacking and mload_packing REVERT: aa1c0948 use mstore_unpacking and mload_packing REVERT: fee17b24 addressed comments & cleanup REVERT: 13ecb30a cleanup REVERT: b5988603 fmt REVERT: 2b62b747 fix REVERT: eb221da2 fix REVERT: 2934ca5a precompile memory context change REVERT: cfd44224 fix REVERT: 78b64e61 fix REVERT: c192d36f refactor memory/core.asm to make code more reusable REVERT: c5c4701d Fix doubly_encode_rlp_scalar in the 0 case. (#1022) REVERT: c0e7c388 Merge pull request #1017 from mir-protocol/expmod-fix REVERT: 0f13ee22 expmod edge case fix REVERT: 360ecacb Merge pull request #1013 from topos-network/overflow-check REVERT: bf812fce Merge pull request #1009 from mir-protocol/expmod_precompile REVERT: d2b015cf fixes REVERT: 94be0f58 Change add_or_fault macro REVERT: a9e73e90 Address overflow-related TODOs in ASM code, using a macro add_or_fault. This is related to https://github.com/mir-protocol/plonky2/pull/930/files/a4ea0965d79561c345e2f77836c07949c7e0bc69 REVERT: 8b76f5d0 Merge pull request #1014 from toposware/bootstrap_constraint REVERT: fae32d6d remove test file REVERT: abc1106b fix REVERT: 487055aa fix: calculate gas properly REVERT: 8b049e80 Merge pull request #1015 from mir-protocol/clippy-fix REVERT: d85305d5 fix REVERT: 64e29031 remove unneeded mut REVERT: 7319e0d0 addressed comments REVERT: 80a0d102 addressed comments REVERT: 81ed27d4 Fix todo in kernel bootstrapping REVERT: 034d3028 addressed comments REVERT: 1ce1d5e3 Merge pull request #997 from mir-protocol/pairing-test REVERT: 2fc99ddd comments REVERT: d680dfe2 neutral input REVERT: 9c6e89e7 fix REVERT: fe70d837 Merge branch 'main' into expmod_precompile REVERT: dc7a96c8 cleanup REVERT: 204ebee6 store and unpack at end REVERT: 2515a65d addressed comments REVERT: 95c2ada9 fix REVERT: 1cde9d38 Merge pull request #1006 from mir-protocol/blake_precompile REVERT: 4c8b1376 addressed comments REVERT: 44360fbe addressed comments REVERT: 53070582 addressed comments REVERT: 78923c26 fix unit REVERT: 43e7b29d peculiar... REVERT: 989d325a fmt REVERT: f8636257 fair naming REVERT: 9d3121c0 on stack REVERT: a2edb48f redundant REVERT: e7280e97 Merge branch 'pairing-test' of github.com:mir-protocol/plonky2 into pairing-test REVERT: 34fe620f neutral name REVERT: db62176e random inp REVERT: 41bc585d SNARKV precompile (#1010) REVERT: 65deb145 minor REVERT: feec2d38 SSTORE gas (#1007) REVERT: afa39ab5 Merge pull request #980 from mir-protocol/serialize_common_circuit_data REVERT: be6f0432 mul works REVERT: 2c6fa159 abstraction REVERT: 08709a18 return bool REVERT: 51281869 it works REVERT: c85fea3f fmt REVERT: cf20ad5d test REVERT: aa9d2a23 Merge branch 'main' of github.com:mir-protocol/plonky2 into pairing-test REVERT: 871b1970 fix REVERT: 8c906947 clean up REVERT: bfcbc19a clean REVERT: 4bf405dc fixes REVERT: 613a4113 Precompiles exist (#1008) REVERT: a964f0fd formatting REVERT: b9be4f2c expmod precompile REVERT: 9be3b2fe cleanup REVERT: dc3f782f charge gas! REVERT: 57247418 rename blake2b REVERT: a8b25ac0 fix REVERT: 32b8ac79 fix REVERT: 814e6ece fixed blake tests REVERT: 23fdd472 Merge pull request #1005 from mir-protocol/precompile-fixes REVERT: 961e5a9a clippy fix REVERT: a4e650b0 fmt REVERT: c5426d51 commented out unused functions REVERT: 6a76ed7d Merge branch 'main' into blake_precompile REVERT: 9a346558 fixed blake2_f, and testing REVERT: 639bfb8e Merge pull request #998 from mir-protocol/even-smaller-bignum-modexp-test REVERT: 9d9cbe0b add comment REVERT: 67b669d4 Merge branch 'precompile-fixes' into blake_precompile REVERT: 8a6fbb6b mload_packing macro REVERT: 7b850759 Merge branch 'main' into precompile-fixes REVERT: d00419ab clean more REVERT: c8f39756 cleanup REVERT: a1fa9c7d fixes REVERT: 8d195679 New contract hook (#1002) REVERT: 4d580721 EIP-3541: Reject new contract code starting with the 0xEF byte (#1003) REVERT: 9f73a4fb EIP-3860: Limit and meter initcode (#999) REVERT: 82a19821 cleanup REVERT: 94be2f01 Serialize impls, and use in Fibonacci example REVERT: be1a1981 Merge pull request #981 from toposware/serialization REVERT: 39c1652b blake precompile progress REVERT: 2c382692 Merge branch 'precompile-fixes' into blake_precompile REVERT: e85dfb34 precompile optimizations REVERT: f6d5d6c9 it works REVERT: 72318cd3 blake precompile progress REVERT: f910d38f dont panic REVERT: 7e472d11 memory compress REVERT: 38c8baeb clean REVERT: 5b04ff59 clean REVERT: a8a3013a renumber memory REVERT: 84cde44d it works REVERT: c5da830d minor REVERT: 91703f9f initial work on blake precompile REVERT: eda911c9 initialize out in asm REVERT: bebadc14 Merge branch 'main' of github.com:mir-protocol/plonky2 into pairing-test REVERT: 7626fca4 Bumped `eth_trie_utils` to `0.6.0` REVERT: c8654bab EIP170 (#1000) REVERT: 937d357e msg REVERT: 1b16a95e error REVERT: f6d23294 twisted check REVERT: 856ae388 Move serialization files into dedicated module REVERT: 620b695d Customize range specification for AllRecursiveCircuits REVERT: a36e758e Add serialisation support for gates, generators, and various structs REVERT: 98185c8a Gas and more for `CREATE(2)` (#995) REVERT: 8c4de514 EOA to precompiles logic (#993) REVERT: bc5d566c fix for full modexp test REVERT: dffe3c72 fmt REVERT: 8005417a check for special cases and align with yellow paper REVERT: be84b244 tests passing REVERT: 4401773b fmt REVERT: 86ad222f restructure tate test REVERT: f5f8fefa oops remove more debug stuff REVERT: ba07c995 undo debug commenting REVERT: 8f699b0f tests REVERT: f30f8d7d missing file REVERT: 507c253d even smaller bignum modexp test, and fixes REVERT: a89a2ada reorg REVERT: 6f3a50e8 Merge branch 'main' of github.com:mir-protocol/plonky2 into pairing-test REVERT: adae322f refactor REVERT: 26b65977 reorg REVERT: c349fed9 Merge branch 'main' into smaller-bignum-modexp-test REVERT: c682b2d8 Replace %stack calls with equivalent opcodes. (#994) REVERT: ff8a7c95 fmt REVERT: db78bb9e works REVERT: 258c66bf new api REVERT: 3a2fac16 compiles REVERT: b9dd2c60 fmt REVERT: 05bf9e3e even less thorough :P REVERT: c125d030 less thorough bignum modexp test REVERT: 20c163f0 Disable a couple tests REVERT: 8f6117b6 Labels for failed MPT read/insert REVERT: 71087fca Provide methods for serializing Kernel REVERT: 966cf758 Merge pull request #970 from toposware/env REVERT: 19f9e8f2 wip REVERT: 54ce63f6 Implement returndatasize/returndatacopy for interpreter REVERT: 12ab36cd comment REVERT: 7c784e54 getting there REVERT: f7bcef55 nl REVERT: 52ec9042 Merge branch 'bls-fp2' of github.com:mir-protocol/plonky2 into bls-fp2 REVERT: 1f789104 Merge branch 'main' of github.com:mir-protocol/plonky2 into bls-fp2 REVERT: a0c4d075 Update evm/src/extension_tower.rs REVERT: e4fc3003 Implement codesize/codecopy for interpreter REVERT: 646cf769 Implement gasprice on the interpreter REVERT: 4729858a Impl caller/address/origin opcodes for interpreter REVERT: ab034650 Precompiles interface (#983) REVERT: 7dfe82df Remove dummy_yield_constr REVERT: 7032b371 Merge branch 'main' into stack_bound REVERT: 4f45af26 Merge pull request #978 from toposware/stack_constraints REVERT: ea40ae89 Merge pull request #971 from toposware/keccak_sponge_is_final_block REVERT: 1d010741 Merge pull request #982 from toposware/sys_chainid REVERT: 0292263e Use Block chain id for sys_chainid REVERT: dd6d6943 Change shl/shr behavior as well as BASIC_TERNARY_OP REVERT: d5a37d65 Fix copy_returndata_to_mem (#976) REVERT: b6d8e500 Check if context is static for state-changing opcodes (#973) REVERT: 4c6b8cf2 Implement rest of *CALL opcodes (#972) REVERT: 0d4e4d98 switch REVERT: fdf8a01a Merge branch 'main' of github.com:mir-protocol/plonky2 into bls-fp2 REVERT: cd437c33 Remove unnecessary constraint REVERT: 1af725af Apply review REVERT: 8c4dcf6c Merge pull request #966 from toposware/interpreter REVERT: 5a8bf316 Set stack_len_bounds_aux properly REVERT: f2b61de2 Remove is_final_block column in KeccakSpongeStark REVERT: 85d53481 Fix from review REVERT: cdcbb958 CALL gas (#969) REVERT: 55e5c49b Merge branch 'main' of github.com:mir-protocol/plonky2 into bls-fp2 REVERT: 2a6260d4 Merge pull request #950 from toposware/keccak_sponge REVERT: 720bac3e Change endianness within generate_keccak_general REVERT: 8288bf5f Implement sar in interpreter REVERT: b996794e Implement signextend in interpreter REVERT: 61de673a Implement sgt in interpreter REVERT: d4c3fcb7 Implement slt in interpreter REVERT: 756b82c8 Implement smod in interpreter REVERT: a5c91732 Implement sdiv in interpreter. REVERT: b9b3e0e6 Merge pull request #968 from toposware/block_interpreter REVERT: 9e3092b6 Fix BlockCircuitData proofs REVERT: 3ed30da7 Cleanup REVERT: c6ebe9ef Reactivate CTL for keccak sponge REVERT: d827d984 Fix hash output writing to memory REVERT: 3e914298 Add test for keccakf_u8s REVERT: 7c449d81 Implement KeccakSpongeStark constraints REVERT: 69bb93cb Impl gaslimit opcode for interpreter REVERT: 740de6ef Impl chain_id opcode for interpreter REVERT: 9353a396 Impl coinbase opcode for interpreter REVERT: 3e8d77b5 Impl basefee opcode for interpreter REVERT: 6091ac93 Impl difficulty opcode for interpreter REVERT: 91f82e6f Impl number opcode for interpreter REVERT: 77b142e6 Impl timestamp opcode for interpreter REVERT: 1d5ba634 Merge pull request #965 from mir-protocol/fix_run_constructor REVERT: e065b60e Delete %set_new_ctx_parent_ctx REVERT: b9d91096 Fix call logic (#963) REVERT: f4256c3f Minor fixes to context creation (#961) REVERT: b1e5d31d Fix decode constraint REVERT: 72977adc Fix run_constructor REVERT: 0c5dc9d3 Implement sys_return and sys_revert (#959) REVERT: 43eb93a7 Fix sys_exp (#958) REVERT: b212a208 Fix copy opcodes when offset is large (#957) REVERT: f5f2297a Merge pull request #925 from mir-protocol/bignum-modexp REVERT: a5b5c276 fix REVERT: 95d339f3 addressed final comments REVERT: b8c7ca10 Merge pull request #956 from mir-protocol/doubly_encode_storage_values REVERT: ce679cc3 Doubly RLP-encode storage values REVERT: c7f286cf redundancy REVERT: 790a841a fmt REVERT: f5573aeb better names REVERT: 4e46bdcb systematize names REVERT: 46ef7723 all Stacks REVERT: 4496b86b frob REVERT: 6b61f3d4 rev stack REVERT: c3127018 Merge branch 'main' of github.com:mir-protocol/plonky2 into bls-fp2 REVERT: ea9d430e Fix MSTORE8 (#955) REVERT: d340526c Fix signed syscalls stack (#954) REVERT: 92fc42d9 Merge branch 'main' of github.com:mir-protocol/plonky2 into bls-fp2 REVERT: 3b75b2be Merge pull request #873 from toposware/hashconfig REVERT: f487df73 Move HashConfig into GenericConfig associated types REVERT: 9c52f85d Copy txn data to calldata (#935) REVERT: 76ff3dd4 Self-destruct list (#948) REVERT: c6398ba9 Transaction validity checks (#949) REVERT: 2b0e1233 Make hash functions generic REVERT: 25ce9636 Selfdestruct gas and set (#947) REVERT: 4454cf3c Merge pull request #946 from mir-protocol/selfBalanceGasCost REVERT: eca560c2 Remove dbg REVERT: ae918d52 Add an integration test for the `selfBalanceGasCost` case REVERT: 7f93a516 Charge gas for extcodecopy (#942) REVERT: 5a2fec18 Merge pull request #945 from mir-protocol/remove_CONSUME_GAS REVERT: 666fedb5 Signed operations as syscalls (#933) REVERT: 62b45d7f Remove `CONSUME_GAS` REVERT: c653e076 Merge pull request #939 from mir-protocol/termination_fixes REVERT: 95be28ea Merge pull request #938 from mir-protocol/rework_create_create2 REVERT: 058d4005 Feedback REVERT: 64548bce Fix Wcopy when size=0 (#944) REVERT: 2890f357 Charge gas for keccak (#943) REVERT: c95b3f1a Merge branch 'main' into bignum-modexp REVERT: 94099c65 Merge pull request #940 from mir-protocol/eth_trie_utils_bump REVERT: 8dc39b53 Removed a type alias REVERT: 6cd59d33 addressed comment REVERT: 15c492a5 fix REVERT: ebc67b0a check for x < m REVERT: fc699888 documentation REVERT: efad6bae comments REVERT: c83308dd cleanup REVERT: 3c8276b3 uncommented REVERT: cc6dc072 Merge branch 'main' into bignum-modexp REVERT: 1ea6d1c5 fix modexp test REVERT: b78472b9 fp2 works REVERT: 6c1f560c bls method REVERT: ef50e7f5 Merge branch 'main' of github.com:mir-protocol/plonky2 into bls-fp2 REVERT: 4af2b493 prover input minor improvements REVERT: c5ca29c4 Bumped `eth_trie_utils` to `0.5.0` REVERT: 021ea2a0 merge REVERT: 3a1c161f skeleton REVERT: 0ec53b99 Rework CREATE, CREATE2 syscalls REVERT: 7dd8efaf A few fixes for terminal instructions REVERT: 7de1c9da Access lists (#937) REVERT: c7ced3d8 Fix new account insert key REVERT: c74ad9d9 comment REVERT: 2f1ebc9d Merge branch 'main' of github.com:mir-protocol/plonky2 into fp381-opcodes REVERT: ea8d1019 remove .scale REVERT: 1e492bd9 Merge branch 'main' of github.com:mir-protocol/plonky2 into fp318 REVERT: b134eded fmt REVERT: 2bc50035 remove imports REVERT: 899189fd Fix bugs in `wcopy` and `update_mem_words` (#934) REVERT: 759ac673 Implement CREATE2 address generation (#936) REVERT: c79e4d4e Merge branch 'fp318' of github.com:mir-protocol/plonky2 into fp381-opcodes REVERT: 09ef7a3c comment REVERT: 22f55f9d Merge branch 'main' of github.com:mir-protocol/plonky2 into fp318 REVERT: 463d0cab more comments REVERT: deb7f894 More MemoryError (#932) REVERT: 52876e11 clean and generalize REVERT: dd05ccf0 tests pass REVERT: 39063734 comments REVERT: 308d29ee Merge branch 'main' into bignum-modexp REVERT: f3702182 Implement various syscalls (#930) REVERT: 28c7224a cleanup REVERT: c48ea86c on stack REVERT: a28f98ab test skeleton REVERT: 91451e80 compiles REVERT: 513d8ce3 skeleton REVERT: 1f0fb182 fmt REVERT: 6e64510e redundancy REVERT: 1351b209 merge REVERT: fd71a777 Merge branch 'main' of github.com:mir-protocol/plonky2 into fp318 REVERT: 19c000a0 finish REVERT: c8510934 cleanup REVERT: 3f0e9df5 fixes, testing, and in-progress debugging REVERT: ae9f10e5 fp6 works REVERT: 3892b6c3 frob works REVERT: dd27cc70 Return error instead of panic in memory operation (#928) REVERT: 23ab6fca Minor account code fixes (#929) REVERT: 04de69c5 merge fields REVERT: 24fda2d3 Merge branch 'main' of github.com:mir-protocol/plonky2 into fp318 REVERT: 0383cd62 addressed comments REVERT: 964b1fe7 Fix test REVERT: 16623688 Contract creation fixes REVERT: 6f34d05f Implement syscalls for BALANCE and SELFBALANCE (#922) REVERT: f9c8715a Merge pull request #926 from mir-protocol/fix_gas REVERT: 97098eed Fix GAS and implement storage value parsing REVERT: a2006e3b cleanup from comments REVERT: 0d04da30 addressed comments REVERT: f3ea930c addressed comments REVERT: aa4feca8 addressed comments REVERT: 57bcb5e9 fix REVERT: 61941107 restored neq macro REVERT: 729662b7 fmt REVERT: ffc03a53 fmt REVERT: 22a63c8f resolved conflicts REVERT: d9e3051f modexp fix REVERT: 2cd20105 modmul fix REVERT: 3729c4aa fixes REVERT: 8d8f8219 fix REVERT: 101e6438 modmul and modexp REVERT: 9ca69b8f basic bignum REVERT: e0afbb8c Merge pull request #881 from mir-protocol/bignum-basic REVERT: d52e31e5 allow empty stack replacement REVERT: c04cacb7 addressed comments REVERT: cd66b485 addressed comments REVERT: 76300d1d fmt REVERT: 6c5aeccf addressed comments REVERT: 61e66990 adj trait REVERT: deff0733 rename REVERT: 2517da08 cleanup REVERT: a251b940 Merge branch 'main' of github.com:mir-protocol/plonky2 into fp318 REVERT: b5b7195d Bump eth_trie_utils version. (#923) REVERT: 5f1a0787 Misc REVERT: 6debc7f2 Misc REVERT: ceae6bc3 Couple fixes & minor refactor REVERT: db51b59f Merge branch 'main' into bignum-basic REVERT: 825aba1e Charge for memory expansion REVERT: bcf47f3e Merge branch 'main' into bignum-basic REVERT: 918ce727 Fix reads from not-found ext nodes REVERT: 4adce09d Fix clobbering of RLP data memory REVERT: 8d6c2986 Merge branch 'main' into bignum-basic REVERT: 5a0b8ea6 Fix account cloning REVERT: 6f4a302a Merge branch 'main' into bignum-basic REVERT: 6dd630e0 Merge pull request #912 from mir-protocol/stack_on_panic REVERT: 134bda75 updated function name REVERT: efdd838d Merge branch 'main' into bignum-basic REVERT: e7ba4776 optimizations with rep REVERT: 1a6353f2 Merge branch 'main' into hash-asm-optimization REVERT: abf43ab6 Merge branch 'main' into bignum-basic REVERT: 7154ad46 Merge pull request #914 from mir-protocol/return_post_state REVERT: 59fc9f79 Fix tests - need to supply empty code REVERT: 59ad0e2c Input addresses REVERT: 7151763d Add a `prove_with_outputs` method REVERT: 47ce80d7 div instead of shr REVERT: 1bf3819f fit REVERT: 501b22c5 addmul fix REVERT: 618cd24d fixes REVERT: 470d7de5 fix REVERT: 774f0762 cleanup REVERT: 2600764b cleanup REVERT: 395185f6 fmt REVERT: 8120f712 new testing interface, and test data REVERT: 29d32e16 fix REVERT: 9dd73d01 fixes REVERT: 957c25ef fmt REVERT: 50c2b164 test data REVERT: 582d8f5b cleanup REVERT: fe4549c3 fix REVERT: bb3461c7 more efficient divmod REVERT: b91f0d14 Merge branch 'main' into hash-asm-optimization REVERT: 2d27f9d3 Merge branch 'main' of github.com:mir-protocol/plonky2 into fp318 REVERT: aa9fb868 folder REVERT: 3b32390d Merge branch 'main' into bignum-basic REVERT: 9d089c52 Merge pull request #817 from mir-protocol/non-inv REVERT: 811e8828 fixed iszero and cleanup REVERT: e72742c6 fixed messed up merge REVERT: b0dfde65 Merge branch 'main' into hash-asm-optimization REVERT: 38c07a94 fmt REVERT: ac87ebd9 Merge branch 'main' into bignum-basic REVERT: ff89fd69 interface changes REVERT: 92837e0e cleanup REVERT: ce58d4ec initial test data REVERT: da731494 TODO for possible future mul optimization REVERT: 302aec60 carry -> carry_limb REVERT: 40a82d8c ge -> cmp and returns 0, 1, -1 REVERT: 7ce83a1d Log stack on panic REVERT: 8502f513 Merge branch 'main' into optimize-blake2b REVERT: 39e93887 flip limbs REVERT: 12c18a1f run_ops REVERT: f00c31c8 merge REVERT: 345a4eeb Merge branch 'main' into bignum-basic REVERT: 61df1639 small optimizations REVERT: 34a9df8a more optimizations REVERT: 50c08dc7 optimizations REVERT: 7399f1de bug fix REVERT: ede61416 optimization REVERT: aba63906 cleanup REVERT: fdfbb422 fixes REVERT: d16707b3 fix REVERT: 4afe8754 fix REVERT: f810d189 refactor sha2 compression REVERT: 19657d26 more small optimizations REVERT: 6d1e85d0 small optimizations REVERT: 401125b0 optimized initial hash value generation REVERT: e6471d86 cleanup REVERT: 01b6f160 optimize hash generation further further REVERT: b0d3e195 optimize hash generation further REVERT: c2133590 hash function optimization REVERT: 8d1210aa cleanup REVERT: 35c40e2e fixes REVERT: 45e4c963 optimizations REVERT: 9f143642 deal with and test zero-len case REVERT: 70545557 fix REVERT: e3e8efca compiles REVERT: 31ea3135 addressed comments REVERT: 768a5e36 cleanup REVERT: 71603256 name change REVERT: 4202e55e addressed comments REVERT: 3bec5091 addressed comments REVERT: c6f400c2 Merge branch 'non-inv' of github.com:mir-protocol/plonky2 into fp318 REVERT: ddd1366d Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv REVERT: 34cdadb1 fix REVERT: 5bc50500 OR -> ADD REVERT: a6dae0e9 addressed comments REVERT: 96c7ea3c addressed comments REVERT: fb919d0c addressed comments REVERT: d3a6f553 Merge branch 'main' into bignum-basic REVERT: 8b942086 Merge branch 'main' into optimize-blake2b REVERT: f2914991 Gas fees go to coinbase REVERT: 89247c99 Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv REVERT: c6bf598f skeleton REVERT: 8ba89ac7 Couple minor fixes REVERT: 0bc4d75b unused test REVERT: f24b5121 small optimizations REVERT: 5928c0f5 restored blake2b_g_function and call_blake2b_g_function macros REVERT: 62b3f645 Merge branch 'non-inv' of github.com:mir-protocol/plonky2 into fp318 REVERT: bd39df7a Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv REVERT: 6222b9df more tests REVERT: e9543690 cleanup REVERT: d2b9a192 fix REVERT: 52f05791 fix REVERT: 34e88ef7 fixes REVERT: 1dbe9eb3 cleanup REVERT: d818e7b9 cleanup REVERT: 934c34ee cleanup REVERT: d42e2699 more thorough tests REVERT: 2a5a099b cleanup REVERT: 59acf12c bug fixes REVERT: d0cbd889 cleanup REVERT: d17bb783 cleanup REVERT: bab66a62 addmul test: use carry REVERT: 3ad5e00c cleanup REVERT: 1e7122de fixes REVERT: cc879261 fix REVERT: 56bb6e21 addmul initial REVERT: e277ac9c flag functions used only in tests REVERT: a05b5651 basic bignum REVERT: 8c1c4be9 optimized initial hash value generation REVERT: 59ccf78b cleanup REVERT: ec0a6207 optimize hash generation further further REVERT: 474e2b69 optimize hash generation further REVERT: d4aa8e26 hash function optimization REVERT: d0f4361a cleanup REVERT: 93c9fde7 fixes REVERT: 6f24671a optimizations REVERT: df807ef3 Misc EVM fixes REVERT: 1816a230 Merge pull request #902 from mir-protocol/debug_tries_2 REVERT: c08a1b2c Some tooling for debugging tests where the updated tries are not correct REVERT: c564c6da sys_gas REVERT: 476a9d19 Misc EVM fixes REVERT: 53462533 fmt REVERT: dc320e91 fixed multiplication REVERT: ef94c473 correct mul impl REVERT: 1dee3498 bls field arithmetic REVERT: 2d7201e0 cleaner rand REVERT: c55d5475 arithmetic skeleton REVERT: b1f54ba4 Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv REVERT: 2d995ce0 Fix code that looks for an account's storage trie REVERT: b55915f5 comment REVERT: 62479c8c add inverse doc REVERT: 5387b79d loop test REVERT: 54082561 minor changes REVERT: 3999fbd3 Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv REVERT: 3414f044 Use new plonky2 REVERT: 912f73a7 Fix a few issues found by EVM tests REVERT: d61b4b18 minor REVERT: e35065fd more general kernel peek REVERT: 262c4e68 minor REVERT: 1d5c51a7 Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv REVERT: d8a3830e Merge pull request #885 from mir-protocol/skip_log REVERT: e6cfb7bc Skip log_kernel_instruction if debug logs disabled REVERT: f9985542 put extract in interpreter REVERT: 5ea65f58 fmt REVERT: a0d40bff Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv REVERT: dd889b83 format REVERT: fe5c12d0 make hash functions take a location pointer REVERT: 89617ff1 simplify byte extraction REVERT: 0ed2f7fc function API / remove redundancy REVERT: 648da87e remove blake storage REVERT: c0ce0d3d remove sha2 storage REVERT: 80ec8554 cleaner arithmetic REVERT: fe1be8c7 abstract REVERT: e597ac75 remove custom REVERT: 99540987 simplify ripe md test REVERT: 313df5d2 Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv REVERT: 39f56a4d Gas constraints (#880) REVERT: aee01a8a better comments REVERT: 51ea60d6 segment virts REVERT: 43bce9f9 update curve add with ops REVERT: c42e87ef change segment REVERT: fc6101b0 fix pairing code after big BN PR merge REVERT: 02df2bba merge REVERT: 8d074867 segment REVERT: fd90e57f Optimize `ecMul` precompile (scalar multiplication on BN254) (#852) REVERT: 9850de88 Merge pull request #882 from mir-protocol/back_to_nightly REVERT: 66251a8f Revert "Set CI to use an older version of nightly" REVERT: 69c1bab1 Refactor arithmetic operation traits (#876) REVERT: b0d0c956 merge fix REVERT: d8b94cbb merge REVERT: 20529de1 Move SHL and SHR generation to the CPU. (#878) REVERT: 18de685e remove macro REVERT: 80934105 stack macro REVERT: 843eea01 naming REVERT: a846b0cb stack macro REVERT: 6c0cc0fe tests and stacks REVERT: 7499e5a6 more comments REVERT: a849e1e5 refactor REVERT: 6e882722 miller loop test REVERT: 9b1686f6 Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv REVERT: fb8887f9 align REVERT: ee65e61f Unify generation and verification of ADD/SUB/LT/GT operations (#872) REVERT: a948b739 fp -> fp254 REVERT: 27c52c7a fmt REVERT: 17822597 naming for global labels REVERT: b96fd2ee comments REVERT: ec188f43 name REVERT: 9927fabb rand impl REVERT: a4ba281f add comments REVERT: bcd6044a merge REVERT: e62bda56 Optimize `ecrecover` ASM (#840) REVERT: 0b020067 Prep for publishing to crates.io REVERT: 95781455 TODO REVERT: 28b225aa Add range checks to the arithmetic Stark (#866) REVERT: 1beedc9f comment REVERT: acfe56b9 minor REVERT: 92a58bad fix REVERT: 15281726 bools REVERT: 762e9283 fmt REVERT: 603e4810 names and comments REVERT: eeb87509 comments REVERT: fca60610 move consts REVERT: af439e48 better comments REVERT: 29d7257c clean asm REVERT: f309fbfc ocd REVERT: 46286ba1 better comments REVERT: 3cbfa01d stack macros REVERT: 54b6c74e new power works REVERT: 73a75148 refactor power REVERT: 5ccb97db clean REVERT: a4ad6344 clean REVERT: da8fdd2e tate test REVERT: 9deece47 rewrite w methods REVERT: fc54b25d inv as method REVERT: 4215ed04 en route to ownership REVERT: b3855753 reorg REVERT: 84056c23 extra comments REVERT: 8de57aaa extra comments REVERT: b2268173 meh REVERT: 3b6ba59c cleanup REVERT: e6b77cba cleanup REVERT: 1700b74e slight refactor REVERT: 346f8524 improved prover input and test api REVERT: fae95c89 duh REVERT: 849cb99e space REVERT: c96857ff fmt REVERT: 7a2bc999 stack macro REVERT: d25eb5ec remove redundant macros and improve comments REVERT: b8312ed0 fmt REVERT: fe95196d add module and fix errors REVERT: 888fd63e redundant macro REVERT: 7592fd27 test inv from memory REVERT: 05063283 test frob from memory REVERT: 73db9354 read output from memory REVERT: 96278560 mul test from memory REVERT: 9bcda941 reorg REVERT: e0d40224 Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv REVERT: 8508b44b Merge pull request #865 from mir-protocol/increment_nonce REVERT: 37c61ea1 fix clippy REVERT: 3ab6614b cleaner description REVERT: 52990c23 complete description REVERT: 899ff443 more comments REVERT: bb5c2b74 more comments REVERT: 0222ac3f transmute + comments REVERT: d1ffa2b5 comments REVERT: 0ec3710c Increment sender nonce + buy gas REVERT: 0948be0d org REVERT: d051380e remove comments REVERT: 8d7e3480 new inverse REVERT: c4faa2e1 frob format REVERT: 38fabfde frob format REVERT: eedd3cf8 frob tests REVERT: 6a6b012c Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv REVERT: a7466b0f struct impl style arithmetic REVERT: e440315f rename module REVERT: a452d9e1 remove make_stack REVERT: 097b4080 Block circuit REVERT: 50c4cd5d Merge pull request #863 from mir-protocol/smart_contract_test REVERT: 7a8e0c6b log REVERT: d00053e8 Basic smart contract test REVERT: 9aea9630 Use error instead of panicking in FromStr REVERT: 3db4274f move comment REVERT: b61ebce9 name REVERT: 75b88db1 name REVERT: 02d3f87a zero name REVERT: 50ba6d13 Merge branch 'non-inv' of github.com:mir-protocol/plonky2 into non-inv REVERT: e5f94e52 name REVERT: 55a98605 Update evm/src/cpu/kernel/asm/curve/bn254/field_arithmetic/inverse.asm REVERT: fa69458a name REVERT: 006a86cd cleaner inv REVERT: 5fc7ee3a Update evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/curve_add.asm REVERT: 03afa0b9 Merge branch 'non-inv' of github.com:mir-protocol/plonky2 into non-inv REVERT: 88230c68 Update evm/src/bn254.rs REVERT: 9275ac85 \n REVERT: a060241d Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv REVERT: 0976d580 Update evm/src/witness/util.rs REVERT: 7d5053ad Update evm/src/bn254.rs REVERT: 1570aff4 Fix stack overflow REVERT: b05c8c13 Merge pull request #860 from mir-protocol/agg_circuit_2 REVERT: f0d9d7a0 Feedback REVERT: 745f63cb Merge pull request #859 from mir-protocol/remove_older_evm_recursion_logic REVERT: 4de81f27 Fix vk REVERT: a90cb34c Add aggregation circuit REVERT: 09f64d94 more REVERT: c19f988e warning REVERT: e30091f8 Remove some older EVM recursion logic REVERT: c321a510 Remove CTL defaults REVERT: c5770a21 feedback REVERT: 84ef3f6f Disable slow test on CI REVERT: 26f72bd4 Shrink STARK proofs to a constant degree REVERT: d9b9ff94 names and format REVERT: 334780b6 simplify miller loop REVERT: 96c52fd7 remove prints REVERT: c311f61d remove loop endpoint REVERT: 9ca12d38 name REVERT: 1c7be8bc remove redundant definition REVERT: 5fecaf78 TATE TEST PASSES REVERT: a4e5093a better vec to fp12 REVERT: 219ea822 clean REVERT: 17c03f86 clean REVERT: eb4b86b8 rearrange REVERT: 301513bf clean REVERT: 284de35d tuck const REVERT: 90141968 miller test passes REVERT: 66be3aaf miller in rust REVERT: 5bb18a56 more clean REVERT: bb8f714e clippy REVERT: eaac763d more cleaning REVERT: 4c75b10f clean up REVERT: 49135f4b clean up prover code REVERT: 767f1948 clean up code org REVERT: 4abce5a3 tangent and cords work REVERT: cf210af4 it runs REVERT: 8dfe18e2 POP REVERT: 07aa3899 fix REVERT: 53fea2f9 hex REVERT: 25c87507 setup miller REVERT: ae3df7ec rename REVERT: f0b106a8 POWER WORKS REVERT: a0bcbd17 space REVERT: 0dcd3d46 first part works REVERT: ae198465 debug pow REVERT: 7eed8d4f test REVERT: 5e5cb880 setup pow REVERT: 1af7c8d2 refactor REVERT: c835db6d clean up inverse REVERT: b728eb0b inverse edits REVERT: 4c3ddb45 Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv REVERT: 7cbfa6b1 inverse works REVERT: 3fde9446 skeleton inv REVERT: 7a2b77db Fix logic and Keccak CTLs REVERT: 991a3a97 fix REVERT: 27167552 all but inv REVERT: b8b5c403 all but inv REVERT: ac547f56 frob tests work REVERT: c664d6d0 refactor REVERT: ab305992 naming REVERT: 88ac47c7 delete dead code REVERT: f4a5a301 streamline tests REVERT: 5fba29fa name REVERT: 05738bfb fix REVERT: e5a8d3a8 name REVERT: 0264d9c1 minor REVERT: a6d35495 minor REVERT: 035ba870 aggregator REVERT: ab799758 fix REVERT: 1fba353b U256ify REVERT: cee14375 merge REVERT: 427eef48 Ignore failing test REVERT: 53257aa7 Fixes REVERT: 51e0a2d0 Use the order of the BN base field in the interpreter REVERT: 5bd70a91 merge REVERT: aa05df1f spacing REVERT: 029a9975 block_size macro REVERT: 6f9306dc deps fix REVERT: fa8a1fed macro REVERT: e144061c fixes and addressed comments REVERT: 486c8223 addressed comments REVERT: acdcb223 fmt REVERT: 04fd3474 cleanup REVERT: 22319e95 another clippy fix REVERT: 438e0a7d clippy fix REVERT: 496819e6 documentation REVERT: 5a212134 documentation REVERT: 8340fe22 rename blake -> blake2b REVERT: e1f61756 fixes REVERT: 38adcc0a minor memory access refactor REVERT: 292bd288 cleaned up hash tests REVERT: a766fbe2 clippy REVERT: 224f82be fmt REVERT: 8731225a cleanup REVERT: fb1b2cca FIX REVERT: 5505c8d8 fixes REVERT: fd24de79 debugging REVERT: fbd5e2fd fixes galore REVERT: db89f5b3 fixes REVERT: 8699af39 concat REVERT: de4c86d7 Blake progress REVERT: a57d73fd progress REVERT: e95b7d13 util file REVERT: 398d528e fixes and testing REVERT: 049c20c6 progress REVERT: ea8af7c9 progress REVERT: 07fe9a9d fmt REVERT: 405e2313 fixes and test infrastructure REVERT: 95bd861f updates REVERT: 09d1d61e fix REVERT: 36fd58a5 fixes REVERT: cefb3ab3 Blake progress REVERT: ffbf3f4c Blake progress REVERT: 3a2c36d8 progress REVERT: 0e9d96f4 progress REVERT: 02a155ba progress REVERT: 42f8a47d progress REVERT: 19a258f1 blake initial REVERT: a217f915 blake initial REVERT: 4033e256 Removed unused deps unovered by `cargo-udeps` REVERT: ccb5ebe3 Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv REVERT: 8b303f62 Merge pull request #848 from mir-protocol/more_cyclic_recursion_changes REVERT: b42f5793 A few more cyclic recursion changes REVERT: f8248ef3 Simplify `JUMP`/`JUMPI` constraints and finish witness generation (#846) REVERT: 876c1888 Constrain memory channels in JUMPDEST (#844) REVERT: 410b0c1d Get/set context (#843) REVERT: 62d7ceea Implement `PC` instruction (#847) REVERT: 73b984a0 log level REVERT: 20073d4a More timing for zkEVM proofs REVERT: cf8703b8 fix eth_to_wei REVERT: 2ea7f443 Fix to add_eth REVERT: b55f640a Memory load/store constraints (#839) REVERT: b35c49e2 Merge pull request #837 from mir-protocol/fill_memory_gaps REVERT: d40e0767 Comments REVERT: d21b9df6 Fixes to get test_simple_transfer working REVERT: 6bc2fb3f fix miller REVERT: ad2607f2 fix tate REVERT: bda438e3 fix dups REVERT: bdf5dec6 storefp12 macro REVERT: eb07bb01 macros REVERT: 7c301294 fixed miller + conts REVERT: cb322bba power function complete REVERT: 7be324d5 power function REVERT: ef4893cc call curve add REVERT: 9d8b0456 fmt REVERT: d3be32e0 inverse REVERT: 45634ea8 update curve add REVERT: 4c9b46d1 Add dummy reads to fill any large gaps in memory fields REVERT: 52654771 div name REVERT: d58493bb simplify original REVERT: 68156503 , REVERT: 9540a9be frob fix REVERT: 178ac802 cord and tangent REVERT: 5955ea22 Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv REVERT: 3e12b3ea minor REVERT: 4656cdeb Jemalloc for EVM REVERT: 820ebc80 fmt REVERT: 5db626c5 fixes REVERT: 02ce2c75 Misc witness generation fixes REVERT: c4cad5fc Fix REVERT: f4d79306 fmt REVERT: 467d709f Use Keccak sponge table for bootloading REVERT: 36894701 Fixes & re-enabling most constraints REVERT: f8d714a5 Merge pull request #816 from mir-protocol/jacqui/witness-generation REVERT: 6319e694 Fix recursive constraints REVERT: edd95b9f fmt REVERT: ccef0501 Interpreter fixes REVERT: 84bdb236 clippies REVERT: 94722907 Warnings REVERT: fcf150a0 Misc fixes REVERT: 3330dbae TODO REVERT: e4ce70d8 Misc fixes REVERT: 35bb6ca4 Halve number of columns used by arithmetic table (#813) REVERT: 69579c31 Fixes REVERT: e3aa1a1e Fixes REVERT: 2c0b2807 Fixes REVERT: 46dc2216 Keccak fix REVERT: 99eb60ef generate_keccak_general, generate_byte REVERT: 24a93c5f fix REVERT: 29f23ff1 Pop REVERT: 26fd6852 Refactor to support PROVER_INPUT REVERT: 3e4fd8cf stubs REVERT: 073af204 misc REVERT: 32ec0865 fix REVERT: 504383d3 Generate memory ops REVERT: 2726a124 Flush out operation list REVERT: 3a0afdb1 Fixes REVERT: f02a3049 generate_push and misc other progress REVERT: d6c7841f Push and arithmetic ops REVERT: 8dad6039 Misc work on witness generation REVERT: ba71de8d Fix warning REVERT: fd9d593b Merge branch 'main' into jacqui/witness-generation REVERT: a6aa11ee fix REVERT: 38d986dd miller loop REVERT: 5bbfbc1c Trie fix REVERT: e6c2c3bd fix REVERT: 8db15e69 Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv REVERT: 82d40f65 tate REVERT: dff18970 Compiler errors + refactor REVERT: 0e7c7c58 alphabetical REVERT: 49b28eb2 Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv REVERT: c0452623 Make load_code a bit more general REVERT: 5badf5ba Kernel code to do jumpdest analysis REVERT: db0b8141 Implement `CALLVALUE, CALLDATALOAD, CALLDATASIZE, CALLDATACOPY` in interpreter (#819) REVERT: 51d32c3b frob macro REVERT: e0c7a56c tate REVERT: 8d33ef9b reorg REVERT: f7349a7d Merge branch 'feat/no-std-support' of https://github.com/openzklib/plonky2 into openzklib-feat/no-std-support REVERT: 20855d4a macros REVERT: 7ec124ba frob_fp12 REVERT: 0154c288 frob_fp6 REVERT: 992079ce mul_fp2 REVERT: c60371b5 comments REVERT: df0dfead fp12 sq works REVERT: d589ae43 fp6 sq REVERT: 64069c2f Witness generation work REVERT: 5b2d4c5c chore: merge branch `main` REVERT: 867a745b Fork Update (#3) REVERT: 3b329c3f correct ops REVERT: ee79f3a5 sparse mul works! REVERT: c533e9f6 fix mul_fp2_fp6_sh2 REVERT: ad7f52c4 update fp12 REVERT: 5e59bf75 add total count REVERT: 2c8fbc2d fix fp12 test REVERT: 36d1a5b5 Merge branch 'main' of github.com:mir-protocol/plonky2 into non-inv REVERT: 1a260a7e Remove signed operation placeholders from arithmetic table. (#812) REVERT: 2f3fea86 aggregator REVERT: 7ae75b57 scaling macros REVERT: 4d562e88 sparse mul finished REVERT: 57990a56 EVM shift left/right operations (#801) REVERT: 21b3caa7 Combine all syscalls into one flag (#802) REVERT: a63eff73 Update criterion REVERT: c2fa1eb2 Merge pull request #804 from mir-protocol/static_kernel_in_tests REVERT: c2ff445d Print opcode count REVERT: 648b1cee Use static `KERNEL` in tests REVERT: 05c94591 chore: fix serde-cbor and run cargo-sort REVERT: 7b028c45 feat: upgrade Sampling APIs REVERT: 9794ae36 fix: remove unstable features from plonky2 REVERT: 9211de98 Constrain `mem_channel.used` flag to be binary (#800) REVERT: 4d5a1bbb Fixed bad PR merge REVERT: dfc3428c Merge pull request #792 from mir-protocol/eth_trie_utils_0_3_0 REVERT: c1a0cd32 Merge branch 'main' into account_code_opcodes REVERT: bcd32193 Merge pull request #797 from mir-protocol/balance REVERT: 35e9efef Fix + Use context=0 REVERT: 8d362b6a Merge branch 'main' into account_code_opcodes REVERT: 2cc913d8 Return 0 if account doesn't exist REVERT: b1d84b23 Return 0 if account doesn't exist REVERT: 0665830d Updated `eth_trie_utils` to `0.4.0` REVERT: a8d9ccfd Bumped `eth_trie_utils` to `0.3.1` REVERT: faac585e Use address macro instead of opcode REVERT: ccbdae74 Use address macro instead of opcode REVERT: f59ec071 comment dead code REVERT: 554bc6cb Balance REVERT: bb01c045 fmt REVERT: cce445d2 spacing REVERT: 461b2114 minor REVERT: f02f1635 comment about 107 REVERT: bb430078 merge REVERT: fc94f36d Merge branch 'main' of github.com:mir-protocol/plonky2 into Fp12 REVERT: b4541f9a improve comments REVERT: 7aec098a Merge pull request #782 from mir-protocol/cyclic_recursion REVERT: 4f737c2a Minor REVERT: a94169da Codecopy test REVERT: 6968911f Clippy REVERT: d2f135bf Merge branch 'main' into account_code_opcodes REVERT: 11ec65a0 Working with random address and code REVERT: f6d26447 Working codesize REVERT: 783f809e Fix `keccak256_word` REVERT: 5b24adfa Found bug REVERT: 30c66d9b Minor REVERT: 1758c01b comments REVERT: 63e2f0fc minor REVERT: e72cdc2d better comments REVERT: 7532a219 clean up test REVERT: e9ce2f56 Merge branch 'main' into cyclic_recursion REVERT: c55be8c9 New clippy lint REVERT: cb9176d1 Remove Config from `CommonCircuitData` REVERT: 133edf54 fmt REVERT: fe079a27 better comments REVERT: ebde7307 cleanup REVERT: b2e01dc1 fp12 passes tests! REVERT: f416376d cleanup comments REVERT: 3780fd35 better comments REVERT: 50532655 fp6 test passes REVERT: f2f0598c new fp6 REVERT: 38f8bd36 Finish extcodecopy REVERT: ad2589cf Merge branch 'main' into account_code_opcodes REVERT: 01b2f7ce Start REVERT: 38cd46dc Merge pull request #791 from mir-protocol/kernel_msize REVERT: c14ca18d Implement DIV instruction (#790) REVERT: 699cda13 fp6 as fn REVERT: 4b85315b richer comments REVERT: 5d5fa3d7 richer comments REVERT: 6920bcd5 fp6 mul REVERT: 0032c4a3 finish macros REVERT: 25eac62b Minor REVERT: 18e83d7e Minor REVERT: 60401286 Minor REVERT: c703ec97 Add msize REVERT: ed030c75 load/store macros REVERT: 00461738 addr on stack REVERT: 34ddc7fa bus -> subr REVERT: afd1d28f Fix fix interpreter REVERT: 5dc15f69 Fix interpreter REVERT: 9be13aec Implement SUBMOD instruction (#789) REVERT: fbe8bafd more comments REVERT: 763d9490 more comments REVERT: aaff606a replace mul_const REVERT: a913df0e minor REVERT: a9f63315 aggregator REVERT: 11b2bd7d casing? REVERT: 5a277f99 detailed accounting REVERT: cc42ef77 fmt REVERT: bb8b75d6 change to 107 REVERT: 4271ce8b lint REVERT: 89f54160 capitalization?? REVERT: d01d789e alphabetical REVERT: b69dcb97 clippy REVERT: 18d6b254 better abstraction REVERT: 0112c008 Small storage fixes REVERT: 99bc8df6 MPT storage logic REVERT: 3c248cd9 naming REVERT: c60d962f remove fp6 test REVERT: 60b07151 remove fp6 test since it's redundant REVERT: 339ccb6b move fp6mul and add more comments REVERT: 9f0f1606 Merge branch 'main' of github.com:mir-protocol/plonky2 into Fp12 REVERT: b9b629ce fp12 is running REVERT: 09fb71a6 fp6 passes randomized tests REVERT: d0b4fd47 fix fp6 subtraction---fp12 tests running! REVERT: 4cc4042e fix fp6, better test function REVERT: 434c6e41 Merge pull request #786 from mir-protocol/mpt_dirs REVERT: a898ca34 better test API REVERT: 2f50d580 Merge branch 'main' into cyclic_recursion REVERT: b724be7c Merge branch 'main' into generate_dummy_proof REVERT: 29673946 More directories for MPT logic REVERT: 31de2ce5 Treat storage tries as sub-tries of the state trie REVERT: d23debf4 Merge pull request #784 from mir-protocol/avoid_current_memory REVERT: 26b74672 Switch a few uses of current-context memory to kernel memory REVERT: ba55c850 Merge pull request #781 from mir-protocol/redundant_degree_bits REVERT: bc4958d2 MPT format tweaks REVERT: 64540ab6 Fp12 REVERT: 4a8556c8 Fp6 mul test passes REVERT: ed49e0f1 new op codes REVERT: ac212ad2 Merge branch 'main' of github.com:mir-protocol/plonky2 into Fp12 REVERT: 8e36ad9b Add Fp254 ops to the CPU table (#779) REVERT: 444ca18e Redundant `degree_bits` REVERT: e9f9bee9 Merge branch 'generate_dummy_proof' into cyclic_recursion REVERT: c4286a17 stuff REVERT: fb3a4065 Bumped patch version REVERT: 7658d13e Updated `eth_trie_utils` to `0.2.0` REVERT: cd1acaa7 Merge pull request #777 from mir-protocol/mpt_insert_7 REVERT: d441f200 Fix branch hashing bug REVERT: 3f071393 Merge pull request #774 from mir-protocol/debug_offsets REVERT: 894cf236 Interpreter feature to configure debug offsets REVERT: a6fb578a Represent input columns as ranges rather than arrays (#776) REVERT: e42b773f Working REVERT: 30c75c78 Refactor and tidy up `mul.rs` (#764) REVERT: 1607ee43 MPT logic for inserts into extension nodes REVERT: 9bb5513c MPT insert into leaf, overlapping keys case REVERT: 2ba1d52f More MPT insert logic REVERT: 7367b252 Insertion optimization for leaf case REVERT: f16d5852 MPT insert logic, part 2 REVERT: de24782a MPT insert logic, part 1 REVERT: 6c7f711b Clippy fix REVERT: a095036e Tweak MPT value storage REVERT: 4196d8d0 Begin MPT insert REVERT: f4032791 separate module + stack comments REVERT: c462adce Fp6 mult purely on stack REVERT: 99433b4a Fp12 mult + Fp6 macros REVERT: df1b8cb0 Some more uses of %increment, %decrement REVERT: cdb010ab Fp6 mult REVERT: 1a65c66e Merge pull request #708 from mir-protocol/per_table_recursion REVERT: 4c31fea6 Hardcode verifier data in the circuit REVERT: bb1ad5dd Modular operations for the EVM arithmetic unit (#755) REVERT: 4517eec1 Hash MPT extension nodes REVERT: bb6f5ca9 Update spec REVERT: 2db680a2 MPT fixes REVERT: 316f64f8 mload_packing REVERT: 0e16d173 PR feedback REVERT: 0b739e70 PR feedback REVERT: 4fb8ca8b MPT logic to hash branch nodes REVERT: 60ad39ff Rework MPT hashing to support inlining <32 byte children REVERT: d830df8a clean up and format REVERT: 916ec11a fmt REVERT: 285dc7c6 fix padlength issue REVERT: 14f5243c Now uses `eth_trie_utils` on `crates.io` REVERT: 4c60bd4e Merge pull request #756 from mir-protocol/rlp_fixes REVERT: 47fda119 RLP related fixes REVERT: 2a7d830f Split circuit and witness generation REVERT: d87e8b4c Merge pull request #640 from mir-protocol/ripeMD REVERT: cc32332e done REVERT: dddc07db almost done REVERT: 599a76a3 Merge pull request #754 from mir-protocol/sha2_inline_consts REVERT: ee17e785 rearrange REVERT: 9e5f77fd Inline some SHA2 constants REVERT: a17375e0 fix merge problem REVERT: abe91d51 Unroll num_bytes REVERT: 047a05e2 delete duplicates REVERT: 1d5ef15a Merge pull request #752 from mir-protocol/hash_kernel REVERT: 2dc80d68 merge REVERT: ce577eb8 Fill in hash_kernel REVERT: 363b5d75 alphabetical REVERT: fe2208da clippy REVERT: c95765ee rearranging and cleanup REVERT: 29ab9776 minor REVERT: da388a4a minor fixes REVERT: 3cacf185 fix REVERT: 727f45e2 fix REVERT: d9ae6f72 fmt REVERT: 2bfe45f9 fix REVERT: 61bdc402 label name simplification REVERT: cf302094 moved memory functions to memory ASM file (not sha2) REVERT: 7c11b97c remove prover_inputs from Interpreter REVERT: df8a50ff Update evm/src/cpu/kernel/tests/sha2.rs REVERT: 03a829d4 Update evm/src/cpu/kernel/tests/sha2.rs REVERT: 10b5b52c cleaned up test: compare as U256, not string REVERT: ffa9f16b fix REVERT: 97876997 addressed comments REVERT: 40e4d28a cleanup and comments REVERT: e77850e2 comment REVERT: aa34aa1d addressed comments REVERT: 839348cb in %stack, treat identifiers as 1-length blocks REVERT: c4ff8b2e removed parentheses REVERT: c5ff1d3e addressed comments REVERT: 87de1d36 more %stack sha2 cleanup REVERT: 61c090e3 more %stack sha2 cleanup REVERT: 926f1b10 more %stack REVERT: 7d96b027 more use of %stack macro to make sha2 cleaner REVERT: 99e2042f addressed comments REVERT: 2b5058e7 opcodes to uppercase, and cleanup REVERT: 0a37d501 started on using %stack in sha2 asm REVERT: 99344a0f removed JUMPDESTs REVERT: 4dc3da36 fix REVERT: bf8fda4d fix REVERT: 6a51dd52 fmt REVERT: 043076e3 simplification and documentation REVERT: e3a914ce cleanup REVERT: 80486042 fmt REVERT: 346de64a cleanup REVERT: b5afeb35 split up sha2.asm file REVERT: 54ca420a cleaned up test REVERT: bcd5a813 randomized Sha2 test REVERT: 49f1942e fixes REVERT: 970187b0 more fixes REVERT: eb988ebd fix of message schedule REVERT: dcdbfb58 fmt REVERT: 909a7e01 cleanup REVERT: b9be02e2 cleanup REVERT: bfbb6e75 fix REVERT: a62ff11f sha2 testing infrastructure REVERT: 975fdfcd clippy REVERT: d4d2b032 clippy REVERT: 23d6c62b many fixes REVERT: 8ec1331d fix REVERT: 2486d575 fix REVERT: 199adaea fix REVERT: a136144c updates REVERT: 094efb7c more compression REVERT: ec721a1c compression REVERT: f31c0c59 cleanup REVERT: 2dd907b3 MESSAGE SCHEDULE WORKS REVERT: d7698bba many fixes REVERT: 8483c86f debugging, and progress REVERT: a621c51a fixes REVERT: a3a058aa fmt REVERT: 8072761f many fixes REVERT: f2601e65 fmt REVERT: 8c6e7328 clippy REVERT: 0552729c fmt REVERT: 07240fda removed duplicate macros REVERT: a86073bf fix REVERT: aae6a8a9 fix REVERT: 56ad4070 fixes REVERT: ae0432a9 a great many fixes REVERT: 8dcbe219 updates REVERT: f2affe18 finished sha2 pad REVERT: 62532959 fixes and updates REVERT: a84f8a92 fixes REVERT: 8f0ca551 fixes REVERT: 39168be0 fixes REVERT: 4e157cbe files REVERT: 059e8ff4 first test, and fixes REVERT: 32eb62e7 mstore_kernel_general_u32 macro REVERT: c66391b4 updates REVERT: 619b2d50 updates REVERT: 76a75958 h constants REVERT: f52dfc59 constants REVERT: 0a94c9b1 memory commands REVERT: 3abc5b7c message schedule progress REVERT: 96d83ca7 progress REVERT: afdaffa6 functions --> macros REVERT: 5c6e79f0 redest, and progress REVERT: 069463f9 constants as macros instead of functions REVERT: 2d146aaa fixes REVERT: 1e7785a2 progress REVERT: a9c49c8a progress REVERT: 869e482b new padding REVERT: 1a62d29d ops REVERT: 34b650aa constants REVERT: bb4f40e3 finished pad REVERT: b76af332 fix REVERT: b003f460 fixes REVERT: 4594d527 updates REVERT: b19c9fcb using memory REVERT: 042ffc93 progress REVERT: 6dc7a96a starting on sha2 REVERT: 29339f00 minor REVERT: 91e17b30 move buffer_update to memory module REVERT: 70e5d8c0 fix arg order for memory version REVERT: ccee1f6e Merge pull request #751 from mir-protocol/move_constants REVERT: 4fb3c548 put macros in more general module REVERT: 227efad2 extraxt box into own module REVERT: 2d49c27f Constraints for dup/swap (#743) REVERT: 67a0cf45 clean up macros, remove ripemd segment REVERT: 6a42730b Merge branch 'main' of github.com:mir-protocol/plonky2 into ripeMD REVERT: 9237a3f9 Move some constants REVERT: 472eaaf1 Minor refactor of RLP code REVERT: 6c4d7e64 Minor REVERT: 4da7fd47 Merge branch 'main' into per_table_recursion REVERT: 140f01a2 PR feedback REVERT: 34b289ec state() -> compact() REVERT: 6f9b4e40 Fix optimization REVERT: 101f6e39 Finish some misc storage logic REVERT: ae0b56f6 Add TODO REVERT: d65f1fb8 MPT hashing logic, part 3 REVERT: a3fd5386 MPT hashing logic, part 2 REVERT: bd0f84e7 Merge pull request #746 from mir-protocol/mpt_hashing REVERT: 5912641e MPT hashing logic, part 1 REVERT: 917b8317 Merge pull request #744 from mir-protocol/bootstrap_fix REVERT: 87a9ff68 Fix bootstrap channel indices REVERT: 8d86e698 Empty txn list test (disabled for now) REVERT: bf73e248 Merge branch 'main' of github.com:mir-protocol/plonky2 into ripeMD REVERT: e489127e Merge pull request #742 from mir-protocol/msize REVERT: fc8fc9c9 fixes REVERT: c5aaa47a Merge pull request #740 from mir-protocol/h256_trie_roots REVERT: 694d0aed MSIZE REVERT: 07ea13f1 Main function, txn processing loop REVERT: b0d7a2aa Few small changes related to switching to `H256` REVERT: 12d55aad reverse bytes via BYTE code REVERT: 6164c9f2 Trie roots now use `H256` instead of `U256` REVERT: 5bbe4379 Daniel's comments REVERT: 6e55a341 Simplify `num_ctl_zs` REVERT: 3292bec4 spacing REVERT: eb81f9a9 Merge branch 'main' of github.com:mir-protocol/plonky2 into ripeMD REVERT: 0e01174e Connect stack to memory (#735) REVERT: 6214ef74 spacing REVERT: e96f51a5 unused macro REVERT: 95b31b27 merge REVERT: 1893c466 Nick's comments REVERT: 4c5ae5d4 MPT read for extension nodes REVERT: 7b4cafe4 format REVERT: 3872ca17 SHR REVERT: a6db3ed8 merge REVERT: 22232351 all reference tests are working! REVERT: 845ab524 majorly simplify update2 REVERT: 041e93ec loop thru official ripemd tests REVERT: 368c8d91 Finish MPT read logic REVERT: 83a4c107 all small inputs work REVERT: 93ee1940 fix update2 REVERT: a80c6d6e clean up REVERT: afcd6da9 Update comments REVERT: 037e0c38 Method to compute verifier data without proving REVERT: ddd76d07 More MPT logic REVERT: a6f75306 fix length zero input issue REVERT: 0f814b28 fix padlength overflow error REVERT: ea88ebc1 assert as U256 REVERT: 6960a242 better test API REVERT: 6784ace4 fancy new stack macro REVERT: 7d4bac67 Merge branch 'main' of github.com:mir-protocol/plonky2 into ripeMD REVERT: e0df0e9c Merge pull request #734 from mir-protocol/stack-manipulation-empty-lhs REVERT: 0c09db82 fmt REVERT: f8b0dd60 stack manipulation: allow empty LHS REVERT: 70ee3bbb Merge branch 'main' of github.com:mir-protocol/plonky2 into ripeMD REVERT: f5075dd9 Keccak benchmark REVERT: 4f49b50b format, stack macro, remove prints REVERT: c999275f FIRST UNIT TEST PASSED! REVERT: 12c85011 full run but fails REVERT: 8d193372 fix update REVERT: f2015f02 Comment REVERT: 3bdb0995 Cleaning REVERT: 2b08f975 Working REVERT: 0bca03cc Add CTL verification REVERT: f46fdfa5 Challenger state works REVERT: fdd09451 compression test works! REVERT: 3729dbcc shr REVERT: 304f6273 fix stack manipulation REVERT: 32f0f404 Merge pull request #732 from mir-protocol/macro_overloading REVERT: a55f6c57 Support macro overloading REVERT: 82df15aa Memory channel for program counter (#717) REVERT: 8952660a Merge pull request #731 from mir-protocol/mpt REVERT: c25391c8 Basic MPT logic REVERT: 9af676a3 test compress REVERT: 04c8bef2 fix constants REVERT: b47f12cb cargo format REVERT: 54aba49c style REVERT: 5c603eb8 test is running, but failing REVERT: 8c144e2d assembler is running REVERT: 404636a6 fix macro REVERT: 19426c79 Fix REVERT: 90d036cd Add challenger state REVERT: 9df56a26 cargo format REVERT: 716e112a Nick's comments REVERT: cfc6b5c0 Nick's comments REVERT: 846392fb fake test REVERT: 79753c07 Merge branch 'main' of github.com:mir-protocol/plonky2 into ripeMD REVERT: da8ca9f4 0xdeadbeef REVERT: e31572f5 tweak REVERT: 38e92c21 fix REVERT: d4051b5f parse error REVERT: 00fc777b Added a mapping between code hashes and contract byte code REVERT: 19004c22 use blocks REVERT: b9386123 Fix prohibited macro names REVERT: a4e3c41a Merge branch 'main' of github.com:mir-protocol/plonky2 into ripeMD REVERT: 7f9d867f fix test REVERT: 4eae4ffb Fix macro vars in %stack directive REVERT: 673e665c it runs (but forever...) REVERT: 4ae7a054 everything is parsing REVERT: 34e25780 Merge branch 'main' of github.com:mir-protocol/plonky2 into ripeMD REVERT: 2fa93a4b fix bugs REVERT: 1a60e067 add to include files REVERT: 80d7f06c Minor REVERT: 3765b7f4 Shape check in starky REVERT: 79a03dc6 fix REVERT: 3a4a3c27 Validate EVM proof shape REVERT: 8e0af575 test REVERT: 57e7ed72 test running REVERT: 24690069 alt api for testing REVERT: a585d8a1 duplicate macro, test skeleton REVERT: d205e090 delete needless REVERT: b7c8ebd7 merge REVERT: 881e0a35 finish?? REVERT: bd235ec4 Validate the shape of each proof REVERT: 3d1f79c6 zkEVM spec REVERT: a02e7f93 ripemd storage REVERT: 12026e16 change to 8 bit words REVERT: e1e3d6d6 add ripeMD segment REVERT: 9997f5d2 Replaced `PartialTrie` definitions with `eth-trie-utils` crate REVERT: c8649795 update skeleton REVERT: b094e552 outer function REVERT: b4c44f17 Merge pull request #683 from mir-protocol/call_common REVERT: 204661f2 Verify that comparison output is zero or one (#715) REVERT: 1ec013df remove jumpdests / add macro REVERT: c68e7fa8 remove jumpdests / fix name REVERT: 113a7a02 update REVERT: 98c5cdb7 flip bytes of a u32 macro REVERT: 21730b7d scale indices by 4 REVERT: 9eb72d3e diff name REVERT: 33ff71f5 consume offset REVERT: 2aa260e4 load little endian REVERT: c265d3f0 fix REVERT: c9c322b3 parentheses change REVERT: 27a110c1 allow offset variable REVERT: 03fd2143 Merge pull request #716 from mir-protocol/s/l1/l0 REVERT: 21fdb928 s/l1/l0 REVERT: dcccc6d9 Merge pull request #714 from mir-protocol/stack-manipulation-blocks REVERT: df4f1d11 Stack pointer + underflow/overflow checks (#710) REVERT: c5d8d2b2 formatting REVERT: 0045e39f final error and formatting REVERT: 29419589 blocks in stack manipulation REVERT: cbc55956 fix REVERT: 35736eb9 fix errors REVERT: 1478e488 minor errors REVERT: bdf1bdc5 Merge pull request #705 from mir-protocol/packed_len_div REVERT: 1a4ebdf2 Fill in call_common routine REVERT: b7fdfd73 Tweak features REVERT: 69e2d193 fix default features in starky & evm REVERT: c511e451 Merge pull request #704 from mir-protocol/partial_trie_derive REVERT: a4199d2f Feedback REVERT: ca17e286 let_chains REVERT: 463fdc6d Clippy REVERT: 84654d0b Comments REVERT: ecd15133 Minor REVERT: dcaa3bf8 Recursively verify REVERT: fdf818de Add verify REVERT: aeafbf06 Minor REVERT: 5608dd28 Merge branch 'main' into per_table_recursion REVERT: 4940ac78 Remove `JUMPDEST`s REVERT: 8ee27392 Use `ceil_div_usize` for `PACKED_LEN` REVERT: a0c15ff7 Merge pull request #702 from mir-protocol/keccak_sponge_table_v2 REVERT: 5d7a2767 fix REVERT: a4667573 Minor REVERT: 93ef39f0 Logic CTL for xor REVERT: 8e08d62a Feedback REVERT: 451a6c0c Added a few derives to `Trie` types REVERT: 0a6735f3 clippy: remove unused 'peekable' REVERT: 0881799b Keccak sponge STARK REVERT: a9f14867 Added `let_chains` feature gates REVERT: ad52f029 Save columns by verifying invalid opcodes in software (#701) REVERT: f5222a9b Fill in `keccakf_u32s` REVERT: b677cd20 Transpose memory columns (make it an array of channel structs) (#700) REVERT: 8621413c Make jumps, logic, and syscalls read from/write to memory columns (#699) REVERT: 93983783 Minor REVERT: 9383f26e Minor REVERT: 8e2bd6ef First pass REVERT: f57915f3 All recursive proofs REVERT: 327454af Merge conflicts REVERT: 5984c9fd Merge branch 'main' into per_table_recursion REVERT: f0c3294c Merge pull request #696 from mir-protocol/public_memory REVERT: 481f84a5 Fix test REVERT: 2c431ebf Feedback REVERT: fe543628 Keccak generation tweak REVERT: 4c9570b9 Fix test REVERT: ebb1f0fa Public memory REVERT: a99b2549 finished hash loop REVERT: 252af382 EVM arithmetic unit: unsigned comparisons (#688) REVERT: 43b450f4 Merge pull request #669 from mir-protocol/keccak_memory REVERT: e74a7c57 all but blocks REVERT: c9497f81 Feedback REVERT: 26f4c210 Per table recursion REVERT: 497a1ecd Remove compressed proofs in EVM crate REVERT: 9ddda207 Delete opcode column (#672) REVERT: a900b662 Feedback REVERT: 3683388e Have witness generation take a partial trie instead of Merkle proofs REVERT: 002dc5af Use KECCAK_WIDTH_BYTES REVERT: 01cccf2b Fix REVERT: d3781347 Remove keccak_rust in favor of tiny-keccak REVERT: c7e84f48 Fix REVERT: 15ac5fcd Minor fixes REVERT: 39fa4ff4 Keccak memory stark REVERT: cdd6c0f2 Simpler CPU <-> memory CTL REVERT: 6ec2b1be Fix for CTL challenges REVERT: 725d2fa7 add constants REVERT: a066c245 Revert "Support accessing local row in CTLs" REVERT: 8be48ed4 all but memory REVERT: ba3e62b5 Merge pull request #682 from mir-protocol/ctl_prev REVERT: a77bc821 feedback REVERT: 2d62e375 Merge pull request #665 from mir-protocol/trie_metadata REVERT: c82e3c32 Support accessing previous row in CTLs REVERT: 3c8c0a73 Merge pull request #678 from proxima-one/fix-starky-avx REVERT: 257d699b fmt REVERT: 81b6c31a add fix to evm REVERT: 492dec32 Change logic limb size to 32 bits (#674) REVERT: e5c9dc09 Start with PC=route_txn instead of 0 REVERT: a6297321 Merge branch 'main' of github.com:mir-protocol/plonky2 into ripeMD REVERT: 064ff64b Permission levels, jumps, traps (#653) REVERT: 7a7edcd8 Merge pull request #666 from mir-protocol/rlp_encode REVERT: a2dfad50 Merge pull request #663 from mir-protocol/use_mstore_txn_field REVERT: c15e1328 Improved Keccak implementation REVERT: 895fa80a RLP encoding functions REVERT: 6744bf44 Trie related segments and metadata REVERT: 109cb8ef Add static REVERT: a91bbf34 Revert "Add static" REVERT: 04435cf1 Add static REVERT: cc5167a1 TODOs REVERT: 5faebd2f TODOs REVERT: 0aa2c794 Core transaction processing logic REVERT: fb894429 Merge pull request #662 from mir-protocol/stack_pruning_opt_perms REVERT: 7d740617 Fix shift ordering REVERT: b3eb5561 Make use of `mstore_txn_field` in type 0 parsing REVERT: 114c5326 draft implementation REVERT: 75cb630a swap shift orders REVERT: f5f9a5c4 For permutations, find the optimal sequence of swaps REVERT: ec36ba38 Merge pull request #660 from mir-protocol/packing REVERT: bdc7c373 space REVERT: 61000aaa fix merge conflict REVERT: 8de14373 merge REVERT: d6448fcf merge REVERT: 27b983aa Merge remote-tracking branch 'proxima/log-portability' REVERT: d2bdd051 Merge pull request #1 from mir-protocol/log-portability-stubs REVERT: 99c775e0 Unsuppress warnings REVERT: 6f937429 remove explicit feature include REVERT: f4f065bb Merge pull request #658 from mir-protocol/memory_stark_fix REVERT: 175f0ac5 Merge pull request #655 from mir-protocol/more_metadata REVERT: 3c0c20c8 Packing memory operations REVERT: 936299eb Support macro-local labels REVERT: 49436c0c fmt REVERT: 1b8f1450 fix REVERT: 41ca6840 newline REVERT: ca5cccaf Split up memory asm and add more helper functions REVERT: 4b3da80b Missing retdest REVERT: e2777224 Merge pull request #656 from mir-protocol/min_max REVERT: ce89e917 Fix case where a valid constant propagation a broke test REVERT: 06be898d min, max macros REVERT: 7aefc70b More metadata fields REVERT: ec41dbd3 Merge branch 'main' into optimizer REVERT: f8704fd0 Check if suggested code is actually better REVERT: d2d78f4c Feedback REVERT: 29187cdd Feedback REVERT: b1f527e4 fix REVERT: 9542d1b8 More commutative fns REVERT: 11b29ec6 Merge pull request #651 from mir-protocol/asm_constants REVERT: 0621857d Revert "UserspaceProgramCounter" REVERT: e3a4b3bb Feedback REVERT: ef1cc316 More succinct deadbeef REVERT: 1addac94 Update evm/src/cpu/kernel/global_metadata.rs REVERT: c7a96bbc Merge pull request #650 from mir-protocol/type_0_test REVERT: 48eaa87f Feedback REVERT: 88c6bc3a UserspaceProgramCounter REVERT: 438c761c More constants for kernel ASM REVERT: ac13f383 More succinct deadbeef REVERT: d1d43b9e Misc REVERT: 2cba3123 Tweak py-evm code REVERT: c70cac43 Test for parsing type 0 transactions REVERT: f67802e1 Merge pull request #649 from mir-protocol/rlp_tests REVERT: 1f8409ad More binops REVERT: 20f0f949 remove_swaps_commutative REVERT: 4345b918 remove_ignored_values REVERT: b22464eb fixes REVERT: c252adfc Feedback REVERT: 2159b71a RLP decoding tests REVERT: 82b58591 clippy REVERT: c61a5497 Misc REVERT: eff6cc8a fix REVERT: f4935a84 Some simple optimization rules REVERT: 7d5cdbff Store literals as `U256` (or `u8` for `BYTES`) REVERT: 9aa869da Allow `%stack` to work with labels REVERT: 476595ee Move couple asm files REVERT: 9963d44b Move ecrecover REVERT: e2746208 fix REVERT: ce92f8b9 fix REVERT: c435e94a Add a asm/curve/ directory REVERT: 933a0b3e add u32 macros REVERT: 58eb83f4 fix shift direction REVERT: 67380738 Merge pull request #643 from mir-protocol/interpreter_remove_stack_code REVERT: c0b448e1 Implement PANIC instruction (#644) REVERT: bd343186 update dups, write out stack states REVERT: 44423bca Merge pull request #642 from mir-protocol/type_0_fix REVERT: fca08314 Remove stack and code in interpreter REVERT: 47d94074 Merge pull request #635 from mir-protocol/nondeterministic_ec_ops REVERT: dfa430f0 Typo REVERT: 209bf7aa Increment program counter on native instructions (#641) REVERT: 994c0df5 Small fix for type 0 txns REVERT: 23549a9b profiling REVERT: 95aeab06 Merge branch 'main' into cpu_shared_cols REVERT: c12a4eba Merge branch 'main' into cpu_shared_cols REVERT: 6f5882ee Merge pull request #629 from proxima-one/maybe-rayon REVERT: 1d588062 PR feedback REVERT: 973922f5 apparently i need to update rust REVERT: 125d6ee0 allow unused mut when feature disabled REVERT: 51a8aabe add rest of files REVERT: 5c7500fc Inter-row program counter constraints (#639) REVERT: 2018ca42 subroutines draft REVERT: dce69ab5 Implement sqrt REVERT: cacb99ed Inverse for other fields REVERT: fa4d4e0b Minor REVERT: 35c1aee9 Merge branch 'main' into nondeterministic_ec_ops REVERT: f5e8c114 Merge conflicts REVERT: c7e05a88 Merge branch 'main' into interpreter_context_segments REVERT: ce3c7032 PR feedback REVERT: e9722b90 style REVERT: 23db7e4f terminology REVERT: de47e1c4 Merge branch 'main' into rlp_3 REVERT: d8ae5625 Feedback REVERT: 76e1b8c0 Minor REVERT: 3a0625e9 Add assert to range check memory values REVERT: 1d698d25 Implement mload/store_general REVERT: 98aab52b fmt REVERT: ce1ed88b use maybe_rayon in starky and evm REVERT: 88ec4bb0 Move storage asm REVERT: 88e652de Transaction (RLP) parsing REVERT: 26d32a3f Start implementing context and segments in interpreter REVERT: 4bb94f3d Collect prover inputs REVERT: 0f7e9d44 Oh Clippy... REVERT: 18fbc3be Comments REVERT: 163a4dd5 Add `run_with_kernel` fn REVERT: fa673775 Working REVERT: 7e0c5942 Implement prover input fns REVERT: efb93e1d Modify parser REVERT: 8ce7a6da Minor REVERT: 66aea9c8 Modify `inverse` asm REVERT: 60180174 A few ASM fixes REVERT: df4f1310 Merge branch 'main' into prover_input_instruction REVERT: 22cfe0cb Feedback REVERT: 1168a736 Minor REVERT: c05394ee Pruning REVERT: f583085b Stack manipulation macro REVERT: 77e0e6ab Add a `mload_kernel_code_u32` macro REVERT: fe05ff44 Merge pull request #622 from mir-protocol/memcpy REVERT: 4876ce66 Feedback REVERT: 160dcd8f `PROVER_INPUT` instruction REVERT: 7276536a Import fix REVERT: ebafb909 Merge branch 'main' into sha3_interpreter_ecrecover REVERT: 71b62667 s/sha3/keccak256 REVERT: 62ab2a5e fixes REVERT: 9c2f6f3b Implement memcpy REVERT: 7c424fa8 Enable assertions, now working REVERT: b5c11ed6 move REVERT: f2fa09fe rename REVERT: 1399024d Shared CPU columns REVERT: 94903866 Merge pull request #619 from mir-protocol/add_priviledged_opcodes REVERT: 01db4217 fix REVERT: 86136864 Merge pull request #618 from mir-protocol/asm_assertions REVERT: 29560f81 tweak REVERT: 89ba9af2 PANIC returns error REVERT: 524edcca Merge branch 'main' into add_priviledged_opcodes REVERT: 0ea22194 Fix comment REVERT: a5e221c4 Modify ecrecover tests REVERT: c8389230 SHA3 in asm REVERT: 32a94b57 SHA3 in interpreter REVERT: 27cadd7f Merge conflicts REVERT: b384cc24 Merge branch 'main' into ecrecover_kernel REVERT: 20ca7a95 PR feedback REVERT: 5254414c Merge pull request #614 from mir-protocol/evm_interpreter_memory REVERT: 04b4e070 Include assertions, disabled for now REVERT: a97ad97e Add custom opcodes REVERT: a2f8efe5 tweaks REVERT: 9b3f429d More basic ASM utility functions REVERT: c36a822c Address some feedback on #591 REVERT: 3d3532f0 Store memory values as `U256`s REVERT: 27376fb4 Organize segments in an enum REVERT: 6584c69d Merge pull request #612 from mir-protocol/bootstrapping_continued REVERT: b9025af5 Missing TODO REVERT: cf57f432 Implement memory for the interpreter REVERT: 54850d39 PR feedback REVERT: 71fbad94 Merge pull request #613 from mir-protocol/asm_rep REVERT: 23e2f3ca Add `%rep` syntax for repeating a block REVERT: e89e23bf Continue work on bootstrapping REVERT: 6fd0b1b7 Add `_base` suffix REVERT: 7732556b Comments REVERT: da09bc2d Redundant x-coord in lifting REVERT: 6a6ba7d6 More tests REVERT: cd632a24 Passing tests REVERT: 896d761c Merge branch 'main' into ecrecover_kernel REVERT: 4370234b Merge pull request #606 from mir-protocol/jumpdest_push_data REVERT: d976f8b0 Merge branch 'main' into ecrecover_kernel REVERT: 5d527b0b Add test REVERT: 855926f8 Minor fixes REVERT: 578506f6 Ecrecover until hashing REVERT: d1be33fc Merge pull request #609 from mir-protocol/row_wise_memory_gen REVERT: 87f28206 Merge pull request #608 from mir-protocol/kernel_size_logs REVERT: 4d8ea23d Generate most of the memory table while it's in row-wise form REVERT: cf30cc3a Merge pull request #607 from mir-protocol/duplicate_macros REVERT: 052fc37f Merge pull request #605 from mir-protocol/memory_misc REVERT: d1e03bd9 Have `make_kernel` log the size of each (assembled) file REVERT: bd368ad9 Start ecrecover REVERT: f19d9883 Working secp mul REVERT: 1087372f Working secp add REVERT: 01f6caa8 Avoid duplicate macros REVERT: de1a6733 Files shuffling REVERT: 089aa293 Fix jumpdest check REVERT: ec17c0a6 fix REVERT: 8e86ad33 More realistic padding rows in memory REVERT: 74570c9a Use FFTs to get subgroup evaluations in `check_constraints` REVERT: a3dce725 fix REVERT: 64573386 fix REVERT: ca28c893 Simplify memory table REVERT: 5e68d805 feedback REVERT: d167d521 fix REVERT: 5feb1a12 Merge branch 'main' into evm_generation REVERT: 2337d31c Merge pull request #602 from mir-protocol/better_ctl_error REVERT: 62587c94 PR feedback + underflow check REVERT: b8a35051 TODO REVERT: 426e8909 update REVERT: 93f67995 fmt REVERT: 75471b55 addressed comments REVERT: 59a9f493 addressed comments REVERT: 026fbc6e fix REVERT: 4feeaacf fix REVERT: fe844ca0 fixes REVERT: e1f9a7ca restored timestamp column to CTL REVERT: a10fc6ec removed debug prints REVERT: 94a0bc4e clippy REVERT: aa515686 fmt REVERT: f2d14562 another padding-row constraint fix REVERT: 2a4c09c6 updates to recursive constraints REVERT: 889c8bd3 fix: ignore padding rows in constraints REVERT: d8135082 permutation pairs REVERT: ea030c24 fix REVERT: 2fb22997 updates REVERT: 227f828c fixes REVERT: e8e6cfc5 initial change REVERT: e269acf9 Merge branch 'main' into evm_generation REVERT: d5e51024 unwrap_or_else -> unwrap_or REVERT: 8e5fde10 Comment REVERT: 827fcbca Minor REVERT: 87cb667b Comments REVERT: 9d829ae0 Better CTL error REVERT: efd960bf Allow constants to be passed from Rust into our assembly (#598) REVERT: 65ca2888 PR feedback REVERT: fd6c0f2f Merge branch 'ec_use_macro_params' into evm_interpreter REVERT: 729e53f7 More macros REVERT: 2201a789 Merge branch 'ec_use_macro_params' into evm_interpreter REVERT: dcd310c4 Test exp kernel function REVERT: 44a0d6a1 Test exp kernel function REVERT: 50d19cc7 Merge remote-tracking branch 'origin/evm_interpreter' into evm_interpreter REVERT: 6697ad2d Minor REVERT: fb2ba843 Minor REVERT: cb18b99e EC ops test REVERT: 29ce4021 Macros with arguments (#595) REVERT: 8dee53a0 EVM interpreter REVERT: 3f702963 PR feedback + comments REVERT: 06902f92 Minor REVERT: 20350ffb PR feedback REVERT: c4b6c9d8 Minor REVERT: ee9a33d9 Add zero case for mul REVERT: 0be38f62 Working ecmul REVERT: 0522e845 Merge branch 'main' into elliptic_curve_asm REVERT: e67abd87 Add check for zero point REVERT: 4f727014 Add range check REVERT: 3b99fa0a Minor REVERT: b3b95999 Minor REVERT: e5ff123d Find labels before assembly REVERT: 8142a19f Fixes REVERT: 35d7c01c Add moddiv for testing REVERT: ba9f557b Curve mul assembly REVERT: 5ff93dbe Comment REVERT: 5a0c5587 Simplify REVERT: 52c490a5 Minor REVERT: 5868026b Minor REVERT: 5cc07d1c Impl double REVERT: 6cd52302 Merge branch 'main' into elliptic_curve_asm REVERT: 9196a2d9 Add test (won't work for a while, but to illustrate) REVERT: 0885f8b5 other segments REVERT: b97d7934 fixes REVERT: cd12de93 Begin work on witness generation and kernel bootstrapping REVERT: e45a0354 Serialize zero as `[0]` rather than `[]` in bytecode (#592) REVERT: 7c35e349 First attempt REVERT: aa4db05e Structured wrapper over CPU table row (#589) REVERT: 0ed59d7f Merge pull request #587 from mir-protocol/s_columns_registers REVERT: 544fe102 NUM_REGISTERS -> NUM_COLUMNS REVERT: 59b4fb12 s/registers/columns REVERT: 9da89538 EVM Arithmetic Stark table (#559) REVERT: 5acae5cd Util for assembling EVM code to hex (#586) REVERT: 81382c37 s/columns/registers REVERT: 73fee20d Memory naming tweaks (#579) REVERT: 9d7e7859 `packed_field` -> `packed` (#584) REVERT: 5fa59a13 `field_types` -> `types` (#583) REVERT: 2eaf37bf Move CTL config out of test (#578) REVERT: de7def65 `extension_field` -> `extension` (#581) REVERT: fec86296 ASM macro support (#580) REVERT: 0927bf41 CTL: limbs (CPU) <-> bits (logic) (#577) REVERT: bf486606 Fix EVM dependency list (#576) REVERT: 583f2ef1 Exponentiation kernel function (#574) REVERT: 0e5ae7a8 updated in line with main changes REVERT: 149bb5d3 cleanup REVERT: 3750c4ce fix and cleanup REVERT: 62a6160e fixes REVERT: 164d7c52 added lookup file REVERT: bd584d15 fixes REVERT: d70402f7 attempted fix REVERT: 2e4fddf8 multiple indices per timestamp REVERT: 27f77673 fmt REVERT: e3aac863 fixes REVERT: 3cda16c3 addressed comments REVERT: 478b8aad fmt REVERT: 62de5111 addressed comments REVERT: 5cfa6008 fmt REVERT: 46883e41 fixes REVERT: 0f53fd53 fixes REVERT: 1832949e allow 'unused' functions REVERT: 3bbf82ab addressed comments REVERT: bb3d66e4 timestamp fixes REVERT: 37fd1522 removed structs REVERT: a019a80a addressed comment REVERT: 1c181ac9 okay you win, clippy REVERT: af8196fa addressed comments REVERT: 55afa354 rename REVERT: 80eacbb3 addressed comments REVERT: 9908d126 fix REVERT: 495a474b fix REVERT: b8fc37b9 updates to registers, new cross-table lookups REVERT: c87fbae5 fixes REVERT: 25e14367 all_stark REVERT: 0b7f9e38 fmt REVERT: a6b71bd0 uncommenting REVERT: 761a9b80 transpose fix REVERT: 7b1d59a8 transition constraints, and debugging REVERT: 5514899d updated all_stark framework to include memory stark (doesn't pass yet) REVERT: 5e02d882 clippy and fix REVERT: eceb0581 fmt REVERT: 4532ee9f memory row generation REVERT: 3c6159e6 cleanup REVERT: 10148f0a tests REVERT: 37df112f fmt REVERT: 20db51ba lookup argument for range check REVERT: 6268a110 redoing columns REVERT: 8b0927cc memory REVERT: cbe979d6 memory REVERT: 167f35df define columns for CTL closer to the constraints (#573) REVERT: d1908fa2 Parse and assemble kernel functions (#567) REVERT: e96b7a34 Fix check_constraints to only look at subgroup points, vs coset points (#572) REVERT: 8e33f996 Connect logic stark to CPU (#569) REVERT: 4feaeb6b Sanity check REVERT: ec688853 Fix CTL verification REVERT: a234e311 Remove redundant constraints (#568) REVERT: 403e7b4e Merge pull request #564 from mir-protocol/ctl_linear_comb REVERT: 8a519eab Fix new lints REVERT: a82c6eb0 PR feedback REVERT: b156ff0a EQ and ISZERO (#566) REVERT: 48df7a6e NOT stark (#565) REVERT: 405b48f2 Remove enum and use Option for filter REVERT: b85a1260 Minor REVERT: d2394bea Minor REVERT: a0446626 Remove Keccak input limbs REVERT: 2ddf6501 Column enum REVERT: df6a99a3 Logic stark (#562) REVERT: ddad627c Merge pull request #558 from mir-protocol/filtered_ctl REVERT: 8b5fd525 PR feedback REVERT: 08c05de4 Keccak round flags constraints REVERT: d4c99d90 Wired CPU and Keccak REVERT: dd16de57 Merge branch 'main' into filtered_ctl REVERT: f8417111 INPUT_LIMBS -> NUM_INPUTS REVERT: f9b83422 Fix REVERT: 9d9969ee Merge branch 'main' into keccak_input_registers REVERT: 89f8c491 Minor REVERT: 1945b018 Circuit fix REVERT: 3682cf0b Fix constraint REVERT: fb789aea Progress REVERT: 06d4ee11 Add Keccak input registers REVERT: 522a6de7 Merge conflicts REVERT: 1a3a23c3 Merge branch 'evm_keccak_stark' into filtered_ctl REVERT: 0341eca1 Checks REVERT: d7fb4f6b moved back haha REVERT: b65263b6 moved allow to local REVERT: 7484ce39 Add constraints REVERT: fa8c9f66 Merge branch 'main' into filtered_ctl REVERT: f74db8c3 ignore silly clippy warning REVERT: 5bf81b80 Set default to an Option REVERT: e85333e3 fix REVERT: 13ca24cb use bit operations REVERT: 7b791b2a fix REVERT: 4ace2953 fmt REVERT: 35a4e994 fix REVERT: e01e612b fix REVERT: 294557ef constants crate REVERT: f56a7b8a clippy REVERT: 32d7e92b added check on length of REVERT: 57ec6fff fix REVERT: 5194c19c fmt REVERT: e6be6b4f trying to fix CTL REVERT: 836981b0 fixed constraints, in line with generator REVERT: 985841d6 cleanup REVERT: d69d916c fmt REVERT: 2d7e701a fixes, cleanup, and correctness test REVERT: ae661a83 fmt REVERT: befabe4b fix yay REVERT: bf259151 fix REVERT: 0e44737b fmt REVERT: 57e5f656 fixes REVERT: 81fb5f44 fix REVERT: a33fd720 fix REVERT: 3e78b1e2 fix REVERT: c15a4ec6 fmt REVERT: f2fd0cdf fixes and debugging REVERT: d99a4931 included everything REVERT: 4db2b6c5 keccak stark REVERT: a87ee71c Merge pull request #556 from mir-protocol/slice_to_iter REVERT: b834d54c Change partial product REVERT: ef3a72bc Add linear combination of filter columns REVERT: 83ae1125 Add TableWithColumns struct REVERT: 678f2b7b EVM decode (#553) REVERT: 355d57b4 Change some fn to take iterators instead of slices REVERT: 3fb5682d s/right/next REVERT: 6b4c84cf Remove collect REVERT: a11dfac5 Minor REVERT: 492c79af Implement multi-table CTLs REVERT: e55d2496 Merge pull request #551 from mir-protocol/starkevm_recursive_verifier REVERT: 4b6818dd PR feedback and add `reduce_with_powers_circuit` fn REVERT: 72d34026 Clippy REVERT: 9dbc6fd9 Fix bug. Recursive verifier test passes REVERT: 027e0283 Recursive proof isn't correct (yet) REVERT: a67bef29 Add num_ctl_zs REVERT: 15704025 Merge pull request #550 from mir-protocol/stark_circuit_constraint_test REVERT: d00d0bc5 Recursive verifier test compiles but fails REVERT: 72566690 Compiles REVERT: 56a52d30 Add ctl check vars logic REVERT: 48a45f21 Start of impl REVERT: 80451206 Detupling REVERT: 270ab4d7 Ignore test instead of failing REVERT: 7354d181 Finish test REVERT: a53a7238 Make evm structs more generic REVERT: c16a193f Rename starky2 -> evm (#547) git-subtree-dir: evm_arithmetization git-subtree-split: 016b000eab8432e65fe6693ed5f416f360230734 --- .cargo/katex-header.html | 31 +- .github/dependabot.yml | 6 + .../continuous-integration-workflow.yml | 163 ++ .gitignore | 10 + Cargo.toml | 83 +- README.md | 190 +- ...olygon Zero Plonky2 Final Audit Report.pdf | Bin 0 -> 238137 bytes evm/.cargo/katex-header.html | 1 + evm/Cargo.toml | 71 + LICENSE-APACHE => evm/LICENSE-APACHE | 0 LICENSE-MIT => evm/LICENSE-MIT | 0 evm/README.md | 36 + .../benches}/stack_manipulation.rs | 0 {spec => evm/spec}/.gitignore | 0 {spec => evm/spec}/Makefile | 0 {spec => evm/spec}/bibliography.bib | 0 {spec => evm/spec}/cpulogic.tex | 0 {spec => evm/spec}/framework.tex | 0 {spec => evm/spec}/introduction.tex | 0 {spec => evm/spec}/mpts.tex | 0 {spec => evm/spec}/tables.tex | 0 {spec => evm/spec}/tables/arithmetic.tex | 0 {spec => evm/spec}/tables/byte-packing.tex | 0 {spec => evm/spec}/tables/cpu.tex | 0 {spec => evm/spec}/tables/keccak-f.tex | 0 {spec => evm/spec}/tables/keccak-sponge.tex | 0 {spec => evm/spec}/tables/logic.tex | 0 {spec => evm/spec}/tables/memory.tex | 0 {spec => evm/spec}/zkevm.pdf | Bin {spec => evm/spec}/zkevm.tex | 0 {src => evm/src}/all_stark.rs | 30 +- {src => evm/src}/arithmetic/addcy.rs | 0 .../src}/arithmetic/arithmetic_stark.rs | 19 +- {src => evm/src}/arithmetic/byte.rs | 13 +- {src => evm/src}/arithmetic/columns.rs | 3 +- {src => evm/src}/arithmetic/divmod.rs | 0 {src => evm/src}/arithmetic/mod.rs | 24 +- {src => evm/src}/arithmetic/modular.rs | 21 +- {src => evm/src}/arithmetic/mul.rs | 3 +- {src => evm/src}/arithmetic/shift.rs | 5 +- {src => evm/src}/arithmetic/utils.rs | 6 +- {src => evm/src}/bin/assemble.rs | 0 .../src}/byte_packing/byte_packing_stark.rs | 41 +- {src => evm/src}/byte_packing/columns.rs | 3 +- {src => evm/src}/byte_packing/mod.rs | 0 {src => evm/src}/cpu/byte_unpacking.rs | 6 +- {src => evm/src}/cpu/clock.rs | 0 {src => evm/src}/cpu/columns/general.rs | 36 +- {src => evm/src}/cpu/columns/mod.rs | 17 +- {src => evm/src}/cpu/columns/ops.rs | 0 {src => evm/src}/cpu/contextops.rs | 24 +- {src => evm/src}/cpu/control_flow.rs | 36 +- {src => evm/src}/cpu/cpu_stark.rs | 59 +- {src => evm/src}/cpu/decode.rs | 53 +- {src => evm/src}/cpu/dup_swap.rs | 28 +- {src => evm/src}/cpu/gas.rs | 12 +- {src => evm/src}/cpu/halt.rs | 5 +- {src => evm/src}/cpu/jumps.rs | 37 +- {src => evm/src}/cpu/kernel/aggregator.rs | 0 .../src}/cpu/kernel/asm/account_code.asm | 0 {src => evm/src}/cpu/kernel/asm/balance.asm | 0 .../src}/cpu/kernel/asm/bignum/add.asm | 0 .../src}/cpu/kernel/asm/bignum/addmul.asm | 0 .../src}/cpu/kernel/asm/bignum/cmp.asm | 0 .../src}/cpu/kernel/asm/bignum/isone.asm | 0 .../src}/cpu/kernel/asm/bignum/iszero.asm | 0 .../src}/cpu/kernel/asm/bignum/modexp.asm | 0 .../src}/cpu/kernel/asm/bignum/modmul.asm | 0 .../src}/cpu/kernel/asm/bignum/mul.asm | 0 .../src}/cpu/kernel/asm/bignum/shr.asm | 0 .../src}/cpu/kernel/asm/bignum/util.asm | 0 .../src}/cpu/kernel/asm/bloom_filter.asm | 0 .../src}/cpu/kernel/asm/core/access_lists.asm | 0 {src => evm/src}/cpu/kernel/asm/core/call.asm | 0 .../src}/cpu/kernel/asm/core/call_gas.asm | 0 .../src}/cpu/kernel/asm/core/create.asm | 0 .../cpu/kernel/asm/core/create_addresses.asm | 0 .../asm/core/create_contract_account.asm | 19 +- .../cpu/kernel/asm/core/create_receipt.asm | 0 .../src}/cpu/kernel/asm/core/exception.asm | 10 +- {src => evm/src}/cpu/kernel/asm/core/gas.asm | 0 .../cpu/kernel/asm/core/intrinsic_gas.asm | 0 .../cpu/kernel/asm/core/jumpdest_analysis.asm | 0 {src => evm/src}/cpu/kernel/asm/core/log.asm | 0 .../src}/cpu/kernel/asm/core/nonce.asm | 0 .../kernel/asm/core/precompiles/blake2_f.asm | 0 .../kernel/asm/core/precompiles/bn_add.asm | 0 .../kernel/asm/core/precompiles/bn_mul.asm | 0 .../cpu/kernel/asm/core/precompiles/ecrec.asm | 0 .../kernel/asm/core/precompiles/expmod.asm | 0 .../cpu/kernel/asm/core/precompiles/id.asm | 0 .../cpu/kernel/asm/core/precompiles/main.asm | 0 .../kernel/asm/core/precompiles/rip160.asm | 0 .../kernel/asm/core/precompiles/sha256.asm | 0 .../kernel/asm/core/precompiles/snarkv.asm | 0 .../src}/cpu/kernel/asm/core/process_txn.asm | 0 .../cpu/kernel/asm/core/selfdestruct_list.asm | 8 +- .../src}/cpu/kernel/asm/core/syscall.asm | 8 +- .../src}/cpu/kernel/asm/core/terminate.asm | 74 +- .../cpu/kernel/asm/core/touched_addresses.asm | 0 .../src}/cpu/kernel/asm/core/transfer.asm | 4 +- {src => evm/src}/cpu/kernel/asm/core/util.asm | 0 .../src}/cpu/kernel/asm/core/withdrawals.asm | 0 .../src}/cpu/kernel/asm/curve/bls381/util.asm | 0 .../bn254/curve_arithmetic/constants.asm | 0 .../bn254/curve_arithmetic/curve_add.asm | 0 .../bn254/curve_arithmetic/curve_mul.asm | 0 .../bn254/curve_arithmetic/final_exponent.asm | 0 .../asm/curve/bn254/curve_arithmetic/glv.asm | 0 .../bn254/curve_arithmetic/miller_loop.asm | 0 .../asm/curve/bn254/curve_arithmetic/msm.asm | 0 .../curve/bn254/curve_arithmetic/pairing.asm | 0 .../bn254/curve_arithmetic/precomputation.asm | 0 .../bn254/curve_arithmetic/twisted_curve.asm | 0 .../bn254/field_arithmetic/degree_12_mul.asm | 0 .../bn254/field_arithmetic/degree_6_mul.asm | 0 .../bn254/field_arithmetic/frobenius.asm | 0 .../curve/bn254/field_arithmetic/inverse.asm | 0 .../asm/curve/bn254/field_arithmetic/util.asm | 0 .../src}/cpu/kernel/asm/curve/common.asm | 0 .../kernel/asm/curve/secp256k1/curve_add.asm | 0 .../kernel/asm/curve/secp256k1/ecrecover.asm | 0 .../cpu/kernel/asm/curve/secp256k1/glv.asm | 0 .../asm/curve/secp256k1/inverse_scalar.asm | 0 .../cpu/kernel/asm/curve/secp256k1/lift_x.asm | 0 .../cpu/kernel/asm/curve/secp256k1/moddiv.asm | 0 .../asm/curve/secp256k1/precomputation.asm | 0 .../src}/cpu/kernel/asm/curve/wnaf.asm | 0 {src => evm/src}/cpu/kernel/asm/exp.asm | 0 {src => evm/src}/cpu/kernel/asm/halt.asm | 0 .../cpu/kernel/asm/hash/blake2/addresses.asm | 0 .../cpu/kernel/asm/hash/blake2/blake2_f.asm | 0 .../cpu/kernel/asm/hash/blake2/blake2b.asm | 0 .../kernel/asm/hash/blake2/compression.asm | 0 .../kernel/asm/hash/blake2/g_functions.asm | 0 .../src}/cpu/kernel/asm/hash/blake2/hash.asm | 0 .../src}/cpu/kernel/asm/hash/blake2/iv.asm | 0 .../src}/cpu/kernel/asm/hash/blake2/ops.asm | 0 .../kernel/asm/hash/blake2/permutations.asm | 0 .../src}/cpu/kernel/asm/hash/ripemd/box.asm | 0 .../kernel/asm/hash/ripemd/compression.asm | 0 .../cpu/kernel/asm/hash/ripemd/constants.asm | 0 .../cpu/kernel/asm/hash/ripemd/functions.asm | 0 .../src}/cpu/kernel/asm/hash/ripemd/main.asm | 0 .../cpu/kernel/asm/hash/ripemd/update.asm | 0 .../cpu/kernel/asm/hash/sha2/compression.asm | 0 .../cpu/kernel/asm/hash/sha2/constants.asm | 0 .../src}/cpu/kernel/asm/hash/sha2/main.asm | 0 .../kernel/asm/hash/sha2/message_schedule.asm | 0 .../src}/cpu/kernel/asm/hash/sha2/ops.asm | 0 .../cpu/kernel/asm/hash/sha2/temp_words.asm | 0 .../cpu/kernel/asm/hash/sha2/write_length.asm | 0 .../kernel/asm/journal/account_created.asm | 23 + .../kernel/asm/journal/account_destroyed.asm | 0 .../cpu/kernel/asm/journal/account_loaded.asm | 0 .../kernel/asm/journal/account_touched.asm | 0 .../kernel/asm/journal/balance_transfer.asm | 0 .../cpu/kernel/asm/journal/code_change.asm | 0 .../src}/cpu/kernel/asm/journal/journal.asm | 0 .../src}/cpu/kernel/asm/journal/log.asm | 0 .../cpu/kernel/asm/journal/nonce_change.asm | 0 .../src}/cpu/kernel/asm/journal/refund.asm | 0 .../src}/cpu/kernel/asm/journal/revert.asm | 0 .../cpu/kernel/asm/journal/storage_change.asm | 0 .../cpu/kernel/asm/journal/storage_loaded.asm | 0 {src => evm/src}/cpu/kernel/asm/main.asm | 0 .../src}/cpu/kernel/asm/memory/core.asm | 0 .../src}/cpu/kernel/asm/memory/memcpy.asm | 0 .../src}/cpu/kernel/asm/memory/memset.asm | 0 .../src}/cpu/kernel/asm/memory/metadata.asm | 13 + .../src}/cpu/kernel/asm/memory/packing.asm | 0 .../src}/cpu/kernel/asm/memory/syscalls.asm | 66 + .../src}/cpu/kernel/asm/memory/txn_fields.asm | 0 .../src}/cpu/kernel/asm/mpt/accounts.asm | 0 .../src}/cpu/kernel/asm/mpt/delete/delete.asm | 0 .../kernel/asm/mpt/delete/delete_branch.asm | 0 .../asm/mpt/delete/delete_extension.asm | 0 .../src}/cpu/kernel/asm/mpt/hash/hash.asm | 0 .../asm/mpt/hash/hash_trie_specific.asm | 0 .../src}/cpu/kernel/asm/mpt/hex_prefix.asm | 0 .../src}/cpu/kernel/asm/mpt/insert/insert.asm | 0 .../asm/mpt/insert/insert_extension.asm | 0 .../cpu/kernel/asm/mpt/insert/insert_leaf.asm | 0 .../asm/mpt/insert/insert_trie_specific.asm | 0 {src => evm/src}/cpu/kernel/asm/mpt/read.asm | 0 .../kernel/asm/mpt/storage/storage_read.asm | 0 .../kernel/asm/mpt/storage/storage_write.asm | 0 {src => evm/src}/cpu/kernel/asm/mpt/util.asm | 0 .../src}/cpu/kernel/asm/rlp/decode.asm | 0 .../src}/cpu/kernel/asm/rlp/encode.asm | 0 .../cpu/kernel/asm/rlp/encode_rlp_scalar.asm | 0 .../cpu/kernel/asm/rlp/encode_rlp_string.asm | 0 .../kernel/asm/rlp/increment_bounded_rlp.asm | 0 .../src}/cpu/kernel/asm/rlp/num_bytes.asm | 0 .../cpu/kernel/asm/rlp/read_to_memory.asm | 0 {src => evm/src}/cpu/kernel/asm/shift.asm | 0 {src => evm/src}/cpu/kernel/asm/signed.asm | 0 .../asm/transactions/common_decoding.asm | 0 .../cpu/kernel/asm/transactions/router.asm | 0 .../cpu/kernel/asm/transactions/type_0.asm | 0 .../cpu/kernel/asm/transactions/type_1.asm | 0 .../cpu/kernel/asm/transactions/type_2.asm | 0 .../src}/cpu/kernel/asm/util/assertions.asm | 0 .../src}/cpu/kernel/asm/util/basic_macros.asm | 0 .../src}/cpu/kernel/asm/util/keccak.asm | 0 {src => evm/src}/cpu/kernel/asm/util/math.asm | 0 {src => evm/src}/cpu/kernel/assembler.rs | 18 +- {src => evm/src}/cpu/kernel/ast.rs | 0 .../cpu/kernel/constants/context_metadata.rs | 16 +- .../cpu/kernel/constants/exc_bitfields.rs | 8 +- .../cpu/kernel/constants/global_metadata.rs | 35 +- .../cpu/kernel/constants/journal_entry.rs | 0 {src => evm/src}/cpu/kernel/constants/mod.rs | 3 +- .../src}/cpu/kernel/constants/trie_type.rs | 0 .../src}/cpu/kernel/constants/txn_fields.rs | 17 +- {src => evm/src}/cpu/kernel/cost_estimator.rs | 5 +- {src => evm/src}/cpu/kernel/evm_asm.pest | 0 {src => evm/src}/cpu/kernel/interpreter.rs | 104 +- {src => evm/src}/cpu/kernel/keccak_util.rs | 3 +- {src => evm/src}/cpu/kernel/mod.rs | 0 {src => evm/src}/cpu/kernel/opcodes.rs | 1 + {src => evm/src}/cpu/kernel/optimizer.rs | 19 +- {src => evm/src}/cpu/kernel/parser.rs | 0 {src => evm/src}/cpu/kernel/stack/mod.rs | 0 .../src}/cpu/kernel/stack/permutations.rs | 50 +- .../cpu/kernel/stack/stack_manipulation.rs | 36 +- .../src}/cpu/kernel/tests/account_code.rs | 16 +- {src => evm/src}/cpu/kernel/tests/add11.rs | 8 +- {src => evm/src}/cpu/kernel/tests/balance.rs | 0 .../src}/cpu/kernel/tests/bignum/mod.rs | 3 +- .../kernel/tests/bignum/test_data/add_outputs | 0 .../tests/bignum/test_data/addmul_outputs | 0 .../tests/bignum/test_data/bignum_inputs | 0 .../kernel/tests/bignum/test_data/cmp_outputs | 0 .../tests/bignum/test_data/iszero_outputs | 0 .../tests/bignum/test_data/modexp_outputs | 0 .../bignum/test_data/modexp_outputs_full | 0 .../tests/bignum/test_data/modmul_outputs | 0 .../kernel/tests/bignum/test_data/mul_outputs | 0 .../kernel/tests/bignum/test_data/shr_outputs | 0 .../kernel/tests/bignum/test_data/u128_inputs | 0 {src => evm/src}/cpu/kernel/tests/blake2_f.rs | 0 .../src}/cpu/kernel/tests/block_hash.rs | 0 {src => evm/src}/cpu/kernel/tests/bls381.rs | 0 {src => evm/src}/cpu/kernel/tests/bn254.rs | 0 .../cpu/kernel/tests/core/access_lists.rs | 0 .../cpu/kernel/tests/core/create_addresses.rs | 0 .../cpu/kernel/tests/core/intrinsic_gas.rs | 0 .../kernel/tests/core/jumpdest_analysis.rs | 6 +- {src => evm/src}/cpu/kernel/tests/core/mod.rs | 0 .../cpu/kernel/tests/ecc/bn_glv_test_data | 0 .../src}/cpu/kernel/tests/ecc/curve_ops.rs | 0 .../src}/cpu/kernel/tests/ecc/ecrecover.rs | 3 +- .../cpu/kernel/tests/ecc/ecrecover_test_data | 0 {src => evm/src}/cpu/kernel/tests/ecc/mod.rs | 0 .../cpu/kernel/tests/ecc/secp_glv_test_data | 0 {src => evm/src}/cpu/kernel/tests/exp.rs | 0 {src => evm/src}/cpu/kernel/tests/hash.rs | 10 +- .../cpu/kernel/tests/kernel_consistency.rs | 0 {src => evm/src}/cpu/kernel/tests/log.rs | 13 +- {src => evm/src}/cpu/kernel/tests/mod.rs | 0 .../src}/cpu/kernel/tests/mpt/delete.rs | 5 +- {src => evm/src}/cpu/kernel/tests/mpt/hash.rs | 0 .../src}/cpu/kernel/tests/mpt/hex_prefix.rs | 0 .../src}/cpu/kernel/tests/mpt/insert.rs | 5 +- {src => evm/src}/cpu/kernel/tests/mpt/load.rs | 0 {src => evm/src}/cpu/kernel/tests/mpt/mod.rs | 3 +- {src => evm/src}/cpu/kernel/tests/mpt/read.rs | 0 {src => evm/src}/cpu/kernel/tests/packing.rs | 0 {src => evm/src}/cpu/kernel/tests/receipt.rs | 14 +- .../src}/cpu/kernel/tests/rlp/decode.rs | 0 .../src}/cpu/kernel/tests/rlp/encode.rs | 0 {src => evm/src}/cpu/kernel/tests/rlp/mod.rs | 0 .../src}/cpu/kernel/tests/rlp/num_bytes.rs | 0 .../src}/cpu/kernel/tests/signed_syscalls.rs | 3 +- .../kernel/tests/transaction_parsing/mod.rs | 0 .../transaction_parsing/parse_type_0_txn.rs | 14 +- {src => evm/src}/cpu/kernel/utils.rs | 6 +- {src => evm/src}/cpu/membus.rs | 21 +- {src => evm/src}/cpu/memio.rs | 21 +- {src => evm/src}/cpu/mod.rs | 0 {src => evm/src}/cpu/modfp254.rs | 14 +- {src => evm/src}/cpu/pc.rs | 0 {src => evm/src}/cpu/push0.rs | 0 {src => evm/src}/cpu/shift.rs | 0 .../src}/cpu/simple_logic/eq_iszero.rs | 34 +- {src => evm/src}/cpu/simple_logic/mod.rs | 0 {src => evm/src}/cpu/simple_logic/not.rs | 0 {src => evm/src}/cpu/stack.rs | 60 +- {src => evm/src}/cpu/syscalls_exceptions.rs | 12 +- {src => evm/src}/curve_pairings.rs | 6 +- {src => evm/src}/extension_tower.rs | 25 +- {src => evm/src}/fixed_recursive_verifier.rs | 272 +- {src => evm/src}/generation/mod.rs | 54 +- {src => evm/src}/generation/mpt.rs | 0 {src => evm/src}/generation/prover_input.rs | 24 +- {src => evm/src}/generation/rlp.rs | 0 {src => evm/src}/generation/state.rs | 44 +- {src => evm/src}/generation/trie_extractor.rs | 9 +- {src => evm/src}/get_challenges.rs | 4 + {src => evm/src}/keccak/columns.rs | 0 {src => evm/src}/keccak/constants.rs | 0 {src => evm/src}/keccak/keccak_stark.rs | 5 +- {src => evm/src}/keccak/logic.rs | 0 {src => evm/src}/keccak/mod.rs | 0 {src => evm/src}/keccak/round_flags.rs | 0 {src => evm/src}/keccak_sponge/columns.rs | 40 +- .../src}/keccak_sponge/keccak_sponge_stark.rs | 98 +- evm/src/keccak_sponge/mod.rs | 6 + {src => evm/src}/lib.rs | 107 +- {src => evm/src}/logic.rs | 32 +- {src => evm/src}/memory/columns.rs | 11 +- {src => evm/src}/memory/memory_stark.rs | 119 +- {src => evm/src}/memory/mod.rs | 10 +- {src => evm/src}/memory/segments.rs | 32 +- {src => evm/src}/proof.rs | 125 +- {src => evm/src}/prover.rs | 20 +- {src => evm/src}/recursive_verifier.rs | 23 +- {src => evm/src}/util.rs | 21 +- {src => evm/src}/verifier.rs | 18 +- {src => evm/src}/witness/errors.rs | 0 {src => evm/src}/witness/gas.rs | 0 {src => evm/src}/witness/memory.rs | 7 +- {src => evm/src}/witness/mod.rs | 0 {src => evm/src}/witness/operation.rs | 50 +- {src => evm/src}/witness/state.rs | 0 {src => evm/src}/witness/traces.rs | 0 {src => evm/src}/witness/transition.rs | 21 +- {src => evm/src}/witness/util.rs | 6 +- {tests => evm/tests}/add11_yml.rs | 1 + {tests => evm/tests}/basic_smart_contract.rs | 1 + {tests => evm/tests}/empty_txn_list.rs | 6 +- {tests => evm/tests}/erc20.rs | 1 + {tests => evm/tests}/erc721.rs | 6 +- {tests => evm/tests}/log_opcode.rs | 40 +- {tests => evm/tests}/self_balance_gas_cost.rs | 1 + {tests => evm/tests}/selfdestruct.rs | 13 +- {tests => evm/tests}/simple_transfer.rs | 1 + {tests => evm/tests}/withdrawals.rs | 0 field/.cargo/katex-header.html | 1 + field/Cargo.toml | 21 + field/LICENSE-APACHE | 202 ++ field/LICENSE-MIT | 21 + field/README.md | 13 + field/src/arch/mod.rs | 2 + .../src/arch/x86_64/avx2_goldilocks_field.rs | 689 +++++ .../arch/x86_64/avx512_goldilocks_field.rs | 650 +++++ field/src/arch/x86_64/mod.rs | 20 + field/src/batch_util.rs | 65 + field/src/cosets.rs | 55 + field/src/extension/algebra.rs | 295 +++ field/src/extension/mod.rs | 150 ++ field/src/extension/quadratic.rs | 252 ++ field/src/extension/quartic.rs | 278 ++ field/src/extension/quintic.rs | 292 +++ field/src/fft.rs | 288 +++ field/src/field_testing.rs | 206 ++ field/src/goldilocks_extensions.rs | 497 ++++ field/src/goldilocks_field.rs | 468 ++++ field/src/interpolation.rs | 149 ++ field/src/lib.rs | 33 + field/src/ops.rs | 11 + field/src/packable.rs | 41 + field/src/packed.rs | 128 + field/src/polynomial/division.rs | 161 ++ field/src/polynomial/mod.rs | 677 +++++ field/src/prime_field_testing.rs | 183 ++ field/src/secp256k1_base.rs | 271 ++ field/src/secp256k1_scalar.rs | 279 ++ field/src/types.rs | 610 +++++ field/src/zero_poly_coset.rs | 62 + maybe_rayon/.cargo/katex-header.html | 1 + maybe_rayon/Cargo.toml | 16 + maybe_rayon/LICENSE-APACHE | 202 ++ maybe_rayon/LICENSE-MIT | 21 + maybe_rayon/README.md | 13 + maybe_rayon/src/lib.rs | 282 +++ pgo-profile.sh | 31 + plonky2/.cargo/katex-header.html | 1 + plonky2/Cargo.toml | 85 + plonky2/LICENSE-APACHE | 202 ++ plonky2/LICENSE-MIT | 21 + plonky2/README.md | 20 + plonky2/benches/allocator/mod.rs | 7 + plonky2/benches/ffts.rs | 46 + plonky2/benches/field_arithmetic.rs | 182 ++ plonky2/benches/hashing.rs | 41 + plonky2/benches/merkle.rs | 37 + plonky2/benches/reverse_index_bits.rs | 32 + plonky2/benches/transpose.rs | 29 + plonky2/examples/bench_recursion.rs | 430 ++++ plonky2/examples/factorial.rs | 44 + plonky2/examples/fibonacci.rs | 51 + plonky2/examples/fibonacci_serialization.rs | 69 + plonky2/examples/range_check.rs | 37 + plonky2/examples/square_root.rs | 154 ++ plonky2/plonky2.pdf | Bin 0 -> 236419 bytes plonky2/src/bin/generate_constants.rs | 31 + plonky2/src/fri/challenges.rs | 113 + plonky2/src/fri/mod.rs | 116 + plonky2/src/fri/oracle.rs | 239 ++ plonky2/src/fri/proof.rs | 390 +++ plonky2/src/fri/prover.rs | 224 ++ plonky2/src/fri/recursive_verifier.rs | 489 ++++ plonky2/src/fri/reduction_strategies.rs | 168 ++ plonky2/src/fri/structure.rs | 90 + plonky2/src/fri/validate_shape.rs | 67 + plonky2/src/fri/verifier.rs | 264 ++ plonky2/src/fri/witness_util.rs | 72 + plonky2/src/gadgets/arithmetic.rs | 441 ++++ plonky2/src/gadgets/arithmetic_extension.rs | 715 ++++++ plonky2/src/gadgets/hash.rs | 26 + plonky2/src/gadgets/interpolation.rs | 115 + plonky2/src/gadgets/lookup.rs | 169 ++ plonky2/src/gadgets/mod.rs | 15 + plonky2/src/gadgets/polynomial.rs | 93 + plonky2/src/gadgets/random_access.rs | 150 ++ plonky2/src/gadgets/range_check.rs | 105 + plonky2/src/gadgets/select.rs | 82 + plonky2/src/gadgets/split_base.rs | 202 ++ plonky2/src/gadgets/split_join.rs | 167 ++ plonky2/src/gates/arithmetic_base.rs | 274 ++ plonky2/src/gates/arithmetic_extension.rs | 269 ++ plonky2/src/gates/base_sum.rs | 246 ++ plonky2/src/gates/constant.rs | 158 ++ plonky2/src/gates/coset_interpolation.rs | 881 +++++++ plonky2/src/gates/exponentiation.rs | 429 ++++ plonky2/src/gates/gate.rs | 372 +++ plonky2/src/gates/gate_testing.rs | 164 ++ plonky2/src/gates/lookup.rs | 241 ++ plonky2/src/gates/lookup_table.rs | 260 ++ plonky2/src/gates/mod.rs | 54 + plonky2/src/gates/multiplication_extension.rs | 236 ++ plonky2/src/gates/noop.rs | 90 + plonky2/src/gates/packed_util.rs | 43 + plonky2/src/gates/poseidon.rs | 641 +++++ plonky2/src/gates/poseidon_mds.rs | 296 +++ plonky2/src/gates/public_input.rs | 134 + plonky2/src/gates/random_access.rs | 537 ++++ plonky2/src/gates/reducing.rs | 267 ++ plonky2/src/gates/reducing_extension.rs | 262 ++ plonky2/src/gates/selectors.rs | 194 ++ plonky2/src/gates/util.rs | 66 + plonky2/src/hash/arch/aarch64/mod.rs | 2 + .../arch/aarch64/poseidon_goldilocks_neon.rs | 969 +++++++ plonky2/src/hash/arch/aarch64/readme-asm.md | 495 ++++ plonky2/src/hash/arch/mod.rs | 5 + plonky2/src/hash/arch/x86_64/mod.rs | 5 + .../x86_64/poseidon_goldilocks_avx2_bmi2.rs | 981 +++++++ plonky2/src/hash/hash_types.rs | 212 ++ plonky2/src/hash/hashing.rs | 148 ++ plonky2/src/hash/keccak.rs | 127 + plonky2/src/hash/merkle_proofs.rs | 240 ++ plonky2/src/hash/merkle_tree.rs | 306 +++ plonky2/src/hash/mod.rs | 12 + plonky2/src/hash/path_compression.rs | 164 ++ plonky2/src/hash/poseidon.rs | 791 ++++++ plonky2/src/hash/poseidon_crandall.rs | 1 + plonky2/src/hash/poseidon_goldilocks.rs | 502 ++++ plonky2/src/iop/challenger.rs | 381 +++ plonky2/src/iop/ext_target.rs | 159 ++ plonky2/src/iop/generator.rs | 424 ++++ plonky2/src/iop/mod.rs | 8 + plonky2/src/iop/target.rs | 93 + plonky2/src/iop/wire.rs | 31 + plonky2/src/iop/witness.rs | 368 +++ plonky2/src/lib.rs | 22 + plonky2/src/lookup_test.rs | 526 ++++ plonky2/src/plonk/circuit_builder.rs | 1336 ++++++++++ plonky2/src/plonk/circuit_data.rs | 693 +++++ plonky2/src/plonk/config.rs | 129 + plonky2/src/plonk/copy_constraint.rs | 25 + plonky2/src/plonk/get_challenges.rs | 371 +++ plonky2/src/plonk/mod.rs | 19 + plonky2/src/plonk/permutation_argument.rs | 161 ++ plonky2/src/plonk/plonk_common.rs | 159 ++ plonky2/src/plonk/proof.rs | 568 +++++ plonky2/src/plonk/prover.rs | 820 ++++++ plonky2/src/plonk/validate_shape.rs | 72 + plonky2/src/plonk/vanishing_poly.rs | 1153 +++++++++ plonky2/src/plonk/vars.rs | 238 ++ plonky2/src/plonk/verifier.rs | 122 + .../conditional_recursive_verifier.rs | 412 +++ plonky2/src/recursion/cyclic_recursion.rs | 386 +++ plonky2/src/recursion/dummy_circuit.rs | 262 ++ plonky2/src/recursion/mod.rs | 10 + plonky2/src/recursion/recursive_verifier.rs | 727 ++++++ plonky2/src/util/context_tree.rs | 149 ++ plonky2/src/util/mod.rs | 149 ++ plonky2/src/util/partial_products.rs | 156 ++ plonky2/src/util/reducing.rs | 368 +++ .../util/serialization/gate_serialization.rs | 139 + .../serialization/generator_serialization.rs | 168 ++ plonky2/src/util/serialization/mod.rs | 2247 +++++++++++++++++ plonky2/src/util/strided_view.rs | 327 +++ plonky2/src/util/timing.rs | 193 ++ projects/cache-friendly-fft/__init__.py | 229 ++ projects/cache-friendly-fft/transpose.py | 61 + projects/cache-friendly-fft/util.py | 6 + rust-toolchain | 1 + rustfmt.toml | 3 + .../kernel/asm/journal/account_created.asm | 13 - src/keccak_sponge/mod.rs | 6 - starky/.cargo/katex-header.html | 1 + starky/Cargo.toml | 35 + starky/LICENSE-APACHE | 202 ++ starky/LICENSE-MIT | 21 + starky/README.md | 13 + starky/src/config.rs | 151 ++ starky/src/constraint_consumer.rs | 184 ++ starky/src/cross_table_lookup.rs | 1201 +++++++++ starky/src/evaluation_frame.rs | 74 + starky/src/fibonacci_stark.rs | 444 ++++ starky/src/get_challenges.rs | 415 +++ starky/src/lib.rs | 342 +++ starky/src/lookup.rs | 1042 ++++++++ starky/src/proof.rs | 475 ++++ starky/src/prover.rs | 661 +++++ starky/src/recursive_verifier.rs | 390 +++ starky/src/stark.rs | 271 ++ starky/src/stark_testing.rs | 158 ++ starky/src/util.rs | 22 + starky/src/vanishing_poly.rs | 85 + starky/src/verifier.rs | 353 +++ util/.cargo/katex-header.html | 1 + util/Cargo.toml | 13 + util/LICENSE-APACHE | 202 ++ util/LICENSE-MIT | 21 + util/src/lib.rs | 411 +++ util/src/transpose_util.rs | 116 + 530 files changed, 48049 insertions(+), 1028 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/continuous-integration-workflow.yml create mode 100644 .gitignore create mode 100644 audits/Least Authority - Polygon Zero Plonky2 Final Audit Report.pdf create mode 100644 evm/.cargo/katex-header.html create mode 100644 evm/Cargo.toml rename LICENSE-APACHE => evm/LICENSE-APACHE (100%) rename LICENSE-MIT => evm/LICENSE-MIT (100%) create mode 100644 evm/README.md rename {benches => evm/benches}/stack_manipulation.rs (100%) rename {spec => evm/spec}/.gitignore (100%) rename {spec => evm/spec}/Makefile (100%) rename {spec => evm/spec}/bibliography.bib (100%) rename {spec => evm/spec}/cpulogic.tex (100%) rename {spec => evm/spec}/framework.tex (100%) rename {spec => evm/spec}/introduction.tex (100%) rename {spec => evm/spec}/mpts.tex (100%) rename {spec => evm/spec}/tables.tex (100%) rename {spec => evm/spec}/tables/arithmetic.tex (100%) rename {spec => evm/spec}/tables/byte-packing.tex (100%) rename {spec => evm/spec}/tables/cpu.tex (100%) rename {spec => evm/spec}/tables/keccak-f.tex (100%) rename {spec => evm/spec}/tables/keccak-sponge.tex (100%) rename {spec => evm/spec}/tables/logic.tex (100%) rename {spec => evm/spec}/tables/memory.tex (100%) rename {spec => evm/spec}/zkevm.pdf (100%) rename {spec => evm/spec}/zkevm.tex (100%) rename {src => evm/src}/all_stark.rs (95%) rename {src => evm/src}/arithmetic/addcy.rs (100%) rename {src => evm/src}/arithmetic/arithmetic_stark.rs (98%) rename {src => evm/src}/arithmetic/byte.rs (98%) rename {src => evm/src}/arithmetic/columns.rs (99%) rename {src => evm/src}/arithmetic/divmod.rs (100%) rename {src => evm/src}/arithmetic/mod.rs (97%) rename {src => evm/src}/arithmetic/modular.rs (98%) rename {src => evm/src}/arithmetic/mul.rs (99%) rename {src => evm/src}/arithmetic/shift.rs (98%) rename {src => evm/src}/arithmetic/utils.rs (99%) rename {src => evm/src}/bin/assemble.rs (100%) rename {src => evm/src}/byte_packing/byte_packing_stark.rs (93%) rename {src => evm/src}/byte_packing/columns.rs (98%) rename {src => evm/src}/byte_packing/mod.rs (100%) rename {src => evm/src}/cpu/byte_unpacking.rs (96%) rename {src => evm/src}/cpu/clock.rs (100%) rename {src => evm/src}/cpu/columns/general.rs (88%) rename {src => evm/src}/cpu/columns/mod.rs (96%) rename {src => evm/src}/cpu/columns/ops.rs (100%) rename {src => evm/src}/cpu/contextops.rs (96%) rename {src => evm/src}/cpu/control_flow.rs (87%) rename {src => evm/src}/cpu/cpu_stark.rs (95%) rename {src => evm/src}/cpu/decode.rs (92%) rename {src => evm/src}/cpu/dup_swap.rs (96%) rename {src => evm/src}/cpu/gas.rs (98%) rename {src => evm/src}/cpu/halt.rs (97%) rename {src => evm/src}/cpu/jumps.rs (94%) rename {src => evm/src}/cpu/kernel/aggregator.rs (100%) rename {src => evm/src}/cpu/kernel/asm/account_code.asm (100%) rename {src => evm/src}/cpu/kernel/asm/balance.asm (100%) rename {src => evm/src}/cpu/kernel/asm/bignum/add.asm (100%) rename {src => evm/src}/cpu/kernel/asm/bignum/addmul.asm (100%) rename {src => evm/src}/cpu/kernel/asm/bignum/cmp.asm (100%) rename {src => evm/src}/cpu/kernel/asm/bignum/isone.asm (100%) rename {src => evm/src}/cpu/kernel/asm/bignum/iszero.asm (100%) rename {src => evm/src}/cpu/kernel/asm/bignum/modexp.asm (100%) rename {src => evm/src}/cpu/kernel/asm/bignum/modmul.asm (100%) rename {src => evm/src}/cpu/kernel/asm/bignum/mul.asm (100%) rename {src => evm/src}/cpu/kernel/asm/bignum/shr.asm (100%) rename {src => evm/src}/cpu/kernel/asm/bignum/util.asm (100%) rename {src => evm/src}/cpu/kernel/asm/bloom_filter.asm (100%) rename {src => evm/src}/cpu/kernel/asm/core/access_lists.asm (100%) rename {src => evm/src}/cpu/kernel/asm/core/call.asm (100%) rename {src => evm/src}/cpu/kernel/asm/core/call_gas.asm (100%) rename {src => evm/src}/cpu/kernel/asm/core/create.asm (100%) rename {src => evm/src}/cpu/kernel/asm/core/create_addresses.asm (100%) rename {src => evm/src}/cpu/kernel/asm/core/create_contract_account.asm (79%) rename {src => evm/src}/cpu/kernel/asm/core/create_receipt.asm (100%) rename {src => evm/src}/cpu/kernel/asm/core/exception.asm (98%) rename {src => evm/src}/cpu/kernel/asm/core/gas.asm (100%) rename {src => evm/src}/cpu/kernel/asm/core/intrinsic_gas.asm (100%) rename {src => evm/src}/cpu/kernel/asm/core/jumpdest_analysis.asm (100%) rename {src => evm/src}/cpu/kernel/asm/core/log.asm (100%) rename {src => evm/src}/cpu/kernel/asm/core/nonce.asm (100%) rename {src => evm/src}/cpu/kernel/asm/core/precompiles/blake2_f.asm (100%) rename {src => evm/src}/cpu/kernel/asm/core/precompiles/bn_add.asm (100%) rename {src => evm/src}/cpu/kernel/asm/core/precompiles/bn_mul.asm (100%) rename {src => evm/src}/cpu/kernel/asm/core/precompiles/ecrec.asm (100%) rename {src => evm/src}/cpu/kernel/asm/core/precompiles/expmod.asm (100%) rename {src => evm/src}/cpu/kernel/asm/core/precompiles/id.asm (100%) rename {src => evm/src}/cpu/kernel/asm/core/precompiles/main.asm (100%) rename {src => evm/src}/cpu/kernel/asm/core/precompiles/rip160.asm (100%) rename {src => evm/src}/cpu/kernel/asm/core/precompiles/sha256.asm (100%) rename {src => evm/src}/cpu/kernel/asm/core/precompiles/snarkv.asm (100%) rename {src => evm/src}/cpu/kernel/asm/core/process_txn.asm (100%) rename {src => evm/src}/cpu/kernel/asm/core/selfdestruct_list.asm (94%) rename {src => evm/src}/cpu/kernel/asm/core/syscall.asm (96%) rename {src => evm/src}/cpu/kernel/asm/core/terminate.asm (76%) rename {src => evm/src}/cpu/kernel/asm/core/touched_addresses.asm (100%) rename {src => evm/src}/cpu/kernel/asm/core/transfer.asm (97%) rename {src => evm/src}/cpu/kernel/asm/core/util.asm (100%) rename {src => evm/src}/cpu/kernel/asm/core/withdrawals.asm (100%) rename {src => evm/src}/cpu/kernel/asm/curve/bls381/util.asm (100%) rename {src => evm/src}/cpu/kernel/asm/curve/bn254/curve_arithmetic/constants.asm (100%) rename {src => evm/src}/cpu/kernel/asm/curve/bn254/curve_arithmetic/curve_add.asm (100%) rename {src => evm/src}/cpu/kernel/asm/curve/bn254/curve_arithmetic/curve_mul.asm (100%) rename {src => evm/src}/cpu/kernel/asm/curve/bn254/curve_arithmetic/final_exponent.asm (100%) rename {src => evm/src}/cpu/kernel/asm/curve/bn254/curve_arithmetic/glv.asm (100%) rename {src => evm/src}/cpu/kernel/asm/curve/bn254/curve_arithmetic/miller_loop.asm (100%) rename {src => evm/src}/cpu/kernel/asm/curve/bn254/curve_arithmetic/msm.asm (100%) rename {src => evm/src}/cpu/kernel/asm/curve/bn254/curve_arithmetic/pairing.asm (100%) rename {src => evm/src}/cpu/kernel/asm/curve/bn254/curve_arithmetic/precomputation.asm (100%) rename {src => evm/src}/cpu/kernel/asm/curve/bn254/curve_arithmetic/twisted_curve.asm (100%) rename {src => evm/src}/cpu/kernel/asm/curve/bn254/field_arithmetic/degree_12_mul.asm (100%) rename {src => evm/src}/cpu/kernel/asm/curve/bn254/field_arithmetic/degree_6_mul.asm (100%) rename {src => evm/src}/cpu/kernel/asm/curve/bn254/field_arithmetic/frobenius.asm (100%) rename {src => evm/src}/cpu/kernel/asm/curve/bn254/field_arithmetic/inverse.asm (100%) rename {src => evm/src}/cpu/kernel/asm/curve/bn254/field_arithmetic/util.asm (100%) rename {src => evm/src}/cpu/kernel/asm/curve/common.asm (100%) rename {src => evm/src}/cpu/kernel/asm/curve/secp256k1/curve_add.asm (100%) rename {src => evm/src}/cpu/kernel/asm/curve/secp256k1/ecrecover.asm (100%) rename {src => evm/src}/cpu/kernel/asm/curve/secp256k1/glv.asm (100%) rename {src => evm/src}/cpu/kernel/asm/curve/secp256k1/inverse_scalar.asm (100%) rename {src => evm/src}/cpu/kernel/asm/curve/secp256k1/lift_x.asm (100%) rename {src => evm/src}/cpu/kernel/asm/curve/secp256k1/moddiv.asm (100%) rename {src => evm/src}/cpu/kernel/asm/curve/secp256k1/precomputation.asm (100%) rename {src => evm/src}/cpu/kernel/asm/curve/wnaf.asm (100%) rename {src => evm/src}/cpu/kernel/asm/exp.asm (100%) rename {src => evm/src}/cpu/kernel/asm/halt.asm (100%) rename {src => evm/src}/cpu/kernel/asm/hash/blake2/addresses.asm (100%) rename {src => evm/src}/cpu/kernel/asm/hash/blake2/blake2_f.asm (100%) rename {src => evm/src}/cpu/kernel/asm/hash/blake2/blake2b.asm (100%) rename {src => evm/src}/cpu/kernel/asm/hash/blake2/compression.asm (100%) rename {src => evm/src}/cpu/kernel/asm/hash/blake2/g_functions.asm (100%) rename {src => evm/src}/cpu/kernel/asm/hash/blake2/hash.asm (100%) rename {src => evm/src}/cpu/kernel/asm/hash/blake2/iv.asm (100%) rename {src => evm/src}/cpu/kernel/asm/hash/blake2/ops.asm (100%) rename {src => evm/src}/cpu/kernel/asm/hash/blake2/permutations.asm (100%) rename {src => evm/src}/cpu/kernel/asm/hash/ripemd/box.asm (100%) rename {src => evm/src}/cpu/kernel/asm/hash/ripemd/compression.asm (100%) rename {src => evm/src}/cpu/kernel/asm/hash/ripemd/constants.asm (100%) rename {src => evm/src}/cpu/kernel/asm/hash/ripemd/functions.asm (100%) rename {src => evm/src}/cpu/kernel/asm/hash/ripemd/main.asm (100%) rename {src => evm/src}/cpu/kernel/asm/hash/ripemd/update.asm (100%) rename {src => evm/src}/cpu/kernel/asm/hash/sha2/compression.asm (100%) rename {src => evm/src}/cpu/kernel/asm/hash/sha2/constants.asm (100%) rename {src => evm/src}/cpu/kernel/asm/hash/sha2/main.asm (100%) rename {src => evm/src}/cpu/kernel/asm/hash/sha2/message_schedule.asm (100%) rename {src => evm/src}/cpu/kernel/asm/hash/sha2/ops.asm (100%) rename {src => evm/src}/cpu/kernel/asm/hash/sha2/temp_words.asm (100%) rename {src => evm/src}/cpu/kernel/asm/hash/sha2/write_length.asm (100%) create mode 100644 evm/src/cpu/kernel/asm/journal/account_created.asm rename {src => evm/src}/cpu/kernel/asm/journal/account_destroyed.asm (100%) rename {src => evm/src}/cpu/kernel/asm/journal/account_loaded.asm (100%) rename {src => evm/src}/cpu/kernel/asm/journal/account_touched.asm (100%) rename {src => evm/src}/cpu/kernel/asm/journal/balance_transfer.asm (100%) rename {src => evm/src}/cpu/kernel/asm/journal/code_change.asm (100%) rename {src => evm/src}/cpu/kernel/asm/journal/journal.asm (100%) rename {src => evm/src}/cpu/kernel/asm/journal/log.asm (100%) rename {src => evm/src}/cpu/kernel/asm/journal/nonce_change.asm (100%) rename {src => evm/src}/cpu/kernel/asm/journal/refund.asm (100%) rename {src => evm/src}/cpu/kernel/asm/journal/revert.asm (100%) rename {src => evm/src}/cpu/kernel/asm/journal/storage_change.asm (100%) rename {src => evm/src}/cpu/kernel/asm/journal/storage_loaded.asm (100%) rename {src => evm/src}/cpu/kernel/asm/main.asm (100%) rename {src => evm/src}/cpu/kernel/asm/memory/core.asm (100%) rename {src => evm/src}/cpu/kernel/asm/memory/memcpy.asm (100%) rename {src => evm/src}/cpu/kernel/asm/memory/memset.asm (100%) rename {src => evm/src}/cpu/kernel/asm/memory/metadata.asm (97%) rename {src => evm/src}/cpu/kernel/asm/memory/packing.asm (100%) rename {src => evm/src}/cpu/kernel/asm/memory/syscalls.asm (73%) rename {src => evm/src}/cpu/kernel/asm/memory/txn_fields.asm (100%) rename {src => evm/src}/cpu/kernel/asm/mpt/accounts.asm (100%) rename {src => evm/src}/cpu/kernel/asm/mpt/delete/delete.asm (100%) rename {src => evm/src}/cpu/kernel/asm/mpt/delete/delete_branch.asm (100%) rename {src => evm/src}/cpu/kernel/asm/mpt/delete/delete_extension.asm (100%) rename {src => evm/src}/cpu/kernel/asm/mpt/hash/hash.asm (100%) rename {src => evm/src}/cpu/kernel/asm/mpt/hash/hash_trie_specific.asm (100%) rename {src => evm/src}/cpu/kernel/asm/mpt/hex_prefix.asm (100%) rename {src => evm/src}/cpu/kernel/asm/mpt/insert/insert.asm (100%) rename {src => evm/src}/cpu/kernel/asm/mpt/insert/insert_extension.asm (100%) rename {src => evm/src}/cpu/kernel/asm/mpt/insert/insert_leaf.asm (100%) rename {src => evm/src}/cpu/kernel/asm/mpt/insert/insert_trie_specific.asm (100%) rename {src => evm/src}/cpu/kernel/asm/mpt/read.asm (100%) rename {src => evm/src}/cpu/kernel/asm/mpt/storage/storage_read.asm (100%) rename {src => evm/src}/cpu/kernel/asm/mpt/storage/storage_write.asm (100%) rename {src => evm/src}/cpu/kernel/asm/mpt/util.asm (100%) rename {src => evm/src}/cpu/kernel/asm/rlp/decode.asm (100%) rename {src => evm/src}/cpu/kernel/asm/rlp/encode.asm (100%) rename {src => evm/src}/cpu/kernel/asm/rlp/encode_rlp_scalar.asm (100%) rename {src => evm/src}/cpu/kernel/asm/rlp/encode_rlp_string.asm (100%) rename {src => evm/src}/cpu/kernel/asm/rlp/increment_bounded_rlp.asm (100%) rename {src => evm/src}/cpu/kernel/asm/rlp/num_bytes.asm (100%) rename {src => evm/src}/cpu/kernel/asm/rlp/read_to_memory.asm (100%) rename {src => evm/src}/cpu/kernel/asm/shift.asm (100%) rename {src => evm/src}/cpu/kernel/asm/signed.asm (100%) rename {src => evm/src}/cpu/kernel/asm/transactions/common_decoding.asm (100%) rename {src => evm/src}/cpu/kernel/asm/transactions/router.asm (100%) rename {src => evm/src}/cpu/kernel/asm/transactions/type_0.asm (100%) rename {src => evm/src}/cpu/kernel/asm/transactions/type_1.asm (100%) rename {src => evm/src}/cpu/kernel/asm/transactions/type_2.asm (100%) rename {src => evm/src}/cpu/kernel/asm/util/assertions.asm (100%) rename {src => evm/src}/cpu/kernel/asm/util/basic_macros.asm (100%) rename {src => evm/src}/cpu/kernel/asm/util/keccak.asm (100%) rename {src => evm/src}/cpu/kernel/asm/util/math.asm (100%) rename {src => evm/src}/cpu/kernel/assembler.rs (98%) rename {src => evm/src}/cpu/kernel/ast.rs (100%) rename {src => evm/src}/cpu/kernel/constants/context_metadata.rs (90%) rename {src => evm/src}/cpu/kernel/constants/exc_bitfields.rs (85%) rename {src => evm/src}/cpu/kernel/constants/global_metadata.rs (90%) rename {src => evm/src}/cpu/kernel/constants/journal_entry.rs (100%) rename {src => evm/src}/cpu/kernel/constants/mod.rs (99%) rename {src => evm/src}/cpu/kernel/constants/trie_type.rs (100%) rename {src => evm/src}/cpu/kernel/constants/txn_fields.rs (92%) rename {src => evm/src}/cpu/kernel/cost_estimator.rs (90%) rename {src => evm/src}/cpu/kernel/evm_asm.pest (100%) rename {src => evm/src}/cpu/kernel/interpreter.rs (96%) rename {src => evm/src}/cpu/kernel/keccak_util.rs (99%) rename {src => evm/src}/cpu/kernel/mod.rs (100%) rename {src => evm/src}/cpu/kernel/opcodes.rs (99%) rename {src => evm/src}/cpu/kernel/optimizer.rs (96%) rename {src => evm/src}/cpu/kernel/parser.rs (100%) rename {src => evm/src}/cpu/kernel/stack/mod.rs (100%) rename {src => evm/src}/cpu/kernel/stack/permutations.rs (90%) rename {src => evm/src}/cpu/kernel/stack/stack_manipulation.rs (94%) rename {src => evm/src}/cpu/kernel/tests/account_code.rs (98%) rename {src => evm/src}/cpu/kernel/tests/add11.rs (98%) rename {src => evm/src}/cpu/kernel/tests/balance.rs (100%) rename {src => evm/src}/cpu/kernel/tests/bignum/mod.rs (99%) rename {src => evm/src}/cpu/kernel/tests/bignum/test_data/add_outputs (100%) rename {src => evm/src}/cpu/kernel/tests/bignum/test_data/addmul_outputs (100%) rename {src => evm/src}/cpu/kernel/tests/bignum/test_data/bignum_inputs (100%) rename {src => evm/src}/cpu/kernel/tests/bignum/test_data/cmp_outputs (100%) rename {src => evm/src}/cpu/kernel/tests/bignum/test_data/iszero_outputs (100%) rename {src => evm/src}/cpu/kernel/tests/bignum/test_data/modexp_outputs (100%) rename {src => evm/src}/cpu/kernel/tests/bignum/test_data/modexp_outputs_full (100%) rename {src => evm/src}/cpu/kernel/tests/bignum/test_data/modmul_outputs (100%) rename {src => evm/src}/cpu/kernel/tests/bignum/test_data/mul_outputs (100%) rename {src => evm/src}/cpu/kernel/tests/bignum/test_data/shr_outputs (100%) rename {src => evm/src}/cpu/kernel/tests/bignum/test_data/u128_inputs (100%) rename {src => evm/src}/cpu/kernel/tests/blake2_f.rs (100%) rename {src => evm/src}/cpu/kernel/tests/block_hash.rs (100%) rename {src => evm/src}/cpu/kernel/tests/bls381.rs (100%) rename {src => evm/src}/cpu/kernel/tests/bn254.rs (100%) rename {src => evm/src}/cpu/kernel/tests/core/access_lists.rs (100%) rename {src => evm/src}/cpu/kernel/tests/core/create_addresses.rs (100%) rename {src => evm/src}/cpu/kernel/tests/core/intrinsic_gas.rs (100%) rename {src => evm/src}/cpu/kernel/tests/core/jumpdest_analysis.rs (98%) rename {src => evm/src}/cpu/kernel/tests/core/mod.rs (100%) rename {src => evm/src}/cpu/kernel/tests/ecc/bn_glv_test_data (100%) rename {src => evm/src}/cpu/kernel/tests/ecc/curve_ops.rs (100%) rename {src => evm/src}/cpu/kernel/tests/ecc/ecrecover.rs (96%) rename {src => evm/src}/cpu/kernel/tests/ecc/ecrecover_test_data (100%) rename {src => evm/src}/cpu/kernel/tests/ecc/mod.rs (100%) rename {src => evm/src}/cpu/kernel/tests/ecc/secp_glv_test_data (100%) rename {src => evm/src}/cpu/kernel/tests/exp.rs (100%) rename {src => evm/src}/cpu/kernel/tests/hash.rs (92%) rename {src => evm/src}/cpu/kernel/tests/kernel_consistency.rs (100%) rename {src => evm/src}/cpu/kernel/tests/log.rs (92%) rename {src => evm/src}/cpu/kernel/tests/mod.rs (100%) rename {src => evm/src}/cpu/kernel/tests/mpt/delete.rs (98%) rename {src => evm/src}/cpu/kernel/tests/mpt/hash.rs (100%) rename {src => evm/src}/cpu/kernel/tests/mpt/hex_prefix.rs (100%) rename {src => evm/src}/cpu/kernel/tests/mpt/insert.rs (98%) rename {src => evm/src}/cpu/kernel/tests/mpt/load.rs (100%) rename {src => evm/src}/cpu/kernel/tests/mpt/mod.rs (98%) rename {src => evm/src}/cpu/kernel/tests/mpt/read.rs (100%) rename {src => evm/src}/cpu/kernel/tests/packing.rs (100%) rename {src => evm/src}/cpu/kernel/tests/receipt.rs (98%) rename {src => evm/src}/cpu/kernel/tests/rlp/decode.rs (100%) rename {src => evm/src}/cpu/kernel/tests/rlp/encode.rs (100%) rename {src => evm/src}/cpu/kernel/tests/rlp/mod.rs (100%) rename {src => evm/src}/cpu/kernel/tests/rlp/num_bytes.rs (100%) rename {src => evm/src}/cpu/kernel/tests/signed_syscalls.rs (99%) rename {src => evm/src}/cpu/kernel/tests/transaction_parsing/mod.rs (100%) rename {src => evm/src}/cpu/kernel/tests/transaction_parsing/parse_type_0_txn.rs (88%) rename {src => evm/src}/cpu/kernel/utils.rs (97%) rename {src => evm/src}/cpu/membus.rs (91%) rename {src => evm/src}/cpu/memio.rs (98%) rename {src => evm/src}/cpu/mod.rs (100%) rename {src => evm/src}/cpu/modfp254.rs (83%) rename {src => evm/src}/cpu/pc.rs (100%) rename {src => evm/src}/cpu/push0.rs (100%) rename {src => evm/src}/cpu/shift.rs (100%) rename {src => evm/src}/cpu/simple_logic/eq_iszero.rs (89%) rename {src => evm/src}/cpu/simple_logic/mod.rs (100%) rename {src => evm/src}/cpu/simple_logic/not.rs (100%) rename {src => evm/src}/cpu/stack.rs (95%) rename {src => evm/src}/cpu/syscalls_exceptions.rs (98%) rename {src => evm/src}/curve_pairings.rs (99%) rename {src => evm/src}/extension_tower.rs (98%) rename {src => evm/src}/fixed_recursive_verifier.rs (91%) rename {src => evm/src}/generation/mod.rs (91%) rename {src => evm/src}/generation/mpt.rs (100%) rename {src => evm/src}/generation/prover_input.rs (98%) rename {src => evm/src}/generation/rlp.rs (100%) rename {src => evm/src}/generation/state.rs (88%) rename {src => evm/src}/generation/trie_extractor.rs (98%) rename {src => evm/src}/get_challenges.rs (97%) rename {src => evm/src}/keccak/columns.rs (100%) rename {src => evm/src}/keccak/constants.rs (100%) rename {src => evm/src}/keccak/keccak_stark.rs (99%) rename {src => evm/src}/keccak/logic.rs (100%) rename {src => evm/src}/keccak/mod.rs (100%) rename {src => evm/src}/keccak/round_flags.rs (100%) rename {src => evm/src}/keccak_sponge/columns.rs (87%) rename {src => evm/src}/keccak_sponge/keccak_sponge_stark.rs (94%) create mode 100644 evm/src/keccak_sponge/mod.rs rename {src => evm/src}/lib.rs (62%) rename {src => evm/src}/logic.rs (96%) rename {src => evm/src}/memory/columns.rs (88%) rename {src => evm/src}/memory/memory_stark.rs (89%) rename {src => evm/src}/memory/mod.rs (64%) rename {src => evm/src}/memory/segments.rs (91%) rename {src => evm/src}/proof.rs (90%) rename {src => evm/src}/prover.rs (96%) rename {src => evm/src}/recursive_verifier.rs (96%) rename {src => evm/src}/util.rs (97%) rename {src => evm/src}/verifier.rs (96%) rename {src => evm/src}/witness/errors.rs (100%) rename {src => evm/src}/witness/gas.rs (100%) rename {src => evm/src}/witness/memory.rs (98%) rename {src => evm/src}/witness/mod.rs (100%) rename {src => evm/src}/witness/operation.rs (96%) rename {src => evm/src}/witness/state.rs (100%) rename {src => evm/src}/witness/traces.rs (100%) rename {src => evm/src}/witness/transition.rs (97%) rename {src => evm/src}/witness/util.rs (99%) rename {tests => evm/tests}/add11_yml.rs (99%) rename {tests => evm/tests}/basic_smart_contract.rs (99%) rename {tests => evm/tests}/empty_txn_list.rs (97%) rename {tests => evm/tests}/erc20.rs (99%) rename {tests => evm/tests}/erc721.rs (99%) rename {tests => evm/tests}/log_opcode.rs (97%) rename {tests => evm/tests}/self_balance_gas_cost.rs (99%) rename {tests => evm/tests}/selfdestruct.rs (90%) rename {tests => evm/tests}/simple_transfer.rs (99%) rename {tests => evm/tests}/withdrawals.rs (100%) create mode 100644 field/.cargo/katex-header.html create mode 100644 field/Cargo.toml create mode 100644 field/LICENSE-APACHE create mode 100644 field/LICENSE-MIT create mode 100644 field/README.md create mode 100644 field/src/arch/mod.rs create mode 100644 field/src/arch/x86_64/avx2_goldilocks_field.rs create mode 100644 field/src/arch/x86_64/avx512_goldilocks_field.rs create mode 100644 field/src/arch/x86_64/mod.rs create mode 100644 field/src/batch_util.rs create mode 100644 field/src/cosets.rs create mode 100644 field/src/extension/algebra.rs create mode 100644 field/src/extension/mod.rs create mode 100644 field/src/extension/quadratic.rs create mode 100644 field/src/extension/quartic.rs create mode 100644 field/src/extension/quintic.rs create mode 100644 field/src/fft.rs create mode 100644 field/src/field_testing.rs create mode 100644 field/src/goldilocks_extensions.rs create mode 100644 field/src/goldilocks_field.rs create mode 100644 field/src/interpolation.rs create mode 100644 field/src/lib.rs create mode 100644 field/src/ops.rs create mode 100644 field/src/packable.rs create mode 100644 field/src/packed.rs create mode 100644 field/src/polynomial/division.rs create mode 100644 field/src/polynomial/mod.rs create mode 100644 field/src/prime_field_testing.rs create mode 100644 field/src/secp256k1_base.rs create mode 100644 field/src/secp256k1_scalar.rs create mode 100644 field/src/types.rs create mode 100644 field/src/zero_poly_coset.rs create mode 100644 maybe_rayon/.cargo/katex-header.html create mode 100644 maybe_rayon/Cargo.toml create mode 100644 maybe_rayon/LICENSE-APACHE create mode 100644 maybe_rayon/LICENSE-MIT create mode 100644 maybe_rayon/README.md create mode 100644 maybe_rayon/src/lib.rs create mode 100755 pgo-profile.sh create mode 100644 plonky2/.cargo/katex-header.html create mode 100644 plonky2/Cargo.toml create mode 100644 plonky2/LICENSE-APACHE create mode 100644 plonky2/LICENSE-MIT create mode 100644 plonky2/README.md create mode 100644 plonky2/benches/allocator/mod.rs create mode 100644 plonky2/benches/ffts.rs create mode 100644 plonky2/benches/field_arithmetic.rs create mode 100644 plonky2/benches/hashing.rs create mode 100644 plonky2/benches/merkle.rs create mode 100644 plonky2/benches/reverse_index_bits.rs create mode 100644 plonky2/benches/transpose.rs create mode 100644 plonky2/examples/bench_recursion.rs create mode 100644 plonky2/examples/factorial.rs create mode 100644 plonky2/examples/fibonacci.rs create mode 100644 plonky2/examples/fibonacci_serialization.rs create mode 100644 plonky2/examples/range_check.rs create mode 100644 plonky2/examples/square_root.rs create mode 100644 plonky2/plonky2.pdf create mode 100644 plonky2/src/bin/generate_constants.rs create mode 100644 plonky2/src/fri/challenges.rs create mode 100644 plonky2/src/fri/mod.rs create mode 100644 plonky2/src/fri/oracle.rs create mode 100644 plonky2/src/fri/proof.rs create mode 100644 plonky2/src/fri/prover.rs create mode 100644 plonky2/src/fri/recursive_verifier.rs create mode 100644 plonky2/src/fri/reduction_strategies.rs create mode 100644 plonky2/src/fri/structure.rs create mode 100644 plonky2/src/fri/validate_shape.rs create mode 100644 plonky2/src/fri/verifier.rs create mode 100644 plonky2/src/fri/witness_util.rs create mode 100644 plonky2/src/gadgets/arithmetic.rs create mode 100644 plonky2/src/gadgets/arithmetic_extension.rs create mode 100644 plonky2/src/gadgets/hash.rs create mode 100644 plonky2/src/gadgets/interpolation.rs create mode 100644 plonky2/src/gadgets/lookup.rs create mode 100644 plonky2/src/gadgets/mod.rs create mode 100644 plonky2/src/gadgets/polynomial.rs create mode 100644 plonky2/src/gadgets/random_access.rs create mode 100644 plonky2/src/gadgets/range_check.rs create mode 100644 plonky2/src/gadgets/select.rs create mode 100644 plonky2/src/gadgets/split_base.rs create mode 100644 plonky2/src/gadgets/split_join.rs create mode 100644 plonky2/src/gates/arithmetic_base.rs create mode 100644 plonky2/src/gates/arithmetic_extension.rs create mode 100644 plonky2/src/gates/base_sum.rs create mode 100644 plonky2/src/gates/constant.rs create mode 100644 plonky2/src/gates/coset_interpolation.rs create mode 100644 plonky2/src/gates/exponentiation.rs create mode 100644 plonky2/src/gates/gate.rs create mode 100644 plonky2/src/gates/gate_testing.rs create mode 100644 plonky2/src/gates/lookup.rs create mode 100644 plonky2/src/gates/lookup_table.rs create mode 100644 plonky2/src/gates/mod.rs create mode 100644 plonky2/src/gates/multiplication_extension.rs create mode 100644 plonky2/src/gates/noop.rs create mode 100644 plonky2/src/gates/packed_util.rs create mode 100644 plonky2/src/gates/poseidon.rs create mode 100644 plonky2/src/gates/poseidon_mds.rs create mode 100644 plonky2/src/gates/public_input.rs create mode 100644 plonky2/src/gates/random_access.rs create mode 100644 plonky2/src/gates/reducing.rs create mode 100644 plonky2/src/gates/reducing_extension.rs create mode 100644 plonky2/src/gates/selectors.rs create mode 100644 plonky2/src/gates/util.rs create mode 100644 plonky2/src/hash/arch/aarch64/mod.rs create mode 100644 plonky2/src/hash/arch/aarch64/poseidon_goldilocks_neon.rs create mode 100644 plonky2/src/hash/arch/aarch64/readme-asm.md create mode 100644 plonky2/src/hash/arch/mod.rs create mode 100644 plonky2/src/hash/arch/x86_64/mod.rs create mode 100644 plonky2/src/hash/arch/x86_64/poseidon_goldilocks_avx2_bmi2.rs create mode 100644 plonky2/src/hash/hash_types.rs create mode 100644 plonky2/src/hash/hashing.rs create mode 100644 plonky2/src/hash/keccak.rs create mode 100644 plonky2/src/hash/merkle_proofs.rs create mode 100644 plonky2/src/hash/merkle_tree.rs create mode 100644 plonky2/src/hash/mod.rs create mode 100644 plonky2/src/hash/path_compression.rs create mode 100644 plonky2/src/hash/poseidon.rs create mode 100644 plonky2/src/hash/poseidon_crandall.rs create mode 100644 plonky2/src/hash/poseidon_goldilocks.rs create mode 100644 plonky2/src/iop/challenger.rs create mode 100644 plonky2/src/iop/ext_target.rs create mode 100644 plonky2/src/iop/generator.rs create mode 100644 plonky2/src/iop/mod.rs create mode 100644 plonky2/src/iop/target.rs create mode 100644 plonky2/src/iop/wire.rs create mode 100644 plonky2/src/iop/witness.rs create mode 100644 plonky2/src/lib.rs create mode 100644 plonky2/src/lookup_test.rs create mode 100644 plonky2/src/plonk/circuit_builder.rs create mode 100644 plonky2/src/plonk/circuit_data.rs create mode 100644 plonky2/src/plonk/config.rs create mode 100644 plonky2/src/plonk/copy_constraint.rs create mode 100644 plonky2/src/plonk/get_challenges.rs create mode 100644 plonky2/src/plonk/mod.rs create mode 100644 plonky2/src/plonk/permutation_argument.rs create mode 100644 plonky2/src/plonk/plonk_common.rs create mode 100644 plonky2/src/plonk/proof.rs create mode 100644 plonky2/src/plonk/prover.rs create mode 100644 plonky2/src/plonk/validate_shape.rs create mode 100644 plonky2/src/plonk/vanishing_poly.rs create mode 100644 plonky2/src/plonk/vars.rs create mode 100644 plonky2/src/plonk/verifier.rs create mode 100644 plonky2/src/recursion/conditional_recursive_verifier.rs create mode 100644 plonky2/src/recursion/cyclic_recursion.rs create mode 100644 plonky2/src/recursion/dummy_circuit.rs create mode 100644 plonky2/src/recursion/mod.rs create mode 100644 plonky2/src/recursion/recursive_verifier.rs create mode 100644 plonky2/src/util/context_tree.rs create mode 100644 plonky2/src/util/mod.rs create mode 100644 plonky2/src/util/partial_products.rs create mode 100644 plonky2/src/util/reducing.rs create mode 100644 plonky2/src/util/serialization/gate_serialization.rs create mode 100644 plonky2/src/util/serialization/generator_serialization.rs create mode 100644 plonky2/src/util/serialization/mod.rs create mode 100644 plonky2/src/util/strided_view.rs create mode 100644 plonky2/src/util/timing.rs 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 create mode 100644 rust-toolchain create mode 100644 rustfmt.toml delete mode 100644 src/cpu/kernel/asm/journal/account_created.asm delete mode 100644 src/keccak_sponge/mod.rs create mode 100644 starky/.cargo/katex-header.html create mode 100644 starky/Cargo.toml create mode 100644 starky/LICENSE-APACHE create mode 100644 starky/LICENSE-MIT create mode 100644 starky/README.md create mode 100644 starky/src/config.rs create mode 100644 starky/src/constraint_consumer.rs create mode 100644 starky/src/cross_table_lookup.rs create mode 100644 starky/src/evaluation_frame.rs create mode 100644 starky/src/fibonacci_stark.rs create mode 100644 starky/src/get_challenges.rs create mode 100644 starky/src/lib.rs create mode 100644 starky/src/lookup.rs create mode 100644 starky/src/proof.rs create mode 100644 starky/src/prover.rs create mode 100644 starky/src/recursive_verifier.rs create mode 100644 starky/src/stark.rs create mode 100644 starky/src/stark_testing.rs create mode 100644 starky/src/util.rs create mode 100644 starky/src/vanishing_poly.rs create mode 100644 starky/src/verifier.rs create mode 100644 util/.cargo/katex-header.html create mode 100644 util/Cargo.toml create mode 100644 util/LICENSE-APACHE create mode 100644 util/LICENSE-MIT create mode 100644 util/src/lib.rs create mode 100644 util/src/transpose_util.rs diff --git a/.cargo/katex-header.html b/.cargo/katex-header.html index 20723b5d2..5db5bc0b1 100644 --- a/.cargo/katex-header.html +++ b/.cargo/katex-header.html @@ -1 +1,30 @@ -../../.cargo/katex-header.html \ No newline at end of file + + + + \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..53f8242a9 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "cargo" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml new file mode 100644 index 000000000..9da841bca --- /dev/null +++ b/.github/workflows/continuous-integration-workflow.yml @@ -0,0 +1,163 @@ +name: Continuous Integration + +on: + push: + branches: [main] + pull_request: + branches: + - "**" + workflow_dispatch: + branches: + - "**" + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + +jobs: + test: + name: Test Suite + runs-on: ubuntu-latest + timeout-minutes: 30 + if: "! contains(toJSON(github.event.commits.*.message), '[skip-ci]')" + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Install nightly toolchain + uses: dtolnay/rust-toolchain@nightly + + - name: Set up rust cache + uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + + - name: Check in plonky2 subdirectory + run: cargo check --manifest-path plonky2/Cargo.toml + env: + RUSTFLAGS: -Copt-level=3 -Cdebug-assertions -Coverflow-checks=y -Cdebuginfo=0 + RUST_LOG: 1 + CARGO_INCREMENTAL: 1 + RUST_BACKTRACE: 1 + + - name: Check in starky subdirectory + run: cargo check --manifest-path starky/Cargo.toml + env: + RUSTFLAGS: -Copt-level=3 -Cdebug-assertions -Coverflow-checks=y -Cdebuginfo=0 + RUST_LOG: 1 + CARGO_INCREMENTAL: 1 + RUST_BACKTRACE: 1 + + - name: Check in evm subdirectory + run: cargo check --manifest-path evm/Cargo.toml + env: + RUSTFLAGS: -Copt-level=3 -Cdebug-assertions -Coverflow-checks=y -Cdebuginfo=0 + RUST_LOG: 1 + CARGO_INCREMENTAL: 1 + RUST_BACKTRACE: 1 + + - name: Run cargo test + run: cargo test --workspace + env: + RUSTFLAGS: -Copt-level=3 -Cdebug-assertions -Coverflow-checks=y -Cdebuginfo=0 + RUST_LOG: 1 + CARGO_INCREMENTAL: 1 + RUST_BACKTRACE: 1 + + wasm: + name: Check wasm32 compatibility + runs-on: ubuntu-latest + timeout-minutes: 30 + if: "! contains(toJSON(github.event.commits.*.message), '[skip-ci]')" + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Install nightly toolchain + uses: dtolnay/rust-toolchain@nightly + with: + targets: wasm32-unknown-unknown + + - name: Set up rust cache + uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + + - name: Check in plonky2 subdirectory for wasm targets + run: cargo check --manifest-path plonky2/Cargo.toml --target wasm32-unknown-unknown --no-default-features + env: + RUSTFLAGS: -Copt-level=3 -Cdebug-assertions -Coverflow-checks=y -Cdebuginfo=0 + RUST_LOG: 1 + CARGO_INCREMENTAL: 1 + RUST_BACKTRACE: 1 + + - name: Check in starky subdirectory for wasm targets + run: cargo check --manifest-path starky/Cargo.toml --target wasm32-unknown-unknown --no-default-features + env: + RUSTFLAGS: -Copt-level=3 -Cdebug-assertions -Coverflow-checks=y -Cdebuginfo=0 + RUST_LOG: 1 + CARGO_INCREMENTAL: 1 + RUST_BACKTRACE: 1 + + no_std: + name: Test Suite in no-std + runs-on: ubuntu-latest + timeout-minutes: 30 + if: "! contains(toJSON(github.event.commits.*.message), '[skip-ci]')" + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Install nightly toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2024-02-01 + + - name: Set up rust cache + uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + + - name: Run cargo test in plonky2 subdirectory (no-std) + run: cargo test --manifest-path plonky2/Cargo.toml --no-default-features --lib + env: + RUSTFLAGS: -Copt-level=3 -Cdebug-assertions -Coverflow-checks=y -Cdebuginfo=0 + RUST_LOG: 1 + CARGO_INCREMENTAL: 1 + RUST_BACKTRACE: 1 + + - name: Run cargo test in starky subdirectory (no-std) + run: cargo test --manifest-path starky/Cargo.toml --no-default-features --lib + env: + RUSTFLAGS: -Copt-level=3 -Cdebug-assertions -Coverflow-checks=y -Cdebuginfo=0 + RUST_LOG: 1 + CARGO_INCREMENTAL: 1 + RUST_BACKTRACE: 1 + + lints: + name: Formatting and Clippy + runs-on: ubuntu-latest + timeout-minutes: 10 + if: "! contains(toJSON(github.event.commits.*.message), '[skip-ci]')" + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Install nightly toolchain + uses: dtolnay/rust-toolchain@nightly + with: + components: rustfmt, clippy + + - name: Set up rust cache + uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + + - name: Run cargo fmt + run: cargo fmt --all --check + + - name: Run cargo clippy + run: cargo clippy --all-features --all-targets -- -D warnings -A incomplete-features diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..293a17bb6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Cargo build +/target +Cargo.lock + +# Profile-guided optimization +/tmp +pgo-data.profdata + +# MacOS nuisances +.DS_Store diff --git a/Cargo.toml b/Cargo.toml index df8401b05..7a5141758 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,71 +1,12 @@ -[package] -name = "plonky2_evm" -description = "Implementation of STARKs for the Ethereum Virtual Machine" -version = "0.1.1" -license = "MIT or Apache-2.0" -authors = ["Daniel Lubarov ", "William Borgeaud "] -readme = "README.md" -repository = "https://github.com/0xPolygonZero/plonky2" -keywords = ["EVM", "STARK", "Ethereum"] -categories = ["cryptography"] -edition = "2021" - -[dependencies] -anyhow = "1.0.40" -bytes = "1.4.0" -env_logger = "0.10.0" -eth_trie_utils = { git = "https://github.com/0xPolygonZero/eth_trie_utils.git", rev = "7fc3c3f54b3cec9c6fc5ffc5230910bd1cb77f76" } -ethereum-types = "0.14.0" -hex = { version = "0.4.3", optional = true } -hex-literal = "0.4.1" -itertools = "0.11.0" -keccak-hash = "0.10.0" -log = "0.4.14" -plonky2_maybe_rayon = { path = "../maybe_rayon" } -num = "0.4.0" -num-bigint = "0.4.3" -once_cell = "1.13.0" -pest = "2.1.3" -pest_derive = "2.1.0" -plonky2 = { path = "../plonky2", features = ["timing"] } -plonky2_util = { path = "../util" } -starky = { path = "../starky" } -rand = "0.8.5" -rand_chacha = "0.3.1" -rlp = "0.5.1" -rlp-derive = "0.1.0" -serde = { version = "1.0.144", features = ["derive"] } -static_assertions = "1.1.0" -hashbrown = { version = "0.14.0" } -tiny-keccak = "2.0.2" -serde_json = "1.0" - -[target.'cfg(not(target_env = "msvc"))'.dependencies] -jemallocator = "0.5.0" - -[dev-dependencies] -criterion = "0.5.1" -hex = "0.4.3" -ripemd = "0.1.3" -sha2 = "0.10.6" - -[features] -default = ["parallel"] -asmtools = ["hex"] -parallel = [ - "plonky2/parallel", - "plonky2_maybe_rayon/parallel", - "starky/parallel" -] - -[[bin]] -name = "assemble" -required-features = ["asmtools"] - -[[bench]] -name = "stack_manipulation" -harness = false - -# Display math equations properly in documentation -[package.metadata.docs.rs] -rustdoc-args = ["--html-in-header", ".cargo/katex-header.html"] +[workspace] +members = ["evm", "field", "maybe_rayon", "plonky2", "starky", "util"] +resolver = "2" + +[profile.release] +opt-level = 3 +incremental = true +#lto = "fat" +#codegen-units = 1 + +[profile.bench] +opt-level = 3 diff --git a/README.md b/README.md index a5c201550..6ee6b82a0 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,186 @@ -# Provable Stateless ZK-EVM +# Plonky2 & more +[![Discord](https://img.shields.io/discord/743511677072572486?logo=discord)](https://discord.gg/QZKRUpqCJ6) -Included here is an implementation of a stateless, recursive ZK-EVM client implemented using Plonky2. It currently supports the full Merkle-Patricia Trie and has all Shanghai opcodes implemented. +This repository was originally for Plonky2, a SNARK implementation based on techniques from PLONK and FRI. It has since expanded to include tools such as Starky, a highly performant STARK implementation. -## Performance -This implementation is able to provide transaction level proofs which are then recursively aggregated into a block proof. This means that proofs for a block can be efficiently distributed across a cluster of computers. As these proofs use Plonky2 they are CPU and Memory bound. The ability to scale horizontally across transactions increases the total performance of the system dramatically. End-to-end workflows are currently in progress to support this proving mode against live evm networks. +## Documentation -Furthermore the implementation itself is highly optimized to provide fast proving times on generally available cloud instances and does not require GPUs or special hardware. +For more details about the Plonky2 argument system, see this [writeup](plonky2/plonky2.pdf). -## Ethereum Compatibility +Polymer Labs has written up a helpful tutorial [here](https://polymerlabs.medium.com/a-tutorial-on-writing-zk-proofs-with-plonky2-part-i-be5812f6b798)! -The aim of this module is to initially provide full ethereum compatibility. Today, all [EVM tests](https://github.com/0xPolygonZero/evm-tests) for the Shanghai hardfork are implemented. Work is progressing on supporting the upcoming [Cancun](https://github.com/0xPolygonZero/plonky2/labels/cancun) EVM changes. Furthermore, this prover uses the full ethereum state tree and hashing modes. -## Audits +## Examples -Audits for the ZK-EVM will begin on November 27th, 2023. See the [Audit RC1 Milestone](https://github.com/0xPolygonZero/plonky2/milestone/2?closed=1). This README will be updated with the proper branches and hashes when the audit has commenced. +A good starting point for how to use Plonky2 for simple applications is the included examples: -## Documentation / Specification +* [`factorial`](plonky2/examples/factorial.rs): Proving knowledge of 100! +* [`fibonacci`](plonky2/examples/fibonacci.rs): Proving knowledge of the hundredth Fibonacci number +* [`range_check`](plonky2/examples/range_check.rs): Proving that a field element is in a given range +* [`square_root`](plonky2/examples/square_root.rs): Proving knowledge of the square root of a given field element -The current specification is located in the [/spec](/spec) directory, with the most currently up-to-date PDF [available here](https://github.com/0xPolygonZero/plonky2/blob/main/evm/spec/zkevm.pdf). Further documentation will be made over the coming months. +To run an example, use -## License -Copyright (c) 2023 PT Services DMCC +```sh +cargo run --example +``` -Licensed under either of: -* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) -* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) -at your option. +## Building -The SPDX license identifier for this project is `MIT OR Apache-2.0`. +Plonky2 requires a recent nightly toolchain, although we plan to transition to stable in the future. -### Contribution +To use a nightly toolchain for Plonky2 by default, you can run +``` +rustup override set nightly +``` +in the Plonky2 directory. -Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. + +## Running + +To see recursion performance, one can run this bench, which generates a chain of three recursion proofs: + +```sh +RUSTFLAGS=-Ctarget-cpu=native cargo run --release --example bench_recursion -- -vv +``` + +## Jemalloc + +Plonky2 prefers the [Jemalloc](http://jemalloc.net) memory allocator due to its superior performance. To use it, include `jemallocator = "0.5.0"` in your `Cargo.toml` and add the following lines +to your `main.rs`: + +```rust +use jemallocator::Jemalloc; + +#[global_allocator] +static GLOBAL: Jemalloc = Jemalloc; +``` + +Jemalloc is known to cause crashes when a binary compiled for x86 is run on an Apple silicon-based Mac under [Rosetta 2](https://support.apple.com/en-us/HT211861). If you are experiencing crashes on your Apple silicon Mac, run `rustc --print target-libdir`. The output should contain `aarch64-apple-darwin`. If the output contains `x86_64-apple-darwin`, then you are running the Rust toolchain for x86; we recommend switching to the native ARM version. + + +## Guidance for external contributors + +Do you feel keen and able to help with Plonky2? That's great! We +encourage external contributions! + +We want to make it easy for you to contribute, but at the same time we +must manage the burden of reviewing external contributions. We are a +small team, and the time we spend reviewing external contributions is +time we are not developing ourselves. + +We also want to help you to avoid inadvertently duplicating work that +is already underway, or building something that we will not +want to incorporate. + +First and foremost, please keep in mind that this is a highly +technical piece of software and contributing is only suitable for +experienced mathematicians, cryptographers and software engineers. + +The Polygon Zero Team reserves the right to accept or reject any +external contribution for any reason, including a simple lack of time +to maintain it (now or in the future); we may even decline to review +something that is not considered a sufficiently high priority for us. + +To avoid disappointment, please communicate your intention to +contribute openly, while respecting the limited time and availability +we have to review and provide guidance for external contributions. It +is a good idea to drop a note in our public Discord #development +channel of your intention to work on something, whether an issue, a +new feature, or a performance improvement. This is probably all that's +really required to avoid duplication of work with other contributors. + +What follows are some more specific requests for how to write PRs in a +way that will make them easy for us to review. Deviating from these +guidelines may result in your PR being rejected, ignored or forgotten. + + +### General guidance for your PR + +Obviously PRs will not be considered unless they pass our Github +CI. The Github CI is not executed for PRs from forks, but you can +simulate the Github CI by running the commands in +`.github/workflows/ci.yml`. + +Under no circumstances should a single PR mix different purposes: Your +PR is either a bug fix, a new feature, or a performance improvement, +never a combination. Nor should you include, for example, two +unrelated performance improvements in one PR. Please just submit +separate PRs. The goal is to make reviewing your PR as simple as +possible, and you should be thinking about how to compose the PR to +minimise the burden on the reviewer. + +Also note that any PR that depends on unstable features will be +automatically rejected. The Polygon Zero Team may enable a small +number of unstable features in the future for our exclusive use; +nevertheless we aim to minimise the number of such features, and the +number of uses of them, to the greatest extent possible. + +Here are a few specific guidelines for the three main categories of +PRs that we expect: + + +#### The PR fixes a bug + +In the PR description, please clearly but briefly describe + +1. the bug (could be a reference to a GH issue; if it is from a + discussion (on Discord/email/etc. for example), please copy in the + relevant parts of the discussion); +2. what turned out to the cause the bug; and +3. how the PR fixes the bug. + +Wherever possible, PRs that fix bugs should include additional tests +that (i) trigger the original bug and (ii) pass after applying the PR. + + +#### The PR implements a new feature + +If you plan to contribute an implementation of a new feature, please +double-check with the Polygon Zero team that it is a sufficient +priority for us that it will be reviewed and integrated. + +In the PR description, please clearly but briefly describe + +1. what the feature does +2. the approach taken to implement it + +All PRs for new features must include a suitable test suite. + + +#### The PR improves performance + +Performance improvements are particularly welcome! Please note that it +can be quite difficult to establish true improvements for the +workloads we care about. To help filter out false positives, the PR +description for a performance improvement must clearly identify + +1. the target bottleneck (only one per PR to avoid confusing things!) +2. how performance is measured +3. characteristics of the machine used (CPU, OS, #threads if appropriate) +4. performance before and after the PR + + +## Licenses + +As this is a monorepo, see the individual crates within for license information. + + +## Security + +This code has not yet been audited, and should not be used in any production systems. + +While Plonky2 is configurable, its defaults generally target 100 bits of security. The default FRI configuration targets 100 bits of *conjectured* security based on the conjecture in [ethSTARK](https://eprint.iacr.org/2021/582). + +Plonky2's default hash function is Poseidon, configured with 8 full rounds, 22 partial rounds, a width of 12 field elements (each ~64 bits), and an S-box of `x^7`. [BBLP22](https://tosc.iacr.org/index.php/ToSC/article/view/9850) suggests that this configuration may have around 95 bits of security, falling a bit short of our 100 bit target. + + +## Links + +- [System Zero](https://github.com/0xPolygonZero/system-zero), a zkVM built on top of Starky (no longer maintained) +- [Waksman](https://github.com/0xPolygonZero/plonky2-waksman), Plonky2 gadgets for permutation checking using Waksman networks (no longer maintained) +- [Insertion](https://github.com/0xPolygonZero/plonky2-insertion), Plonky2 gadgets for insertion into a list (no longer maintained) +- [u32](https://github.com/0xPolygonZero/plonky2-u32), Plonky2 gadgets for u32 arithmetic (no longer actively maintained) +- [ECDSA](https://github.com/0xPolygonZero/plonky2-ecdsa), Plonky2 gadgets for the ECDSA algorithm (no longer actively maintained) diff --git a/audits/Least Authority - Polygon Zero Plonky2 Final Audit Report.pdf b/audits/Least Authority - Polygon Zero Plonky2 Final Audit Report.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4df70d30b9a15f48158f89cd543ab46a5b509264 GIT binary patch literal 238137 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
Q9rZSPy`5$0#2te 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ⅅaA)q;Lto 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

g4WpynFpRkMVpi99To*L^VWIP;^Luh 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 bw9h! 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 LMnFJLN&f!;YRO7; literal 0 HcmV?d00001 diff --git a/evm/.cargo/katex-header.html b/evm/.cargo/katex-header.html new file mode 100644 index 000000000..20723b5d2 --- /dev/null +++ b/evm/.cargo/katex-header.html @@ -0,0 +1 @@ +../../.cargo/katex-header.html \ No newline at end of file diff --git a/evm/Cargo.toml b/evm/Cargo.toml new file mode 100644 index 000000000..df8401b05 --- /dev/null +++ b/evm/Cargo.toml @@ -0,0 +1,71 @@ +[package] +name = "plonky2_evm" +description = "Implementation of STARKs for the Ethereum Virtual Machine" +version = "0.1.1" +license = "MIT or Apache-2.0" +authors = ["Daniel Lubarov ", "William Borgeaud "] +readme = "README.md" +repository = "https://github.com/0xPolygonZero/plonky2" +keywords = ["EVM", "STARK", "Ethereum"] +categories = ["cryptography"] +edition = "2021" + +[dependencies] +anyhow = "1.0.40" +bytes = "1.4.0" +env_logger = "0.10.0" +eth_trie_utils = { git = "https://github.com/0xPolygonZero/eth_trie_utils.git", rev = "7fc3c3f54b3cec9c6fc5ffc5230910bd1cb77f76" } +ethereum-types = "0.14.0" +hex = { version = "0.4.3", optional = true } +hex-literal = "0.4.1" +itertools = "0.11.0" +keccak-hash = "0.10.0" +log = "0.4.14" +plonky2_maybe_rayon = { path = "../maybe_rayon" } +num = "0.4.0" +num-bigint = "0.4.3" +once_cell = "1.13.0" +pest = "2.1.3" +pest_derive = "2.1.0" +plonky2 = { path = "../plonky2", features = ["timing"] } +plonky2_util = { path = "../util" } +starky = { path = "../starky" } +rand = "0.8.5" +rand_chacha = "0.3.1" +rlp = "0.5.1" +rlp-derive = "0.1.0" +serde = { version = "1.0.144", features = ["derive"] } +static_assertions = "1.1.0" +hashbrown = { version = "0.14.0" } +tiny-keccak = "2.0.2" +serde_json = "1.0" + +[target.'cfg(not(target_env = "msvc"))'.dependencies] +jemallocator = "0.5.0" + +[dev-dependencies] +criterion = "0.5.1" +hex = "0.4.3" +ripemd = "0.1.3" +sha2 = "0.10.6" + +[features] +default = ["parallel"] +asmtools = ["hex"] +parallel = [ + "plonky2/parallel", + "plonky2_maybe_rayon/parallel", + "starky/parallel" +] + +[[bin]] +name = "assemble" +required-features = ["asmtools"] + +[[bench]] +name = "stack_manipulation" +harness = false + +# Display math equations properly in documentation +[package.metadata.docs.rs] +rustdoc-args = ["--html-in-header", ".cargo/katex-header.html"] diff --git a/LICENSE-APACHE b/evm/LICENSE-APACHE similarity index 100% rename from LICENSE-APACHE rename to evm/LICENSE-APACHE diff --git a/LICENSE-MIT b/evm/LICENSE-MIT similarity index 100% rename from LICENSE-MIT rename to evm/LICENSE-MIT diff --git a/evm/README.md b/evm/README.md new file mode 100644 index 000000000..a5c201550 --- /dev/null +++ b/evm/README.md @@ -0,0 +1,36 @@ +# Provable Stateless ZK-EVM + +Included here is an implementation of a stateless, recursive ZK-EVM client implemented using Plonky2. It currently supports the full Merkle-Patricia Trie and has all Shanghai opcodes implemented. + +## Performance + +This implementation is able to provide transaction level proofs which are then recursively aggregated into a block proof. This means that proofs for a block can be efficiently distributed across a cluster of computers. As these proofs use Plonky2 they are CPU and Memory bound. The ability to scale horizontally across transactions increases the total performance of the system dramatically. End-to-end workflows are currently in progress to support this proving mode against live evm networks. + +Furthermore the implementation itself is highly optimized to provide fast proving times on generally available cloud instances and does not require GPUs or special hardware. + +## Ethereum Compatibility + +The aim of this module is to initially provide full ethereum compatibility. Today, all [EVM tests](https://github.com/0xPolygonZero/evm-tests) for the Shanghai hardfork are implemented. Work is progressing on supporting the upcoming [Cancun](https://github.com/0xPolygonZero/plonky2/labels/cancun) EVM changes. Furthermore, this prover uses the full ethereum state tree and hashing modes. + +## Audits + +Audits for the ZK-EVM will begin on November 27th, 2023. See the [Audit RC1 Milestone](https://github.com/0xPolygonZero/plonky2/milestone/2?closed=1). This README will be updated with the proper branches and hashes when the audit has commenced. + +## Documentation / Specification + +The current specification is located in the [/spec](/spec) directory, with the most currently up-to-date PDF [available here](https://github.com/0xPolygonZero/plonky2/blob/main/evm/spec/zkevm.pdf). Further documentation will be made over the coming months. + +## License +Copyright (c) 2023 PT Services DMCC + +Licensed under either of: +* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +The SPDX license identifier for this project is `MIT OR Apache-2.0`. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/benches/stack_manipulation.rs b/evm/benches/stack_manipulation.rs similarity index 100% rename from benches/stack_manipulation.rs rename to evm/benches/stack_manipulation.rs diff --git a/spec/.gitignore b/evm/spec/.gitignore similarity index 100% rename from spec/.gitignore rename to evm/spec/.gitignore diff --git a/spec/Makefile b/evm/spec/Makefile similarity index 100% rename from spec/Makefile rename to evm/spec/Makefile diff --git a/spec/bibliography.bib b/evm/spec/bibliography.bib similarity index 100% rename from spec/bibliography.bib rename to evm/spec/bibliography.bib diff --git a/spec/cpulogic.tex b/evm/spec/cpulogic.tex similarity index 100% rename from spec/cpulogic.tex rename to evm/spec/cpulogic.tex diff --git a/spec/framework.tex b/evm/spec/framework.tex similarity index 100% rename from spec/framework.tex rename to evm/spec/framework.tex diff --git a/spec/introduction.tex b/evm/spec/introduction.tex similarity index 100% rename from spec/introduction.tex rename to evm/spec/introduction.tex diff --git a/spec/mpts.tex b/evm/spec/mpts.tex similarity index 100% rename from spec/mpts.tex rename to evm/spec/mpts.tex diff --git a/spec/tables.tex b/evm/spec/tables.tex similarity index 100% rename from spec/tables.tex rename to evm/spec/tables.tex diff --git a/spec/tables/arithmetic.tex b/evm/spec/tables/arithmetic.tex similarity index 100% rename from spec/tables/arithmetic.tex rename to evm/spec/tables/arithmetic.tex diff --git a/spec/tables/byte-packing.tex b/evm/spec/tables/byte-packing.tex similarity index 100% rename from spec/tables/byte-packing.tex rename to evm/spec/tables/byte-packing.tex diff --git a/spec/tables/cpu.tex b/evm/spec/tables/cpu.tex similarity index 100% rename from spec/tables/cpu.tex rename to evm/spec/tables/cpu.tex diff --git a/spec/tables/keccak-f.tex b/evm/spec/tables/keccak-f.tex similarity index 100% rename from spec/tables/keccak-f.tex rename to evm/spec/tables/keccak-f.tex diff --git a/spec/tables/keccak-sponge.tex b/evm/spec/tables/keccak-sponge.tex similarity index 100% rename from spec/tables/keccak-sponge.tex rename to evm/spec/tables/keccak-sponge.tex diff --git a/spec/tables/logic.tex b/evm/spec/tables/logic.tex similarity index 100% rename from spec/tables/logic.tex rename to evm/spec/tables/logic.tex diff --git a/spec/tables/memory.tex b/evm/spec/tables/memory.tex similarity index 100% rename from spec/tables/memory.tex rename to evm/spec/tables/memory.tex diff --git a/spec/zkevm.pdf b/evm/spec/zkevm.pdf similarity index 100% rename from spec/zkevm.pdf rename to evm/spec/zkevm.pdf diff --git a/spec/zkevm.tex b/evm/spec/zkevm.tex similarity index 100% rename from spec/zkevm.tex rename to evm/spec/zkevm.tex diff --git a/src/all_stark.rs b/evm/src/all_stark.rs similarity index 95% rename from src/all_stark.rs rename to evm/src/all_stark.rs index ec218ef8e..942b5cd2f 100644 --- a/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -38,7 +38,8 @@ pub struct AllStark, const D: usize> { } impl, const D: usize> Default for AllStark { - /// Returns an `AllStark` containing all the STARKs initialized with default values. + /// Returns an `AllStark` containing all the STARKs initialized with default + /// values. fn default() -> Self { Self { arithmetic_stark: ArithmeticStark::default(), @@ -122,7 +123,8 @@ pub(crate) fn all_cross_table_lookups() -> Vec> { ] } -/// `CrossTableLookup` for `ArithmeticStark`, to connect it with the `Cpu` module. +/// `CrossTableLookup` for `ArithmeticStark`, to connect it with the `Cpu` +/// module. fn ctl_arithmetic() -> CrossTableLookup { CrossTableLookup::new( vec![cpu_stark::ctl_arithmetic_base_rows()], @@ -130,7 +132,8 @@ fn ctl_arithmetic() -> CrossTableLookup { ) } -/// `CrossTableLookup` for `BytePackingStark`, to connect it with the `Cpu` module. +/// `CrossTableLookup` for `BytePackingStark`, to connect it with the `Cpu` +/// module. fn ctl_byte_packing() -> CrossTableLookup { let cpu_packing_looking = TableWithColumns::new( *Table::Cpu, @@ -168,9 +171,10 @@ fn ctl_byte_packing() -> CrossTableLookup { ) } -/// `CrossTableLookup` for `KeccakStark` inputs, to connect it with the `KeccakSponge` module. -/// `KeccakStarkSponge` looks into `KeccakStark` to give the inputs of the sponge. -/// Its consistency with the 'output' CTL is ensured through a timestamp column on the `KeccakStark` side. +/// `CrossTableLookup` for `KeccakStark` inputs, to connect it with the +/// `KeccakSponge` module. `KeccakStarkSponge` looks into `KeccakStark` to give +/// the inputs of the sponge. Its consistency with the 'output' CTL is ensured +/// through a timestamp column on the `KeccakStark` side. fn ctl_keccak_inputs() -> CrossTableLookup { let keccak_sponge_looking = TableWithColumns::new( *Table::KeccakSponge, @@ -185,8 +189,9 @@ fn ctl_keccak_inputs() -> CrossTableLookup { CrossTableLookup::new(vec![keccak_sponge_looking], keccak_looked) } -/// `CrossTableLookup` for `KeccakStark` outputs, to connect it with the `KeccakSponge` module. -/// `KeccakStarkSponge` looks into `KeccakStark` to give the outputs of the sponge. +/// `CrossTableLookup` for `KeccakStark` outputs, to connect it with the +/// `KeccakSponge` module. `KeccakStarkSponge` looks into `KeccakStark` to give +/// the outputs of the sponge. fn ctl_keccak_outputs() -> CrossTableLookup { let keccak_sponge_looking = TableWithColumns::new( *Table::KeccakSponge, @@ -201,7 +206,8 @@ fn ctl_keccak_outputs() -> CrossTableLookup { CrossTableLookup::new(vec![keccak_sponge_looking], keccak_looked) } -/// `CrossTableLookup` for `KeccakSpongeStark` to connect it with the `Cpu` module. +/// `CrossTableLookup` for `KeccakSpongeStark` to connect it with the `Cpu` +/// module. fn ctl_keccak_sponge() -> CrossTableLookup { let cpu_looking = TableWithColumns::new( *Table::Cpu, @@ -216,7 +222,8 @@ fn ctl_keccak_sponge() -> CrossTableLookup { CrossTableLookup::new(vec![cpu_looking], keccak_sponge_looked) } -/// `CrossTableLookup` for `LogicStark` to connect it with the `Cpu` and `KeccakSponge` modules. +/// `CrossTableLookup` for `LogicStark` to connect it with the `Cpu` and +/// `KeccakSponge` modules. fn ctl_logic() -> CrossTableLookup { let cpu_looking = TableWithColumns::new( *Table::Cpu, @@ -237,7 +244,8 @@ fn ctl_logic() -> CrossTableLookup { CrossTableLookup::new(all_lookers, logic_looked) } -/// `CrossTableLookup` for `MemoryStark` to connect it with all the modules which need memory accesses. +/// `CrossTableLookup` for `MemoryStark` to connect it with all the modules +/// which need memory accesses. fn ctl_memory() -> CrossTableLookup { let cpu_memory_code_read = TableWithColumns::new( *Table::Cpu, diff --git a/src/arithmetic/addcy.rs b/evm/src/arithmetic/addcy.rs similarity index 100% rename from src/arithmetic/addcy.rs rename to evm/src/arithmetic/addcy.rs diff --git a/src/arithmetic/arithmetic_stark.rs b/evm/src/arithmetic/arithmetic_stark.rs similarity index 98% rename from src/arithmetic/arithmetic_stark.rs rename to evm/src/arithmetic/arithmetic_stark.rs index 75fd9fe2a..43681e84c 100644 --- a/src/arithmetic/arithmetic_stark.rs +++ b/evm/src/arithmetic/arithmetic_stark.rs @@ -22,9 +22,10 @@ use crate::all_stark::{EvmStarkFrame, Table}; use crate::arithmetic::columns::{NUM_SHARED_COLS, RANGE_COUNTER, RC_FREQUENCIES, SHARED_COLS}; use crate::arithmetic::{addcy, byte, columns, divmod, modular, mul, Operation}; -/// Creates a vector of `Columns` to link the 16-bit columns of the arithmetic table, -/// split into groups of N_LIMBS at a time in `regs`, with the corresponding 32-bit -/// columns of the CPU table. Does this for all ops in `ops`. +/// Creates a vector of `Columns` to link the 16-bit columns of the arithmetic +/// table, split into groups of N_LIMBS at a time in `regs`, with the +/// corresponding 32-bit columns of the CPU table. Does this for all ops in +/// `ops`. /// /// This is done by taking pairs of columns (x, y) of the arithmetic /// table and combining them as x + y*2^16 to ensure they equal the @@ -57,7 +58,8 @@ fn cpu_arith_data_link( res } -/// Returns the `TableWithColumns` for `ArithmeticStark` rows where one of the arithmetic operations has been called. +/// Returns the `TableWithColumns` for `ArithmeticStark` rows where one of the +/// arithmetic operations has been called. pub(crate) fn ctl_arithmetic_rows() -> TableWithColumns { // We scale each filter flag with the associated opcode value. // If an arithmetic operation is happening on the CPU side, @@ -116,7 +118,8 @@ pub(crate) fn ctl_arithmetic_rows() -> TableWithColumns { ) } -/// Structure representing the `Arithmetic` STARK, which carries out all the arithmetic operations. +/// Structure representing the `Arithmetic` STARK, which carries out all the +/// arithmetic operations. #[derive(Copy, Clone, Default)] pub(crate) struct ArithmeticStark { pub f: PhantomData, @@ -240,7 +243,8 @@ impl, const D: usize> Stark for ArithmeticSta addcy::eval_packed_generic(lv, yield_constr); // Evaluate constraints for DIV and MOD operations. divmod::eval_packed(lv, nv, yield_constr); - // Evaluate constraints for ADDMOD, SUBMOD, MULMOD and for FP254 modular operations. + // Evaluate constraints for ADDMOD, SUBMOD, MULMOD and for FP254 modular + // operations. modular::eval_packed(lv, nv, yield_constr); // Evaluate constraints for the BYTE operation. byte::eval_packed(lv, yield_constr); @@ -301,7 +305,8 @@ impl, const D: usize> Stark for ArithmeticSta addcy::eval_ext_circuit(builder, lv, yield_constr); // Evaluate constraints for DIV and MOD operations. divmod::eval_ext_circuit(builder, lv, nv, yield_constr); - // Evaluate constraints for ADDMOD, SUBMOD, MULMOD and for FP254 modular operations. + // Evaluate constraints for ADDMOD, SUBMOD, MULMOD and for FP254 modular + // operations. modular::eval_ext_circuit(builder, lv, nv, yield_constr); // Evaluate constraints for the BYTE operation. byte::eval_ext_circuit(builder, lv, yield_constr); diff --git a/src/arithmetic/byte.rs b/evm/src/arithmetic/byte.rs similarity index 98% rename from src/arithmetic/byte.rs rename to evm/src/arithmetic/byte.rs index 272a78431..faebb76bd 100644 --- a/src/arithmetic/byte.rs +++ b/evm/src/arithmetic/byte.rs @@ -50,14 +50,15 @@ //! //! 1. The given L, w and y are range-checked to be less than 2^16. //! 2. y * 256 ∈ {0, 256, 512, ..., 2^24 - 512, 2^24 - 256} -//! 3. w / 256 = L - y * 256 ∈ {-2^24 + 256, -2^24 + 257, ..., 2^16 - 2, 2^16 - 1} -//! 4. By inspection, for w < 2^16, if w / 256 < 2^16 or -//! w / 256 >= P - 2^24 + 256 (i.e. if w / 256 falls in the range -//! of point 3 above), then w = 256 * m for some 0 <= m < 256. +//! 3. w / 256 = L - y * 256 ∈ {-2^24 + 256, -2^24 + 257, ..., 2^16 - 2, 2^16 - +//! 1} +//! 4. By inspection, for w < 2^16, if w / 256 < 2^16 or w / 256 >= P - 2^24 + +//! 256 (i.e. if w / 256 falls in the range of point 3 above), then w = 256 * +//! m for some 0 <= m < 256. //! 5. Hence w / 256 ∈ {0, 1, ..., 255} //! 6. Hence y * 256 = L - w / 256 ∈ {-255, -254, ..., 2^16 - 1} -//! 7. Taking the intersection of ranges in 2. and 6. we see that -//! y * 256 ∈ {0, 256, 512, ..., 2^16 - 256} +//! 7. Taking the intersection of ranges in 2. and 6. we see that y * 256 ∈ {0, +//! 256, 512, ..., 2^16 - 256} //! 8. Hence y ∈ {0, 1, ..., 255} use core::ops::Range; diff --git a/src/arithmetic/columns.rs b/evm/src/arithmetic/columns.rs similarity index 99% rename from src/arithmetic/columns.rs rename to evm/src/arithmetic/columns.rs index e4172bc07..ff97f8314 100644 --- a/src/arithmetic/columns.rs +++ b/evm/src/arithmetic/columns.rs @@ -107,7 +107,8 @@ pub(crate) const MODULAR_OUT_AUX_RED: Range = AUX_REGISTER_0; pub(crate) const MODULAR_MOD_IS_ZERO: usize = AUX_REGISTER_1.start; pub(crate) const MODULAR_AUX_INPUT_LO: Range = AUX_REGISTER_1.start + 1..AUX_REGISTER_1.end; pub(crate) const MODULAR_AUX_INPUT_HI: Range = AUX_REGISTER_2; -// Must be set to MOD_IS_ZERO for DIV and SHR operations i.e. MOD_IS_ZERO * (lv[IS_DIV] + lv[IS_SHR]). +// Must be set to MOD_IS_ZERO for DIV and SHR operations i.e. MOD_IS_ZERO * +// (lv[IS_DIV] + lv[IS_SHR]). pub(crate) const MODULAR_DIV_DENOM_IS_ZERO: usize = AUX_REGISTER_2.end; /// The counter column (used for the range check) starts from 0 and increments. diff --git a/src/arithmetic/divmod.rs b/evm/src/arithmetic/divmod.rs similarity index 100% rename from src/arithmetic/divmod.rs rename to evm/src/arithmetic/divmod.rs diff --git a/src/arithmetic/mod.rs b/evm/src/arithmetic/mod.rs similarity index 97% rename from src/arithmetic/mod.rs rename to evm/src/arithmetic/mod.rs index f9a816c1f..0ca1bbe7a 100644 --- a/src/arithmetic/mod.rs +++ b/evm/src/arithmetic/mod.rs @@ -22,7 +22,8 @@ pub(crate) mod columns; /// An enum representing different binary operations. /// -/// `Shl` and `Shr` are handled differently, by leveraging `Mul` and `Div` respectively. +/// `Shl` and `Shr` are handled differently, by leveraging `Mul` and `Div` +/// respectively. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(crate) enum BinaryOperator { Add, @@ -90,7 +91,8 @@ impl BinaryOperator { } } - /// Maps a binary arithmetic operation to its associated flag column in the trace. + /// Maps a binary arithmetic operation to its associated flag column in the + /// trace. pub(crate) const fn row_filter(&self) -> usize { match self { BinaryOperator::Add => columns::IS_ADD, @@ -120,7 +122,8 @@ pub(crate) enum TernaryOperator { } impl TernaryOperator { - /// Computes the result of a ternary arithmetic operation given three inputs. + /// Computes the result of a ternary arithmetic operation given three + /// inputs. pub(crate) fn result(&self, input0: U256, input1: U256, input2: U256) -> U256 { match self { TernaryOperator::AddMod => addmod(input0, input1, input2), @@ -129,7 +132,8 @@ impl TernaryOperator { } } - /// Maps a ternary arithmetic operation to its associated flag column in the trace. + /// Maps a ternary arithmetic operation to its associated flag column in the + /// trace. pub(crate) const fn row_filter(&self) -> usize { match self { TernaryOperator::AddMod => columns::IS_ADDMOD, @@ -139,7 +143,8 @@ impl TernaryOperator { } } -/// An enum representing arithmetic operations that can be either binary or ternary. +/// An enum representing arithmetic operations that can be either binary or +/// ternary. #[allow(clippy::enum_variant_names)] #[derive(Debug)] pub(crate) enum Operation { @@ -242,7 +247,8 @@ impl Operation { /// /// The `is_simulated` bool indicates whether we use a native arithmetic /// operation or simulate one with another. This is used to distinguish - /// SHL and SHR operations that are simulated through MUL and DIV respectively. + /// SHL and SHR operations that are simulated through MUL and DIV + /// respectively. fn to_rows(&self) -> (Vec, Option>) { match *self { Operation::BinaryOperation { @@ -269,7 +275,8 @@ impl Operation { } } -/// Converts a ternary arithmetic operation to one or two rows of the `ArithmeticStark` table. +/// Converts a ternary arithmetic operation to one or two rows of the +/// `ArithmeticStark` table. fn ternary_op_to_rows( row_filter: usize, input0: U256, @@ -287,7 +294,8 @@ fn ternary_op_to_rows( (row1, Some(row2)) } -/// Converts a binary arithmetic operation to one or two rows of the `ArithmeticStark` table. +/// Converts a binary arithmetic operation to one or two rows of the +/// `ArithmeticStark` table. fn binary_op_to_rows( op: BinaryOperator, input0: U256, diff --git a/src/arithmetic/modular.rs b/evm/src/arithmetic/modular.rs similarity index 98% rename from src/arithmetic/modular.rs rename to evm/src/arithmetic/modular.rs index a3806862a..df9499397 100644 --- a/src/arithmetic/modular.rs +++ b/evm/src/arithmetic/modular.rs @@ -81,8 +81,8 @@ //! - given modulus can be 0 or non-zero //! - updated modulus is same as given //! - if modulus is non-zero, correct output is obtained -//! - if modulus is 0, then the test output < modulus, checking that -//! the output is reduced, will fail, because output is non-negative. +//! - if modulus is 0, then the test output < modulus, checking that the +//! output is reduced, will fail, because output is non-negative. //! //! In the case of DIV, we do something similar, except that we "replace" //! the modulus with "2^256" to force the quotient to be zero. @@ -95,18 +95,17 @@ //! general modular code, they also take 144 columns. Possible //! improvements: //! -//! - We could reduce the number of columns to 112 for ADDMOD, SUBMOD, -//! etc. if they were implemented separately, so they don't pay the -//! full cost of the general MULMOD. +//! - We could reduce the number of columns to 112 for ADDMOD, SUBMOD, etc. if +//! they were implemented separately, so they don't pay the full cost of the +//! general MULMOD. //! -//! - All these operations could have alternative forms where the -//! output was not guaranteed to be reduced, which is often sufficient -//! in practice, and which would save a further 16 columns. +//! - All these operations could have alternative forms where the output was not +//! guaranteed to be reduced, which is often sufficient in practice, and which +//! would save a further 16 columns. //! //! - If the modulus is known in advance (such as for elliptic curve -//! arithmetic), specialised handling of MULMOD in that case would -//! only require 96 columns, or 80 if the output doesn't need to be -//! reduced. +//! arithmetic), specialised handling of MULMOD in that case would only +//! require 96 columns, or 80 if the output doesn't need to be reduced. use core::ops::Range; diff --git a/src/arithmetic/mul.rs b/evm/src/arithmetic/mul.rs similarity index 99% rename from src/arithmetic/mul.rs rename to evm/src/arithmetic/mul.rs index 112ef7ebb..978543537 100644 --- a/src/arithmetic/mul.rs +++ b/evm/src/arithmetic/mul.rs @@ -67,7 +67,8 @@ use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsume use crate::arithmetic::columns::*; use crate::arithmetic::utils::*; -/// Given the two limbs of `left_in` and `right_in`, computes `left_in * right_in`. +/// Given the two limbs of `left_in` and `right_in`, computes `left_in * +/// right_in`. pub(crate) fn generate_mul(lv: &mut [F], left_in: [i64; 16], right_in: [i64; 16]) { const MASK: i64 = (1i64 << LIMB_BITS) - 1i64; diff --git a/src/arithmetic/shift.rs b/evm/src/arithmetic/shift.rs similarity index 98% rename from src/arithmetic/shift.rs rename to evm/src/arithmetic/shift.rs index bc6276b1b..5b59073e3 100644 --- a/src/arithmetic/shift.rs +++ b/evm/src/arithmetic/shift.rs @@ -71,8 +71,9 @@ pub(crate) fn generate( // We generate the multiplication input0 * input1 using mul.rs. mul::generate_mul(lv, input0, input1); } else { - // If the operation is SHR, we compute: `input / shifted_displacement` if `shifted_displacement == 0` - // otherwise, the output is 0. We use the logic in divmod.rs to achieve that. + // If the operation is SHR, we compute: `input / shifted_displacement` if + // `shifted_displacement == 0` otherwise, the output is 0. We use the + // logic in divmod.rs to achieve that. divmod::generate_divmod(lv, nv, IS_SHR, INPUT_REGISTER_1, INPUT_REGISTER_2); } } diff --git a/src/arithmetic/utils.rs b/evm/src/arithmetic/utils.rs similarity index 99% rename from src/arithmetic/utils.rs rename to evm/src/arithmetic/utils.rs index 7350dd326..a429d5f17 100644 --- a/src/arithmetic/utils.rs +++ b/evm/src/arithmetic/utils.rs @@ -319,7 +319,8 @@ pub(crate) fn read_value_i64_limbs( } #[inline] -/// Turn a 64-bit integer into 4 16-bit limbs and convert them to field elements. +/// Turn a 64-bit integer into 4 16-bit limbs and convert them to field +/// elements. fn u64_to_array(out: &mut [F], x: u64) { const_assert!(LIMB_BITS == 16); debug_assert!(out.len() == 4); @@ -330,7 +331,8 @@ fn u64_to_array(out: &mut [F], x: u64) { out[3] = F::from_canonical_u16((x >> 48) as u16); } -/// Turn a 256-bit integer into 16 16-bit limbs and convert them to field elements. +/// Turn a 256-bit integer into 16 16-bit limbs and convert them to field +/// elements. // TODO: Refactor/replace u256_limbs in evm/src/util.rs pub(crate) fn u256_to_array(out: &mut [F], x: U256) { const_assert!(N_LIMBS == 16); diff --git a/src/bin/assemble.rs b/evm/src/bin/assemble.rs similarity index 100% rename from src/bin/assemble.rs rename to evm/src/bin/assemble.rs diff --git a/src/byte_packing/byte_packing_stark.rs b/evm/src/byte_packing/byte_packing_stark.rs similarity index 93% rename from src/byte_packing/byte_packing_stark.rs rename to evm/src/byte_packing/byte_packing_stark.rs index 14cf61d5e..8708e2040 100644 --- a/src/byte_packing/byte_packing_stark.rs +++ b/evm/src/byte_packing/byte_packing_stark.rs @@ -1,7 +1,8 @@ //! This crate enforces the correctness of reading and writing sequences //! of bytes in Big-Endian ordering from and to the memory. //! -//! The trace layout consists in one row for an `N` byte sequence (where 32 ≥ `N` > 0). +//! The trace layout consists in one row for an `N` byte sequence (where 32 ≥ +//! `N` > 0). //! //! At each row the `i`-th byte flag will be activated to indicate a sequence of //! length i+1. @@ -12,18 +13,19 @@ //! //! where b[i] is the `i`-th byte flag. //! -//! Because of the discrepancy in endianness between the different tables, the byte sequences -//! are actually written in the trace in reverse order from the order they are provided. -//! We only store the virtual address `virt` of the first byte, and the virtual address for byte `i` -//! can be recovered as: +//! Because of the discrepancy in endianness between the different tables, the +//! byte sequences are actually written in the trace in reverse order from the +//! order they are provided. We only store the virtual address `virt` of the +//! first byte, and the virtual address for byte `i` can be recovered as: //! virt_i = virt + sequence_length - 1 - i //! -//! Note that, when writing a sequence of bytes to memory, both the `U256` value and the -//! corresponding sequence length are being read from the stack. Because of the endianness -//! discrepancy mentioned above, we first convert the value to a byte sequence in Little-Endian, -//! then resize the sequence to prune unneeded zeros before reverting the sequence order. -//! This means that the higher-order bytes will be thrown away during the process, if the value -//! is greater than 256^length, and as a result a different value will be stored in memory. +//! Note that, when writing a sequence of bytes to memory, both the `U256` value +//! and the corresponding sequence length are being read from the stack. Because +//! of the endianness discrepancy mentioned above, we first convert the value to +//! a byte sequence in Little-Endian, then resize the sequence to prune unneeded +//! zeros before reverting the sequence order. This means that the higher-order +//! bytes will be thrown away during the process, if the value is greater than +//! 256^length, and as a result a different value will be stored in memory. use core::marker::PhantomData; @@ -53,8 +55,9 @@ use crate::witness::memory::MemoryAddress; /// Strict upper bound for the individual bytes range-check. const BYTE_RANGE_MAX: usize = 1usize << 8; -/// Creates the vector of `Columns` for `BytePackingStark` corresponding to the final packed limbs being read/written. -/// `CpuStark` will look into these columns, as the CPU needs the output of byte packing. +/// Creates the vector of `Columns` for `BytePackingStark` corresponding to the +/// final packed limbs being read/written. `CpuStark` will look into these +/// columns, as the CPU needs the output of byte packing. pub(crate) fn ctl_looked_data() -> Vec> { // Reconstruct the u32 limbs composing the final `U256` word // being read/written from the underlying byte values. For each, @@ -89,7 +92,8 @@ pub(crate) fn ctl_looked_filter() -> Filter { Filter::new_simple(Column::sum((0..NUM_BYTES).map(index_len))) } -/// Column linear combination for the `BytePackingStark` table reading/writing the `i`th byte sequence from `MemoryStark`. +/// Column linear combination for the `BytePackingStark` table reading/writing +/// the `i`th byte sequence from `MemoryStark`. pub(crate) fn ctl_looking_memory(i: usize) -> Vec> { let mut res = Column::singles([IS_READ, ADDR_CONTEXT, ADDR_SEGMENT]).collect_vec(); @@ -117,7 +121,8 @@ pub(crate) fn ctl_looking_memory(i: usize) -> Vec> { res } -/// CTL filter for reading/writing the `i`th byte of the byte sequence from/to memory. +/// CTL filter for reading/writing the `i`th byte of the byte sequence from/to +/// memory. pub(crate) fn ctl_looking_memory_filter(i: usize) -> Filter { Filter::new_simple(Column::sum((i..NUM_BYTES).map(index_len))) } @@ -314,7 +319,8 @@ impl, const D: usize> Stark for BytePackingSt // Check that all limbs after final length are 0. for i in 0..NUM_BYTES - 1 { - // If the length is i+1, then value_bytes(i+1),...,value_bytes(NUM_BYTES-1) must be 0. + // If the length is i+1, then value_bytes(i+1),...,value_bytes(NUM_BYTES-1) must + // be 0. for j in i + 1..NUM_BYTES { yield_constr.constraint(local_values[index_len(i)] * local_values[value_bytes(j)]); } @@ -377,7 +383,8 @@ impl, const D: usize> Stark for BytePackingSt // Check that all limbs after final length are 0. for i in 0..NUM_BYTES - 1 { - // If the length is i+1, then value_bytes(i+1),...,value_bytes(NUM_BYTES-1) must be 0. + // If the length is i+1, then value_bytes(i+1),...,value_bytes(NUM_BYTES-1) must + // be 0. for j in i + 1..NUM_BYTES { let constr = builder.mul_extension(local_values[index_len(i)], local_values[value_bytes(j)]); diff --git a/src/byte_packing/columns.rs b/evm/src/byte_packing/columns.rs similarity index 98% rename from src/byte_packing/columns.rs rename to evm/src/byte_packing/columns.rs index cbed53de1..45e3211de 100644 --- a/src/byte_packing/columns.rs +++ b/evm/src/byte_packing/columns.rs @@ -16,7 +16,8 @@ pub(crate) const fn index_len(i: usize) -> usize { LEN_INDICES_START + i } -// Note: Those are used to obtain the length of a sequence of bytes being processed. +// Note: Those are used to obtain the length of a sequence of bytes being +// processed. pub(crate) const LEN_INDICES_COLS: Range = LEN_INDICES_START..LEN_INDICES_START + NUM_BYTES; pub(crate) const ADDR_CONTEXT: usize = LEN_INDICES_START + NUM_BYTES; diff --git a/src/byte_packing/mod.rs b/evm/src/byte_packing/mod.rs similarity index 100% rename from src/byte_packing/mod.rs rename to evm/src/byte_packing/mod.rs diff --git a/src/cpu/byte_unpacking.rs b/evm/src/cpu/byte_unpacking.rs similarity index 96% rename from src/cpu/byte_unpacking.rs rename to evm/src/cpu/byte_unpacking.rs index 4de1855da..768d8c84f 100644 --- a/src/cpu/byte_unpacking.rs +++ b/evm/src/cpu/byte_unpacking.rs @@ -19,7 +19,8 @@ pub(crate) fn eval_packed( // The address to write to is stored in the first memory channel. // It contains virt, segment, ctx in its first 3 limbs, and 0 otherwise. - // The new address is identical, except for its `virtual` limb that is increased by the corresponding `len` offset. + // The new address is identical, except for its `virtual` limb that is increased + // by the corresponding `len` offset. let new_addr = nv.mem_channels[0].value; let written_addr = lv.mem_channels[0].value; @@ -57,7 +58,8 @@ pub(crate) fn eval_ext_circuit, const D: usize>( // The address to write to is stored in the first memory channel. // It contains virt, segment, ctx in its first 3 limbs, and 0 otherwise. - // The new address is identical, except for its `virtual` limb that is increased by the corresponding `len` offset. + // The new address is identical, except for its `virtual` limb that is increased + // by the corresponding `len` offset. let new_addr = nv.mem_channels[0].value; let written_addr = lv.mem_channels[0].value; diff --git a/src/cpu/clock.rs b/evm/src/cpu/clock.rs similarity index 100% rename from src/cpu/clock.rs rename to evm/src/cpu/clock.rs diff --git a/src/cpu/columns/general.rs b/evm/src/cpu/columns/general.rs similarity index 88% rename from src/cpu/columns/general.rs rename to evm/src/cpu/columns/general.rs index f565acc62..8b2c2bc41 100644 --- a/src/cpu/columns/general.rs +++ b/evm/src/cpu/columns/general.rs @@ -2,8 +2,8 @@ use core::borrow::{Borrow, BorrowMut}; use core::fmt::{Debug, Formatter}; use core::mem::{size_of, transmute}; -/// General purpose columns, which can have different meanings depending on what CTL or other -/// operation is occurring at this row. +/// General purpose columns, which can have different meanings depending on what +/// CTL or other operation is occurring at this row. #[derive(Clone, Copy)] pub(crate) union CpuGeneralColumnsView { exception: CpuExceptionView, @@ -14,14 +14,16 @@ pub(crate) union CpuGeneralColumnsView { } impl CpuGeneralColumnsView { - /// View of the columns used for exceptions: they are the exception code bits. - /// SAFETY: Each view is a valid interpretation of the underlying array. + /// View of the columns used for exceptions: they are the exception code + /// bits. SAFETY: Each view is a valid interpretation of the underlying + /// array. pub(crate) fn exception(&self) -> &CpuExceptionView { unsafe { &self.exception } } - /// Mutable view of the column required for exceptions: they are the exception code bits. - /// SAFETY: Each view is a valid interpretation of the underlying array. + /// Mutable view of the column required for exceptions: they are the + /// exception code bits. SAFETY: Each view is a valid interpretation of + /// the underlying array. pub(crate) fn exception_mut(&mut self) -> &mut CpuExceptionView { unsafe { &mut self.exception } } @@ -112,14 +114,17 @@ pub(crate) struct CpuExceptionView { pub(crate) exc_code_bits: [T; 3], } -/// View of the `CpuGeneralColumns` storing pseudo-inverses used to prove logic operations. +/// View of the `CpuGeneralColumns` storing pseudo-inverses used to prove logic +/// operations. #[derive(Copy, Clone)] pub(crate) struct CpuLogicView { - /// Pseudoinverse of `(input0 - input1)`. Used prove that they are unequal. Assumes 32-bit limbs. + /// Pseudoinverse of `(input0 - input1)`. Used prove that they are unequal. + /// Assumes 32-bit limbs. pub(crate) diff_pinv: [T; 8], } -/// View of the first two `CpuGeneralColumns` storing a flag and a pseudoinverse used to prove jumps. +/// View of the first two `CpuGeneralColumns` storing a flag and a pseudoinverse +/// used to prove jumps. #[derive(Copy, Clone)] pub(crate) struct CpuJumpsView { /// A flag indicating whether a jump should occur. @@ -128,7 +133,8 @@ pub(crate) struct CpuJumpsView { pub(crate) cond_sum_pinv: T, } -/// View of the first `CpuGeneralColumns` storing a pseudoinverse used to prove shift operations. +/// View of the first `CpuGeneralColumns` storing a pseudoinverse used to prove +/// shift operations. #[derive(Copy, Clone)] pub(crate) struct CpuShiftView { /// For a shift amount of displacement: [T], this is the inverse of @@ -136,9 +142,10 @@ pub(crate) struct CpuShiftView { pub(crate) high_limb_sum_inv: T, } -/// View of the last four `CpuGeneralColumns` storing stack-related variables. The first three are used -/// for conditionally enabling and disabling channels when reading the next `stack_top`, and the fourth one -/// is used to check for stack overflow. +/// View of the last four `CpuGeneralColumns` storing stack-related variables. +/// The first three are used for conditionally enabling and disabling channels +/// when reading the next `stack_top`, and the fourth one is used to check for +/// stack overflow. #[derive(Copy, Clone)] pub(crate) struct CpuStackView { _unused: [T; 4], @@ -148,7 +155,8 @@ pub(crate) struct CpuStackView { pub(crate) stack_inv_aux: T, /// Used to reduce the degree of stack constraints when needed. pub(crate) stack_inv_aux_2: T, - /// Pseudoinverse of `nv.stack_len - (MAX_USER_STACK_SIZE + 1)` to check for stack overflow. + /// Pseudoinverse of `nv.stack_len - (MAX_USER_STACK_SIZE + 1)` to check for + /// stack overflow. pub(crate) stack_len_bounds_aux: T, } diff --git a/src/cpu/columns/mod.rs b/evm/src/cpu/columns/mod.rs similarity index 96% rename from src/cpu/columns/mod.rs rename to evm/src/cpu/columns/mod.rs index 92da4e997..d4abd71d9 100644 --- a/src/cpu/columns/mod.rs +++ b/evm/src/cpu/columns/mod.rs @@ -22,18 +22,21 @@ pub type MemValue = [T; memory::VALUE_LIMBS]; #[repr(C)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(crate) struct MemoryChannelView { - /// 1 if this row includes a memory operation in the `i`th channel of the memory bus, otherwise - /// 0. + /// 1 if this row includes a memory operation in the `i`th channel of the + /// memory bus, otherwise 0. pub used: T, - /// 1 if a read is performed on the `i`th channel of the memory bus, otherwise 0. + /// 1 if a read is performed on the `i`th channel of the memory bus, + /// otherwise 0. pub is_read: T, /// Context of the memory operation in the `i`th channel of the memory bus. pub addr_context: T, /// Segment of the memory operation in the `ith` channel of the memory bus. pub addr_segment: T, - /// Virtual address of the memory operation in the `ith` channel of the memory bus. + /// Virtual address of the memory operation in the `ith` channel of the + /// memory bus. pub addr_virtual: T, - /// Value, subdivided into 32-bit limbs, stored in the `ith` channel of the memory bus. + /// Value, subdivided into 32-bit limbs, stored in the `ith` channel of the + /// memory bus. pub value: MemValue, } @@ -71,8 +74,8 @@ pub(crate) struct CpuColumnsView { /// If CPU cycle: Gas counter. pub gas: T, - /// If CPU cycle: flags for EVM instructions (a few cannot be shared; see the comments in - /// `OpsColumnsView`). + /// If CPU cycle: flags for EVM instructions (a few cannot be shared; see + /// the comments in `OpsColumnsView`). pub op: OpsColumnsView, /// If CPU cycle: the opcode, broken up into bits in little-endian order. diff --git a/src/cpu/columns/ops.rs b/evm/src/cpu/columns/ops.rs similarity index 100% rename from src/cpu/columns/ops.rs rename to evm/src/cpu/columns/ops.rs diff --git a/src/cpu/contextops.rs b/evm/src/cpu/contextops.rs similarity index 96% rename from src/cpu/contextops.rs rename to evm/src/cpu/contextops.rs index 9a0bb7483..6a7abed89 100644 --- a/src/cpu/contextops.rs +++ b/evm/src/cpu/contextops.rs @@ -152,8 +152,8 @@ fn eval_packed_set( yield_constr.constraint(filter * limb); } - // The old SP is decremented (since the new context was popped) and stored in memory. - // The new SP is loaded from memory. + // The old SP is decremented (since the new context was popped) and stored in + // memory. The new SP is loaded from memory. // This is all done with CTLs: nothing is constrained here. // Constrain stack_inv_aux_2. @@ -163,7 +163,8 @@ fn eval_packed_set( * (lv.general.stack().stack_inv_aux * lv.opcode_bits[0] - lv.general.stack().stack_inv_aux_2), ); - // The new top is loaded in memory channel 2, if the stack isn't empty (see eval_packed). + // The new top is loaded in memory channel 2, if the stack isn't empty (see + // eval_packed). for (&limb_new_top, &limb_read_top) in new_top_channel .value .iter() @@ -201,8 +202,8 @@ fn eval_ext_circuit_set, const D: usize>( yield_constr.constraint(builder, constr); } - // The old SP is decremented (since the new context was popped) and stored in memory. - // The new SP is loaded from memory. + // The old SP is decremented (since the new context was popped) and stored in + // memory. The new SP is loaded from memory. // This is all done with CTLs: nothing is constrained here. // Constrain stack_inv_aux_2. @@ -216,7 +217,8 @@ fn eval_ext_circuit_set, const D: usize>( let constr = builder.mul_extension(lv.op.context_op, diff); yield_constr.constraint(builder, constr); } - // The new top is loaded in memory channel 2, if the stack isn't empty (see eval_packed). + // The new top is loaded in memory channel 2, if the stack isn't empty (see + // eval_packed). for (&limb_new_top, &limb_read_top) in new_top_channel .value .iter() @@ -251,8 +253,9 @@ pub(crate) fn eval_packed( // we can constrain both at the same time. let filter = lv.op.context_op; let channel = lv.mem_channels[2]; - // For get_context, we check if lv.stack_len is 0. For set_context, we check if nv.stack_len is 0. - // However, for get_context, we can deduce lv.stack_len from nv.stack_len since the operation only pushes. + // For get_context, we check if lv.stack_len is 0. For set_context, we check if + // nv.stack_len is 0. However, for get_context, we can deduce lv.stack_len + // from nv.stack_len since the operation only pushes. let stack_len = nv.stack_len - (P::ONES - lv.opcode_bits[0]); // Constrain stack_inv_aux. It's 0 if the relevant stack is empty, 1 otherwise. yield_constr.constraint( @@ -292,8 +295,9 @@ pub(crate) fn eval_ext_circuit, const D: usize>( // we can constrain both at the same time. let filter = lv.op.context_op; let channel = lv.mem_channels[2]; - // For get_context, we check if lv.stack_len is 0. For set_context, we check if nv.stack_len is 0. - // However, for get_context, we can deduce lv.stack_len from nv.stack_len since the operation only pushes. + // For get_context, we check if lv.stack_len is 0. For set_context, we check if + // nv.stack_len is 0. However, for get_context, we can deduce lv.stack_len + // from nv.stack_len since the operation only pushes. let diff = builder.add_const_extension(lv.opcode_bits[0], -F::ONE); let stack_len = builder.add_extension(nv.stack_len, diff); // Constrain stack_inv_aux. It's 0 if the relevant stack is empty, 1 otherwise. diff --git a/src/cpu/control_flow.rs b/evm/src/cpu/control_flow.rs similarity index 87% rename from src/cpu/control_flow.rs rename to evm/src/cpu/control_flow.rs index a28874624..832db1961 100644 --- a/src/cpu/control_flow.rs +++ b/evm/src/cpu/control_flow.rs @@ -53,16 +53,17 @@ pub(crate) fn eval_packed_generic( let next_halt_state = P::ONES - is_cpu_cycle_next; - // Once we start executing instructions, then we continue until the end of the table - // or we reach dummy padding rows. This, along with the constraints on the first row, - // enforces that operation flags and the halt flag are mutually exclusive over the entire - // CPU trace. + // Once we start executing instructions, then we continue until the end of the + // table or we reach dummy padding rows. This, along with the constraints on + // the first row, enforces that operation flags and the halt flag are + // mutually exclusive over the entire CPU trace. yield_constr .constraint_transition(is_cpu_cycle * (is_cpu_cycle_next + next_halt_state - P::ONES)); - // If a row is a CPU cycle and executing a native instruction (implemented as a table row; not - // microcoded) then the program counter is incremented by 1 to obtain the next row's program - // counter. Also, the next row has the same kernel flag. + // If a row is a CPU cycle and executing a native instruction (implemented as a + // table row; not microcoded) then the program counter is incremented by 1 + // to obtain the next row's program counter. Also, the next row has the same + // kernel flag. let is_native_instruction: P = NATIVE_INSTRUCTIONS.iter().map(|&col_i| lv[col_i]).sum(); yield_constr.constraint_transition( is_native_instruction * (lv.program_counter - nv.program_counter + P::ONES), @@ -78,7 +79,8 @@ pub(crate) fn eval_packed_generic( yield_constr.constraint_transition(is_prover_input * (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 `main` (the entry point of our kernel), + // - the `program_counter` of the CPU cycle row is `main` (the entry point of + // our kernel), // - execution is in kernel mode, and // - the stack is empty. let is_last_noncpu_cycle = (is_cpu_cycle - P::ONES) * is_cpu_cycle_next; @@ -103,19 +105,20 @@ pub(crate) fn eval_ext_circuit, const D: usize>( let next_halt_state = builder.sub_extension(one, is_cpu_cycle_next); - // Once we start executing instructions, then we continue until the end of the table - // or we reach dummy padding rows. This, along with the constraints on the first row, - // enforces that operation flags and the halt flag are mutually exclusive over the entire - // CPU trace. + // Once we start executing instructions, then we continue until the end of the + // table or we reach dummy padding rows. This, along with the constraints on + // the first row, enforces that operation flags and the halt flag are + // mutually exclusive over the entire CPU trace. { let constr = builder.add_extension(is_cpu_cycle_next, next_halt_state); let constr = builder.mul_sub_extension(is_cpu_cycle, constr, is_cpu_cycle); yield_constr.constraint_transition(builder, constr); } - // If a row is a CPU cycle and executing a native instruction (implemented as a table row; not - // microcoded) then the program counter is incremented by 1 to obtain the next row's program - // counter. Also, the next row has the same kernel flag. + // If a row is a CPU cycle and executing a native instruction (implemented as a + // table row; not microcoded) then the program counter is incremented by 1 + // to obtain the next row's program counter. Also, the next row has the same + // kernel flag. { let filter = builder.add_many_extension(NATIVE_INSTRUCTIONS.iter().map(|&col_i| lv[col_i])); let pc_diff = builder.sub_extension(lv.program_counter, nv.program_counter); @@ -138,7 +141,8 @@ pub(crate) 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 `main` (the entry point of our kernel), + // - the `program_counter` of the CPU cycle row is `main` (the entry point of + // our kernel), // - execution is in kernel mode, and // - the stack is empty. { diff --git a/src/cpu/cpu_stark.rs b/evm/src/cpu/cpu_stark.rs similarity index 95% rename from src/cpu/cpu_stark.rs rename to evm/src/cpu/cpu_stark.rs index 340eede50..4e60694b0 100644 --- a/src/cpu/cpu_stark.rs +++ b/evm/src/cpu/cpu_stark.rs @@ -27,8 +27,9 @@ use crate::cpu::{ use crate::memory::segments::Segment; use crate::memory::{NUM_CHANNELS, VALUE_LIMBS}; -/// Creates the vector of `Columns` corresponding to the General Purpose channels when calling the Keccak sponge: -/// the CPU reads the output of the sponge directly from the `KeccakSpongeStark` table. +/// Creates the vector of `Columns` corresponding to the General Purpose +/// channels when calling the Keccak sponge: the CPU reads the output of the +/// sponge directly from the `KeccakSpongeStark` table. pub(crate) fn ctl_data_keccak_sponge() -> Vec> { // When executing KECCAK_GENERAL, the GP memory channels are used as follows: // GP channel 0: stack[-1] = addr (context, segment, virt) @@ -81,9 +82,11 @@ fn ctl_data_ternops() -> Vec> { res } -/// Creates the vector of columns corresponding to the opcode, the two inputs and the output of the logic operation. +/// Creates the vector of columns corresponding to the opcode, the two inputs +/// and the output of the logic operation. pub(crate) fn ctl_data_logic() -> Vec> { - // Instead of taking single columns, we reconstruct the entire opcode value directly. + // Instead of taking single columns, we reconstruct the entire opcode value + // directly. let mut res = vec![Column::le_bits(COL_MAP.opcode_bits)]; res.extend(ctl_data_binops()); res @@ -94,9 +97,11 @@ pub(crate) fn ctl_filter_logic() -> Filter { Filter::new_simple(Column::single(COL_MAP.op.logic_op)) } -/// Returns the `TableWithColumns` for the CPU rows calling arithmetic operations. +/// Returns the `TableWithColumns` for the CPU rows calling arithmetic +/// operations. pub(crate) fn ctl_arithmetic_base_rows() -> TableWithColumns { - // Instead of taking single columns, we reconstruct the entire opcode value directly. + // Instead of taking single columns, we reconstruct the entire opcode value + // directly. let mut columns = vec![Column::le_bits(COL_MAP.opcode_bits)]; columns.extend(ctl_data_ternops()); // Create the CPU Table whose columns are those with the three @@ -125,8 +130,10 @@ pub(crate) fn ctl_arithmetic_base_rows() -> TableWithColumns { ) } -/// Creates the vector of `Columns` corresponding to the contents of General Purpose channels when calling byte packing. -/// We use `ctl_data_keccak_sponge` because the `Columns` are the same as the ones computed for `KeccakSpongeStark`. +/// Creates the vector of `Columns` corresponding to the contents of General +/// Purpose channels when calling byte packing. We use `ctl_data_keccak_sponge` +/// because the `Columns` are the same as the ones computed for +/// `KeccakSpongeStark`. pub(crate) fn ctl_data_byte_packing() -> Vec> { let mut res = vec![Column::constant(F::ONE)]; // is_read res.extend(ctl_data_keccak_sponge()); @@ -134,7 +141,8 @@ pub(crate) fn ctl_data_byte_packing() -> Vec> { } /// CTL filter for the `MLOAD_32BYTES` operation. -/// MLOAD_32 BYTES is differentiated from MSTORE_32BYTES by its fifth bit set to 1. +/// MLOAD_32 BYTES is differentiated from MSTORE_32BYTES by its fifth bit set to +/// 1. pub(crate) fn ctl_filter_byte_packing() -> Filter { Filter::new( vec![( @@ -145,7 +153,8 @@ pub(crate) fn ctl_filter_byte_packing() -> Filter { ) } -/// Creates the vector of `Columns` corresponding to the contents of General Purpose channels when calling byte unpacking. +/// Creates the vector of `Columns` corresponding to the contents of General +/// Purpose channels when calling byte unpacking. pub(crate) fn ctl_data_byte_unpacking() -> Vec> { let is_read = Column::constant(F::ZERO); @@ -180,7 +189,8 @@ pub(crate) fn ctl_data_byte_unpacking() -> Vec> { } /// CTL filter for the `MSTORE_32BYTES` operation. -/// MSTORE_32BYTES is differentiated from MLOAD_32BYTES by its fifth bit set to 0. +/// MSTORE_32BYTES is differentiated from MLOAD_32BYTES by its fifth bit set to +/// 0. pub(crate) fn ctl_filter_byte_unpacking() -> Filter { Filter::new( vec![( @@ -191,8 +201,9 @@ pub(crate) fn ctl_filter_byte_unpacking() -> Filter { ) } -/// Creates the vector of `Columns` corresponding to three consecutive (byte) reads in memory. -/// It's used by syscalls and exceptions to read an address in a jumptable. +/// Creates the vector of `Columns` corresponding to three consecutive (byte) +/// reads in memory. It's used by syscalls and exceptions to read an address in +/// a jumptable. pub(crate) fn ctl_data_jumptable_read() -> Vec> { let is_read = Column::constant(F::ONE); let mut res = vec![is_read]; @@ -225,8 +236,9 @@ pub(crate) fn ctl_filter_syscall_exceptions() -> Filter { Filter::new_simple(Column::sum([COL_MAP.op.syscall, COL_MAP.op.exception])) } -/// Creates the vector of `Columns` corresponding to the contents of the CPU registers when performing a `PUSH`. -/// `PUSH` internal reads are done by calling `BytePackingStark`. +/// Creates the vector of `Columns` corresponding to the contents of the CPU +/// registers when performing a `PUSH`. `PUSH` internal reads are done by +/// calling `BytePackingStark`. pub(crate) fn ctl_data_byte_packing_push() -> Vec> { let is_read = Column::constant(F::ONE); let context = Column::single(COL_MAP.code_context); @@ -236,7 +248,8 @@ pub(crate) fn ctl_data_byte_packing_push() -> Vec> { Column::linear_combination_with_constant([(COL_MAP.program_counter, F::ONE)], F::ONE); let val = Column::singles_next_row(COL_MAP.mem_channels[0].value); - // We fetch the length from the `PUSH` opcode lower bits, that indicate `len - 1`. + // We fetch the length from the `PUSH` opcode lower bits, that indicate `len - + // 1`. let len = Column::le_bits_with_constant(&COL_MAP.opcode_bits[0..5], F::ONE); let num_channels = F::from_canonical_usize(NUM_CHANNELS); @@ -283,7 +296,8 @@ fn mem_time_and_channel(channel: usize) -> Column { Column::linear_combination_with_constant([(COL_MAP.clock, scalar)], addend) } -/// Creates the vector of `Columns` corresponding to the contents of the code channel when reading code values. +/// Creates the vector of `Columns` corresponding to the contents of the code +/// channel when reading code values. pub(crate) fn ctl_data_code_memory() -> Vec> { let mut cols = vec![ Column::constant(F::ONE), // is_read @@ -303,7 +317,8 @@ pub(crate) fn ctl_data_code_memory() -> Vec> { cols } -/// Creates the vector of `Columns` corresponding to the contents of General Purpose channels. +/// Creates the vector of `Columns` corresponding to the contents of General +/// Purpose channels. pub(crate) fn ctl_data_gp_memory(channel: usize) -> Vec> { let channel_map = COL_MAP.mem_channels[channel]; let mut cols: Vec<_> = Column::singles([ @@ -346,10 +361,10 @@ pub(crate) fn ctl_data_memory_old_sp_write_set_context() -> Vec() -> Vec( // Finally, classify all opcodes, together with the kernel flag, into blocks 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. + // 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, @@ -126,8 +129,8 @@ pub(crate) fn eval_packed_generic( }) .sum(); - // If unavailable + opcode_mismatch is 0, then the opcode bits all match and we are in the - // correct mode. + // If unavailable + opcode_mismatch is 0, then the opcode bits all match and we + // are in the correct mode. yield_constr.constraint(lv[col] * (unavailable + opcode_mismatch)); } @@ -269,8 +272,8 @@ pub(crate) fn eval_ext_circuit, const D: usize>( // Finally, classify all opcodes, together with the kernel flag, into blocks 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. + // 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), @@ -290,8 +293,8 @@ pub(crate) fn eval_ext_circuit, const D: usize>( builder.add_extension(cumul, to_add) }); - // If unavailable + opcode_mismatch is 0, then the opcode bits all match and we are in the - // correct mode. + // If unavailable + opcode_mismatch is 0, then the opcode bits all match and we + // are in the correct mode. let constr = builder.add_extension(unavailable, opcode_mismatch); let constr = builder.mul_extension(lv[col], constr); yield_constr.constraint(builder, constr); diff --git a/src/cpu/dup_swap.rs b/evm/src/cpu/dup_swap.rs similarity index 96% rename from src/cpu/dup_swap.rs rename to evm/src/cpu/dup_swap.rs index e67eaa625..e4aee6b75 100644 --- a/src/cpu/dup_swap.rs +++ b/evm/src/cpu/dup_swap.rs @@ -39,8 +39,8 @@ fn channels_equal_ext_circuit, const D: usize>( /// Set `used`, `is_read`, and address for channel. /// -/// `offset` is the stack index before this instruction is executed, e.g. `0` for the top of the -/// stack. +/// `offset` is the stack index before this instruction is executed, e.g. `0` +/// for the top of the stack. fn constrain_channel_packed( is_read: bool, filter: P, @@ -62,8 +62,8 @@ fn constrain_channel_packed( /// Set `used`, `is_read`, and address for channel. /// -/// `offset` is the stack index before this instruction is executed, e.g. `0` for the top of the -/// stack. +/// `offset` is the stack index before this instruction is executed, e.g. `0` +/// for the top of the stack. fn constrain_channel_ext_circuit, const D: usize>( builder: &mut CircuitBuilder, is_read: bool, @@ -217,13 +217,15 @@ fn eval_packed_swap( let in2_channel = &lv.mem_channels[1]; let out_channel = &lv.mem_channels[2]; - // Constrain the first input channel value to be equal to the output channel value. + // Constrain the first input channel value to be equal to the output channel + // value. channels_equal_packed(filter, in1_channel, out_channel, yield_constr); - // We set `is_read`, `used` and the address for the first input. The first input is - // read from the top of the stack, and is therefore not a memory read. + // We set `is_read`, `used` and the address for the first input. The first input + // is read from the top of the stack, and is therefore not a memory read. constrain_channel_packed(false, filter, n_plus_one, out_channel, lv, yield_constr); - // Constrain the second input channel value to be equal to the new top of the stack. + // Constrain the second input channel value to be equal to the new top of the + // stack. channels_equal_packed(filter, in2_channel, &nv.mem_channels[0], yield_constr); // We set `is_read`, `used` and the address for the second input. constrain_channel_packed(true, filter, n_plus_one, in2_channel, lv, yield_constr); @@ -254,10 +256,11 @@ fn eval_ext_circuit_swap, const D: usize>( let in2_channel = &lv.mem_channels[1]; let out_channel = &lv.mem_channels[2]; - // Constrain the first input channel value to be equal to the output channel value. + // Constrain the first input channel value to be equal to the output channel + // value. channels_equal_ext_circuit(builder, filter, in1_channel, out_channel, yield_constr); - // We set `is_read`, `used` and the address for the first input. The first input is - // read from the top of the stack, and is therefore not a memory read. + // We set `is_read`, `used` and the address for the first input. The first input + // is read from the top of the stack, and is therefore not a memory read. constrain_channel_ext_circuit( builder, false, @@ -268,7 +271,8 @@ fn eval_ext_circuit_swap, const D: usize>( yield_constr, ); - // Constrain the second input channel value to be equal to the new top of the stack. + // Constrain the second input channel value to be equal to the new top of the + // stack. channels_equal_ext_circuit( builder, filter, diff --git a/src/cpu/gas.rs b/evm/src/cpu/gas.rs similarity index 98% rename from src/cpu/gas.rs rename to evm/src/cpu/gas.rs index 37097adce..69ebf2c51 100644 --- a/src/cpu/gas.rs +++ b/evm/src/cpu/gas.rs @@ -82,7 +82,8 @@ fn eval_packed_accumulate( yield_constr.constraint_transition(lv.op.jumps * (gas_diff - jump_gas_cost)); // For binary_ops. - // MUL, DIV and MOD are differentiated from ADD, SUB, LT, GT and BYTE by their first and fifth bits set to 0. + // MUL, DIV and MOD are differentiated from ADD, SUB, LT, GT and BYTE by their + // first and fifth bits set to 0. let cost_filter = lv.opcode_bits[0] + lv.opcode_bits[4] - lv.opcode_bits[0] * lv.opcode_bits[4]; let binary_op_cost = P::Scalar::from_canonical_u32(G_LOW.unwrap()) + cost_filter @@ -113,7 +114,8 @@ fn eval_packed_accumulate( ); // For PROVER_INPUT and PUSH operations. - // PUSH operations are differentiated from PROVER_INPUT by their 6th bit set to 1. + // PUSH operations are differentiated from PROVER_INPUT by their 6th bit set to + // 1. let push_prover_input_gas_cost = lv.opcode_bits[5] * P::Scalar::from_canonical_u32(G_VERYLOW.unwrap()) + (P::ONES - lv.opcode_bits[5]) * P::Scalar::from_canonical_u32(KERNEL_ONLY_INSTR.unwrap()); @@ -211,7 +213,8 @@ fn eval_ext_circuit_accumulate, const D: usize>( yield_constr.constraint_transition(builder, constr); // For binary_ops. - // MUL, DIV and MOD are differentiated from ADD, SUB, LT, GT and BYTE by their first and fifth bits set to 0. + // MUL, DIV and MOD are differentiated from ADD, SUB, LT, GT and BYTE by their + // first and fifth bits set to 0. let filter = lv.op.binary_op; let cost_filter = { let a = builder.add_extension(lv.opcode_bits[0], lv.opcode_bits[4]); @@ -279,7 +282,8 @@ fn eval_ext_circuit_accumulate, const D: usize>( yield_constr.constraint_transition(builder, constr); // For PROVER_INPUT and PUSH operations. - // PUSH operations are differentiated from PROVER_INPUT by their 6th bit set to 1. + // PUSH operations are differentiated from PROVER_INPUT by their 6th bit set to + // 1. let push_prover_input_gas_cost = builder.arithmetic_extension( F::from_canonical_u32(G_VERYLOW.unwrap()) - F::from_canonical_u32(KERNEL_ONLY_INSTR.unwrap()), diff --git a/src/cpu/halt.rs b/evm/src/cpu/halt.rs similarity index 97% rename from src/cpu/halt.rs rename to evm/src/cpu/halt.rs index a04128608..32e46fc35 100644 --- a/src/cpu/halt.rs +++ b/evm/src/cpu/halt.rs @@ -1,5 +1,6 @@ -//! Once the CPU execution is over (i.e. reached the `halt` label in the kernel), -//! the CPU trace will be padded with special dummy rows, incurring no memory overhead. +//! Once the CPU execution is over (i.e. reached the `halt` label in the +//! kernel), the CPU trace will be padded with special dummy rows, incurring no +//! memory overhead. use plonky2::field::extension::Extendable; use plonky2::field::packed::PackedField; diff --git a/src/cpu/jumps.rs b/evm/src/cpu/jumps.rs similarity index 94% rename from src/cpu/jumps.rs rename to evm/src/cpu/jumps.rs index f3413b0f0..b6706035d 100644 --- a/src/cpu/jumps.rs +++ b/evm/src/cpu/jumps.rs @@ -18,9 +18,10 @@ pub(crate) fn eval_packed_exit_kernel( let input = lv.mem_channels[0].value; let filter = lv.op.exit_kernel; - // If we are executing `EXIT_KERNEL` then we simply restore the program counter, kernel mode - // flag, and gas counter. The middle 4 (32-bit) limbs are ignored (this is not part of the spec, - // but we trust the kernel to set them to zero). + // If we are executing `EXIT_KERNEL` then we simply restore the program counter, + // kernel mode flag, and gas counter. The middle 4 (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(filter * (input[0] - nv.program_counter)); yield_constr.constraint_transition(filter * (input[1] - nv.is_kernel_mode)); yield_constr.constraint_transition(filter * (input[6] - nv.gas)); @@ -39,9 +40,9 @@ pub(crate) fn eval_ext_circuit_exit_kernel, const D let input = lv.mem_channels[0].value; let filter = lv.op.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). + // 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(input[0], nv.program_counter); let pc_constr = builder.mul_extension(filter, pc_constr); @@ -100,8 +101,9 @@ pub(crate) fn eval_packed_jump_jumpi( let empty_stack_filter = filter * (lv.general.stack().stack_inv_aux - P::ONES); yield_constr.constraint_transition(empty_stack_filter * channel.used); - // If `JUMP`, re-use the `JUMPI` logic, but setting the second input (the predicate) to be 1. - // In other words, we implement `JUMP(dst)` as `JUMPI(dst, cond=1)`. + // If `JUMP`, re-use the `JUMPI` logic, but setting the second input (the + // predicate) to be 1. In other words, we implement `JUMP(dst)` as + // `JUMPI(dst, cond=1)`. yield_constr.constraint(is_jump * (cond[0] - P::ONES)); for &limb in &cond[1..] { // Set all limbs (other than the least-significant limb) to 0. @@ -119,9 +121,10 @@ pub(crate) fn eval_packed_jump_jumpi( // If we're jumping, then the high 7 limbs of the destination must be 0. let dst_hi_sum: P = dst[1..].iter().copied().sum(); yield_constr.constraint(filter * jumps_lv.should_jump * dst_hi_sum); - // Check that the destination address holds a `JUMPDEST` instruction. Note that this constraint - // does not need to be conditioned on `should_jump` because no read takes place if we're not - // jumping, so we're free to set the channel to 1. + // Check that the destination address holds a `JUMPDEST` instruction. Note that + // this constraint does not need to be conditioned on `should_jump` because + // no read takes place if we're not jumping, so we're free to set the + // channel to 1. yield_constr.constraint(filter * (jumpdest_flag_channel.value[0] - P::ONES)); // Make sure that the JUMPDEST flag channel is constrained. @@ -233,8 +236,9 @@ pub(crate) fn eval_ext_circuit_jump_jumpi, const D: yield_constr.constraint_transition(builder, constr); } - // If `JUMP`, re-use the `JUMPI` logic, but setting the second input (the predicate) to be 1. - // In other words, we implement `JUMP(dst)` as `JUMPI(dst, cond=1)`. + // If `JUMP`, re-use the `JUMPI` logic, but setting the second input (the + // predicate) to be 1. In other words, we implement `JUMP(dst)` as + // `JUMPI(dst, cond=1)`. { let constr = builder.mul_sub_extension(is_jump, cond[0], is_jump); yield_constr.constraint(builder, constr); @@ -277,9 +281,10 @@ pub(crate) fn eval_ext_circuit_jump_jumpi, const D: let constr = builder.mul_extension(filter, constr); yield_constr.constraint(builder, constr); } - // Check that the destination address holds a `JUMPDEST` instruction. Note that this constraint - // does not need to be conditioned on `should_jump` because no read takes place if we're not - // jumping, so we're free to set the channel to 1. + // Check that the destination address holds a `JUMPDEST` instruction. Note that + // this constraint does not need to be conditioned on `should_jump` because + // no read takes place if we're not jumping, so we're free to set the + // channel to 1. { let constr = builder.mul_sub_extension(filter, jumpdest_flag_channel.value[0], filter); yield_constr.constraint(builder, constr); diff --git a/src/cpu/kernel/aggregator.rs b/evm/src/cpu/kernel/aggregator.rs similarity index 100% rename from src/cpu/kernel/aggregator.rs rename to evm/src/cpu/kernel/aggregator.rs diff --git a/src/cpu/kernel/asm/account_code.asm b/evm/src/cpu/kernel/asm/account_code.asm similarity index 100% rename from src/cpu/kernel/asm/account_code.asm rename to evm/src/cpu/kernel/asm/account_code.asm diff --git a/src/cpu/kernel/asm/balance.asm b/evm/src/cpu/kernel/asm/balance.asm similarity index 100% rename from src/cpu/kernel/asm/balance.asm rename to evm/src/cpu/kernel/asm/balance.asm diff --git a/src/cpu/kernel/asm/bignum/add.asm b/evm/src/cpu/kernel/asm/bignum/add.asm similarity index 100% rename from src/cpu/kernel/asm/bignum/add.asm rename to evm/src/cpu/kernel/asm/bignum/add.asm diff --git a/src/cpu/kernel/asm/bignum/addmul.asm b/evm/src/cpu/kernel/asm/bignum/addmul.asm similarity index 100% rename from src/cpu/kernel/asm/bignum/addmul.asm rename to evm/src/cpu/kernel/asm/bignum/addmul.asm diff --git a/src/cpu/kernel/asm/bignum/cmp.asm b/evm/src/cpu/kernel/asm/bignum/cmp.asm similarity index 100% rename from src/cpu/kernel/asm/bignum/cmp.asm rename to evm/src/cpu/kernel/asm/bignum/cmp.asm diff --git a/src/cpu/kernel/asm/bignum/isone.asm b/evm/src/cpu/kernel/asm/bignum/isone.asm similarity index 100% rename from src/cpu/kernel/asm/bignum/isone.asm rename to evm/src/cpu/kernel/asm/bignum/isone.asm diff --git a/src/cpu/kernel/asm/bignum/iszero.asm b/evm/src/cpu/kernel/asm/bignum/iszero.asm similarity index 100% rename from src/cpu/kernel/asm/bignum/iszero.asm rename to evm/src/cpu/kernel/asm/bignum/iszero.asm diff --git a/src/cpu/kernel/asm/bignum/modexp.asm b/evm/src/cpu/kernel/asm/bignum/modexp.asm similarity index 100% rename from src/cpu/kernel/asm/bignum/modexp.asm rename to evm/src/cpu/kernel/asm/bignum/modexp.asm diff --git a/src/cpu/kernel/asm/bignum/modmul.asm b/evm/src/cpu/kernel/asm/bignum/modmul.asm similarity index 100% rename from src/cpu/kernel/asm/bignum/modmul.asm rename to evm/src/cpu/kernel/asm/bignum/modmul.asm diff --git a/src/cpu/kernel/asm/bignum/mul.asm b/evm/src/cpu/kernel/asm/bignum/mul.asm similarity index 100% rename from src/cpu/kernel/asm/bignum/mul.asm rename to evm/src/cpu/kernel/asm/bignum/mul.asm diff --git a/src/cpu/kernel/asm/bignum/shr.asm b/evm/src/cpu/kernel/asm/bignum/shr.asm similarity index 100% rename from src/cpu/kernel/asm/bignum/shr.asm rename to evm/src/cpu/kernel/asm/bignum/shr.asm diff --git a/src/cpu/kernel/asm/bignum/util.asm b/evm/src/cpu/kernel/asm/bignum/util.asm similarity index 100% rename from src/cpu/kernel/asm/bignum/util.asm rename to evm/src/cpu/kernel/asm/bignum/util.asm diff --git a/src/cpu/kernel/asm/bloom_filter.asm b/evm/src/cpu/kernel/asm/bloom_filter.asm similarity index 100% rename from src/cpu/kernel/asm/bloom_filter.asm rename to evm/src/cpu/kernel/asm/bloom_filter.asm diff --git a/src/cpu/kernel/asm/core/access_lists.asm b/evm/src/cpu/kernel/asm/core/access_lists.asm similarity index 100% rename from src/cpu/kernel/asm/core/access_lists.asm rename to evm/src/cpu/kernel/asm/core/access_lists.asm diff --git a/src/cpu/kernel/asm/core/call.asm b/evm/src/cpu/kernel/asm/core/call.asm similarity index 100% rename from src/cpu/kernel/asm/core/call.asm rename to evm/src/cpu/kernel/asm/core/call.asm diff --git a/src/cpu/kernel/asm/core/call_gas.asm b/evm/src/cpu/kernel/asm/core/call_gas.asm similarity index 100% rename from src/cpu/kernel/asm/core/call_gas.asm rename to evm/src/cpu/kernel/asm/core/call_gas.asm diff --git a/src/cpu/kernel/asm/core/create.asm b/evm/src/cpu/kernel/asm/core/create.asm similarity index 100% rename from src/cpu/kernel/asm/core/create.asm rename to evm/src/cpu/kernel/asm/core/create.asm diff --git a/src/cpu/kernel/asm/core/create_addresses.asm b/evm/src/cpu/kernel/asm/core/create_addresses.asm similarity index 100% rename from src/cpu/kernel/asm/core/create_addresses.asm rename to evm/src/cpu/kernel/asm/core/create_addresses.asm diff --git a/src/cpu/kernel/asm/core/create_contract_account.asm b/evm/src/cpu/kernel/asm/core/create_contract_account.asm similarity index 79% rename from src/cpu/kernel/asm/core/create_contract_account.asm rename to evm/src/cpu/kernel/asm/core/create_contract_account.asm index b45d45ca5..051540e17 100644 --- a/src/cpu/kernel/asm/core/create_contract_account.asm +++ b/evm/src/cpu/kernel/asm/core/create_contract_account.asm @@ -27,7 +27,12 @@ %%add_account: // stack: existing_balance, address - DUP2 %journal_add_account_created + DUP2 PUSH 1 + // stack: is_contract, address, existing_balance, addr + %journal_add_account_created + // stack: existing_balance, addr + DUP2 + %append_created_contracts %%do_insert: // stack: new_acct_value, address // Write the new account's data to MPT data, and get a pointer to it. @@ -60,3 +65,15 @@ %%end: // stack: status %endmacro + +%macro append_created_contracts + // stack: address + %mload_global_metadata(@GLOBAL_METADATA_CREATED_CONTRACTS_LEN) + // stack: nb_created_contracts, address + SWAP1 DUP2 + // stack: nb_created_contracts, address, nb_created_contracts + %mstore_kernel(@SEGMENT_CREATED_CONTRACTS) + // stack: nb_created_contracts + %increment + %mstore_global_metadata(@GLOBAL_METADATA_CREATED_CONTRACTS_LEN) +%endmacro diff --git a/src/cpu/kernel/asm/core/create_receipt.asm b/evm/src/cpu/kernel/asm/core/create_receipt.asm similarity index 100% rename from src/cpu/kernel/asm/core/create_receipt.asm rename to evm/src/cpu/kernel/asm/core/create_receipt.asm diff --git a/src/cpu/kernel/asm/core/exception.asm b/evm/src/cpu/kernel/asm/core/exception.asm similarity index 98% rename from src/cpu/kernel/asm/core/exception.asm rename to evm/src/cpu/kernel/asm/core/exception.asm index 6ce2d676d..80bf7dbdf 100644 --- a/src/cpu/kernel/asm/core/exception.asm +++ b/evm/src/cpu/kernel/asm/core/exception.asm @@ -236,7 +236,9 @@ min_stack_len_for_opcode: BYTES 0 // 0x46, CHAINID BYTES 0 // 0x47, SELFBALANCE BYTES 0 // 0x48, BASEFEE - %rep 7 // 0x49-0x4f, invalid + BYTES 0 // 0x49, invalid + BYTES 0 // 0x4a, BLOBBASEFEE + %rep 5 // 0x4b-0x4f, invalid BYTES 0 %endrep @@ -252,9 +254,9 @@ min_stack_len_for_opcode: BYTES 0 // 0x59, MSIZE BYTES 0 // 0x5a, GAS BYTES 0 // 0x5b, JUMPDEST - %rep 3 // 0x5c-0x5e, invalid - BYTES 0 - %endrep + BYTES 0 // 0x5c, invalid + BYTES 0 // 0x5d, invalid + BYTES 3 // 0x5e, MCOPY %rep 33 // 0x5f-0x7f, PUSH0-PUSH32 BYTES 0 diff --git a/src/cpu/kernel/asm/core/gas.asm b/evm/src/cpu/kernel/asm/core/gas.asm similarity index 100% rename from src/cpu/kernel/asm/core/gas.asm rename to evm/src/cpu/kernel/asm/core/gas.asm diff --git a/src/cpu/kernel/asm/core/intrinsic_gas.asm b/evm/src/cpu/kernel/asm/core/intrinsic_gas.asm similarity index 100% rename from src/cpu/kernel/asm/core/intrinsic_gas.asm rename to evm/src/cpu/kernel/asm/core/intrinsic_gas.asm diff --git a/src/cpu/kernel/asm/core/jumpdest_analysis.asm b/evm/src/cpu/kernel/asm/core/jumpdest_analysis.asm similarity index 100% rename from src/cpu/kernel/asm/core/jumpdest_analysis.asm rename to evm/src/cpu/kernel/asm/core/jumpdest_analysis.asm diff --git a/src/cpu/kernel/asm/core/log.asm b/evm/src/cpu/kernel/asm/core/log.asm similarity index 100% rename from src/cpu/kernel/asm/core/log.asm rename to evm/src/cpu/kernel/asm/core/log.asm diff --git a/src/cpu/kernel/asm/core/nonce.asm b/evm/src/cpu/kernel/asm/core/nonce.asm similarity index 100% rename from src/cpu/kernel/asm/core/nonce.asm rename to evm/src/cpu/kernel/asm/core/nonce.asm diff --git a/src/cpu/kernel/asm/core/precompiles/blake2_f.asm b/evm/src/cpu/kernel/asm/core/precompiles/blake2_f.asm similarity index 100% rename from src/cpu/kernel/asm/core/precompiles/blake2_f.asm rename to evm/src/cpu/kernel/asm/core/precompiles/blake2_f.asm diff --git a/src/cpu/kernel/asm/core/precompiles/bn_add.asm b/evm/src/cpu/kernel/asm/core/precompiles/bn_add.asm similarity index 100% rename from src/cpu/kernel/asm/core/precompiles/bn_add.asm rename to evm/src/cpu/kernel/asm/core/precompiles/bn_add.asm diff --git a/src/cpu/kernel/asm/core/precompiles/bn_mul.asm b/evm/src/cpu/kernel/asm/core/precompiles/bn_mul.asm similarity index 100% rename from src/cpu/kernel/asm/core/precompiles/bn_mul.asm rename to evm/src/cpu/kernel/asm/core/precompiles/bn_mul.asm diff --git a/src/cpu/kernel/asm/core/precompiles/ecrec.asm b/evm/src/cpu/kernel/asm/core/precompiles/ecrec.asm similarity index 100% rename from src/cpu/kernel/asm/core/precompiles/ecrec.asm rename to evm/src/cpu/kernel/asm/core/precompiles/ecrec.asm diff --git a/src/cpu/kernel/asm/core/precompiles/expmod.asm b/evm/src/cpu/kernel/asm/core/precompiles/expmod.asm similarity index 100% rename from src/cpu/kernel/asm/core/precompiles/expmod.asm rename to evm/src/cpu/kernel/asm/core/precompiles/expmod.asm diff --git a/src/cpu/kernel/asm/core/precompiles/id.asm b/evm/src/cpu/kernel/asm/core/precompiles/id.asm similarity index 100% rename from src/cpu/kernel/asm/core/precompiles/id.asm rename to evm/src/cpu/kernel/asm/core/precompiles/id.asm diff --git a/src/cpu/kernel/asm/core/precompiles/main.asm b/evm/src/cpu/kernel/asm/core/precompiles/main.asm similarity index 100% rename from src/cpu/kernel/asm/core/precompiles/main.asm rename to evm/src/cpu/kernel/asm/core/precompiles/main.asm diff --git a/src/cpu/kernel/asm/core/precompiles/rip160.asm b/evm/src/cpu/kernel/asm/core/precompiles/rip160.asm similarity index 100% rename from src/cpu/kernel/asm/core/precompiles/rip160.asm rename to evm/src/cpu/kernel/asm/core/precompiles/rip160.asm diff --git a/src/cpu/kernel/asm/core/precompiles/sha256.asm b/evm/src/cpu/kernel/asm/core/precompiles/sha256.asm similarity index 100% rename from src/cpu/kernel/asm/core/precompiles/sha256.asm rename to evm/src/cpu/kernel/asm/core/precompiles/sha256.asm diff --git a/src/cpu/kernel/asm/core/precompiles/snarkv.asm b/evm/src/cpu/kernel/asm/core/precompiles/snarkv.asm similarity index 100% rename from src/cpu/kernel/asm/core/precompiles/snarkv.asm rename to evm/src/cpu/kernel/asm/core/precompiles/snarkv.asm diff --git a/src/cpu/kernel/asm/core/process_txn.asm b/evm/src/cpu/kernel/asm/core/process_txn.asm similarity index 100% rename from src/cpu/kernel/asm/core/process_txn.asm rename to evm/src/cpu/kernel/asm/core/process_txn.asm diff --git a/src/cpu/kernel/asm/core/selfdestruct_list.asm b/evm/src/cpu/kernel/asm/core/selfdestruct_list.asm similarity index 94% rename from src/cpu/kernel/asm/core/selfdestruct_list.asm rename to evm/src/cpu/kernel/asm/core/selfdestruct_list.asm index 258f79405..05e158c34 100644 --- a/src/cpu/kernel/asm/core/selfdestruct_list.asm +++ b/evm/src/cpu/kernel/asm/core/selfdestruct_list.asm @@ -14,7 +14,7 @@ %endmacro /// Remove one occurrence of the address from the list. -/// Panics if the address is not in the list. +/// No effect if the address is not in the list. global remove_selfdestruct_list: // stack: addr, retdest %mload_global_metadata(@GLOBAL_METADATA_SELFDESTRUCT_LIST_LEN) @@ -24,7 +24,7 @@ global remove_selfdestruct_list: remove_selfdestruct_list_loop: // `i` and `len` are both scaled by SEGMENT_SELFDESTRUCT_LIST %stack (i, len, addr, retdest) -> (i, len, i, len, addr, retdest) - EQ %jumpi(panic) + EQ %jumpi(remove_selfdestruct_not_found) // stack: i, len, addr, retdest DUP1 MLOAD_GENERAL // stack: loaded_addr, i, len, addr, retdest @@ -45,6 +45,10 @@ remove_selfdestruct_list_found: // stack: last_addr, i, retdest MSTORE_GENERAL // Store the last address at the position of the removed address. JUMP +remove_selfdestruct_not_found: + // stack: i, len, addr, retdest + %pop3 + JUMP global delete_all_selfdestructed_addresses: // stack: retdest diff --git a/src/cpu/kernel/asm/core/syscall.asm b/evm/src/cpu/kernel/asm/core/syscall.asm similarity index 96% rename from src/cpu/kernel/asm/core/syscall.asm rename to evm/src/cpu/kernel/asm/core/syscall.asm index 5d1a6c95c..673a5fbb9 100644 --- a/src/cpu/kernel/asm/core/syscall.asm +++ b/evm/src/cpu/kernel/asm/core/syscall.asm @@ -69,8 +69,10 @@ global syscall_jumptable: JUMPTABLE sys_chainid JUMPTABLE sys_selfbalance JUMPTABLE sys_basefee - %rep 7 - JUMPTABLE panic // 0x49-0x4f are invalid opcodes + JUMPTABLE panic // 0x49 is invalid + JUMPTABLE sys_blobbasefee + %rep 5 + JUMPTABLE panic // 0x4b-0x4f are invalid opcodes %endrep // 0x50-0x5f @@ -88,7 +90,7 @@ global syscall_jumptable: JUMPTABLE panic // jumpdest is implemented natively JUMPTABLE panic // 0x5c is an invalid opcode JUMPTABLE panic // 0x5d is an invalid opcode - JUMPTABLE panic // 0x5e is an invalid opcode + JUMPTABLE sys_mcopy JUMPTABLE panic // 0x5f is an invalid opcode // 0x60-0x6f diff --git a/src/cpu/kernel/asm/core/terminate.asm b/evm/src/cpu/kernel/asm/core/terminate.asm similarity index 76% rename from src/cpu/kernel/asm/core/terminate.asm rename to evm/src/cpu/kernel/asm/core/terminate.asm index 8572f34f2..6ae04e9fd 100644 --- a/src/cpu/kernel/asm/core/terminate.asm +++ b/evm/src/cpu/kernel/asm/core/terminate.asm @@ -85,10 +85,6 @@ global sys_selfdestruct: %charge_gas %stack (kexit_info, balance, address, recipient) -> (balance, address, recipient, kexit_info) - // Insert address into the selfdestruct set. - // stack: balance, address, recipient, kexit_info - DUP2 %insert_selfdestruct_list - // Set the balance of the address to 0. // stack: balance, address, recipient, kexit_info PUSH 0 @@ -99,14 +95,17 @@ global sys_selfdestruct: // stack: balance_ptr, 0, balance, address, recipient, kexit_info %mstore_trie_data - %stack (balance, address, recipient, kexit_info) -> - (address, recipient, address, recipient, balance, kexit_info) - // If the recipient is the same as the address, then we're done. - // Otherwise, send the balance to the recipient. - // stack: address, recipient, address, recipient, balance, kexit_info - EQ %jumpi(sys_selfdestruct_journal_add) - %stack (address, recipient, balance, kexit_info) -> (recipient, balance, address, recipient, balance, kexit_info) + // EIP-6780: insert address into the selfdestruct set only if contract has been created + // during the current transaction. + // stack: balance, address, recipient, kexit_info + DUP2 %contract_just_created + // stack: is_just_created, balance, address, recipient, kexit_info + %jumpi(sys_selfdestruct_just_created) + + // Send the balance to the recipient. + %stack (balance, address, recipient, kexit_info) -> + (recipient, balance, address, recipient, balance, kexit_info) %add_eth sys_selfdestruct_journal_add: @@ -119,6 +118,21 @@ sys_selfdestruct_journal_add: PUSH 1 // success %jump(terminate_common) +sys_selfdestruct_just_created: + // Send funds to beneficiary only if the recipient isn't the same as the address. + %stack (balance, address, recipient, kexit_info) -> (address, recipient, balance, address, recipient, balance, kexit_info) + EQ ISZERO + // stack: address ≠ recipient, balance, address, recipient, balance, kexit_info + MUL + // stack: maybe_balance, address, recipient, balance, kexit_info + DUP3 + // stack: recipient, maybe_balance, address, recipient, balance, kexit_info + %add_eth + // stack: address, recipient, balance, kexit_info + DUP1 + %insert_selfdestruct_list + %jump(sys_selfdestruct_journal_add) + global sys_revert: // stack: kexit_info, offset, size %stack (kexit_info, offset, size) -> (offset, size, kexit_info, offset, size) @@ -223,3 +237,41 @@ global terminate_common: // stack: parent_pc, success, leftover_gas JUMP + + + + +// Returns 1 if the address is present in SEGMENT_CREATED_CONTRACTS, meaning that it has been +// created during this transaction. Returns 0 otherwise. +// Pre stack: addr +// Post stack: is_just_created +%macro contract_just_created + // stack: addr + %mload_global_metadata(@GLOBAL_METADATA_CREATED_CONTRACTS_LEN) + // stack: nb_created_contracts, addr + PUSH 0 +%%contract_just_created_loop: + %stack (i, nb_created_contracts, addr) -> (i, nb_created_contracts, i, nb_created_contracts, addr) + EQ %jumpi(%%contract_just_created_false) + // stack: i, nb_created_contracts, addr + DUP1 %mload_kernel(@SEGMENT_CREATED_CONTRACTS) + // stack: addr_created_contract, i, nb_created_contracts, addr + DUP4 + // stack: addr, addr_created_contract, i, nb_created_contracts, addr + EQ %jumpi(%%contract_just_created_true) + // stack: i, nb_created_contracts, addr + %increment + %jump(%%contract_just_created_loop) +%%contract_just_created_true: + // stack: i, nb_created_contracts, addr + %pop3 + PUSH 1 + // stack: 1 + %jump(%%after) +%%contract_just_created_false: + // stack: i, nb_created_contracts, addr + %pop3 + PUSH 0 + // stack: 0 +%%after: +%endmacro diff --git a/src/cpu/kernel/asm/core/touched_addresses.asm b/evm/src/cpu/kernel/asm/core/touched_addresses.asm similarity index 100% rename from src/cpu/kernel/asm/core/touched_addresses.asm rename to evm/src/cpu/kernel/asm/core/touched_addresses.asm diff --git a/src/cpu/kernel/asm/core/transfer.asm b/evm/src/cpu/kernel/asm/core/transfer.asm similarity index 97% rename from src/cpu/kernel/asm/core/transfer.asm rename to evm/src/cpu/kernel/asm/core/transfer.asm index 0517cf3a8..8a5aad9fe 100644 --- a/src/cpu/kernel/asm/core/transfer.asm +++ b/evm/src/cpu/kernel/asm/core/transfer.asm @@ -85,7 +85,9 @@ global add_eth_new_account: POP // stack: addr, amount, retdest DUP2 ISZERO %jumpi(add_eth_new_account_zero) - DUP1 %journal_add_account_created + DUP1 PUSH 0 + // stack: is_eoa, addr, addr, amount, retdest + %journal_add_account_created %get_trie_data_size // pointer to new account we're about to create // stack: new_account_ptr, addr, amount, retdest SWAP2 diff --git a/src/cpu/kernel/asm/core/util.asm b/evm/src/cpu/kernel/asm/core/util.asm similarity index 100% rename from src/cpu/kernel/asm/core/util.asm rename to evm/src/cpu/kernel/asm/core/util.asm diff --git a/src/cpu/kernel/asm/core/withdrawals.asm b/evm/src/cpu/kernel/asm/core/withdrawals.asm similarity index 100% rename from src/cpu/kernel/asm/core/withdrawals.asm rename to evm/src/cpu/kernel/asm/core/withdrawals.asm diff --git a/src/cpu/kernel/asm/curve/bls381/util.asm b/evm/src/cpu/kernel/asm/curve/bls381/util.asm similarity index 100% rename from src/cpu/kernel/asm/curve/bls381/util.asm rename to evm/src/cpu/kernel/asm/curve/bls381/util.asm diff --git a/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/constants.asm b/evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/constants.asm similarity index 100% rename from src/cpu/kernel/asm/curve/bn254/curve_arithmetic/constants.asm rename to evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/constants.asm diff --git a/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/curve_add.asm b/evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/curve_add.asm similarity index 100% rename from src/cpu/kernel/asm/curve/bn254/curve_arithmetic/curve_add.asm rename to evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/curve_add.asm diff --git a/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/curve_mul.asm b/evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/curve_mul.asm similarity index 100% rename from src/cpu/kernel/asm/curve/bn254/curve_arithmetic/curve_mul.asm rename to evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/curve_mul.asm diff --git a/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/final_exponent.asm b/evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/final_exponent.asm similarity index 100% rename from src/cpu/kernel/asm/curve/bn254/curve_arithmetic/final_exponent.asm rename to evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/final_exponent.asm diff --git a/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/glv.asm b/evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/glv.asm similarity index 100% rename from src/cpu/kernel/asm/curve/bn254/curve_arithmetic/glv.asm rename to evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/glv.asm diff --git a/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/miller_loop.asm b/evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/miller_loop.asm similarity index 100% rename from src/cpu/kernel/asm/curve/bn254/curve_arithmetic/miller_loop.asm rename to evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/miller_loop.asm diff --git a/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/msm.asm b/evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/msm.asm similarity index 100% rename from src/cpu/kernel/asm/curve/bn254/curve_arithmetic/msm.asm rename to evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/msm.asm diff --git a/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/pairing.asm b/evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/pairing.asm similarity index 100% rename from src/cpu/kernel/asm/curve/bn254/curve_arithmetic/pairing.asm rename to evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/pairing.asm diff --git a/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/precomputation.asm b/evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/precomputation.asm similarity index 100% rename from src/cpu/kernel/asm/curve/bn254/curve_arithmetic/precomputation.asm rename to evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/precomputation.asm diff --git a/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/twisted_curve.asm b/evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/twisted_curve.asm similarity index 100% rename from src/cpu/kernel/asm/curve/bn254/curve_arithmetic/twisted_curve.asm rename to evm/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/twisted_curve.asm diff --git a/src/cpu/kernel/asm/curve/bn254/field_arithmetic/degree_12_mul.asm b/evm/src/cpu/kernel/asm/curve/bn254/field_arithmetic/degree_12_mul.asm similarity index 100% rename from src/cpu/kernel/asm/curve/bn254/field_arithmetic/degree_12_mul.asm rename to evm/src/cpu/kernel/asm/curve/bn254/field_arithmetic/degree_12_mul.asm diff --git a/src/cpu/kernel/asm/curve/bn254/field_arithmetic/degree_6_mul.asm b/evm/src/cpu/kernel/asm/curve/bn254/field_arithmetic/degree_6_mul.asm similarity index 100% rename from src/cpu/kernel/asm/curve/bn254/field_arithmetic/degree_6_mul.asm rename to evm/src/cpu/kernel/asm/curve/bn254/field_arithmetic/degree_6_mul.asm diff --git a/src/cpu/kernel/asm/curve/bn254/field_arithmetic/frobenius.asm b/evm/src/cpu/kernel/asm/curve/bn254/field_arithmetic/frobenius.asm similarity index 100% rename from src/cpu/kernel/asm/curve/bn254/field_arithmetic/frobenius.asm rename to evm/src/cpu/kernel/asm/curve/bn254/field_arithmetic/frobenius.asm diff --git a/src/cpu/kernel/asm/curve/bn254/field_arithmetic/inverse.asm b/evm/src/cpu/kernel/asm/curve/bn254/field_arithmetic/inverse.asm similarity index 100% rename from src/cpu/kernel/asm/curve/bn254/field_arithmetic/inverse.asm rename to evm/src/cpu/kernel/asm/curve/bn254/field_arithmetic/inverse.asm diff --git a/src/cpu/kernel/asm/curve/bn254/field_arithmetic/util.asm b/evm/src/cpu/kernel/asm/curve/bn254/field_arithmetic/util.asm similarity index 100% rename from src/cpu/kernel/asm/curve/bn254/field_arithmetic/util.asm rename to evm/src/cpu/kernel/asm/curve/bn254/field_arithmetic/util.asm diff --git a/src/cpu/kernel/asm/curve/common.asm b/evm/src/cpu/kernel/asm/curve/common.asm similarity index 100% rename from src/cpu/kernel/asm/curve/common.asm rename to evm/src/cpu/kernel/asm/curve/common.asm diff --git a/src/cpu/kernel/asm/curve/secp256k1/curve_add.asm b/evm/src/cpu/kernel/asm/curve/secp256k1/curve_add.asm similarity index 100% rename from src/cpu/kernel/asm/curve/secp256k1/curve_add.asm rename to evm/src/cpu/kernel/asm/curve/secp256k1/curve_add.asm diff --git a/src/cpu/kernel/asm/curve/secp256k1/ecrecover.asm b/evm/src/cpu/kernel/asm/curve/secp256k1/ecrecover.asm similarity index 100% rename from src/cpu/kernel/asm/curve/secp256k1/ecrecover.asm rename to evm/src/cpu/kernel/asm/curve/secp256k1/ecrecover.asm diff --git a/src/cpu/kernel/asm/curve/secp256k1/glv.asm b/evm/src/cpu/kernel/asm/curve/secp256k1/glv.asm similarity index 100% rename from src/cpu/kernel/asm/curve/secp256k1/glv.asm rename to evm/src/cpu/kernel/asm/curve/secp256k1/glv.asm diff --git a/src/cpu/kernel/asm/curve/secp256k1/inverse_scalar.asm b/evm/src/cpu/kernel/asm/curve/secp256k1/inverse_scalar.asm similarity index 100% rename from src/cpu/kernel/asm/curve/secp256k1/inverse_scalar.asm rename to evm/src/cpu/kernel/asm/curve/secp256k1/inverse_scalar.asm diff --git a/src/cpu/kernel/asm/curve/secp256k1/lift_x.asm b/evm/src/cpu/kernel/asm/curve/secp256k1/lift_x.asm similarity index 100% rename from src/cpu/kernel/asm/curve/secp256k1/lift_x.asm rename to evm/src/cpu/kernel/asm/curve/secp256k1/lift_x.asm diff --git a/src/cpu/kernel/asm/curve/secp256k1/moddiv.asm b/evm/src/cpu/kernel/asm/curve/secp256k1/moddiv.asm similarity index 100% rename from src/cpu/kernel/asm/curve/secp256k1/moddiv.asm rename to evm/src/cpu/kernel/asm/curve/secp256k1/moddiv.asm diff --git a/src/cpu/kernel/asm/curve/secp256k1/precomputation.asm b/evm/src/cpu/kernel/asm/curve/secp256k1/precomputation.asm similarity index 100% rename from src/cpu/kernel/asm/curve/secp256k1/precomputation.asm rename to evm/src/cpu/kernel/asm/curve/secp256k1/precomputation.asm diff --git a/src/cpu/kernel/asm/curve/wnaf.asm b/evm/src/cpu/kernel/asm/curve/wnaf.asm similarity index 100% rename from src/cpu/kernel/asm/curve/wnaf.asm rename to evm/src/cpu/kernel/asm/curve/wnaf.asm diff --git a/src/cpu/kernel/asm/exp.asm b/evm/src/cpu/kernel/asm/exp.asm similarity index 100% rename from src/cpu/kernel/asm/exp.asm rename to evm/src/cpu/kernel/asm/exp.asm diff --git a/src/cpu/kernel/asm/halt.asm b/evm/src/cpu/kernel/asm/halt.asm similarity index 100% rename from src/cpu/kernel/asm/halt.asm rename to evm/src/cpu/kernel/asm/halt.asm diff --git a/src/cpu/kernel/asm/hash/blake2/addresses.asm b/evm/src/cpu/kernel/asm/hash/blake2/addresses.asm similarity index 100% rename from src/cpu/kernel/asm/hash/blake2/addresses.asm rename to evm/src/cpu/kernel/asm/hash/blake2/addresses.asm diff --git a/src/cpu/kernel/asm/hash/blake2/blake2_f.asm b/evm/src/cpu/kernel/asm/hash/blake2/blake2_f.asm similarity index 100% rename from src/cpu/kernel/asm/hash/blake2/blake2_f.asm rename to evm/src/cpu/kernel/asm/hash/blake2/blake2_f.asm diff --git a/src/cpu/kernel/asm/hash/blake2/blake2b.asm b/evm/src/cpu/kernel/asm/hash/blake2/blake2b.asm similarity index 100% rename from src/cpu/kernel/asm/hash/blake2/blake2b.asm rename to evm/src/cpu/kernel/asm/hash/blake2/blake2b.asm diff --git a/src/cpu/kernel/asm/hash/blake2/compression.asm b/evm/src/cpu/kernel/asm/hash/blake2/compression.asm similarity index 100% rename from src/cpu/kernel/asm/hash/blake2/compression.asm rename to evm/src/cpu/kernel/asm/hash/blake2/compression.asm diff --git a/src/cpu/kernel/asm/hash/blake2/g_functions.asm b/evm/src/cpu/kernel/asm/hash/blake2/g_functions.asm similarity index 100% rename from src/cpu/kernel/asm/hash/blake2/g_functions.asm rename to evm/src/cpu/kernel/asm/hash/blake2/g_functions.asm diff --git a/src/cpu/kernel/asm/hash/blake2/hash.asm b/evm/src/cpu/kernel/asm/hash/blake2/hash.asm similarity index 100% rename from src/cpu/kernel/asm/hash/blake2/hash.asm rename to evm/src/cpu/kernel/asm/hash/blake2/hash.asm diff --git a/src/cpu/kernel/asm/hash/blake2/iv.asm b/evm/src/cpu/kernel/asm/hash/blake2/iv.asm similarity index 100% rename from src/cpu/kernel/asm/hash/blake2/iv.asm rename to evm/src/cpu/kernel/asm/hash/blake2/iv.asm diff --git a/src/cpu/kernel/asm/hash/blake2/ops.asm b/evm/src/cpu/kernel/asm/hash/blake2/ops.asm similarity index 100% rename from src/cpu/kernel/asm/hash/blake2/ops.asm rename to evm/src/cpu/kernel/asm/hash/blake2/ops.asm diff --git a/src/cpu/kernel/asm/hash/blake2/permutations.asm b/evm/src/cpu/kernel/asm/hash/blake2/permutations.asm similarity index 100% rename from src/cpu/kernel/asm/hash/blake2/permutations.asm rename to evm/src/cpu/kernel/asm/hash/blake2/permutations.asm diff --git a/src/cpu/kernel/asm/hash/ripemd/box.asm b/evm/src/cpu/kernel/asm/hash/ripemd/box.asm similarity index 100% rename from src/cpu/kernel/asm/hash/ripemd/box.asm rename to evm/src/cpu/kernel/asm/hash/ripemd/box.asm diff --git a/src/cpu/kernel/asm/hash/ripemd/compression.asm b/evm/src/cpu/kernel/asm/hash/ripemd/compression.asm similarity index 100% rename from src/cpu/kernel/asm/hash/ripemd/compression.asm rename to evm/src/cpu/kernel/asm/hash/ripemd/compression.asm diff --git a/src/cpu/kernel/asm/hash/ripemd/constants.asm b/evm/src/cpu/kernel/asm/hash/ripemd/constants.asm similarity index 100% rename from src/cpu/kernel/asm/hash/ripemd/constants.asm rename to evm/src/cpu/kernel/asm/hash/ripemd/constants.asm diff --git a/src/cpu/kernel/asm/hash/ripemd/functions.asm b/evm/src/cpu/kernel/asm/hash/ripemd/functions.asm similarity index 100% rename from src/cpu/kernel/asm/hash/ripemd/functions.asm rename to evm/src/cpu/kernel/asm/hash/ripemd/functions.asm diff --git a/src/cpu/kernel/asm/hash/ripemd/main.asm b/evm/src/cpu/kernel/asm/hash/ripemd/main.asm similarity index 100% rename from src/cpu/kernel/asm/hash/ripemd/main.asm rename to evm/src/cpu/kernel/asm/hash/ripemd/main.asm diff --git a/src/cpu/kernel/asm/hash/ripemd/update.asm b/evm/src/cpu/kernel/asm/hash/ripemd/update.asm similarity index 100% rename from src/cpu/kernel/asm/hash/ripemd/update.asm rename to evm/src/cpu/kernel/asm/hash/ripemd/update.asm diff --git a/src/cpu/kernel/asm/hash/sha2/compression.asm b/evm/src/cpu/kernel/asm/hash/sha2/compression.asm similarity index 100% rename from src/cpu/kernel/asm/hash/sha2/compression.asm rename to evm/src/cpu/kernel/asm/hash/sha2/compression.asm diff --git a/src/cpu/kernel/asm/hash/sha2/constants.asm b/evm/src/cpu/kernel/asm/hash/sha2/constants.asm similarity index 100% rename from src/cpu/kernel/asm/hash/sha2/constants.asm rename to evm/src/cpu/kernel/asm/hash/sha2/constants.asm diff --git a/src/cpu/kernel/asm/hash/sha2/main.asm b/evm/src/cpu/kernel/asm/hash/sha2/main.asm similarity index 100% rename from src/cpu/kernel/asm/hash/sha2/main.asm rename to evm/src/cpu/kernel/asm/hash/sha2/main.asm diff --git a/src/cpu/kernel/asm/hash/sha2/message_schedule.asm b/evm/src/cpu/kernel/asm/hash/sha2/message_schedule.asm similarity index 100% rename from src/cpu/kernel/asm/hash/sha2/message_schedule.asm rename to evm/src/cpu/kernel/asm/hash/sha2/message_schedule.asm diff --git a/src/cpu/kernel/asm/hash/sha2/ops.asm b/evm/src/cpu/kernel/asm/hash/sha2/ops.asm similarity index 100% rename from src/cpu/kernel/asm/hash/sha2/ops.asm rename to evm/src/cpu/kernel/asm/hash/sha2/ops.asm diff --git a/src/cpu/kernel/asm/hash/sha2/temp_words.asm b/evm/src/cpu/kernel/asm/hash/sha2/temp_words.asm similarity index 100% rename from src/cpu/kernel/asm/hash/sha2/temp_words.asm rename to evm/src/cpu/kernel/asm/hash/sha2/temp_words.asm diff --git a/src/cpu/kernel/asm/hash/sha2/write_length.asm b/evm/src/cpu/kernel/asm/hash/sha2/write_length.asm similarity index 100% rename from src/cpu/kernel/asm/hash/sha2/write_length.asm rename to evm/src/cpu/kernel/asm/hash/sha2/write_length.asm diff --git a/evm/src/cpu/kernel/asm/journal/account_created.asm b/evm/src/cpu/kernel/asm/journal/account_created.asm new file mode 100644 index 000000000..2fd4c15fa --- /dev/null +++ b/evm/src/cpu/kernel/asm/journal/account_created.asm @@ -0,0 +1,23 @@ +// struct AccountCreated { account_type, address } +// account_type is 0 for an EOA, 1 for a contract. + +%macro journal_add_account_created + %journal_add_2(@JOURNAL_ENTRY_ACCOUNT_CREATED) +%endmacro + +global revert_account_created: + // stack: entry_type, ptr, retdest + POP + %journal_load_2 + // stack: account_type, address, retdest + %jumpi(decrement_created_contracts_len) + +revert_account_finish: + %delete_account + JUMP + +decrement_created_contracts_len: + %mload_global_metadata(@GLOBAL_METADATA_CREATED_CONTRACTS_LEN) + %decrement + %mstore_global_metadata(@GLOBAL_METADATA_CREATED_CONTRACTS_LEN) + %jump(revert_account_finish) diff --git a/src/cpu/kernel/asm/journal/account_destroyed.asm b/evm/src/cpu/kernel/asm/journal/account_destroyed.asm similarity index 100% rename from src/cpu/kernel/asm/journal/account_destroyed.asm rename to evm/src/cpu/kernel/asm/journal/account_destroyed.asm diff --git a/src/cpu/kernel/asm/journal/account_loaded.asm b/evm/src/cpu/kernel/asm/journal/account_loaded.asm similarity index 100% rename from src/cpu/kernel/asm/journal/account_loaded.asm rename to evm/src/cpu/kernel/asm/journal/account_loaded.asm diff --git a/src/cpu/kernel/asm/journal/account_touched.asm b/evm/src/cpu/kernel/asm/journal/account_touched.asm similarity index 100% rename from src/cpu/kernel/asm/journal/account_touched.asm rename to evm/src/cpu/kernel/asm/journal/account_touched.asm diff --git a/src/cpu/kernel/asm/journal/balance_transfer.asm b/evm/src/cpu/kernel/asm/journal/balance_transfer.asm similarity index 100% rename from src/cpu/kernel/asm/journal/balance_transfer.asm rename to evm/src/cpu/kernel/asm/journal/balance_transfer.asm diff --git a/src/cpu/kernel/asm/journal/code_change.asm b/evm/src/cpu/kernel/asm/journal/code_change.asm similarity index 100% rename from src/cpu/kernel/asm/journal/code_change.asm rename to evm/src/cpu/kernel/asm/journal/code_change.asm diff --git a/src/cpu/kernel/asm/journal/journal.asm b/evm/src/cpu/kernel/asm/journal/journal.asm similarity index 100% rename from src/cpu/kernel/asm/journal/journal.asm rename to evm/src/cpu/kernel/asm/journal/journal.asm diff --git a/src/cpu/kernel/asm/journal/log.asm b/evm/src/cpu/kernel/asm/journal/log.asm similarity index 100% rename from src/cpu/kernel/asm/journal/log.asm rename to evm/src/cpu/kernel/asm/journal/log.asm diff --git a/src/cpu/kernel/asm/journal/nonce_change.asm b/evm/src/cpu/kernel/asm/journal/nonce_change.asm similarity index 100% rename from src/cpu/kernel/asm/journal/nonce_change.asm rename to evm/src/cpu/kernel/asm/journal/nonce_change.asm diff --git a/src/cpu/kernel/asm/journal/refund.asm b/evm/src/cpu/kernel/asm/journal/refund.asm similarity index 100% rename from src/cpu/kernel/asm/journal/refund.asm rename to evm/src/cpu/kernel/asm/journal/refund.asm diff --git a/src/cpu/kernel/asm/journal/revert.asm b/evm/src/cpu/kernel/asm/journal/revert.asm similarity index 100% rename from src/cpu/kernel/asm/journal/revert.asm rename to evm/src/cpu/kernel/asm/journal/revert.asm diff --git a/src/cpu/kernel/asm/journal/storage_change.asm b/evm/src/cpu/kernel/asm/journal/storage_change.asm similarity index 100% rename from src/cpu/kernel/asm/journal/storage_change.asm rename to evm/src/cpu/kernel/asm/journal/storage_change.asm diff --git a/src/cpu/kernel/asm/journal/storage_loaded.asm b/evm/src/cpu/kernel/asm/journal/storage_loaded.asm similarity index 100% rename from src/cpu/kernel/asm/journal/storage_loaded.asm rename to evm/src/cpu/kernel/asm/journal/storage_loaded.asm diff --git a/src/cpu/kernel/asm/main.asm b/evm/src/cpu/kernel/asm/main.asm similarity index 100% rename from src/cpu/kernel/asm/main.asm rename to evm/src/cpu/kernel/asm/main.asm diff --git a/src/cpu/kernel/asm/memory/core.asm b/evm/src/cpu/kernel/asm/memory/core.asm similarity index 100% rename from src/cpu/kernel/asm/memory/core.asm rename to evm/src/cpu/kernel/asm/memory/core.asm diff --git a/src/cpu/kernel/asm/memory/memcpy.asm b/evm/src/cpu/kernel/asm/memory/memcpy.asm similarity index 100% rename from src/cpu/kernel/asm/memory/memcpy.asm rename to evm/src/cpu/kernel/asm/memory/memcpy.asm diff --git a/src/cpu/kernel/asm/memory/memset.asm b/evm/src/cpu/kernel/asm/memory/memset.asm similarity index 100% rename from src/cpu/kernel/asm/memory/memset.asm rename to evm/src/cpu/kernel/asm/memory/memset.asm diff --git a/src/cpu/kernel/asm/memory/metadata.asm b/evm/src/cpu/kernel/asm/memory/metadata.asm similarity index 97% rename from src/cpu/kernel/asm/memory/metadata.asm rename to evm/src/cpu/kernel/asm/memory/metadata.asm index e69e292b6..f2dc897a1 100644 --- a/src/cpu/kernel/asm/memory/metadata.asm +++ b/evm/src/cpu/kernel/asm/memory/metadata.asm @@ -268,6 +268,10 @@ global sys_chainid: %mload_global_metadata(@GLOBAL_METADATA_BLOCK_BASE_FEE) %endmacro +%macro blobbasefee + %mload_global_metadata(@GLOBAL_METADATA_BLOCK_BLOB_BASE_FEE) +%endmacro + global sys_basefee: // stack: kexit_info %charge_gas_const(@GAS_BASE) @@ -277,6 +281,15 @@ global sys_basefee: SWAP1 EXIT_KERNEL +global sys_blobbasefee: + // stack: kexit_info + %charge_gas_const(@GAS_BASE) + // stack: kexit_info + %blobbasefee + // stack: blobbasefee, kexit_info + SWAP1 + EXIT_KERNEL + global sys_blockhash: // stack: kexit_info, block_number %charge_gas_const(@GAS_BLOCKHASH) diff --git a/src/cpu/kernel/asm/memory/packing.asm b/evm/src/cpu/kernel/asm/memory/packing.asm similarity index 100% rename from src/cpu/kernel/asm/memory/packing.asm rename to evm/src/cpu/kernel/asm/memory/packing.asm diff --git a/src/cpu/kernel/asm/memory/syscalls.asm b/evm/src/cpu/kernel/asm/memory/syscalls.asm similarity index 73% rename from src/cpu/kernel/asm/memory/syscalls.asm rename to evm/src/cpu/kernel/asm/memory/syscalls.asm index d20f2d0e6..97607d191 100644 --- a/src/cpu/kernel/asm/memory/syscalls.asm +++ b/evm/src/cpu/kernel/asm/memory/syscalls.asm @@ -113,6 +113,7 @@ codecopy_within_bounds: // stack: total_size, segment, src_ctx, kexit_info, dest_offset, offset, size POP wcopy_within_bounds: + // TODO: rework address creation to have less stack manipulation overhead // stack: segment, src_ctx, kexit_info, dest_offset, offset, size GET_CONTEXT %stack (context, segment, src_ctx, kexit_info, dest_offset, offset, size) -> @@ -219,6 +220,71 @@ extcodecopy_contd: // stack: code_size, ctx, kexit_info, dest_offset, offset, size %codecopy_after_checks(@SEGMENT_CODE) +// Same as %wcopy but with special handling in case of overlapping ranges. +global sys_mcopy: + // stack: kexit_info, dest_offset, offset, size + %wcopy_charge_gas + + %stack (kexit_info, dest_offset, offset, size) -> (dest_offset, size, kexit_info, dest_offset, offset, size) + %add_or_fault + // stack: expanded_num_bytes, kexit_info, dest_offset, offset, size, kexit_info + DUP1 %ensure_reasonable_offset + %update_mem_bytes + + // stack: kexit_info, dest_offset, offset, size + DUP3 DUP3 EQ + // stack: dest_offset = offset, kexit_info, dest_offset, offset, size + %jumpi(mcopy_empty) // If SRC == DST, just pop the stack and exit the kernel + + // stack: kexit_info, dest_offset, offset, size + GET_CONTEXT + PUSH @SEGMENT_MAIN_MEMORY + DUP6 DUP6 ADD + // stack: offset + size, segment, context, kexit_info, dest_offset, offset, size + DUP5 LT + // stack: dest_offset < offset + size, segment, context, kexit_info, dest_offset, offset, size + DUP6 DUP6 GT + // stack: dest_offset > offset, dest_offset < offset + size, segment, context, kexit_info, dest_offset, offset, size + MUL // AND + // stack: (dest_offset > offset) && (dest_offset < offset + size), segment, context, kexit_info, dest_offset, offset, size + + // If both conditions are satisfied, that means we will get an overlap, in which case we need to process the copy + // in two chunks to prevent overwriting memory data before reading it. + %jumpi(mcopy_with_overlap) + + // stack: segment, context, kexit_info, dest_offset, offset, size + %jump(wcopy_within_bounds) + +mcopy_with_overlap: + // We do have an overlap between the SRC and DST ranges. We will first copy the overlapping segment + // (i.e. end of the copy portion), then copy the remaining (i.e. beginning) portion. + + // stack: segment, context, kexit_info, dest_offset, offset, size + DUP5 DUP5 SUB + // stack: remaining_size = dest_offset - offset, segment, context, kexit_info, dest_offset, offset, size + DUP1 DUP8 + SUB // overlapping_size = size - remaining_size + // stack: overlapping_size, remaining_size, segment, context, kexit_info, dest_offset, offset, size + + // Shift the initial offsets to copy the overlapping segment first. + DUP2 DUP8 ADD + // stack: offset_first_copy, overlapping_size, remaining_size, segment, context, kexit_info, dest_offset, offset, size + DUP3 DUP8 ADD + // stack: dest_offset_first_copy, offset_first_copy, overlapping_size, remaining_size, segment, context, kexit_info, dest_offset, offset, size + + %stack (dest_offset_first_copy, offset_first_copy, overlapping_size, remaining_size, segment, context, kexit_info, dest_offset, offset, size) -> + (context, segment, offset_first_copy, segment, dest_offset_first_copy, context, overlapping_size, wcopy_within_bounds, segment, context, kexit_info, dest_offset, offset, remaining_size) + %build_address // SRC + SWAP3 + %build_address // DST + // stack: DST, SRC, overlapping_size, wcopy_within_bounds, segment, context, kexit_info, dest_offset, offset, remaining_size + %jump(memcpy_bytes) + +mcopy_empty: + // kexit_info, dest_offset, offset, size + %stack (kexit_info, dest_offset, offset, size) -> (kexit_info) + EXIT_KERNEL + // The internal logic is similar to wcopy, but handles range overflow differently. // It is used for both CODECOPY and EXTCODECOPY. diff --git a/src/cpu/kernel/asm/memory/txn_fields.asm b/evm/src/cpu/kernel/asm/memory/txn_fields.asm similarity index 100% rename from src/cpu/kernel/asm/memory/txn_fields.asm rename to evm/src/cpu/kernel/asm/memory/txn_fields.asm diff --git a/src/cpu/kernel/asm/mpt/accounts.asm b/evm/src/cpu/kernel/asm/mpt/accounts.asm similarity index 100% rename from src/cpu/kernel/asm/mpt/accounts.asm rename to evm/src/cpu/kernel/asm/mpt/accounts.asm diff --git a/src/cpu/kernel/asm/mpt/delete/delete.asm b/evm/src/cpu/kernel/asm/mpt/delete/delete.asm similarity index 100% rename from src/cpu/kernel/asm/mpt/delete/delete.asm rename to evm/src/cpu/kernel/asm/mpt/delete/delete.asm diff --git a/src/cpu/kernel/asm/mpt/delete/delete_branch.asm b/evm/src/cpu/kernel/asm/mpt/delete/delete_branch.asm similarity index 100% rename from src/cpu/kernel/asm/mpt/delete/delete_branch.asm rename to evm/src/cpu/kernel/asm/mpt/delete/delete_branch.asm diff --git a/src/cpu/kernel/asm/mpt/delete/delete_extension.asm b/evm/src/cpu/kernel/asm/mpt/delete/delete_extension.asm similarity index 100% rename from src/cpu/kernel/asm/mpt/delete/delete_extension.asm rename to evm/src/cpu/kernel/asm/mpt/delete/delete_extension.asm diff --git a/src/cpu/kernel/asm/mpt/hash/hash.asm b/evm/src/cpu/kernel/asm/mpt/hash/hash.asm similarity index 100% rename from src/cpu/kernel/asm/mpt/hash/hash.asm rename to evm/src/cpu/kernel/asm/mpt/hash/hash.asm diff --git a/src/cpu/kernel/asm/mpt/hash/hash_trie_specific.asm b/evm/src/cpu/kernel/asm/mpt/hash/hash_trie_specific.asm similarity index 100% rename from src/cpu/kernel/asm/mpt/hash/hash_trie_specific.asm rename to evm/src/cpu/kernel/asm/mpt/hash/hash_trie_specific.asm diff --git a/src/cpu/kernel/asm/mpt/hex_prefix.asm b/evm/src/cpu/kernel/asm/mpt/hex_prefix.asm similarity index 100% rename from src/cpu/kernel/asm/mpt/hex_prefix.asm rename to evm/src/cpu/kernel/asm/mpt/hex_prefix.asm diff --git a/src/cpu/kernel/asm/mpt/insert/insert.asm b/evm/src/cpu/kernel/asm/mpt/insert/insert.asm similarity index 100% rename from src/cpu/kernel/asm/mpt/insert/insert.asm rename to evm/src/cpu/kernel/asm/mpt/insert/insert.asm diff --git a/src/cpu/kernel/asm/mpt/insert/insert_extension.asm b/evm/src/cpu/kernel/asm/mpt/insert/insert_extension.asm similarity index 100% rename from src/cpu/kernel/asm/mpt/insert/insert_extension.asm rename to evm/src/cpu/kernel/asm/mpt/insert/insert_extension.asm diff --git a/src/cpu/kernel/asm/mpt/insert/insert_leaf.asm b/evm/src/cpu/kernel/asm/mpt/insert/insert_leaf.asm similarity index 100% rename from src/cpu/kernel/asm/mpt/insert/insert_leaf.asm rename to evm/src/cpu/kernel/asm/mpt/insert/insert_leaf.asm diff --git a/src/cpu/kernel/asm/mpt/insert/insert_trie_specific.asm b/evm/src/cpu/kernel/asm/mpt/insert/insert_trie_specific.asm similarity index 100% rename from src/cpu/kernel/asm/mpt/insert/insert_trie_specific.asm rename to evm/src/cpu/kernel/asm/mpt/insert/insert_trie_specific.asm diff --git a/src/cpu/kernel/asm/mpt/read.asm b/evm/src/cpu/kernel/asm/mpt/read.asm similarity index 100% rename from src/cpu/kernel/asm/mpt/read.asm rename to evm/src/cpu/kernel/asm/mpt/read.asm diff --git a/src/cpu/kernel/asm/mpt/storage/storage_read.asm b/evm/src/cpu/kernel/asm/mpt/storage/storage_read.asm similarity index 100% rename from src/cpu/kernel/asm/mpt/storage/storage_read.asm rename to evm/src/cpu/kernel/asm/mpt/storage/storage_read.asm diff --git a/src/cpu/kernel/asm/mpt/storage/storage_write.asm b/evm/src/cpu/kernel/asm/mpt/storage/storage_write.asm similarity index 100% rename from src/cpu/kernel/asm/mpt/storage/storage_write.asm rename to evm/src/cpu/kernel/asm/mpt/storage/storage_write.asm diff --git a/src/cpu/kernel/asm/mpt/util.asm b/evm/src/cpu/kernel/asm/mpt/util.asm similarity index 100% rename from src/cpu/kernel/asm/mpt/util.asm rename to evm/src/cpu/kernel/asm/mpt/util.asm diff --git a/src/cpu/kernel/asm/rlp/decode.asm b/evm/src/cpu/kernel/asm/rlp/decode.asm similarity index 100% rename from src/cpu/kernel/asm/rlp/decode.asm rename to evm/src/cpu/kernel/asm/rlp/decode.asm diff --git a/src/cpu/kernel/asm/rlp/encode.asm b/evm/src/cpu/kernel/asm/rlp/encode.asm similarity index 100% rename from src/cpu/kernel/asm/rlp/encode.asm rename to evm/src/cpu/kernel/asm/rlp/encode.asm diff --git a/src/cpu/kernel/asm/rlp/encode_rlp_scalar.asm b/evm/src/cpu/kernel/asm/rlp/encode_rlp_scalar.asm similarity index 100% rename from src/cpu/kernel/asm/rlp/encode_rlp_scalar.asm rename to evm/src/cpu/kernel/asm/rlp/encode_rlp_scalar.asm diff --git a/src/cpu/kernel/asm/rlp/encode_rlp_string.asm b/evm/src/cpu/kernel/asm/rlp/encode_rlp_string.asm similarity index 100% rename from src/cpu/kernel/asm/rlp/encode_rlp_string.asm rename to evm/src/cpu/kernel/asm/rlp/encode_rlp_string.asm diff --git a/src/cpu/kernel/asm/rlp/increment_bounded_rlp.asm b/evm/src/cpu/kernel/asm/rlp/increment_bounded_rlp.asm similarity index 100% rename from src/cpu/kernel/asm/rlp/increment_bounded_rlp.asm rename to evm/src/cpu/kernel/asm/rlp/increment_bounded_rlp.asm diff --git a/src/cpu/kernel/asm/rlp/num_bytes.asm b/evm/src/cpu/kernel/asm/rlp/num_bytes.asm similarity index 100% rename from src/cpu/kernel/asm/rlp/num_bytes.asm rename to evm/src/cpu/kernel/asm/rlp/num_bytes.asm diff --git a/src/cpu/kernel/asm/rlp/read_to_memory.asm b/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm similarity index 100% rename from src/cpu/kernel/asm/rlp/read_to_memory.asm rename to evm/src/cpu/kernel/asm/rlp/read_to_memory.asm diff --git a/src/cpu/kernel/asm/shift.asm b/evm/src/cpu/kernel/asm/shift.asm similarity index 100% rename from src/cpu/kernel/asm/shift.asm rename to evm/src/cpu/kernel/asm/shift.asm diff --git a/src/cpu/kernel/asm/signed.asm b/evm/src/cpu/kernel/asm/signed.asm similarity index 100% rename from src/cpu/kernel/asm/signed.asm rename to evm/src/cpu/kernel/asm/signed.asm diff --git a/src/cpu/kernel/asm/transactions/common_decoding.asm b/evm/src/cpu/kernel/asm/transactions/common_decoding.asm similarity index 100% rename from src/cpu/kernel/asm/transactions/common_decoding.asm rename to evm/src/cpu/kernel/asm/transactions/common_decoding.asm diff --git a/src/cpu/kernel/asm/transactions/router.asm b/evm/src/cpu/kernel/asm/transactions/router.asm similarity index 100% rename from src/cpu/kernel/asm/transactions/router.asm rename to evm/src/cpu/kernel/asm/transactions/router.asm diff --git a/src/cpu/kernel/asm/transactions/type_0.asm b/evm/src/cpu/kernel/asm/transactions/type_0.asm similarity index 100% rename from src/cpu/kernel/asm/transactions/type_0.asm rename to evm/src/cpu/kernel/asm/transactions/type_0.asm diff --git a/src/cpu/kernel/asm/transactions/type_1.asm b/evm/src/cpu/kernel/asm/transactions/type_1.asm similarity index 100% rename from src/cpu/kernel/asm/transactions/type_1.asm rename to evm/src/cpu/kernel/asm/transactions/type_1.asm diff --git a/src/cpu/kernel/asm/transactions/type_2.asm b/evm/src/cpu/kernel/asm/transactions/type_2.asm similarity index 100% rename from src/cpu/kernel/asm/transactions/type_2.asm rename to evm/src/cpu/kernel/asm/transactions/type_2.asm diff --git a/src/cpu/kernel/asm/util/assertions.asm b/evm/src/cpu/kernel/asm/util/assertions.asm similarity index 100% rename from src/cpu/kernel/asm/util/assertions.asm rename to evm/src/cpu/kernel/asm/util/assertions.asm diff --git a/src/cpu/kernel/asm/util/basic_macros.asm b/evm/src/cpu/kernel/asm/util/basic_macros.asm similarity index 100% rename from src/cpu/kernel/asm/util/basic_macros.asm rename to evm/src/cpu/kernel/asm/util/basic_macros.asm diff --git a/src/cpu/kernel/asm/util/keccak.asm b/evm/src/cpu/kernel/asm/util/keccak.asm similarity index 100% rename from src/cpu/kernel/asm/util/keccak.asm rename to evm/src/cpu/kernel/asm/util/keccak.asm diff --git a/src/cpu/kernel/asm/util/math.asm b/evm/src/cpu/kernel/asm/util/math.asm similarity index 100% rename from src/cpu/kernel/asm/util/math.asm rename to evm/src/cpu/kernel/asm/util/math.asm diff --git a/src/cpu/kernel/assembler.rs b/evm/src/cpu/kernel/assembler.rs similarity index 98% rename from src/cpu/kernel/assembler.rs rename to evm/src/cpu/kernel/assembler.rs index 2dc79d611..e28fc815b 100644 --- a/src/cpu/kernel/assembler.rs +++ b/evm/src/cpu/kernel/assembler.rs @@ -17,9 +17,10 @@ use crate::cpu::kernel::stack::stack_manipulation::expand_stack_manipulation; use crate::cpu::kernel::utils::u256_to_trimmed_be_bytes; use crate::generation::prover_input::ProverInputFn; -/// The number of bytes to push when pushing an offset within the code (i.e. when assembling jumps). -/// Ideally we would automatically use the minimal number of bytes required, but that would be -/// nontrivial given the circular dependency between an offset and its size. +/// The number of bytes to push when pushing an offset within the code (i.e. +/// when assembling jumps). Ideally we would automatically use the minimal +/// number of bytes required, but that would be nontrivial given the circular +/// dependency between an offset and its size. pub(crate) const BYTES_PER_OFFSET: u8 = 3; #[derive(PartialEq, Eq, Debug, Serialize, Deserialize)] @@ -68,7 +69,8 @@ impl Kernel { serde_json::from_slice(&bytes).unwrap() } - /// Get a string representation of the current offset for debugging purposes. + /// Get a string representation of the current offset for debugging + /// purposes. pub(crate) fn offset_name(&self, offset: usize) -> String { match self .ordered_labels @@ -435,8 +437,9 @@ mod tests { #[test] fn two_files() { - // We will test two simple files, with a label and a jump, to ensure that jump offsets - // are correctly shifted based on the offset of the containing file. + // We will test two simple files, with a label and a jump, to ensure that jump + // offsets are correctly shifted based on the offset of the containing + // file. let file_1 = File { body: vec![ @@ -678,7 +681,8 @@ mod tests { let mut consts = HashMap::new(); consts.insert("LIFE".into(), 42.into()); parse_and_assemble_ext(&["%stack (a, b) -> (b, @LIFE)"], consts, true); - // We won't check the code since there are two equally efficient implementations. + // We won't check the code since there are two equally efficient + // implementations. let kernel = parse_and_assemble(&["start: %stack (a, b) -> (start)"]); assert_eq!(kernel.code, vec![pop, pop, push_label, 0, 0, 0]); diff --git a/src/cpu/kernel/ast.rs b/evm/src/cpu/kernel/ast.rs similarity index 100% rename from src/cpu/kernel/ast.rs rename to evm/src/cpu/kernel/ast.rs diff --git a/src/cpu/kernel/constants/context_metadata.rs b/evm/src/cpu/kernel/constants/context_metadata.rs similarity index 90% rename from src/cpu/kernel/constants/context_metadata.rs rename to evm/src/cpu/kernel/constants/context_metadata.rs index ffcc65387..45e69e46b 100644 --- a/src/cpu/kernel/constants/context_metadata.rs +++ b/evm/src/cpu/kernel/constants/context_metadata.rs @@ -2,8 +2,8 @@ use crate::memory::segments::Segment; /// These metadata fields contain VM state specific to a particular context. /// -/// Each value is directly scaled by the corresponding `Segment::ContextMetadata` value for faster -/// memory access in the kernel. +/// Each value is directly scaled by the corresponding +/// `Segment::ContextMetadata` value for faster memory access in the kernel. #[allow(clippy::enum_clike_unportable_variant)] #[repr(usize)] #[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Debug)] @@ -17,18 +17,18 @@ pub(crate) enum ContextMetadata { /// The address of the account associated with this context. Address, /// The size of the code under the account associated with this context. - /// While this information could be obtained from the state trie, it is best to cache it since - /// the `CODESIZE` instruction is very cheap. + /// While this information could be obtained from the state trie, it is best + /// to cache it since the `CODESIZE` instruction is very cheap. CodeSize, /// The address of the caller who spawned this context. Caller, /// The value (in wei) deposited by the caller. CallValue, - /// Whether this context was created by `STATICCALL`, in which case state changes are - /// prohibited. + /// Whether this context was created by `STATICCALL`, in which case state + /// changes are prohibited. Static, - /// Pointer to the initial version of the state trie, at the creation of this context. Used when - /// we need to revert a context. + /// Pointer to the initial version of the state trie, at the creation of + /// this context. Used when we need to revert a context. StateTrieCheckpointPointer, /// Size of the active main memory, in (32 byte) words. MemWords, diff --git a/src/cpu/kernel/constants/exc_bitfields.rs b/evm/src/cpu/kernel/constants/exc_bitfields.rs similarity index 85% rename from src/cpu/kernel/constants/exc_bitfields.rs rename to evm/src/cpu/kernel/constants/exc_bitfields.rs index 59dec8b1e..f12b7e252 100644 --- a/src/cpu/kernel/constants/exc_bitfields.rs +++ b/evm/src/cpu/kernel/constants/exc_bitfields.rs @@ -2,8 +2,8 @@ use core::ops::RangeInclusive; use ethereum_types::U256; -/// Create a U256, where the bits at indices inside the specified ranges are set to 1, and all other -/// bits are set to 0. +/// Create a U256, where the bits at indices inside the specified ranges are set +/// to 1, and all other bits are set to 0. const fn u256_from_set_index_ranges(ranges: &[RangeInclusive; N]) -> U256 { let mut j = 0; let mut res_limbs = [0u64; 4]; @@ -35,7 +35,9 @@ pub(crate) const STACK_LENGTH_INCREASING_OPCODES_USER: U256 = u256_from_set_inde 0x38..=0x38, // CODESIZE 0x3a..=0x3a, // GASPRICE 0x3d..=0x3d, // RETURNDATASIZE - 0x41..=0x48, // COINBASE, TIMESTAMP, NUMBER, DIFFICULTY, GASLIMIT, CHAINID, SELFBALANCE, BASEFEE + 0x41..=0x48, /* COINBASE, TIMESTAMP, NUMBER, DIFFICULTY, GASLIMIT, CHAINID, SELFBALANCE, + * BASEFEE */ + 0x4a..=0x4a, // BLOBBASEFEE 0x58..=0x5a, // PC, MSIZE, GAS 0x5f..=0x8f, // PUSH*, DUP* ]); diff --git a/src/cpu/kernel/constants/global_metadata.rs b/evm/src/cpu/kernel/constants/global_metadata.rs similarity index 90% rename from src/cpu/kernel/constants/global_metadata.rs rename to evm/src/cpu/kernel/constants/global_metadata.rs index 0b3f66481..227ec5a16 100644 --- a/src/cpu/kernel/constants/global_metadata.rs +++ b/evm/src/cpu/kernel/constants/global_metadata.rs @@ -1,28 +1,31 @@ use crate::memory::segments::Segment; -/// These metadata fields contain global VM state, stored in the `Segment::Metadata` segment of the -/// kernel's context (which is zero). +/// These metadata fields contain global VM state, stored in the +/// `Segment::Metadata` segment of the kernel's context (which is zero). /// -/// Each value is directly scaled by the corresponding `Segment::GlobalMetadata` value for faster -/// memory access in the kernel. +/// Each value is directly scaled by the corresponding `Segment::GlobalMetadata` +/// value for faster memory access in the kernel. #[allow(clippy::enum_clike_unportable_variant)] #[repr(usize)] #[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Debug)] pub(crate) enum GlobalMetadata { - /// The largest context ID that has been used so far in this execution. Tracking this allows us - /// give each new context a unique ID, so that its memory will be zero-initialized. + /// The largest context ID that has been used so far in this execution. + /// Tracking this allows us give each new context a unique ID, so that + /// its memory will be zero-initialized. LargestContext = Segment::GlobalMetadata as usize, /// The size of active memory, in bytes. MemorySize, - /// The size of the `TrieData` segment, in bytes. In other words, the next address available for - /// appending additional trie data. + /// The size of the `TrieData` segment, in bytes. In other words, the next + /// address available for appending additional trie data. TrieDataSize, - /// The size of the `TrieData` segment, in bytes, represented as a whole address. - /// In other words, the next address available for appending additional trie data. + /// The size of the `TrieData` segment, in bytes, represented as a whole + /// address. In other words, the next address available for appending + /// additional trie data. RlpDataSize, /// A pointer to the root of the state trie within the `TrieData` buffer. StateTrieRoot, - /// A pointer to the root of the transaction trie within the `TrieData` buffer. + /// A pointer to the root of the transaction trie within the `TrieData` + /// buffer. TransactionTrieRoot, /// A pointer to the root of the receipt trie within the `TrieData` buffer. ReceiptTrieRoot, @@ -88,13 +91,17 @@ pub(crate) enum GlobalMetadata { LogsPayloadLen, TxnNumberBefore, TxnNumberAfter, + BlockBlobBaseFee, + + /// Number of created contracts during the current transaction. + CreatedContractsLen, KernelHash, KernelLen, } impl GlobalMetadata { - pub(crate) const COUNT: usize = 47; + pub(crate) const COUNT: usize = 49; /// Unscales this virtual offset by their respective `Segment` value. pub(crate) const fn unscale(&self) -> usize { @@ -148,6 +155,8 @@ impl GlobalMetadata { Self::BlockCurrentHash, Self::TxnNumberBefore, Self::TxnNumberAfter, + Self::BlockBlobBaseFee, + Self::CreatedContractsLen, Self::KernelHash, Self::KernelLen, ] @@ -201,6 +210,8 @@ impl GlobalMetadata { Self::LogsPayloadLen => "GLOBAL_METADATA_LOGS_PAYLOAD_LEN", Self::TxnNumberBefore => "GLOBAL_METADATA_TXN_NUMBER_BEFORE", Self::TxnNumberAfter => "GLOBAL_METADATA_TXN_NUMBER_AFTER", + Self::BlockBlobBaseFee => "GLOBAL_METADATA_BLOCK_BLOB_BASE_FEE", + Self::CreatedContractsLen => "GLOBAL_METADATA_CREATED_CONTRACTS_LEN", Self::KernelHash => "GLOBAL_METADATA_KERNEL_HASH", Self::KernelLen => "GLOBAL_METADATA_KERNEL_LEN", } diff --git a/src/cpu/kernel/constants/journal_entry.rs b/evm/src/cpu/kernel/constants/journal_entry.rs similarity index 100% rename from src/cpu/kernel/constants/journal_entry.rs rename to evm/src/cpu/kernel/constants/journal_entry.rs diff --git a/src/cpu/kernel/constants/mod.rs b/evm/src/cpu/kernel/constants/mod.rs similarity index 99% rename from src/cpu/kernel/constants/mod.rs rename to evm/src/cpu/kernel/constants/mod.rs index 82c820f05..8aea84883 100644 --- a/src/cpu/kernel/constants/mod.rs +++ b/evm/src/cpu/kernel/constants/mod.rs @@ -168,7 +168,8 @@ const EC_CONSTANTS: [(&str, [u8; 32]); 20] = [ ), ( "BN_BNEG_LOC", - // This just needs to be large enough to not interfere with anything else in SEGMENT_BN_TABLE_Q. + // This just needs to be large enough to not interfere with anything else in + // SEGMENT_BN_TABLE_Q. hex!("0000000000000000000000000000000000000000000000000000000000001337"), ), ( diff --git a/src/cpu/kernel/constants/trie_type.rs b/evm/src/cpu/kernel/constants/trie_type.rs similarity index 100% rename from src/cpu/kernel/constants/trie_type.rs rename to evm/src/cpu/kernel/constants/trie_type.rs diff --git a/src/cpu/kernel/constants/txn_fields.rs b/evm/src/cpu/kernel/constants/txn_fields.rs similarity index 92% rename from src/cpu/kernel/constants/txn_fields.rs rename to evm/src/cpu/kernel/constants/txn_fields.rs index 0b74409b3..7b49cf7ff 100644 --- a/src/cpu/kernel/constants/txn_fields.rs +++ b/evm/src/cpu/kernel/constants/txn_fields.rs @@ -1,16 +1,17 @@ use crate::memory::segments::Segment; -/// These are normalized transaction fields, i.e. not specific to any transaction type. +/// These are normalized transaction fields, i.e. not specific to any +/// transaction type. /// -/// Each value is directly scaled by the corresponding `Segment::TxnFields` value for faster -/// memory access in the kernel. +/// Each value is directly scaled by the corresponding `Segment::TxnFields` +/// value for faster memory access in the kernel. #[allow(dead_code)] #[allow(clippy::enum_clike_unportable_variant)] #[repr(usize)] #[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Debug)] pub(crate) enum NormalizedTxnField { - /// Whether a chain ID was present in the txn data. Type 0 transaction with v=27 or v=28 have - /// no chain ID. This affects what fields get signed. + /// Whether a chain ID was present in the txn data. Type 0 transaction with + /// v=27 or v=28 have no chain ID. This affects what fields get signed. ChainIdPresent = Segment::TxnFields as usize, ChainId, Nonce, @@ -20,7 +21,8 @@ pub(crate) enum NormalizedTxnField { IntrinsicGas, To, Value, - /// The length of the data field. The data itself is stored in another segment. + /// The length of the data field. The data itself is stored in another + /// segment. DataLen, YParity, R, @@ -28,7 +30,8 @@ pub(crate) enum NormalizedTxnField { Origin, /// The actual computed gas price for this transaction in the block. - /// This is not technically a transaction field, as it depends on the block's base fee. + /// This is not technically a transaction field, as it depends on the + /// block's base fee. ComputedFeePerGas, ComputedPriorityFeePerGas, } diff --git a/src/cpu/kernel/cost_estimator.rs b/evm/src/cpu/kernel/cost_estimator.rs similarity index 90% rename from src/cpu/kernel/cost_estimator.rs rename to evm/src/cpu/kernel/cost_estimator.rs index 70cc72677..daa416e74 100644 --- a/src/cpu/kernel/cost_estimator.rs +++ b/evm/src/cpu/kernel/cost_estimator.rs @@ -26,8 +26,9 @@ fn cost_estimate_item(item: &Item) -> u32 { } const fn cost_estimate_standard_op(_op: &str) -> u32 { - // For now we just treat any standard operation as having the same cost. This is pretty naive, - // but should work fine with our current set of simple optimization rules. + // For now we just treat any standard operation as having the same cost. This is + // pretty naive, but should work fine with our current set of simple + // optimization rules. 1 } diff --git a/src/cpu/kernel/evm_asm.pest b/evm/src/cpu/kernel/evm_asm.pest similarity index 100% rename from src/cpu/kernel/evm_asm.pest rename to evm/src/cpu/kernel/evm_asm.pest diff --git a/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs similarity index 96% rename from src/cpu/kernel/interpreter.rs rename to evm/src/cpu/kernel/interpreter.rs index a8937cf2e..42f0d108c 100644 --- a/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -4,7 +4,7 @@ use core::cmp::Ordering; use core::ops::Range; use std::collections::{BTreeSet, HashMap}; -use anyhow::{anyhow, bail}; +use anyhow::bail; use eth_trie_utils::partial_trie::PartialTrie; use ethereum_types::{BigEndianHash, H160, H256, U256, U512}; use keccak_hash::keccak; @@ -77,8 +77,8 @@ struct InterpreterRegistersState { } /// Interpreter state at the last checkpoint: we only need to store -/// the state of the registers and the length of the vector of memory operations. -/// This data is enough to revert in case of an exception. +/// the state of the registers and the length of the vector of memory +/// operations. This data is enough to revert in case of an exception. struct InterpreterCheckpoint { registers: InterpreterRegistersState, mem_len: usize, @@ -134,8 +134,8 @@ pub(crate) fn run<'a, F: Field>( Ok(interpreter) } -/// Simulates the CPU execution from `state` until the program counter reaches `final_label` -/// in the current context. +/// Simulates the CPU execution from `state` until the program counter reaches +/// `final_label` in the current context. pub(crate) fn simulate_cpu_and_get_user_jumps( final_label: &str, state: &GenerationState, @@ -164,13 +164,16 @@ pub(crate) fn simulate_cpu_and_get_user_jumps( } } -/// Different types of Memory operations in the interpreter, and the data required to revert them. +/// Different types of Memory operations in the interpreter, and the data +/// required to revert them. enum InterpreterMemOpKind { /// We need to provide the context. Push(usize), - /// If we pop a certain value, we need to push it back to the correct context when reverting. + /// If we pop a certain value, we need to push it back to the correct + /// context when reverting. Pop(U256, usize), - /// If we write a value at a certain address, we need to write the old value back when reverting. + /// If we write a value at a certain address, we need to write the old value + /// back when reverting. Write(U256, usize, usize, usize), } @@ -186,8 +189,8 @@ impl<'a, F: Field> Interpreter<'a, F> { result } - /// Returns an instance of `Interpreter` given `GenerationInputs`, and assuming we are - /// initializing with the `KERNEL` code. + /// Returns an instance of `Interpreter` given `GenerationInputs`, and + /// assuming we are initializing with the `KERNEL` code. pub(crate) fn new_with_generation_inputs_and_kernel( initial_offset: usize, initial_stack: Vec, @@ -248,7 +251,8 @@ impl<'a, F: Field> Interpreter<'a, F> { } } - /// Initializes the interpreter state given `GenerationInputs`, using the KERNEL code. + /// Initializes the interpreter state given `GenerationInputs`, using the + /// KERNEL code. pub(crate) fn initialize_interpreter_state_with_kernel(&mut self, inputs: GenerationInputs) { self.initialize_interpreter_state(inputs, KERNEL.code_hash, KERNEL.code.len()); } @@ -352,7 +356,7 @@ impl<'a, F: Field> Interpreter<'a, F> { (Segment::GlobalBlockBloom.unscale()).into(), i.into(), ) - .expect("This cannot panic as `virt` fits in a `u32`"), + .unwrap(), metadata.block_bloom[i], ) }) @@ -369,7 +373,7 @@ impl<'a, F: Field> Interpreter<'a, F> { (Segment::BlockHashes.unscale()).into(), i.into(), ) - .expect("This cannot panic as `virt` fits in a `u32`"), + .unwrap(), h2u(inputs.block_hashes.prev_hashes[i]), ) }) @@ -390,7 +394,7 @@ impl<'a, F: Field> Interpreter<'a, F> { } } - fn roll_memory_back(&mut self, len: usize) -> Result<(), ProgramError> { + fn roll_memory_back(&mut self, len: usize) { // We roll the memory back until `memops` reaches length `len`. debug_assert!(self.memops.len() >= len); while self.memops.len() > len { @@ -400,28 +404,25 @@ impl<'a, F: Field> Interpreter<'a, F> { self.generation_state.memory.contexts[context].segments [Segment::Stack.unscale()] .content - .pop() - .ok_or(ProgramError::StackUnderflow)?; + .pop(); } InterpreterMemOpKind::Pop(value, context) => { self.generation_state.memory.contexts[context].segments [Segment::Stack.unscale()] .content - .push(value); + .push(value) } InterpreterMemOpKind::Write(value, context, segment, offset) => { self.generation_state.memory.contexts[context].segments [segment >> SEGMENT_SCALING_FACTOR] // we need to unscale the segment value - .content[offset] = value; + .content[offset] = value } } } } - - Ok(()) } - fn rollback(&mut self, checkpoint: InterpreterCheckpoint) -> anyhow::Result<()> { + fn rollback(&mut self, checkpoint: InterpreterCheckpoint) { let InterpreterRegistersState { kernel_mode, context, @@ -430,8 +431,7 @@ impl<'a, F: Field> Interpreter<'a, F> { self.set_is_kernel(kernel_mode); self.set_context(context); self.generation_state.registers = registers; - self.roll_memory_back(checkpoint.mem_len) - .map_err(|_| anyhow!("Memory rollback failed unexpectedly.")) + self.roll_memory_back(checkpoint.mem_len); } fn handle_error(&mut self, err: ProgramError) -> anyhow::Result<()> { @@ -484,7 +484,7 @@ impl<'a, F: Field> Interpreter<'a, F> { .content, ); } - self.rollback(checkpoint)?; + self.rollback(checkpoint); self.handle_error(e) } }?; @@ -692,7 +692,7 @@ impl<'a, F: Field> Interpreter<'a, F> { } pub(crate) fn extract_kernel_memory(self, segment: Segment, range: Range) -> Vec { - let mut output: Vec = Vec::with_capacity(range.end); + let mut output: Vec = vec![]; for i in range { let term = self .generation_state @@ -734,7 +734,7 @@ impl<'a, F: Field> Interpreter<'a, F> { .push(InterpreterMemOpKind::Pop(val, self.context())); } if self.stack_len() > 1 { - let top = stack_peek(&self.generation_state, 1)?; + let top = stack_peek(&self.generation_state, 1).unwrap(); self.generation_state.registers.stack_top = top; } self.generation_state.registers.stack_len -= 1; @@ -838,6 +838,7 @@ impl<'a, F: Field> Interpreter<'a, F> { 0x47 => self.run_syscall(opcode, 0, true), // SELFABALANCE, 0x48 => self.run_syscall(opcode, 0, true), // "BASEFEE", 0x49 => self.run_prover_input(), // "PROVER_INPUT", + 0x4a => self.run_syscall(opcode, 0, true), // "BLOBBASEFEE", 0x50 => self.run_pop(), // "POP", 0x51 => self.run_syscall(opcode, 1, false), // "MLOAD", 0x52 => self.run_syscall(opcode, 2, false), // "MSTORE", @@ -850,6 +851,7 @@ impl<'a, F: Field> Interpreter<'a, F> { 0x59 => self.run_syscall(opcode, 0, true), // "MSIZE", 0x5a => self.run_syscall(opcode, 0, true), // "GAS", 0x5b => self.run_jumpdest(), // "JUMPDEST", + 0x5e => self.run_syscall(opcode, 3, false), // "MCOPY", x if (0x5f..0x80).contains(&x) => self.run_push(x - 0x5f), // "PUSH" x if (0x80..0x90).contains(&x) => self.run_dup(x - 0x7f), // "DUP" x if (0x90..0xa0).contains(&x) => self.run_swap(x - 0x8f), // "SWAP" @@ -867,7 +869,7 @@ impl<'a, F: Field> Interpreter<'a, F> { ); Err(ProgramError::KernelPanic) } // "PANIC", - x if (0xc0..0xe0).contains(&x) => self.run_mstore_32bytes(x - 0xc0 + 1), // "MSTORE_32BYTES", + x if (0xc0..0xe0).contains(&x) => self.run_mstore_32bytes(x - 0xc0 + 1), /* "MSTORE_32BYTES", */ 0xf0 => self.run_syscall(opcode, 3, false), // "CREATE", 0xf1 => self.run_syscall(opcode, 7, false), // "CALL", 0xf2 => self.run_syscall(opcode, 7, false), // "CALLCODE", @@ -1074,7 +1076,6 @@ impl<'a, F: Field> Interpreter<'a, F> { let i = self.pop()?; let x = self.pop()?; let result = if i < 32.into() { - // Calling `as_usize()` here is safe. x.byte(31 - i.as_usize()) } else { 0 @@ -1102,7 +1103,7 @@ impl<'a, F: Field> Interpreter<'a, F> { let addr = self.pop()?; let (context, segment, offset) = unpack_address!(addr); - let size = u256_to_usize(self.pop()?)?; + let size = self.pop()?.as_usize(); let bytes = (offset..offset + size) .map(|i| { self.generation_state @@ -1246,6 +1247,10 @@ impl<'a, F: Field> Interpreter<'a, F> { Ok(()) } + fn run_blobbasefee(&mut self) -> anyhow::Result<(), ProgramError> { + self.push(self.get_global_metadata_field(GlobalMetadata::BlockBlobBaseFee)) + } + fn run_pc(&mut self) -> anyhow::Result<(), ProgramError> { self.push( (self @@ -1262,6 +1267,33 @@ impl<'a, F: Field> Interpreter<'a, F> { Ok(()) } + fn run_mcopy(&mut self) -> anyhow::Result<(), ProgramError> { + let dest_offset = self.pop()?.as_usize(); + let offset = self.pop()?.as_usize(); + let size = self.pop()?.as_usize(); + + let intermediary_memory: Vec = (0..size) + .map(|i| { + self.generation_state.memory.mload_general( + self.context(), + Segment::MainMemory, + offset + i, + ) + }) + .collect(); + + for i in 0..size { + self.generation_state.memory.mstore_general( + self.context(), + Segment::MainMemory, + dest_offset + i, + intermediary_memory[i], + ); + } + + Ok(()) + } + fn jump_to(&mut self, offset: usize, is_jumpi: bool) -> anyhow::Result<(), ProgramError> { self.generation_state.registers.program_counter = offset; @@ -1326,7 +1358,7 @@ impl<'a, F: Field> Interpreter<'a, F> { fn run_set_context(&mut self) -> anyhow::Result<(), ProgramError> { let x = self.pop()?; - let new_ctx = u256_to_usize(x >> CONTEXT_SCALING_FACTOR)?; + let new_ctx = (x >> CONTEXT_SCALING_FACTOR).as_usize(); let sp_to_save = self.stack_len().into(); let old_ctx = self.context(); @@ -1337,7 +1369,7 @@ impl<'a, F: Field> Interpreter<'a, F> { let new_sp_addr = MemoryAddress::new(new_ctx, Segment::ContextMetadata, sp_field); self.generation_state.memory.set(old_sp_addr, sp_to_save); - let new_sp = u256_to_usize(self.generation_state.memory.get(new_sp_addr))?; + let new_sp = self.generation_state.memory.get(new_sp_addr).as_usize(); if new_sp > 0 { let new_stack_top = self.generation_state.memory.contexts[new_ctx].segments @@ -1365,7 +1397,7 @@ impl<'a, F: Field> Interpreter<'a, F> { fn run_mload_32bytes(&mut self) -> anyhow::Result<(), ProgramError> { let addr = self.pop()?; let (context, segment, offset) = unpack_address!(addr); - let len = u256_to_usize(self.pop()?)?; + let len = self.pop()?.as_usize(); if len > 32 { return Err(ProgramError::IntegerTooLarge); } @@ -1458,9 +1490,10 @@ impl<'a, F: Field> Interpreter<'a, F> { self.push(exc_info)?; - // Set registers before pushing to the stack; in particular, we need to set kernel mode so we - // can't incorrectly trigger a stack overflow. However, note that we have to do it _after_ we - // make `exc_info`, which should contain the old values. + // Set registers before pushing to the stack; in particular, we need to set + // kernel mode so we can't incorrectly trigger a stack overflow. + // However, note that we have to do it _after_ we make `exc_info`, which + // should contain the old values. self.generation_state.registers.program_counter = new_program_counter; self.set_is_kernel(true); self.generation_state.registers.gas_used = 0; @@ -1567,6 +1600,7 @@ fn get_mnemonic(opcode: u8) -> &'static str { 0x46 => "CHAINID", 0x48 => "BASEFEE", 0x49 => "PROVER_INPUT", + 0x4a => "BLOBBASEFEE", 0x50 => "POP", 0x51 => "MLOAD", 0x52 => "MSTORE", diff --git a/src/cpu/kernel/keccak_util.rs b/evm/src/cpu/kernel/keccak_util.rs similarity index 99% rename from src/cpu/kernel/keccak_util.rs rename to evm/src/cpu/kernel/keccak_util.rs index e1cae7c27..44b88a0ba 100644 --- a/src/cpu/kernel/keccak_util.rs +++ b/evm/src/cpu/kernel/keccak_util.rs @@ -2,7 +2,8 @@ use tiny_keccak::keccakf; use crate::keccak_sponge::columns::{KECCAK_WIDTH_BYTES, KECCAK_WIDTH_U32S}; -/// Like tiny-keccak's `keccakf`, but deals with `u32` limbs instead of `u64` limbs. +/// Like tiny-keccak's `keccakf`, but deals with `u32` limbs instead of `u64` +/// limbs. pub(crate) fn keccakf_u32s(state_u32s: &mut [u32; KECCAK_WIDTH_U32S]) { let mut state_u64s: [u64; 25] = core::array::from_fn(|i| { let lo = state_u32s[i * 2] as u64; diff --git a/src/cpu/kernel/mod.rs b/evm/src/cpu/kernel/mod.rs similarity index 100% rename from src/cpu/kernel/mod.rs rename to evm/src/cpu/kernel/mod.rs diff --git a/src/cpu/kernel/opcodes.rs b/evm/src/cpu/kernel/opcodes.rs similarity index 99% rename from src/cpu/kernel/opcodes.rs rename to evm/src/cpu/kernel/opcodes.rs index 538fe0a10..503f4182e 100644 --- a/src/cpu/kernel/opcodes.rs +++ b/evm/src/cpu/kernel/opcodes.rs @@ -64,6 +64,7 @@ pub fn get_opcode(mnemonic: &str) -> u8 { "CHAINID" => 0x46, "BASEFEE" => 0x48, "PROVER_INPUT" => 0x49, + "BLOBBASEFEE" => 0x4a, "POP" => 0x50, "MLOAD" => 0x51, "MSTORE" => 0x52, diff --git a/src/cpu/kernel/optimizer.rs b/evm/src/cpu/kernel/optimizer.rs similarity index 96% rename from src/cpu/kernel/optimizer.rs rename to evm/src/cpu/kernel/optimizer.rs index f29c96137..4890b6f37 100644 --- a/src/cpu/kernel/optimizer.rs +++ b/evm/src/cpu/kernel/optimizer.rs @@ -44,7 +44,8 @@ fn constant_propagation(code: &mut Vec) { } }); - // Constant propagation for binary ops: `[PUSH y, PUSH x, BINOP] -> [PUSH BINOP(x, y)]` + // Constant propagation for binary ops: `[PUSH y, PUSH x, BINOP] -> [PUSH + // BINOP(x, y)]` replace_windows_if_better(code, |window| { if let [Push(Literal(y)), Push(Literal(x)), StandardOp(op)] = window { match op.as_str() { @@ -138,7 +139,8 @@ fn remove_swaps_commutative(code: &mut Vec) { } /// Remove push-pop type patterns, such as: `[DUP1, POP]`. -// Could be extended to other non-side-effecting operations, e.g. [DUP1, ADD, POP] -> [POP]. +// Could be extended to other non-side-effecting operations, e.g. [DUP1, ADD, +// POP] -> [POP]. fn remove_ignored_values(code: &mut Vec) { replace_windows(code, |[a, b]| { if let StandardOp(pop) = b @@ -155,8 +157,9 @@ fn remove_ignored_values(code: &mut Vec) { }); } -/// Like `replace_windows`, but specifically for code, and only makes replacements if our cost -/// estimator thinks that the new code is more efficient. +/// Like `replace_windows`, but specifically for code, and only makes +/// replacements if our cost estimator thinks that the new code is more +/// efficient. fn replace_windows_if_better(code: &mut Vec, maybe_replace: F) where F: Fn([Item; W]) -> Option>, @@ -197,10 +200,10 @@ mod tests { ]; let mut code = original.clone(); constant_propagation(&mut code); - // Constant propagation could replace the code with [PUSH U256::MAX], but that's actually - // more expensive, so the code shouldn't be changed. - // (The code could also be replaced with [PUSH 0; NOT], which would be an improvement, but - // our optimizer isn't smart enough yet.) + // Constant propagation could replace the code with [PUSH U256::MAX], but that's + // actually more expensive, so the code shouldn't be changed. + // (The code could also be replaced with [PUSH 0; NOT], which would be an + // improvement, but our optimizer isn't smart enough yet.) assert_eq!(code, original); } diff --git a/src/cpu/kernel/parser.rs b/evm/src/cpu/kernel/parser.rs similarity index 100% rename from src/cpu/kernel/parser.rs rename to evm/src/cpu/kernel/parser.rs diff --git a/src/cpu/kernel/stack/mod.rs b/evm/src/cpu/kernel/stack/mod.rs similarity index 100% rename from src/cpu/kernel/stack/mod.rs rename to evm/src/cpu/kernel/stack/mod.rs diff --git a/src/cpu/kernel/stack/permutations.rs b/evm/src/cpu/kernel/stack/permutations.rs similarity index 90% rename from src/cpu/kernel/stack/permutations.rs rename to evm/src/cpu/kernel/stack/permutations.rs index 71304edd0..77a3a7725 100644 --- a/src/cpu/kernel/stack/permutations.rs +++ b/evm/src/cpu/kernel/stack/permutations.rs @@ -1,15 +1,16 @@ -//! This module contains logic for finding the optimal sequence of swaps to get from one stack state -//! to another, specifically for the case where the source and destination states are permutations -//! of one another. +//! This module contains logic for finding the optimal sequence of swaps to get +//! from one stack state to another, specifically for the case where the source +//! and destination states are permutations of one another. //! //! We solve the problem in three steps: //! 1. Find a permutation `P` such that `P A = B`. -//! 2. If `A` contains duplicates, optimize `P` by reducing the number of cycles. -//! 3. Convert each cycle into a set of `(0 i)` transpositions, which correspond to swap -//! instructions in the EVM. +//! 2. If `A` contains duplicates, optimize `P` by reducing the number of +//! cycles. +//! 3. Convert each cycle into a set of `(0 i)` transpositions, which correspond +//! to swap instructions in the EVM. //! -//! We typically represent a permutation as a sequence of cycles. For example, the permutation -//! `(1 2 3)(1 2)(4 5)` acts as: +//! We typically represent a permutation as a sequence of cycles. For example, +//! the permutation `(1 2 3)(1 2)(4 5)` acts as: //! //! ```ignore //! (1 2 3)(1 2)(4 5)[A_0, A_1, A_2, A_3, A_4, A_5] = (1 2 3)(1 2)[A_0, A_1, A_2, A_3, A_5, A_4] @@ -24,11 +25,12 @@ use std::collections::{HashMap, HashSet}; use crate::cpu::kernel::stack::stack_manipulation::{StackItem, StackOp}; -/// Find the optimal sequence of stack operations to get from `src` to `dst`. Assumes that `src` and -/// `dst` are permutations of one another. +/// Find the optimal sequence of stack operations to get from `src` to `dst`. +/// Assumes that `src` and `dst` are permutations of one another. pub(crate) fn get_stack_ops_for_perm(src: &[StackItem], dst: &[StackItem]) -> Vec { - // We store stacks with the tip at the end, but the permutation calls below use the opposite - // convention. They're a bit simpler when SWAP are (0 i) transposes. + // We store stacks with the tip at the end, but the permutation calls below use + // the opposite convention. They're a bit simpler when SWAP are (0 i) + // transposes. let mut src = src.to_vec(); let mut dst = dst.to_vec(); src.reverse(); @@ -59,12 +61,14 @@ fn apply_perm(permutation: Vec>, mut lst: Vec(lst_a: &[T], lst_b: &[T]) -> Vec> { - // We should check to ensure that A and B are indeed rearrangements of each other. + // We should check to ensure that A and B are indeed rearrangements of each + // other. assert!(is_permutation(lst_a, lst_b)); let n = lst_a.len(); - // Keep track of the A_i's which have been already placed into the correct position. + // Keep track of the A_i's which have been already placed into the correct + // position. let mut correct_a = HashSet::new(); // loc_b is a dictionary where loc_b[b] is the indices i where b = B_i != A_i. @@ -91,7 +95,8 @@ pub(crate) fn find_permutation(lst_a: &[T], lst_b: &[T]) - } for i in 0..n { - // If i is both not in the correct position and not already in a cycle, it will start a new cycle. + // If i is both not in the correct position and not already in a cycle, it will + // start a new cycle. if correct_a.contains(&i) { continue; } @@ -102,7 +107,8 @@ pub(crate) fn find_permutation(lst_a: &[T], lst_b: &[T]) - // lst_a[i] need to be swapped into an index j such that lst_b[j] = lst_a[i]. // This exactly means j should be an element of loc_b[lst_a[i]]. // We pop as each j should only be used once. - // In this step we simply find any permutation. We will improve it to an optimal one in STEP 2. + // In this step we simply find any permutation. We will improve it to an optimal + // one in STEP 2. let mut j = loc_b.get_mut(&lst_a[i]).unwrap().pop().unwrap(); // Keep adding elements to the cycle until we return to our initial index @@ -117,7 +123,8 @@ pub(crate) fn find_permutation(lst_a: &[T], lst_b: &[T]) - permutation } -/// This function does STEP 2. It tests to see if cycles can be combined which might occur if A has duplicates. +/// This function does STEP 2. It tests to see if cycles can be combined which +/// might occur if A has duplicates. fn combine_cycles(mut perm: Vec>, lst_a: &[T]) -> Vec> { // If perm is a single cycle, there is nothing to combine. if perm.len() == 1 { @@ -132,8 +139,8 @@ fn combine_cycles(mut perm: Vec>, lst_a: &[T]) all_a_positions.entry(lst_a[i].clone()).or_default().push(i); } - // For each element a which occurs at positions i1, ..., ij, combine cycles such that all - // ik which occur in a cycle occur in the same cycle. + // For each element a which occurs at positions i1, ..., ij, combine cycles such + // that all ik which occur in a cycle occur in the same cycle. for positions in all_a_positions.values() { if positions.len() == 1 { continue; @@ -154,8 +161,9 @@ fn combine_cycles(mut perm: Vec>, lst_a: &[T]) pos = cycl.iter().position(|x| x == term).unwrap(); } else { // Need to merge 2 cycles. If A_i = A_j then the permutations - // (C_1, ..., C_k1, i, C_{k1 + 1}, ... C_k2)(D_1, ..., D_k3, j, D_{k3 + 1}, ... D_k4) - // (C_1, ..., C_k1, i, D_{k3 + 1}, ... D_k4, D_1, ..., D_k3, j, C_{k1 + 1}, ... C_k2) + // (C_1, ..., C_k1, i, C_{k1 + 1}, ... C_k2)(D_1, ..., D_k3, j, D_{k3 + 1}, + // ... D_k4) (C_1, ..., C_k1, i, D_{k3 + 1}, ... + // D_k4, D_1, ..., D_k3, j, C_{k1 + 1}, ... C_k2) // lead to the same oupput but the second will require less transpositions. let newpos = cycl.iter().position(|x| x == term).unwrap(); joinedperm = [ diff --git a/src/cpu/kernel/stack/stack_manipulation.rs b/evm/src/cpu/kernel/stack/stack_manipulation.rs similarity index 94% rename from src/cpu/kernel/stack/stack_manipulation.rs rename to evm/src/cpu/kernel/stack/stack_manipulation.rs index a7b376c5e..ff0b282c0 100644 --- a/src/cpu/kernel/stack/stack_manipulation.rs +++ b/evm/src/cpu/kernel/stack/stack_manipulation.rs @@ -107,7 +107,8 @@ fn shortest_path( while let Some(node) = queue.pop() { if node.stack == dst { - // The destination is now the lowest-cost node, so we must have found the best path. + // The destination is now the lowest-cost node, so we must have found the best + // path. let mut path = vec![]; let mut stack = &node.stack; // Rewind back to src, recording a list of operations which will be backwards. @@ -122,8 +123,9 @@ fn shortest_path( let (best_cost, _) = node_info[&node.stack]; if best_cost < node.cost { - // Since we can't efficiently remove nodes from the heap, it can contain duplicates. - // In this case, we've already visited this stack state with a lower cost. + // Since we can't efficiently remove nodes from the heap, it can contain + // duplicates. In this case, we've already visited this stack state + // with a lower cost. continue; } @@ -177,13 +179,14 @@ impl PartialOrd for Node { impl Ord for Node { fn cmp(&self, other: &Self) -> Ordering { - // We want a min-heap rather than the default max-heap, so this is the opposite of the - // natural ordering of costs. + // We want a min-heap rather than the default max-heap, so this is the opposite + // of the natural ordering of costs. other.cost.cmp(&self.cost) } } -/// Like `StackReplacement`, but without constants or macro vars, since those were expanded already. +/// Like `StackReplacement`, but without constants or macro vars, since those +/// were expanded already. #[derive(Eq, PartialEq, Hash, Clone, Debug)] pub(crate) enum StackItem { NamedItem(String), @@ -198,7 +201,8 @@ pub(crate) enum StackOp { Swap(u8), } -/// A set of candidate operations to consider for the next step in the path from `src` to `dst`. +/// A set of candidate operations to consider for the next step in the path from +/// `src` to `dst`. fn next_ops( src: &[StackItem], dst: &[StackItem], @@ -207,12 +211,14 @@ fn next_ops( if let Some(top) = src.last() && !dst.contains(top) { - // If the top of src doesn't appear in dst, don't bother with anything other than a POP. + // If the top of src doesn't appear in dst, don't bother with anything other + // than a POP. return vec![StackOp::Pop]; } if is_permutation(src, dst) { - // The transpositions are right-associative, so the last one gets applied first, hence pop. + // The transpositions are right-associative, so the last one gets applied first, + // hence pop. return vec![get_stack_ops_for_perm(src, dst).pop().unwrap()]; } @@ -237,8 +243,9 @@ fn next_ops( ops.extend( (1..=src_len) - // Only consider duplicating this item if we need more occurrences of it, otherwise swaps - // will be a better way to rearrange the existing occurrences as needed. + // Only consider duplicating this item if we need more occurrences of it, otherwise + // swaps will be a better way to rearrange the existing occurrences as + // needed. .filter(|i| { let item = &src[src.len() - *i as usize]; let src_count = src.iter().filter(|x| *x == item).count(); @@ -267,7 +274,8 @@ fn should_try_swap(src: &[StackItem], dst: &[StackItem], i: u8) -> bool { let i_from = src.len() - 1; let i_to = i_from - i; - // Only consider a swap if it places one of the two affected elements in the desired position. + // Only consider a swap if it places one of the two affected elements in the + // desired position. let top_correct_pos = i_to < dst.len() && src[i_from] == dst[i_to]; let other_correct_pos = i_from < dst.len() && src[i_to] == dst[i_from]; top_correct_pos | other_correct_pos @@ -302,8 +310,8 @@ impl StackOp { cpu_cost + memory_cost } - /// Returns an updated stack after this operation is performed, or `None` if this operation - /// would not be valid on the given stack. + /// Returns an updated stack after this operation is performed, or `None` if + /// this operation would not be valid on the given stack. fn apply_to(&self, mut stack: Vec) -> Option> { let len = stack.len(); match self { diff --git a/src/cpu/kernel/tests/account_code.rs b/evm/src/cpu/kernel/tests/account_code.rs similarity index 98% rename from src/cpu/kernel/tests/account_code.rs rename to evm/src/cpu/kernel/tests/account_code.rs index b3a075cf7..6709b12db 100644 --- a/src/cpu/kernel/tests/account_code.rs +++ b/evm/src/cpu/kernel/tests/account_code.rs @@ -200,7 +200,8 @@ fn test_extcodecopy() -> Result<()> { let extcodecopy = KERNEL.global_labels["sys_extcodecopy"]; - // Put random data in main memory and the `KernelAccountCode` segment for realism. + // Put random data in main memory and the `KernelAccountCode` segment for + // realism. let mut rng = thread_rng(); for i in 0..2000 { interpreter.generation_state.memory.contexts[context].segments @@ -255,8 +256,9 @@ fn test_extcodecopy() -> Result<()> { Ok(()) } -/// Prepare the interpreter for storage tests by inserting all necessary accounts -/// in the state trie, adding the code we want to context 1 and switching the context. +/// Prepare the interpreter for storage tests by inserting all necessary +/// accounts in the state trie, adding the code we want to context 1 and +/// switching the context. fn prepare_interpreter_all_accounts( interpreter: &mut Interpreter, trie_inputs: TrieInputs, @@ -387,8 +389,9 @@ fn sload() -> Result<()> { let addr_nibbles = Nibbles::from_bytes_be(addr_hashed.as_bytes()).unwrap(); - // This code is similar to the one in add11_yml's contract, but we pop the added value - // and carry out an SLOAD instead of an SSTORE. We also add a PUSH at the end. + // This code is similar to the one in add11_yml's contract, but we pop the added + // value and carry out an SLOAD instead of an SSTORE. We also add a PUSH at + // the end. let code = [ 0x60, 0x01, 0x60, 0x01, 0x01, 0x50, 0x60, 0x00, 0x54, 0x60, 0x03, 0x00, ]; @@ -437,7 +440,8 @@ fn sload() -> Result<()> { interpreter .pop() .expect("The stack length should not be empty."); - // Now, execute mpt_hash_state_trie. We check that the state trie has not changed. + // Now, execute mpt_hash_state_trie. We check that the state trie has not + // changed. let mpt_hash_state_trie = KERNEL.global_labels["mpt_hash_state_trie"]; interpreter.generation_state.registers.program_counter = mpt_hash_state_trie; interpreter.set_is_kernel(true); diff --git a/src/cpu/kernel/tests/add11.rs b/evm/src/cpu/kernel/tests/add11.rs similarity index 98% rename from src/cpu/kernel/tests/add11.rs rename to evm/src/cpu/kernel/tests/add11.rs index de5450c5c..a9479a594 100644 --- a/src/cpu/kernel/tests/add11.rs +++ b/evm/src/cpu/kernel/tests/add11.rs @@ -135,6 +135,7 @@ fn test_add11_yml() { block_chain_id: 1.into(), block_base_fee: 0xa.into(), block_gas_used: gas_used, + block_blob_base_fee: 0x2.into(), block_bloom: [0.into(); 8], }; @@ -169,7 +170,8 @@ fn test_add11_yml() { #[test] fn test_add11_yml_with_exception() { - // In this test, we make sure that the user code throws a stack underflow exception. + // In this test, we make sure that the user code throws a stack underflow + // exception. let beneficiary = hex!("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"); let sender = hex!("a94f5374fce5edbc8e2a8697c15331677e6ebf0b"); let to = hex!("095e7baea6a6c7c4c2dfeb977efac326af552d87"); @@ -222,7 +224,8 @@ fn test_add11_yml_with_exception() { let txn_gas_limit = 400_000; let gas_price = 10; - // Here, since the transaction fails, it consumes its gas limit, and does nothing else. + // Here, since the transaction fails, it consumes its gas limit, and does + // nothing else. let expected_state_trie_after = { let beneficiary_account_after = beneficiary_account_before; // This is the only account that changes: the nonce and the balance are updated. @@ -277,6 +280,7 @@ fn test_add11_yml_with_exception() { block_chain_id: 1.into(), block_base_fee: 0xa.into(), block_gas_used: txn_gas_limit.into(), + block_blob_base_fee: 0x2.into(), block_bloom: [0.into(); 8], }; diff --git a/src/cpu/kernel/tests/balance.rs b/evm/src/cpu/kernel/tests/balance.rs similarity index 100% rename from src/cpu/kernel/tests/balance.rs rename to evm/src/cpu/kernel/tests/balance.rs diff --git a/src/cpu/kernel/tests/bignum/mod.rs b/evm/src/cpu/kernel/tests/bignum/mod.rs similarity index 99% rename from src/cpu/kernel/tests/bignum/mod.rs rename to evm/src/cpu/kernel/tests/bignum/mod.rs index cc0e47af3..65fe00416 100644 --- a/src/cpu/kernel/tests/bignum/mod.rs +++ b/evm/src/cpu/kernel/tests/bignum/mod.rs @@ -67,7 +67,8 @@ fn test_data_u256(filename: &str) -> Vec { .collect() } -// Convert each biguint to a vector of bignum limbs, pad to the given length, and concatenate. +// Convert each biguint to a vector of bignum limbs, pad to the given length, +// and concatenate. fn pad_bignums(biguints: &[BigUint], length: usize) -> Vec { biguints .iter() diff --git a/src/cpu/kernel/tests/bignum/test_data/add_outputs b/evm/src/cpu/kernel/tests/bignum/test_data/add_outputs similarity index 100% rename from src/cpu/kernel/tests/bignum/test_data/add_outputs rename to evm/src/cpu/kernel/tests/bignum/test_data/add_outputs diff --git a/src/cpu/kernel/tests/bignum/test_data/addmul_outputs b/evm/src/cpu/kernel/tests/bignum/test_data/addmul_outputs similarity index 100% rename from src/cpu/kernel/tests/bignum/test_data/addmul_outputs rename to evm/src/cpu/kernel/tests/bignum/test_data/addmul_outputs diff --git a/src/cpu/kernel/tests/bignum/test_data/bignum_inputs b/evm/src/cpu/kernel/tests/bignum/test_data/bignum_inputs similarity index 100% rename from src/cpu/kernel/tests/bignum/test_data/bignum_inputs rename to evm/src/cpu/kernel/tests/bignum/test_data/bignum_inputs diff --git a/src/cpu/kernel/tests/bignum/test_data/cmp_outputs b/evm/src/cpu/kernel/tests/bignum/test_data/cmp_outputs similarity index 100% rename from src/cpu/kernel/tests/bignum/test_data/cmp_outputs rename to evm/src/cpu/kernel/tests/bignum/test_data/cmp_outputs diff --git a/src/cpu/kernel/tests/bignum/test_data/iszero_outputs b/evm/src/cpu/kernel/tests/bignum/test_data/iszero_outputs similarity index 100% rename from src/cpu/kernel/tests/bignum/test_data/iszero_outputs rename to evm/src/cpu/kernel/tests/bignum/test_data/iszero_outputs diff --git a/src/cpu/kernel/tests/bignum/test_data/modexp_outputs b/evm/src/cpu/kernel/tests/bignum/test_data/modexp_outputs similarity index 100% rename from src/cpu/kernel/tests/bignum/test_data/modexp_outputs rename to evm/src/cpu/kernel/tests/bignum/test_data/modexp_outputs diff --git a/src/cpu/kernel/tests/bignum/test_data/modexp_outputs_full b/evm/src/cpu/kernel/tests/bignum/test_data/modexp_outputs_full similarity index 100% rename from src/cpu/kernel/tests/bignum/test_data/modexp_outputs_full rename to evm/src/cpu/kernel/tests/bignum/test_data/modexp_outputs_full diff --git a/src/cpu/kernel/tests/bignum/test_data/modmul_outputs b/evm/src/cpu/kernel/tests/bignum/test_data/modmul_outputs similarity index 100% rename from src/cpu/kernel/tests/bignum/test_data/modmul_outputs rename to evm/src/cpu/kernel/tests/bignum/test_data/modmul_outputs diff --git a/src/cpu/kernel/tests/bignum/test_data/mul_outputs b/evm/src/cpu/kernel/tests/bignum/test_data/mul_outputs similarity index 100% rename from src/cpu/kernel/tests/bignum/test_data/mul_outputs rename to evm/src/cpu/kernel/tests/bignum/test_data/mul_outputs diff --git a/src/cpu/kernel/tests/bignum/test_data/shr_outputs b/evm/src/cpu/kernel/tests/bignum/test_data/shr_outputs similarity index 100% rename from src/cpu/kernel/tests/bignum/test_data/shr_outputs rename to evm/src/cpu/kernel/tests/bignum/test_data/shr_outputs diff --git a/src/cpu/kernel/tests/bignum/test_data/u128_inputs b/evm/src/cpu/kernel/tests/bignum/test_data/u128_inputs similarity index 100% rename from src/cpu/kernel/tests/bignum/test_data/u128_inputs rename to evm/src/cpu/kernel/tests/bignum/test_data/u128_inputs diff --git a/src/cpu/kernel/tests/blake2_f.rs b/evm/src/cpu/kernel/tests/blake2_f.rs similarity index 100% rename from src/cpu/kernel/tests/blake2_f.rs rename to evm/src/cpu/kernel/tests/blake2_f.rs diff --git a/src/cpu/kernel/tests/block_hash.rs b/evm/src/cpu/kernel/tests/block_hash.rs similarity index 100% rename from src/cpu/kernel/tests/block_hash.rs rename to evm/src/cpu/kernel/tests/block_hash.rs diff --git a/src/cpu/kernel/tests/bls381.rs b/evm/src/cpu/kernel/tests/bls381.rs similarity index 100% rename from src/cpu/kernel/tests/bls381.rs rename to evm/src/cpu/kernel/tests/bls381.rs diff --git a/src/cpu/kernel/tests/bn254.rs b/evm/src/cpu/kernel/tests/bn254.rs similarity index 100% rename from src/cpu/kernel/tests/bn254.rs rename to evm/src/cpu/kernel/tests/bn254.rs diff --git a/src/cpu/kernel/tests/core/access_lists.rs b/evm/src/cpu/kernel/tests/core/access_lists.rs similarity index 100% rename from src/cpu/kernel/tests/core/access_lists.rs rename to evm/src/cpu/kernel/tests/core/access_lists.rs diff --git a/src/cpu/kernel/tests/core/create_addresses.rs b/evm/src/cpu/kernel/tests/core/create_addresses.rs similarity index 100% rename from src/cpu/kernel/tests/core/create_addresses.rs rename to evm/src/cpu/kernel/tests/core/create_addresses.rs diff --git a/src/cpu/kernel/tests/core/intrinsic_gas.rs b/evm/src/cpu/kernel/tests/core/intrinsic_gas.rs similarity index 100% rename from src/cpu/kernel/tests/core/intrinsic_gas.rs rename to evm/src/cpu/kernel/tests/core/intrinsic_gas.rs diff --git a/src/cpu/kernel/tests/core/jumpdest_analysis.rs b/evm/src/cpu/kernel/tests/core/jumpdest_analysis.rs similarity index 98% rename from src/cpu/kernel/tests/core/jumpdest_analysis.rs rename to evm/src/cpu/kernel/tests/core/jumpdest_analysis.rs index 7923997d7..88aee0ef4 100644 --- a/src/cpu/kernel/tests/core/jumpdest_analysis.rs +++ b/evm/src/cpu/kernel/tests/core/jumpdest_analysis.rs @@ -77,7 +77,8 @@ fn test_jumpdest_analysis() -> Result<()> { interpreter.push(code_len.into()); interpreter.push(U256::from(CONTEXT) << CONTEXT_SCALING_FACTOR); - // We need to manually pop the jumpdest_table and push its value on the top of the stack + // We need to manually pop the jumpdest_table and push its value on the top of + // the stack interpreter .generation_state .jumpdest_table @@ -134,7 +135,8 @@ fn test_packed_verification() -> Result<()> { assert_eq!(jumpdest_bits, interpreter.get_jumpdest_bits(CONTEXT)); - // If we add 1 to each opcode the jumpdest at position 32 is never a valid jumpdest + // If we add 1 to each opcode the jumpdest at position 32 is never a valid + // jumpdest for i in 1..=32 { code[i] += 1; let mut interpreter: Interpreter = diff --git a/src/cpu/kernel/tests/core/mod.rs b/evm/src/cpu/kernel/tests/core/mod.rs similarity index 100% rename from src/cpu/kernel/tests/core/mod.rs rename to evm/src/cpu/kernel/tests/core/mod.rs diff --git a/src/cpu/kernel/tests/ecc/bn_glv_test_data b/evm/src/cpu/kernel/tests/ecc/bn_glv_test_data similarity index 100% rename from src/cpu/kernel/tests/ecc/bn_glv_test_data rename to evm/src/cpu/kernel/tests/ecc/bn_glv_test_data diff --git a/src/cpu/kernel/tests/ecc/curve_ops.rs b/evm/src/cpu/kernel/tests/ecc/curve_ops.rs similarity index 100% rename from src/cpu/kernel/tests/ecc/curve_ops.rs rename to evm/src/cpu/kernel/tests/ecc/curve_ops.rs diff --git a/src/cpu/kernel/tests/ecc/ecrecover.rs b/evm/src/cpu/kernel/tests/ecc/ecrecover.rs similarity index 96% rename from src/cpu/kernel/tests/ecc/ecrecover.rs rename to evm/src/cpu/kernel/tests/ecc/ecrecover.rs index baf003d99..721146243 100644 --- a/src/cpu/kernel/tests/ecc/ecrecover.rs +++ b/evm/src/cpu/kernel/tests/ecc/ecrecover.rs @@ -93,7 +93,8 @@ fn test_ecrecover() -> Result<()> { test_invalid_ecrecover( "0x0", "0x1c", - "0x3a18b21408d275dde53c0ea86f9c1982eca60193db0ce15008fa408d43024847", // r^3 + 7 isn't a square + "0x3a18b21408d275dde53c0ea86f9c1982eca60193db0ce15008fa408d43024847", /* r^3 + 7 isn't a + * square */ "0x5db9745f44089305b2f2c980276e7025a594828d878e6e36dd2abd34ca6b9e3d", )?; diff --git a/src/cpu/kernel/tests/ecc/ecrecover_test_data b/evm/src/cpu/kernel/tests/ecc/ecrecover_test_data similarity index 100% rename from src/cpu/kernel/tests/ecc/ecrecover_test_data rename to evm/src/cpu/kernel/tests/ecc/ecrecover_test_data diff --git a/src/cpu/kernel/tests/ecc/mod.rs b/evm/src/cpu/kernel/tests/ecc/mod.rs similarity index 100% rename from src/cpu/kernel/tests/ecc/mod.rs rename to evm/src/cpu/kernel/tests/ecc/mod.rs diff --git a/src/cpu/kernel/tests/ecc/secp_glv_test_data b/evm/src/cpu/kernel/tests/ecc/secp_glv_test_data similarity index 100% rename from src/cpu/kernel/tests/ecc/secp_glv_test_data rename to evm/src/cpu/kernel/tests/ecc/secp_glv_test_data diff --git a/src/cpu/kernel/tests/exp.rs b/evm/src/cpu/kernel/tests/exp.rs similarity index 100% rename from src/cpu/kernel/tests/exp.rs rename to evm/src/cpu/kernel/tests/exp.rs diff --git a/src/cpu/kernel/tests/hash.rs b/evm/src/cpu/kernel/tests/hash.rs similarity index 92% rename from src/cpu/kernel/tests/hash.rs rename to evm/src/cpu/kernel/tests/hash.rs index 672aa5d1a..65b3134a8 100644 --- a/src/cpu/kernel/tests/hash.rs +++ b/evm/src/cpu/kernel/tests/hash.rs @@ -99,9 +99,10 @@ fn test_sha2() -> Result<()> { test_hash_256("sha2", (0, 1), &sha2) } -// Since the Blake precompile requires only the blake2_f compression function instead of the full blake2b hash, -// the full hash function is not included in the kernel. To include it, blake2/compression.asm and blake2/main.asm -// must be added to the kernel. +// Since the Blake precompile requires only the blake2_f compression function +// instead of the full blake2b hash, the full hash function is not included in +// the kernel. To include it, blake2/compression.asm and blake2/main.asm must be +// added to the kernel. // /// Standard Blake2b implementation. // fn blake2b(input: Vec) -> U512 { @@ -120,7 +121,8 @@ fn test_sha2() -> Result<()> { // standard_implementation: &dyn Fn(Vec) -> U512, // ) -> Result<()> { // let (expected, result_stack) = -// prepare_test(hash_fn_label, hash_input_virt, standard_implementation).unwrap(); +// prepare_test(hash_fn_label, hash_input_virt, +// standard_implementation).unwrap(); // // Extract the final output. // let actual = combine_u256s(result_stack[0], result_stack[1]); diff --git a/src/cpu/kernel/tests/kernel_consistency.rs b/evm/src/cpu/kernel/tests/kernel_consistency.rs similarity index 100% rename from src/cpu/kernel/tests/kernel_consistency.rs rename to evm/src/cpu/kernel/tests/kernel_consistency.rs diff --git a/src/cpu/kernel/tests/log.rs b/evm/src/cpu/kernel/tests/log.rs similarity index 92% rename from src/cpu/kernel/tests/log.rs rename to evm/src/cpu/kernel/tests/log.rs index 9c80b4261..4a977e5da 100644 --- a/src/cpu/kernel/tests/log.rs +++ b/evm/src/cpu/kernel/tests/log.rs @@ -32,7 +32,8 @@ fn test_log_0() -> Result<()> { interpreter.run()?; - // The address is encoded in 1+20 bytes. There are no topics or data, so each is encoded in 1 byte. This leads to a payload of 23. + // The address is encoded in 1+20 bytes. There are no topics or data, so each is + // encoded in 1 byte. This leads to a payload of 23. let payload_len = 23; assert_eq!( interpreter.get_memory_segment(Segment::LogsData), @@ -81,7 +82,10 @@ fn test_log_2() -> Result<()> { [0.into(), 0.into(), 5.into(),] ); - // The data has length 3 bytes, and is encoded in 4 bytes. Each of the two topics is encoded in 1+32 bytes. The prefix for the topics list requires 2 bytes. The address is encoded in 1+20 bytes. Overall, we have a logs payload length of 93 bytes. + // The data has length 3 bytes, and is encoded in 4 bytes. Each of the two + // topics is encoded in 1+32 bytes. The prefix for the topics list requires 2 + // bytes. The address is encoded in 1+20 bytes. Overall, we have a logs payload + // length of 93 bytes. let payload_len = 93; assert_eq!( interpreter.get_memory_segment(Segment::LogsData), @@ -142,7 +146,10 @@ fn test_log_4() -> Result<()> { [0.into(), 0.into(), 5.into(),] ); - // The data is of length 1 byte, and is encoded in 1 byte. Each of the four topics is encoded in 1+32 bytes. The topics list is prefixed by 2 bytes. The address is encoded in 1+20 bytes. Overall, this leads to a log payload length of 156. + // The data is of length 1 byte, and is encoded in 1 byte. Each of the four + // topics is encoded in 1+32 bytes. The topics list is prefixed by 2 bytes. The + // address is encoded in 1+20 bytes. Overall, this leads to a log payload length + // of 156. let payload_len = 156; assert_eq!( interpreter.get_memory_segment(Segment::LogsData), diff --git a/src/cpu/kernel/tests/mod.rs b/evm/src/cpu/kernel/tests/mod.rs similarity index 100% rename from src/cpu/kernel/tests/mod.rs rename to evm/src/cpu/kernel/tests/mod.rs diff --git a/src/cpu/kernel/tests/mpt/delete.rs b/evm/src/cpu/kernel/tests/mpt/delete.rs similarity index 98% rename from src/cpu/kernel/tests/mpt/delete.rs rename to evm/src/cpu/kernel/tests/mpt/delete.rs index 34bc0d66b..0be291616 100644 --- a/src/cpu/kernel/tests/mpt/delete.rs +++ b/evm/src/cpu/kernel/tests/mpt/delete.rs @@ -76,8 +76,9 @@ fn test_after_mpt_delete_extension_branch() -> Result<()> { test_state_trie(state_trie, key, test_account_2()) } -/// Note: The account's storage_root is ignored, as we can't insert a new storage_root without the -/// accompanying trie data. An empty trie's storage_root is used instead. +/// Note: The account's storage_root is ignored, as we can't insert a new +/// storage_root without the accompanying trie data. An empty trie's +/// storage_root is used instead. fn test_state_trie( state_trie: HashedPartialTrie, k: Nibbles, diff --git a/src/cpu/kernel/tests/mpt/hash.rs b/evm/src/cpu/kernel/tests/mpt/hash.rs similarity index 100% rename from src/cpu/kernel/tests/mpt/hash.rs rename to evm/src/cpu/kernel/tests/mpt/hash.rs diff --git a/src/cpu/kernel/tests/mpt/hex_prefix.rs b/evm/src/cpu/kernel/tests/mpt/hex_prefix.rs similarity index 100% rename from src/cpu/kernel/tests/mpt/hex_prefix.rs rename to evm/src/cpu/kernel/tests/mpt/hex_prefix.rs diff --git a/src/cpu/kernel/tests/mpt/insert.rs b/evm/src/cpu/kernel/tests/mpt/insert.rs similarity index 98% rename from src/cpu/kernel/tests/mpt/insert.rs rename to evm/src/cpu/kernel/tests/mpt/insert.rs index cbb13b9b4..60441b055 100644 --- a/src/cpu/kernel/tests/mpt/insert.rs +++ b/evm/src/cpu/kernel/tests/mpt/insert.rs @@ -152,8 +152,9 @@ fn mpt_insert_branch_to_leaf_same_key() -> Result<()> { test_state_trie(state_trie, nibbles_64(0xABCD), test_account_2()) } -/// Note: The account's storage_root is ignored, as we can't insert a new storage_root without the -/// accompanying trie data. An empty trie's storage_root is used instead. +/// Note: The account's storage_root is ignored, as we can't insert a new +/// storage_root without the accompanying trie data. An empty trie's +/// storage_root is used instead. fn test_state_trie( mut state_trie: HashedPartialTrie, k: Nibbles, diff --git a/src/cpu/kernel/tests/mpt/load.rs b/evm/src/cpu/kernel/tests/mpt/load.rs similarity index 100% rename from src/cpu/kernel/tests/mpt/load.rs rename to evm/src/cpu/kernel/tests/mpt/load.rs diff --git a/src/cpu/kernel/tests/mpt/mod.rs b/evm/src/cpu/kernel/tests/mpt/mod.rs similarity index 98% rename from src/cpu/kernel/tests/mpt/mod.rs rename to evm/src/cpu/kernel/tests/mpt/mod.rs index 292d064af..e5225ca0e 100644 --- a/src/cpu/kernel/tests/mpt/mod.rs +++ b/evm/src/cpu/kernel/tests/mpt/mod.rs @@ -54,7 +54,8 @@ pub(crate) fn test_account_2_rlp() -> Vec { rlp::encode(&test_account_2()).to_vec() } -/// A `PartialTrie` where an extension node leads to a leaf node containing an account. +/// A `PartialTrie` where an extension node leads to a leaf node containing an +/// account. pub(crate) fn extension_to_leaf(value: Vec) -> HashedPartialTrie { Node::Extension { nibbles: 0xABC_u64.into(), diff --git a/src/cpu/kernel/tests/mpt/read.rs b/evm/src/cpu/kernel/tests/mpt/read.rs similarity index 100% rename from src/cpu/kernel/tests/mpt/read.rs rename to evm/src/cpu/kernel/tests/mpt/read.rs diff --git a/src/cpu/kernel/tests/packing.rs b/evm/src/cpu/kernel/tests/packing.rs similarity index 100% rename from src/cpu/kernel/tests/packing.rs rename to evm/src/cpu/kernel/tests/packing.rs diff --git a/src/cpu/kernel/tests/receipt.rs b/evm/src/cpu/kernel/tests/receipt.rs similarity index 98% rename from src/cpu/kernel/tests/receipt.rs rename to evm/src/cpu/kernel/tests/receipt.rs index cf9f63896..ac28eeebb 100644 --- a/src/cpu/kernel/tests/receipt.rs +++ b/evm/src/cpu/kernel/tests/receipt.rs @@ -70,7 +70,8 @@ fn test_process_receipt() -> Result<()> { let segment_read = interpreter.get_memory_segment(Segment::TrieData); - // The expected TrieData has the form [payload_len, status, cum_gas_used, bloom_filter, logs_payload_len, num_logs, [logs]] + // The expected TrieData has the form [payload_len, status, cum_gas_used, + // bloom_filter, logs_payload_len, num_logs, [logs]] let mut expected_trie_data: Vec = vec![323.into(), success, 2000.into()]; expected_trie_data.extend( expected_bloom @@ -266,7 +267,9 @@ fn test_receipt_bloom_filter() -> Result<()> { .copied() .map(U256::from); logs.extend(cur_data); - // The Bloom filter initialization is required for this test to ensure we have the correct length for the filters. Otherwise, some trailing zeroes could be missing. + // The Bloom filter initialization is required for this test to ensure we have + // the correct length for the filters. Otherwise, some trailing zeroes could be + // missing. interpreter.set_memory_segment(Segment::TxnBloom, vec![0.into(); 256]); // Initialize transaction Bloom filter. interpreter.set_memory_segment(Segment::LogsData, logs); interpreter.set_memory_segment(Segment::Logs, vec![0.into()]); @@ -339,8 +342,8 @@ fn test_mpt_insert_receipt() -> Result<()> { // This test simulates a receipt processing to test `mpt_insert_receipt_trie`. // For this, we need to set the data correctly in memory. // In TrieData, we need to insert a receipt of the form: - // `[payload_len, status, cum_gas_used, bloom, logs_payload_len, num_logs, [logs]]`. - // We also need to set TrieDataSize correctly. + // `[payload_len, status, cum_gas_used, bloom, logs_payload_len, num_logs, + // [logs]]`. We also need to set TrieDataSize correctly. let retdest = 0xDEADBEEFu32.into(); let trie_inputs = Default::default(); @@ -520,7 +523,8 @@ fn test_mpt_insert_receipt() -> Result<()> { .push(retdest) .expect("The stack should not overflow"); interpreter - .push(1.into()) // Initial length of the trie data segment, unused.; // Initial length of the trie data segment, unused. + .push(1.into()) // Initial length of the trie data segment, unused.; // Initial length of the trie data + // segment, unused. .expect("The stack should not overflow"); interpreter.run()?; assert_eq!( diff --git a/src/cpu/kernel/tests/rlp/decode.rs b/evm/src/cpu/kernel/tests/rlp/decode.rs similarity index 100% rename from src/cpu/kernel/tests/rlp/decode.rs rename to evm/src/cpu/kernel/tests/rlp/decode.rs diff --git a/src/cpu/kernel/tests/rlp/encode.rs b/evm/src/cpu/kernel/tests/rlp/encode.rs similarity index 100% rename from src/cpu/kernel/tests/rlp/encode.rs rename to evm/src/cpu/kernel/tests/rlp/encode.rs diff --git a/src/cpu/kernel/tests/rlp/mod.rs b/evm/src/cpu/kernel/tests/rlp/mod.rs similarity index 100% rename from src/cpu/kernel/tests/rlp/mod.rs rename to evm/src/cpu/kernel/tests/rlp/mod.rs diff --git a/src/cpu/kernel/tests/rlp/num_bytes.rs b/evm/src/cpu/kernel/tests/rlp/num_bytes.rs similarity index 100% rename from src/cpu/kernel/tests/rlp/num_bytes.rs rename to evm/src/cpu/kernel/tests/rlp/num_bytes.rs diff --git a/src/cpu/kernel/tests/signed_syscalls.rs b/evm/src/cpu/kernel/tests/signed_syscalls.rs similarity index 99% rename from src/cpu/kernel/tests/signed_syscalls.rs rename to evm/src/cpu/kernel/tests/signed_syscalls.rs index 993b8e03f..75dab226c 100644 --- a/src/cpu/kernel/tests/signed_syscalls.rs +++ b/evm/src/cpu/kernel/tests/signed_syscalls.rs @@ -135,7 +135,8 @@ fn run_test(fn_label: &str, expected_fn: fn(U256, U256) -> U256, opname: &str) { #[test] fn test_sdiv() { - // Double-check that the expected output calculation is correct in the special case. + // Double-check that the expected output calculation is correct in the special + // case. let x = U256::one() << 255; // -2^255 let y = U256::one().overflowing_neg().0; // -1 assert_eq!(u256_sdiv(x, y), x); // SDIV(-2^255, -1) = -2^255. diff --git a/src/cpu/kernel/tests/transaction_parsing/mod.rs b/evm/src/cpu/kernel/tests/transaction_parsing/mod.rs similarity index 100% rename from src/cpu/kernel/tests/transaction_parsing/mod.rs rename to evm/src/cpu/kernel/tests/transaction_parsing/mod.rs diff --git a/src/cpu/kernel/tests/transaction_parsing/parse_type_0_txn.rs b/evm/src/cpu/kernel/tests/transaction_parsing/parse_type_0_txn.rs similarity index 88% rename from src/cpu/kernel/tests/transaction_parsing/parse_type_0_txn.rs rename to evm/src/cpu/kernel/tests/transaction_parsing/parse_type_0_txn.rs index 8415b47bd..2aaf9ba4a 100644 --- a/src/cpu/kernel/tests/transaction_parsing/parse_type_0_txn.rs +++ b/evm/src/cpu/kernel/tests/transaction_parsing/parse_type_0_txn.rs @@ -17,23 +17,25 @@ fn process_type_0_txn() -> Result<()> { let mut interpreter: Interpreter = Interpreter::new_with_kernel(process_type_0_txn, vec![retaddr]); - // When we reach process_normalized_txn, we're done with parsing and normalizing. - // Processing normalized transactions is outside the scope of this test. + // When we reach process_normalized_txn, we're done with parsing and + // normalizing. Processing normalized transactions is outside the scope of + // this test. interpreter.halt_offsets.push(process_normalized_txn); // Generated with py-evm: // import eth, eth_keys, eth_utils, rlp // genesis_params = { 'difficulty': eth.constants.GENESIS_DIFFICULTY } - // chain = eth.chains.mainnet.MainnetChain.from_genesis(eth.db.atomic.AtomicDB(), genesis_params, {}) - // unsigned_txn = chain.create_unsigned_transaction( - // nonce=5, + // chain = eth.chains.mainnet.MainnetChain.from_genesis(eth.db.atomic. + // AtomicDB(), genesis_params, {}) unsigned_txn = + // chain.create_unsigned_transaction( nonce=5, // gas_price=10, // gas=22_000, // to=eth.constants.ZERO_ADDRESS, // value=100, // data=b'\x42\x42', // ) - // sk = eth_keys.keys.PrivateKey(eth_utils.decode_hex('4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318')) + // sk = eth_keys.keys.PrivateKey(eth_utils.decode_hex(' + // 4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318')) // signed_txn = unsigned_txn.as_signed_transaction(sk) // rlp.encode(signed_txn).hex() interpreter.set_rlp_memory(hex!("f861050a8255f0940000000000000000000000000000000000000000648242421ca07c5c61ed975ebd286f6b027b8c504842e50a47d318e1e801719dd744fe93e6c6a01e7b5119b57dd54e175ff2f055c91f3ab1b53eba0b2c184f347cdff0e745aca2").to_vec()); diff --git a/src/cpu/kernel/utils.rs b/evm/src/cpu/kernel/utils.rs similarity index 97% rename from src/cpu/kernel/utils.rs rename to evm/src/cpu/kernel/utils.rs index 18b5f5482..adda086e8 100644 --- a/src/cpu/kernel/utils.rs +++ b/evm/src/cpu/kernel/utils.rs @@ -3,9 +3,11 @@ use core::fmt::Debug; use ethereum_types::U256; use plonky2_util::ceil_div_usize; -/// Enumerate the length `W` windows of `vec`, and run `maybe_replace` on each one. +/// Enumerate the length `W` windows of `vec`, and run `maybe_replace` on each +/// one. /// -/// Whenever `maybe_replace` returns `Some(replacement)`, the given replacement will be applied. +/// Whenever `maybe_replace` returns `Some(replacement)`, the given replacement +/// will be applied. pub(crate) fn replace_windows(vec: &mut Vec, maybe_replace: F) where T: Clone + Debug, diff --git a/src/cpu/membus.rs b/evm/src/cpu/membus.rs similarity index 91% rename from src/cpu/membus.rs rename to evm/src/cpu/membus.rs index b50ab5cce..904b047e4 100644 --- a/src/cpu/membus.rs +++ b/evm/src/cpu/membus.rs @@ -6,7 +6,8 @@ use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsume use crate::cpu::columns::CpuColumnsView; -/// General-purpose memory channels; they can read and write to all contexts/segments/addresses. +/// General-purpose memory channels; they can read and write to all +/// contexts/segments/addresses. pub(crate) const NUM_GP_CHANNELS: usize = 3; /// Indices for code and general purpose memory channels. @@ -17,20 +18,22 @@ pub mod channel_indices { pub(crate) const GP: Range = CODE + 1..(CODE + 1) + super::NUM_GP_CHANNELS; } -/// Total memory channels used by the CPU table. This includes all the `GP_MEM_CHANNELS` as well as -/// all special-purpose memory channels. +/// Total memory channels used by the CPU table. This includes all the +/// `GP_MEM_CHANNELS` as well as all special-purpose memory channels. /// -/// Currently, there is one special-purpose memory channel, which reads the opcode from memory. Its -/// limitations are: +/// Currently, there is one special-purpose memory channel, which reads the +/// opcode from memory. Its limitations are: /// - it is enabled by `is_cpu_cycle`, /// - it always reads and cannot write, -/// - the context is derived from the current context and the `is_kernel_mode` flag, +/// - the context is derived from the current context and the `is_kernel_mode` +/// flag, /// - the segment is hard-wired to the code segment, /// - the address is `program_counter`, -/// - the value must fit in one byte (in the least-significant position) and its eight bits are -/// found in `opcode_bits`. +/// - the value must fit in one byte (in the least-significant position) and +/// its eight bits are found in `opcode_bits`. /// -/// There is also a partial channel, which shares its values with another general purpose channel. +/// There is also a partial channel, which shares its values with another +/// general purpose channel. /// /// These limitations save us numerous columns in the CPU table. pub(crate) const NUM_CHANNELS: usize = channel_indices::GP.end + 1; diff --git a/src/cpu/memio.rs b/evm/src/cpu/memio.rs similarity index 98% rename from src/cpu/memio.rs rename to evm/src/cpu/memio.rs index ac32253da..b68f78ba9 100644 --- a/src/cpu/memio.rs +++ b/evm/src/cpu/memio.rs @@ -24,7 +24,8 @@ fn eval_packed_load( nv: &CpuColumnsView

, yield_constr: &mut ConstraintConsumer

, ) { - // The opcode for MLOAD_GENERAL is 0xfb. If the operation is MLOAD_GENERAL, lv.opcode_bits[0] = 1. + // The opcode for MLOAD_GENERAL is 0xfb. If the operation is MLOAD_GENERAL, + // lv.opcode_bits[0] = 1. let filter = lv.op.m_op_general * lv.opcode_bits[0]; let (addr_context, addr_segment, addr_virtual) = get_addr_load(lv); @@ -70,7 +71,8 @@ fn eval_ext_circuit_load, const D: usize>( nv: &CpuColumnsView>, yield_constr: &mut RecursiveConstraintConsumer, ) { - // The opcode for MLOAD_GENERAL is 0xfb. If the operation is MLOAD_GENERAL, lv.opcode_bits[0] = 1. + // The opcode for MLOAD_GENERAL is 0xfb. If the operation is MLOAD_GENERAL, + // lv.opcode_bits[0] = 1. let mut filter = lv.op.m_op_general; filter = builder.mul_extension(filter, lv.opcode_bits[0]); @@ -169,7 +171,8 @@ fn eval_packed_store( * (channel.addr_segment - P::Scalar::from_canonical_usize(Segment::Stack.unscale())), ); - // Remember that the first read (`i == 1`) is for the second stack element at `stack[stack_len - 1]`. + // Remember that the first read (`i == 1`) is for the second stack element at + // `stack[stack_len - 1]`. let addr_virtual = lv.stack_len - P::Scalar::from_canonical_usize(i + 1); yield_constr.constraint(filter * (channel.addr_virtual - addr_virtual)); } @@ -179,7 +182,8 @@ fn eval_packed_store( lv.op.m_op_general * (len_diff * lv.general.stack().stack_inv - lv.general.stack().stack_inv_aux), ); - // If stack_len != 2 and MSTORE, read new top of the stack in nv.mem_channels[0]. + // If stack_len != 2 and MSTORE, read new top of the stack in + // nv.mem_channels[0]. let top_read_channel = nv.mem_channels[0]; let is_top_read = lv.general.stack().stack_inv_aux * (P::ONES - lv.opcode_bits[0]); // Constrain `stack_inv_aux_2`. It contains `stack_inv_aux * opcode_bits[0]`. @@ -271,7 +275,8 @@ fn eval_ext_circuit_store, const D: usize>( let constr = builder.mul_extension(filter, diff); yield_constr.constraint(builder, constr); } - // Remember that the first read (`i == 1`) is for the second stack element at `stack[stack_len - 1]`. + // Remember that the first read (`i == 1`) is for the second stack element at + // `stack[stack_len - 1]`. let addr_virtual = builder.add_const_extension(lv.stack_len, -F::from_canonical_usize(i + 1)); let diff = builder.sub_extension(channel.addr_virtual, addr_virtual); @@ -289,11 +294,13 @@ fn eval_ext_circuit_store, const D: usize>( let constr = builder.mul_extension(lv.op.m_op_general, diff); yield_constr.constraint(builder, constr); } - // If stack_len != 2 and MSTORE, read new top of the stack in nv.mem_channels[0]. + // If stack_len != 2 and MSTORE, read new top of the stack in + // nv.mem_channels[0]. let top_read_channel = nv.mem_channels[0]; let is_top_read = builder.mul_extension(lv.general.stack().stack_inv_aux, lv.opcode_bits[0]); let is_top_read = builder.sub_extension(lv.general.stack().stack_inv_aux, is_top_read); - // Constrain `stack_inv_aux_2`. It contains `stack_inv_aux * (1 - opcode_bits[0])`. + // Constrain `stack_inv_aux_2`. It contains `stack_inv_aux * (1 - + // opcode_bits[0])`. { let diff = builder.sub_extension(lv.general.stack().stack_inv_aux_2, is_top_read); let constr = builder.mul_extension(lv.op.m_op_general, diff); diff --git a/src/cpu/mod.rs b/evm/src/cpu/mod.rs similarity index 100% rename from src/cpu/mod.rs rename to evm/src/cpu/mod.rs diff --git a/src/cpu/modfp254.rs b/evm/src/cpu/modfp254.rs similarity index 83% rename from src/cpu/modfp254.rs rename to evm/src/cpu/modfp254.rs index a3b40f592..3ea904692 100644 --- a/src/cpu/modfp254.rs +++ b/evm/src/cpu/modfp254.rs @@ -22,9 +22,10 @@ pub(crate) fn eval_packed( ) { let filter = lv.op.fp254_op; - // We want to use all the same logic as the usual mod operations, but without needing to read - // the modulus from the stack. We simply constrain `mem_channels[1]` to be our prime (that's - // where the modulus goes in the generalized operations). + // We want to use all the same logic as the usual mod operations, but without + // needing to read the modulus from the stack. We simply constrain + // `mem_channels[1]` to be our prime (that's where the modulus goes in the + // generalized operations). let channel_val = lv.mem_channels[2].value; for (channel_limb, p_limb) in izip!(channel_val, P_LIMBS) { let p_limb = P::Scalar::from_canonical_u32(p_limb); @@ -41,9 +42,10 @@ pub(crate) fn eval_ext_circuit, const D: usize>( ) { let filter = lv.op.fp254_op; - // We want to use all the same logic as the usual mod operations, but without needing to read - // the modulus from the stack. We simply constrain `mem_channels[1]` to be our prime (that's - // where the modulus goes in the generalized operations). + // We want to use all the same logic as the usual mod operations, but without + // needing to read the modulus from the stack. We simply constrain + // `mem_channels[1]` to be our prime (that's where the modulus goes in the + // generalized operations). let channel_val = lv.mem_channels[2].value; for (channel_limb, p_limb) in izip!(channel_val, P_LIMBS) { let p_limb = F::from_canonical_u32(p_limb); diff --git a/src/cpu/pc.rs b/evm/src/cpu/pc.rs similarity index 100% rename from src/cpu/pc.rs rename to evm/src/cpu/pc.rs diff --git a/src/cpu/push0.rs b/evm/src/cpu/push0.rs similarity index 100% rename from src/cpu/push0.rs rename to evm/src/cpu/push0.rs diff --git a/src/cpu/shift.rs b/evm/src/cpu/shift.rs similarity index 100% rename from src/cpu/shift.rs rename to evm/src/cpu/shift.rs diff --git a/src/cpu/simple_logic/eq_iszero.rs b/evm/src/cpu/simple_logic/eq_iszero.rs similarity index 89% rename from src/cpu/simple_logic/eq_iszero.rs rename to evm/src/cpu/simple_logic/eq_iszero.rs index 43333fd9e..a79b137d9 100644 --- a/src/cpu/simple_logic/eq_iszero.rs +++ b/evm/src/cpu/simple_logic/eq_iszero.rs @@ -20,10 +20,10 @@ fn limbs(x: U256) -> [u32; 8] { res } /// Form `diff_pinv`. -/// Let `diff = val0 - val1`. 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 `diff = val0 - val1`. 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. pub(crate) fn generate_pinv_diff(val0: U256, val1: U256, lv: &mut CpuColumnsView) { let val0_limbs = limbs(val0).map(F::from_canonical_u32); let val1_limbs = limbs(val1).map(F::from_canonical_u32); @@ -61,14 +61,15 @@ pub(crate) fn eval_packed( 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. + // 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 &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). + // If `ISZERO`, constrain input1 to be zero, effectively implementing ISZERO(x) + // as EQ(x, 0). for limb in input1 { yield_constr.constraint(iszero_filter * limb); } @@ -80,9 +81,9 @@ pub(crate) fn eval_packed( } // `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`. + // 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!(input0, input1, logic.diff_pinv) .map(|(limb0, limb1, diff_pinv_el)| (limb0 - limb1) * diff_pinv_el) .sum(); @@ -125,8 +126,8 @@ pub(crate) fn eval_ext_circuit, const D: usize>( 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 - // either 0 or 1. + // 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_extension(equal, unequal); let constr = builder.mul_extension(eq_or_iszero_filter, constr); @@ -137,7 +138,8 @@ pub(crate) fn eval_ext_circuit, const D: usize>( yield_constr.constraint(builder, constr); } - // If `ISZERO`, constrain input1 to be zero, effectively implementing ISZERO(x) as EQ(x, 0). + // If `ISZERO`, constrain input1 to be zero, effectively implementing ISZERO(x) + // as EQ(x, 0). for limb in input1 { let constr = builder.mul_extension(iszero_filter, limb); yield_constr.constraint(builder, constr); @@ -152,9 +154,9 @@ pub(crate) fn eval_ext_circuit, const D: usize>( } // `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`. + // 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: ExtensionTarget = izip!(input0, input1, logic.diff_pinv).fold( zero, diff --git a/src/cpu/simple_logic/mod.rs b/evm/src/cpu/simple_logic/mod.rs similarity index 100% rename from src/cpu/simple_logic/mod.rs rename to evm/src/cpu/simple_logic/mod.rs diff --git a/src/cpu/simple_logic/not.rs b/evm/src/cpu/simple_logic/not.rs similarity index 100% rename from src/cpu/simple_logic/not.rs rename to evm/src/cpu/simple_logic/not.rs diff --git a/src/cpu/stack.rs b/evm/src/cpu/stack.rs similarity index 95% rename from src/cpu/stack.rs rename to evm/src/cpu/stack.rs index e135e3917..cd7ca703d 100644 --- a/src/cpu/stack.rs +++ b/evm/src/cpu/stack.rs @@ -15,10 +15,11 @@ use crate::memory::segments::Segment; pub(crate) const MAX_USER_STACK_SIZE: usize = 1024; -// We check for stack overflows here. An overflow occurs when the stack length is 1025 in user mode, -// which can happen after a non-kernel-only, non-popping, pushing instruction/syscall. -// The check uses `stack_len_bounds_aux`, which is either 0 if next row's `stack_len` is 1025 or -// next row is in kernel mode, or the inverse of `nv.stack_len - 1025` otherwise. +// We check for stack overflows here. An overflow occurs when the stack length +// is 1025 in user mode, which can happen after a non-kernel-only, non-popping, +// pushing instruction/syscall. The check uses `stack_len_bounds_aux`, which is +// either 0 if next row's `stack_len` is 1025 or next row is in kernel mode, or +// the inverse of `nv.stack_len - 1025` otherwise. pub(crate) const MIGHT_OVERFLOW: OpsColumnsView = OpsColumnsView { binary_op: false, ternary_op: false, @@ -100,10 +101,11 @@ pub(crate) const JUMPDEST_OP: StackBehavior = StackBehavior { disable_other_channels: true, }; -// AUDITORS: If the value below is `None`, then the operation must be manually checked to ensure -// that every general-purpose memory channel is either disabled or has its read flag and address -// properly constrained. The same applies when `disable_other_channels` is set to `false`, -// except the first `num_pops` and the last `pushes as usize` channels have their read flag and +// AUDITORS: If the value below is `None`, then the operation must be manually +// checked to ensure that every general-purpose memory channel is either +// disabled or has its read flag and address properly constrained. The same +// applies when `disable_other_channels` is set to `false`, except the first +// `num_pops` and the last `pushes as usize` channels have their read flag and // address constrained automatically in this file. pub(crate) const STACK_BEHAVIORS: OpsColumnsView> = OpsColumnsView { binary_op: BASIC_BINARY_OP, @@ -189,17 +191,20 @@ pub(crate) fn eval_packed_one( * (channel.addr_segment - P::Scalar::from_canonical_usize(Segment::Stack.unscale())), ); - // Remember that the first read (`i == 1`) is for the second stack element at `stack[stack_len - 1]`. + // Remember that the first read (`i == 1`) is for the second stack element at + // `stack[stack_len - 1]`. let addr_virtual = lv.stack_len - P::Scalar::from_canonical_usize(i + 1); yield_constr.constraint(filter * (channel.addr_virtual - addr_virtual)); } - // You can't have a write of the top of the stack, so you disable the corresponding flag. + // You can't have a write of the top of the stack, so you disable the + // corresponding flag. yield_constr.constraint(filter * lv.partial_channel.used); // If you also push, you don't need to read the new top of the stack. // If you don't: - // - if the stack isn't empty after the pops, you read the new top from an extra pop. + // - if the stack isn't empty after the pops, you read the new top from an extra + // pop. // - if not, the extra read is disabled. // These are transition constraints: they don't apply to the last row. if !stack_behavior.pushes { @@ -228,7 +233,8 @@ pub(crate) fn eval_packed_one( yield_constr.constraint_transition(empty_stack_filter * channel.used); } } - // If the op only pushes, you only need to constrain the top of the stack if the stack isn't empty. + // If the op only pushes, you only need to constrain the top of the stack if the stack isn't + // empty. else if stack_behavior.pushes { // If len > 0... let new_filter = lv.stack_len * filter; @@ -264,13 +270,15 @@ pub(crate) fn eval_packed_one( yield_constr.constraint(filter * (*limb_old - *limb_new)); } - // You can't have a write of the top of the stack, so you disable the corresponding flag. + // You can't have a write of the top of the stack, so you disable the + // corresponding flag. yield_constr.constraint(filter * lv.partial_channel.used); } // Unused channels if stack_behavior.disable_other_channels { - // The first channel contains (or not) the top of the stack and is constrained elsewhere. + // The first channel contains (or not) the top of the stack and is constrained + // elsewhere. for i in max(1, stack_behavior.num_pops)..NUM_GP_CHANNELS - (stack_behavior.pushes as usize) { let channel = lv.mem_channels[i]; @@ -337,7 +345,8 @@ pub(crate) fn eval_packed( let top_read_channel = nv.mem_channels[0]; let is_top_read = lv.general.stack().stack_inv_aux * (P::ONES - lv.opcode_bits[0]); - // Constrain `stack_inv_aux_2`. It contains `stack_inv_aux * (1 - opcode_bits[0])`. + // Constrain `stack_inv_aux_2`. It contains `stack_inv_aux * (1 - + // opcode_bits[0])`. yield_constr.constraint(lv.op.not_pop * (lv.general.stack().stack_inv_aux_2 - is_top_read)); let new_filter = lv.op.not_pop * lv.general.stack().stack_inv_aux_2; yield_constr.constraint_transition(new_filter * (top_read_channel.used - P::ONES)); @@ -407,7 +416,8 @@ pub(crate) fn eval_ext_circuit_one, const D: usize> ); yield_constr.constraint(builder, constr); } - // Remember that the first read (`i == 1`) is for the second stack element at `stack[stack_len - 1]`. + // Remember that the first read (`i == 1`) is for the second stack element at + // `stack[stack_len - 1]`. { let diff = builder.sub_extension(channel.addr_virtual, lv.stack_len); let constr = builder.arithmetic_extension( @@ -421,7 +431,8 @@ pub(crate) fn eval_ext_circuit_one, const D: usize> } } - // You can't have a write of the top of the stack, so you disable the corresponding flag. + // You can't have a write of the top of the stack, so you disable the + // corresponding flag. { let constr = builder.mul_extension(filter, lv.partial_channel.used); yield_constr.constraint(builder, constr); @@ -429,7 +440,8 @@ pub(crate) fn eval_ext_circuit_one, const D: usize> // If you also push, you don't need to read the new top of the stack. // If you don't: - // - if the stack isn't empty after the pops, you read the new top from an extra pop. + // - if the stack isn't empty after the pops, you read the new top from an extra + // pop. // - if not, the extra read is disabled. // These are transition constraints: they don't apply to the last row. if !stack_behavior.pushes { @@ -486,7 +498,8 @@ pub(crate) fn eval_ext_circuit_one, const D: usize> } } } - // If the op only pushes, you only need to constrain the top of the stack if the stack isn't empty. + // If the op only pushes, you only need to constrain the top of the stack if the stack isn't + // empty. else if stack_behavior.pushes { // If len > 0... let new_filter = builder.mul_extension(lv.stack_len, filter); @@ -554,7 +567,8 @@ pub(crate) fn eval_ext_circuit_one, const D: usize> } } - // You can't have a write of the top of the stack, so you disable the corresponding flag. + // You can't have a write of the top of the stack, so you disable the + // corresponding flag. { let constr = builder.mul_extension(filter, lv.partial_channel.used); yield_constr.constraint(builder, constr); @@ -563,7 +577,8 @@ pub(crate) fn eval_ext_circuit_one, const D: usize> // Unused channels if stack_behavior.disable_other_channels { - // The first channel contains (or not) the top of the stack and is constrained elsewhere. + // The first channel contains (or not) the top of the stack and is constrained + // elsewhere. for i in max(1, stack_behavior.num_pops)..NUM_GP_CHANNELS - (stack_behavior.pushes as usize) { let channel = lv.mem_channels[i]; @@ -650,7 +665,8 @@ pub(crate) fn eval_ext_circuit, const D: usize>( let constr = builder.mul_extension(lv.op.not_pop, diff); yield_constr.constraint(builder, constr); } - // If stack_len != 4 and MSTORE, read new top of the stack in nv.mem_channels[0]. + // If stack_len != 4 and MSTORE, read new top of the stack in + // nv.mem_channels[0]. let top_read_channel = nv.mem_channels[0]; let is_top_read = builder.mul_extension(lv.general.stack().stack_inv_aux, lv.opcode_bits[0]); let is_top_read = builder.sub_extension(lv.general.stack().stack_inv_aux, is_top_read); diff --git a/src/cpu/syscalls_exceptions.rs b/evm/src/cpu/syscalls_exceptions.rs similarity index 98% rename from src/cpu/syscalls_exceptions.rs rename to evm/src/cpu/syscalls_exceptions.rs index cf7aa72e0..a97d8eb52 100644 --- a/src/cpu/syscalls_exceptions.rs +++ b/evm/src/cpu/syscalls_exceptions.rs @@ -1,6 +1,7 @@ //! Handle instructions that are implemented in terms of system calls. //! -//! These are usually the ones that are too complicated to implement in one CPU table row. +//! These are usually the ones that are too complicated to implement in one CPU +//! table row. use plonky2::field::extension::Extendable; use plonky2::field::packed::PackedField; @@ -105,7 +106,8 @@ pub(crate) fn eval_packed( yield_constr.constraint_transition(total_filter * nv.gas); let output = nv.mem_channels[0].value; - // New top of the stack: current PC + 1 (limb 0), kernel flag (limb 1), gas counter (limbs 6 and 7). + // New top of the stack: current PC + 1 (limb 0), kernel flag (limb 1), gas + // counter (limbs 6 and 7). yield_constr.constraint(filter_syscall * (output[0] - (lv.program_counter + P::ONES))); yield_constr.constraint(filter_exception * (output[0] - lv.program_counter)); // Check the kernel mode, for syscalls only @@ -268,14 +270,16 @@ pub(crate) fn eval_ext_circuit, const D: usize>( // New top of the stack. let output = nv.mem_channels[0].value; - // Push to stack (syscall): current PC + 1 (limb 0), kernel flag (limb 1), gas counter (limbs 6 and 7). + // Push to stack (syscall): current PC + 1 (limb 0), kernel flag (limb 1), gas + // counter (limbs 6 and 7). { let pc_plus_1 = builder.add_const_extension(lv.program_counter, F::ONE); let diff = builder.sub_extension(output[0], pc_plus_1); let constr = builder.mul_extension(filter_syscall, diff); yield_constr.constraint(builder, constr); } - // Push to stack (exception): current PC (limb 0), kernel flag (limb 1), gas counter (limbs 6 and 7). + // Push to stack (exception): current PC (limb 0), kernel flag (limb 1), gas + // counter (limbs 6 and 7). { let diff = builder.sub_extension(output[0], lv.program_counter); let constr = builder.mul_extension(filter_exception, diff); diff --git a/src/curve_pairings.rs b/evm/src/curve_pairings.rs similarity index 99% rename from src/curve_pairings.rs rename to evm/src/curve_pairings.rs index af155cc50..673b3db03 100644 --- a/src/curve_pairings.rs +++ b/evm/src/curve_pairings.rs @@ -194,7 +194,8 @@ impl CyclicGroup for Curve> { }; } -// The tate pairing takes a point each from the curve and its twist and outputs an Fp12 element +// The tate pairing takes a point each from the curve and its twist and outputs +// an Fp12 element pub(crate) fn bn_tate(p: Curve, q: Curve>) -> Fp12 { let miller_output = bn_miller_loop(p, q); bn_final_exponent(miller_output) @@ -202,7 +203,8 @@ pub(crate) fn bn_tate(p: Curve, q: Curve>) -> Fp12 { /// Standard code for miller loop, can be found on page 99 at this url: /// -/// where BN_EXP is a hardcoding of the array of Booleans that the loop traverses +/// where BN_EXP is a hardcoding of the array of Booleans that the loop +/// traverses pub(crate) fn bn_miller_loop(p: Curve, q: Curve>) -> Fp12 { let mut r = p; let mut acc: Fp12 = Fp12::::UNIT; diff --git a/src/extension_tower.rs b/evm/src/extension_tower.rs similarity index 98% rename from src/extension_tower.rs rename to evm/src/extension_tower.rs index ea4e31764..3f51235f9 100644 --- a/src/extension_tower.rs +++ b/evm/src/extension_tower.rs @@ -257,8 +257,8 @@ impl Div for BLS381 { } } -/// The degree 2 field extension Fp2 is given by adjoining i, the square root of -1, to BN254 -/// The arithmetic in this extension is standard complex arithmetic +/// The degree 2 field extension Fp2 is given by adjoining i, the square root of +/// -1, to BN254 The arithmetic in this extension is standard complex arithmetic #[derive(Debug, Copy, Clone, PartialEq)] pub(crate) struct Fp2 where @@ -809,8 +809,8 @@ impl Adj for Fp2 { const FROB_Z: [Fp2; 12] = [Fp2::::ZERO; 12]; } -/// The degree 3 field extension Fp6 over Fp2 is given by adjoining t, where t^3 = 1 + i -/// Fp6 has basis 1, t, t^2 over Fp2 +/// The degree 3 field extension Fp6 over Fp2 is given by adjoining t, where t^3 +/// = 1 + i Fp6 has basis 1, t, t^2 over Fp2 #[derive(Debug, Copy, Clone, PartialEq)] pub(crate) struct Fp6 where @@ -920,8 +920,9 @@ where T: FieldExt, Fp2: Adj, { - /// This function multiplies an Fp6 element by t, and hence shifts the bases, - /// where the t^2 coefficient picks up a factor of 1+i as the 1 coefficient of the output + /// This function multiplies an Fp6 element by t, and hence shifts the + /// bases, where the t^2 coefficient picks up a factor of 1+i as the 1 + /// coefficient of the output fn sh(self) -> Fp6 { Fp6 { t0: self.t2.mul_adj(), @@ -1066,12 +1067,12 @@ where /// phi = Prod_{i=0}^11 x_i /// lands in BN254, and hence the inverse of x is given by /// (Prod_{i=1}^11 x_i) / phi - /// The 6th Frob map is nontrivial but leaves Fp6 fixed and hence must be the conjugate: - /// x_6 = (a + bz)_6 = a - bz = x.conj() - /// Letting prod_17 = x_1 * x_7, the remaining factors in the numerator can be expressed as: - /// [(prod_17) * (prod_17)_2] * (prod_17)_4 * [(prod_17) * (prod_17)_2]_1 - /// By Galois theory, both the following are in Fp2 and are complex conjugates - /// prod_odds, prod_evens + /// The 6th Frob map is nontrivial but leaves Fp6 fixed and hence must be + /// the conjugate: x_6 = (a + bz)_6 = a - bz = x.conj() + /// Letting prod_17 = x_1 * x_7, the remaining factors in the numerator can + /// be expressed as: [(prod_17) * (prod_17)_2] * (prod_17)_4 * + /// [(prod_17) * (prod_17)_2]_1 By Galois theory, both the following are + /// in Fp2 and are complex conjugates prod_odds, prod_evens /// Thus phi = ||prod_odds||^2, and hence the inverse is given by /// prod_odds * prod_evens_except_six * x.conj() / ||prod_odds||^2 fn inv(self) -> Fp12 { diff --git a/src/fixed_recursive_verifier.rs b/evm/src/fixed_recursive_verifier.rs similarity index 91% rename from src/fixed_recursive_verifier.rs rename to evm/src/fixed_recursive_verifier.rs index f12a485e8..6b9835fdf 100644 --- a/src/fixed_recursive_verifier.rs +++ b/evm/src/fixed_recursive_verifier.rs @@ -50,12 +50,14 @@ use crate::recursive_verifier::{ }; use crate::util::h256_limbs; -/// The recursion threshold. We end a chain of recursive proofs once we reach this size. +/// The recursion threshold. We end a chain of recursive proofs once we reach +/// this size. const THRESHOLD_DEGREE_BITS: usize = 13; -/// Contains all recursive circuits used in the system. For each STARK and each initial -/// `degree_bits`, this contains a chain of recursive circuits for shrinking that STARK from -/// `degree_bits` to a constant `THRESHOLD_DEGREE_BITS`. It also contains a special root circuit +/// Contains all recursive circuits used in the system. For each STARK and each +/// initial `degree_bits`, this contains a chain of recursive circuits for +/// shrinking that STARK from `degree_bits` to a constant +/// `THRESHOLD_DEGREE_BITS`. It also contains a special root circuit /// for combining each STARK's shrunk wrapper proof into a single proof. #[derive(Eq, PartialEq, Debug)] pub struct AllRecursiveCircuits @@ -64,19 +66,22 @@ where C: GenericConfig, C::Hasher: AlgebraicHasher, { - /// The EVM root circuit, which aggregates the (shrunk) per-table recursive proofs. + /// The EVM root circuit, which aggregates the (shrunk) per-table recursive + /// proofs. pub root: RootCircuitData, - /// The aggregation circuit, which verifies two proofs that can either be root or - /// aggregation proofs. + /// The aggregation circuit, which verifies two proofs that can either be + /// root or aggregation proofs. pub aggregation: AggregationCircuitData, - /// The block circuit, which verifies an aggregation root proof and an optional previous block proof. + /// The block circuit, which verifies an aggregation root proof and an + /// optional previous block proof. pub block: BlockCircuitData, - /// Holds chains of circuits for each table and for each initial `degree_bits`. + /// Holds chains of circuits for each table and for each initial + /// `degree_bits`. pub by_table: [RecursiveCircuitsForTable; NUM_TABLES], } -/// Data for the EVM root circuit, which is used to combine each STARK's shrunk wrapper proof -/// into a single proof. +/// Data for the EVM root circuit, which is used to combine each STARK's shrunk +/// wrapper proof into a single proof. #[derive(Eq, PartialEq, Debug)] pub struct RootCircuitData where @@ -85,13 +90,15 @@ where { pub circuit: CircuitData, proof_with_pis: [ProofWithPublicInputsTarget; NUM_TABLES], - /// For each table, various inner circuits may be used depending on the initial table size. - /// This target holds the index of the circuit (within `final_circuits()`) that was used. + /// For each table, various inner circuits may be used depending on the + /// initial table size. This target holds the index of the circuit + /// (within `final_circuits()`) that was used. index_verifier_data: [Target; NUM_TABLES], /// Public inputs containing public values. public_values: PublicValuesTarget, - /// Public inputs used for cyclic verification. These aren't actually used for EVM root - /// proofs; the circuit has them just to match the structure of aggregation proofs. + /// Public inputs used for cyclic verification. These aren't actually used + /// for EVM root proofs; the circuit has them just to match the + /// structure of aggregation proofs. cyclic_vk: VerifierCircuitTarget, } @@ -145,8 +152,9 @@ where } } -/// Data for the aggregation circuit, which is used to compress two proofs into one. Each inner -/// proof can be either an EVM root proof or another aggregation proof. +/// Data for the aggregation circuit, which is used to compress two proofs into +/// one. Each inner proof can be either an EVM root proof or another aggregation +/// proof. #[derive(Eq, PartialEq, Debug)] pub struct AggregationCircuitData where @@ -303,11 +311,15 @@ where /// /// # Arguments /// - /// - `skip_tables`: a boolean indicating whether to serialize only the upper circuits - /// or the entire prover state, including recursive circuits to shrink STARK proofs. - /// - `gate_serializer`: a custom gate serializer needed to serialize recursive circuits + /// - `skip_tables`: a boolean indicating whether to serialize only the + /// upper circuits + /// or the entire prover state, including recursive circuits to shrink STARK + /// proofs. + /// - `gate_serializer`: a custom gate serializer needed to serialize + /// recursive circuits /// common data. - /// - `generator_serializer`: a custom generator serializer needed to serialize recursive + /// - `generator_serializer`: a custom generator serializer needed to + /// serialize recursive /// circuits proving data. pub fn to_bytes( &self, @@ -315,7 +327,8 @@ where gate_serializer: &dyn GateSerializer, generator_serializer: &dyn WitnessGeneratorSerializer, ) -> IoResult> { - // TODO: would be better to initialize it dynamically based on the supported max degree. + // TODO: would be better to initialize it dynamically based on the supported max + // degree. let mut buffer = Vec::with_capacity(1 << 34); self.root .to_buffer(&mut buffer, gate_serializer, generator_serializer)?; @@ -331,16 +344,21 @@ where Ok(buffer) } - /// Deserializes a sequence of bytes into an entire prover state containing all recursive circuits. + /// Deserializes a sequence of bytes into an entire prover state containing + /// all recursive circuits. /// /// # Arguments /// /// - `bytes`: a slice of bytes to deserialize this prover state from. - /// - `skip_tables`: a boolean indicating whether to deserialize only the upper circuits - /// or the entire prover state, including recursive circuits to shrink STARK proofs. - /// - `gate_serializer`: a custom gate serializer needed to serialize recursive circuits + /// - `skip_tables`: a boolean indicating whether to deserialize only the + /// upper circuits + /// or the entire prover state, including recursive circuits to shrink STARK + /// proofs. + /// - `gate_serializer`: a custom gate serializer needed to serialize + /// recursive circuits /// common data. - /// - `generator_serializer`: a custom generator serializer needed to serialize recursive + /// - `generator_serializer`: a custom generator serializer needed to + /// serialize recursive /// circuits proving data. pub fn from_bytes( bytes: &[u8], @@ -398,15 +416,20 @@ where /// /// # Arguments /// - /// - `all_stark`: a structure defining the logic of all STARK modules and their associated + /// - `all_stark`: a structure defining the logic of all STARK modules and + /// their associated /// cross-table lookups. - /// - `degree_bits_ranges`: the logarithmic ranges to be supported for the recursive tables. - /// Transactions may yield arbitrary trace lengths for each STARK module (within some bounds), - /// unknown prior generating the witness to create a proof. Thus, for each STARK module, we - /// construct a map from `2^{degree_bits} = length` to a chain of shrinking recursion circuits, - /// starting from that length, for each `degree_bits` in the range specified for this STARK module. - /// Specifying a wide enough range allows a prover to cover all possible scenarios. - /// - `stark_config`: the configuration to be used for the STARK prover. It will usually be a fast + /// - `degree_bits_ranges`: the logarithmic ranges to be supported for the + /// recursive tables. + /// Transactions may yield arbitrary trace lengths for each STARK module + /// (within some bounds), unknown prior generating the witness to create + /// a proof. Thus, for each STARK module, we construct a map from + /// `2^{degree_bits} = length` to a chain of shrinking recursion circuits, + /// starting from that length, for each `degree_bits` in the range specified + /// for this STARK module. Specifying a wide enough range allows a + /// prover to cover all possible scenarios. + /// - `stark_config`: the configuration to be used for the STARK prover. It + /// will usually be a fast /// one yielding large proofs. pub fn new( all_stark: &AllStark, @@ -485,9 +508,10 @@ where /// Outputs the `VerifierCircuitData` needed to verify any block proof /// generated by an honest prover. - /// While the [`AllRecursiveCircuits`] prover state can also verify proofs, verifiers - /// only need a fraction of the state to verify proofs. This allows much less powerful - /// entities to behave as verifiers, by only loading the necessary data to verify block proofs. + /// While the [`AllRecursiveCircuits`] prover state can also verify proofs, + /// verifiers only need a fraction of the state to verify proofs. This + /// allows much less powerful entities to behave as verifiers, by only + /// loading the necessary data to verify block proofs. /// /// # Usage /// @@ -617,8 +641,9 @@ where ); } - // We want EVM root proofs to have the exact same structure as aggregation proofs, so we add - // public inputs for cyclic verification, even though they'll be ignored. + // We want EVM root proofs to have the exact same structure as aggregation + // proofs, so we add public inputs for cyclic verification, even though + // they'll be ignored. let cyclic_vk = builder.add_verifier_data_public_inputs(); builder.add_gate( @@ -731,7 +756,8 @@ where builder.connect(limb0, limb1); } - // Connect the transaction number in public values to the lhs and rhs values correctly. + // Connect the transaction number in public values to the lhs and rhs values + // correctly. builder.connect(pvs.txn_number_before, lhs.txn_number_before); builder.connect(pvs.txn_number_after, rhs.txn_number_after); @@ -768,8 +794,9 @@ where } fn create_block_circuit(agg: &AggregationCircuitData) -> BlockCircuitData { - // The block circuit is similar to the agg circuit; both verify two inner proofs. - // We need to adjust a few things, but it's easier than making a new CommonCircuitData. + // The block circuit is similar to the agg circuit; both verify two inner + // proofs. We need to adjust a few things, but it's easier than making a + // new CommonCircuitData. let expected_common_data = CommonCircuitData { fri_params: FriParams { degree_bits: 14, @@ -818,7 +845,8 @@ where agg_pv.extra_block_data, ); - // Make connections between block proofs, and check initial and final block values. + // Make connections between block proofs, and check initial and final block + // values. Self::connect_block_proof(&mut builder, has_parent_block, &parent_pv, &agg_pv); let cyclic_vk = builder.add_verifier_data_public_inputs(); @@ -901,12 +929,15 @@ where // Check initial block values. Self::connect_initial_values_block(builder, rhs); - // Connect intermediary values for gas_used and bloom filters to the block's final values. We only plug on the right, so there is no need to check the left-handside block. + // Connect intermediary values for gas_used and bloom filters to the block's + // final values. We only plug on the right, so there is no need to check the + // left-handside block. Self::connect_final_block_values_to_intermediary(builder, rhs); let has_not_parent_block = builder.sub(one, has_parent_block.target); - // Check that the checkpoint block has the predetermined state trie root in `ExtraBlockData`. + // Check that the checkpoint block has the predetermined state trie root in + // `ExtraBlockData`. Self::connect_checkpoint_block(builder, rhs, has_not_parent_block); } @@ -960,29 +991,36 @@ where } } - /// For a given transaction payload passed as [`GenerationInputs`], create a proof - /// for each STARK module, then recursively shrink and combine them, eventually - /// culminating in a transaction proof, also called root proof. + /// For a given transaction payload passed as [`GenerationInputs`], create a + /// proof for each STARK module, then recursively shrink and combine + /// them, eventually culminating in a transaction proof, also called + /// root proof. /// /// # Arguments /// - /// - `all_stark`: a structure defining the logic of all STARK modules and their associated + /// - `all_stark`: a structure defining the logic of all STARK modules and + /// their associated /// cross-table lookups. - /// - `config`: the configuration to be used for the STARK prover. It will usually be a fast + /// - `config`: the configuration to be used for the STARK prover. It will + /// usually be a fast /// one yielding large proofs. - /// - `generation_inputs`: a transaction and auxiliary data needed to generate a proof, provided + /// - `generation_inputs`: a transaction and auxiliary data needed to + /// generate a proof, provided /// in Intermediary Representation. - /// - `timing`: a profiler defining a scope hierarchy and the time consumed by each one. - /// - `abort_signal`: an optional [`AtomicBool`] wrapped behind an [`Arc`], to send a kill signal - /// early. This is only necessary in a distributed setting where a worker may be blocking the entire - /// queue. + /// - `timing`: a profiler defining a scope hierarchy and the time consumed + /// by each one. + /// - `abort_signal`: an optional [`AtomicBool`] wrapped behind an [`Arc`], + /// to send a kill signal + /// early. This is only necessary in a distributed setting where a worker + /// may be blocking the entire queue. /// /// # Outputs /// - /// This method outputs a tuple of [`ProofWithPublicInputs`] and its [`PublicValues`]. Only - /// the proof with public inputs is necessary for a verifier to assert correctness of the computation, - /// but the public values are output for the prover convenience, as these are necessary during proof - /// aggregation. + /// This method outputs a tuple of [`ProofWithPublicInputs`] and + /// its [`PublicValues`]. Only the proof with public inputs is necessary + /// for a verifier to assert correctness of the computation, + /// but the public values are output for the prover convenience, as these + /// are necessary during proof aggregation. pub fn prove_root( &self, all_stark: &AllStark, @@ -1048,18 +1086,21 @@ where Ok((root_proof, all_proof.public_values)) } - /// From an initial set of STARK proofs passed with their associated recursive table circuits, - /// generate a recursive transaction proof. - /// It is aimed at being used when preprocessed table circuits have not been loaded to memory. + /// From an initial set of STARK proofs passed with their associated + /// recursive table circuits, generate a recursive transaction proof. + /// It is aimed at being used when preprocessed table circuits have not been + /// loaded to memory. /// /// **Note**: /// The type of the `table_circuits` passed as arguments is - /// `&[(RecursiveCircuitsForTableSize, u8); NUM_TABLES]`. In particular, for each STARK - /// proof contained within the `AllProof` object provided to this method, we need to pass a tuple - /// of [`RecursiveCircuitsForTableSize`] and a [`u8`]. The former is the recursive chain - /// corresponding to the initial degree size of the associated STARK proof. The latter is the - /// index of this degree in the range that was originally passed when constructing the entire prover - /// state. + /// `&[(RecursiveCircuitsForTableSize, u8); NUM_TABLES]`. In + /// particular, for each STARK proof contained within the `AllProof` + /// object provided to this method, we need to pass a tuple + /// of [`RecursiveCircuitsForTableSize`] and a [`u8`]. The former + /// is the recursive chain corresponding to the initial degree size of + /// the associated STARK proof. The latter is the index of this degree + /// in the range that was originally passed when constructing the entire + /// prover state. /// /// # Usage /// @@ -1142,31 +1183,38 @@ where self.root.circuit.verify(agg_proof) } - /// Create an aggregation proof, combining two contiguous proofs into a single one. The combined - /// proofs can either be transaction (aka root) proofs, or other aggregation proofs, as long as - /// their states are contiguous, meaning that the final state of the left child proof is the initial - /// state of the right child proof. + /// Create an aggregation proof, combining two contiguous proofs into a + /// single one. The combined proofs can either be transaction (aka root) + /// proofs, or other aggregation proofs, as long as their states are + /// contiguous, meaning that the final state of the left child proof is the + /// initial state of the right child proof. /// - /// While regular transaction proofs can only assert validity of a single transaction, aggregation - /// proofs can cover an arbitrary range, up to an entire block with all its transactions. + /// While regular transaction proofs can only assert validity of a single + /// transaction, aggregation proofs can cover an arbitrary range, up to + /// an entire block with all its transactions. /// /// # Arguments /// - /// - `lhs_is_agg`: a boolean indicating whether the left child proof is an aggregation proof or + /// - `lhs_is_agg`: a boolean indicating whether the left child proof is an + /// aggregation proof or /// a regular transaction proof. /// - `lhs_proof`: the left child proof. - /// - `lhs_public_values`: the public values associated to the right child proof. - /// - `rhs_is_agg`: a boolean indicating whether the right child proof is an aggregation proof or + /// - `lhs_public_values`: the public values associated to the right child + /// proof. + /// - `rhs_is_agg`: a boolean indicating whether the right child proof is an + /// aggregation proof or /// a regular transaction proof. /// - `rhs_proof`: the right child proof. - /// - `rhs_public_values`: the public values associated to the right child proof. + /// - `rhs_public_values`: the public values associated to the right child + /// proof. /// /// # Outputs /// - /// This method outputs a tuple of [`ProofWithPublicInputs`] and its [`PublicValues`]. Only - /// the proof with public inputs is necessary for a verifier to assert correctness of the computation, - /// but the public values are output for the prover convenience, as these are necessary during proof - /// aggregation. + /// This method outputs a tuple of [`ProofWithPublicInputs`] and + /// its [`PublicValues`]. Only the proof with public inputs is necessary + /// for a verifier to assert correctness of the computation, + /// but the public values are output for the prover convenience, as these + /// are necessary during proof aggregation. pub fn prove_aggregation( &self, lhs_is_agg: bool, @@ -1233,23 +1281,28 @@ where ) } - /// Create a final block proof, once all transactions of a given block have been combined into a - /// single aggregation proof. + /// Create a final block proof, once all transactions of a given block have + /// been combined into a single aggregation proof. /// - /// Block proofs can either be generated as standalone, or combined with a previous block proof - /// to assert validity of a range of blocks. + /// Block proofs can either be generated as standalone, or combined with a + /// previous block proof to assert validity of a range of blocks. /// /// # Arguments /// - /// - `opt_parent_block_proof`: an optional parent block proof. Passing one will generate a proof of - /// validity for both the block range covered by the previous proof and the current block. - /// - `agg_root_proof`: the final aggregation proof containing all transactions within the current block. - /// - `public_values`: the public values associated to the aggregation proof. + /// - `opt_parent_block_proof`: an optional parent block proof. Passing one + /// will generate a proof of + /// validity for both the block range covered by the previous proof and the + /// current block. + /// - `agg_root_proof`: the final aggregation proof containing all + /// transactions within the current block. + /// - `public_values`: the public values associated to the aggregation + /// proof. /// /// # Outputs /// - /// This method outputs a tuple of [`ProofWithPublicInputs`] and its [`PublicValues`]. Only - /// the proof with public inputs is necessary for a verifier to assert correctness of the computation. + /// This method outputs a tuple of [`ProofWithPublicInputs`] and + /// its [`PublicValues`]. Only the proof with public inputs is necessary + /// for a verifier to assert correctness of the computation. pub fn prove_block( &self, opt_parent_block_proof: Option<&ProofWithPublicInputs>, @@ -1276,7 +1329,8 @@ where ))); } - // Initialize some public inputs for correct connection between the checkpoint block and the current one. + // Initialize some public inputs for correct connection between the checkpoint + // block and the current one. let mut nonzero_pis = HashMap::new(); // Initialize the checkpoint block roots before, and state root after. @@ -1344,7 +1398,8 @@ where } // Initialize the checkpoint block number. - // Subtraction would result in an invalid proof for genesis, but we shouldn't try proving this block anyway. + // Subtraction would result in an invalid proof for genesis, but we shouldn't + // try proving this block anyway. let block_number_key = TrieRootsTarget::SIZE * 2 + 6; nonzero_pis.insert( block_number_key, @@ -1366,8 +1421,8 @@ where block_inputs .set_verifier_data_target(&self.block.cyclic_vk, &self.block.circuit.verifier_only); - // This is basically identical to this block public values, apart from the `trie_roots_before` - // that may come from the previous proof, if any. + // This is basically identical to this block public values, apart from the + // `trie_roots_before` that may come from the previous proof, if any. let block_public_values = PublicValues { trie_roots_before: opt_parent_block_proof .map(|p| TrieRoots::from_public_inputs(&p.public_inputs[0..TrieRootsTarget::SIZE])) @@ -1398,7 +1453,8 @@ where } } -/// A map between initial degree sizes and their associated shrinking recursion circuits. +/// A map between initial degree sizes and their associated shrinking recursion +/// circuits. #[derive(Eq, PartialEq, Debug)] pub struct RecursiveCircuitsForTable where @@ -1406,8 +1462,8 @@ where C: GenericConfig, C::Hasher: AlgebraicHasher, { - /// A map from `log_2(height)` to a chain of shrinking recursion circuits starting at that - /// height. + /// A map from `log_2(height)` to a chain of shrinking recursion circuits + /// starting at that height. pub by_stark_size: BTreeMap>, } @@ -1474,8 +1530,9 @@ where Self { by_stark_size } } - /// For each initial `degree_bits`, get the final circuit at the end of that shrinking chain. - /// Each of these final circuits should have degree `THRESHOLD_DEGREE_BITS`. + /// For each initial `degree_bits`, get the final circuit at the end of that + /// shrinking chain. Each of these final circuits should have degree + /// `THRESHOLD_DEGREE_BITS`. fn final_circuits(&self) -> Vec<&CircuitData> { self.by_stark_size .values() @@ -1490,8 +1547,8 @@ where } } -/// A chain of shrinking wrapper circuits, ending with a final circuit with `degree_bits` -/// `THRESHOLD_DEGREE_BITS`. +/// A chain of shrinking wrapper circuits, ending with a final circuit with +/// `degree_bits` `THRESHOLD_DEGREE_BITS`. #[derive(Eq, PartialEq, Debug)] pub struct RecursiveCircuitsForTableSize where @@ -1642,9 +1699,10 @@ where } } -/// Our usual recursion threshold is 2^12 gates, but for these shrinking circuits, we use a few more -/// gates for a constant inner VK and for public inputs. This pushes us over the threshold to 2^13. -/// As long as we're at 2^13 gates, we might as well use a narrower witness. +/// Our usual recursion threshold is 2^12 gates, but for these shrinking +/// circuits, we use a few more gates for a constant inner VK and for public +/// inputs. This pushes us over the threshold to 2^13. As long as we're at 2^13 +/// gates, we might as well use a narrower witness. fn shrinking_config() -> CircuitConfig { CircuitConfig { num_routed_wires: 40, diff --git a/src/generation/mod.rs b/evm/src/generation/mod.rs similarity index 91% rename from src/generation/mod.rs rename to evm/src/generation/mod.rs index 105fb6a90..f6d1b89bd 100644 --- a/src/generation/mod.rs +++ b/evm/src/generation/mod.rs @@ -41,23 +41,28 @@ use crate::witness::util::{mem_write_log, stack_peek}; pub struct GenerationInputs { /// The index of the transaction being proven within its block. pub txn_number_before: U256, - /// The cumulative gas used through the execution of all transactions prior the current one. + /// The cumulative gas used through the execution of all transactions prior + /// the current one. pub gas_used_before: U256, - /// The cumulative gas used after the execution of the current transaction. The exact gas used - /// by the current transaction is `gas_used_after` - `gas_used_before`. + /// The cumulative gas used after the execution of the current transaction. + /// The exact gas used by the current transaction is `gas_used_after` - + /// `gas_used_before`. pub gas_used_after: U256, - /// A None would yield an empty proof, otherwise this contains the encoding of a transaction. + /// A None would yield an empty proof, otherwise this contains the encoding + /// of a transaction. pub signed_txn: Option>, - /// Withdrawal pairs `(addr, amount)`. At the end of the txs, `amount` is added to `addr`'s balance. See EIP-4895. + /// Withdrawal pairs `(addr, amount)`. At the end of the txs, `amount` is + /// added to `addr`'s balance. See EIP-4895. pub withdrawals: Vec<(Address, U256)>, pub tries: TrieInputs, /// Expected trie roots after the transactions are executed. pub trie_roots_after: TrieRoots, /// State trie root of the checkpoint block. - /// This could always be the genesis block of the chain, but it allows a prover to continue proving blocks - /// from certain checkpoint heights without requiring proofs for blocks past this checkpoint. + /// This could always be the genesis block of the chain, but it allows a + /// prover to continue proving blocks from certain checkpoint heights + /// without requiring proofs for blocks past this checkpoint. pub checkpoint_state_trie_root: H256, /// Mapping between smart contract code hashes and the contract byte code. @@ -67,26 +72,31 @@ pub struct GenerationInputs { /// Information contained in the block header. pub block_metadata: BlockMetadata, - /// The hash of the current block, and a list of the 256 previous block hashes. + /// The hash of the current block, and a list of the 256 previous block + /// hashes. pub block_hashes: BlockHashes, } #[derive(Clone, Debug, Deserialize, Serialize, Default)] pub struct TrieInputs { - /// A partial version of the state trie prior to these transactions. It should include all nodes - /// that will be accessed by these transactions. + /// 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: HashedPartialTrie, - /// A partial version of the transaction trie prior to these transactions. It should include all - /// nodes that will be accessed by these transactions. + /// 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: HashedPartialTrie, - /// A partial version of the receipt trie prior to these transactions. It should include all nodes - /// that will be accessed by these transactions. + /// 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: HashedPartialTrie, - /// 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. + /// 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<(H256, HashedPartialTrie)>, } @@ -117,6 +127,10 @@ fn apply_metadata_and_tries_memops, const D: usize> h2u(inputs.block_hashes.cur_hash), ), (GlobalMetadata::BlockGasUsed, metadata.block_gas_used), + ( + GlobalMetadata::BlockBlobBaseFee, + metadata.block_blob_base_fee, + ), (GlobalMetadata::BlockGasUsedBefore, inputs.gas_used_before), (GlobalMetadata::BlockGasUsedAfter, inputs.gas_used_after), (GlobalMetadata::TxnNumberBefore, inputs.txn_number_before), @@ -206,8 +220,9 @@ pub fn generate_traces, const D: usize>( let cpu_res = timed!(timing, "simulate CPU", simulate_cpu(&mut state)); if cpu_res.is_err() { - // Retrieve previous PC (before jumping to KernelPanic), to see if we reached `hash_final_tries`. - // We will output debugging information on the final tries only if we got a root mismatch. + // Retrieve previous PC (before jumping to KernelPanic), to see if we reached + // `hash_final_tries`. We will output debugging information on the final + // tries only if we got a root mismatch. let previous_pc = state .traces .cpu @@ -302,7 +317,8 @@ fn simulate_cpu(state: &mut GenerationState) -> anyhow::Result<()> let halt_pc = KERNEL.global_labels["halt"]; loop { - // If we've reached the kernel's halt routine, and our trace length is a power of 2, stop. + // If we've reached the kernel's halt routine, and our trace length is a power + // of 2, stop. let pc = state.registers.program_counter; let halt = state.registers.is_kernel && pc == halt_pc; if halt { diff --git a/src/generation/mpt.rs b/evm/src/generation/mpt.rs similarity index 100% rename from src/generation/mpt.rs rename to evm/src/generation/mpt.rs diff --git a/src/generation/prover_input.rs b/evm/src/generation/prover_input.rs similarity index 98% rename from src/generation/prover_input.rs rename to evm/src/generation/prover_input.rs index e6fb4dbf8..9a2a38c80 100644 --- a/src/generation/prover_input.rs +++ b/evm/src/generation/prover_input.rs @@ -29,7 +29,8 @@ use crate::witness::operation::CONTEXT_SCALING_FACTOR; use crate::witness::util::{current_context_peek, stack_peek}; /// Prover input function represented as a scoped function name. -/// Example: `PROVER_INPUT(ff::bn254_base::inverse)` is represented as `ProverInputFn([ff, bn254_base, inverse])`. +/// Example: `PROVER_INPUT(ff::bn254_base::inverse)` is represented as +/// `ProverInputFn([ff, bn254_base, inverse])`. #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] pub struct ProverInputFn(Vec); @@ -139,8 +140,8 @@ impl GenerationState { } /// Account code loading. - /// Initializes the code segment of the given context with the code corresponding - /// to the provided hash. + /// Initializes the code segment of the given context with the code + /// corresponding to the provided hash. /// Returns the length of the code. fn run_account_code(&mut self) -> Result { // stack: codehash, ctx, ... @@ -163,7 +164,8 @@ impl GenerationState { // Bignum modular multiplication. // On the first call, calculates the remainder and quotient of the given inputs. // These are stored, as limbs, in self.bignum_modmul_result_limbs. - // Subsequent calls return one limb at a time, in order (first remainder and then quotient). + // Subsequent calls return one limb at a time, in order (first remainder and + // then quotient). fn run_bignum_modmul(&mut self) -> Result { if self.bignum_modmul_result_limbs.is_empty() { let len = stack_peek(self, 2).map(u256_to_usize)??; @@ -237,7 +239,8 @@ impl GenerationState { } } - /// Generate either the next used jump address or the proof for the last jump address. + /// Generate either the next used jump address or the proof for the last + /// jump address. fn run_jumpdest_table(&mut self, input_fn: &ProverInputFn) -> Result { match input_fn.0[1].as_str() { "next_address" => self.run_next_jumpdest_table_address(), @@ -307,18 +310,21 @@ impl GenerationState { } impl GenerationState { - /// Simulate the user's code and store all the jump addresses with their respective contexts. + /// Simulate the user's code and store all the jump addresses with their + /// respective contexts. fn generate_jumpdest_table(&mut self) -> Result<(), ProgramError> { let checkpoint = self.checkpoint(); - // Simulate the user's code and (unnecessarily) part of the kernel code, skipping the validate table call + // Simulate the user's code and (unnecessarily) part of the kernel code, + // skipping the validate table call self.jumpdest_table = simulate_cpu_and_get_user_jumps("terminate_common", self); Ok(()) } - /// Given a HashMap containing the contexts and the jumpdest addresses, compute their respective proofs, - /// by calling `get_proofs_and_jumpdests` + /// Given a HashMap containing the contexts and the jumpdest addresses, + /// compute their respective proofs, by calling + /// `get_proofs_and_jumpdests` pub(crate) fn set_jumpdest_analysis_inputs( &mut self, jumpdest_table: HashMap>, diff --git a/src/generation/rlp.rs b/evm/src/generation/rlp.rs similarity index 100% rename from src/generation/rlp.rs rename to evm/src/generation/rlp.rs diff --git a/src/generation/state.rs b/evm/src/generation/state.rs similarity index 88% rename from src/generation/state.rs rename to evm/src/generation/state.rs index a6df4b333..c492c43a5 100644 --- a/src/generation/state.rs +++ b/evm/src/generation/state.rs @@ -30,29 +30,31 @@ pub(crate) struct GenerationState { pub(crate) memory: MemoryState, pub(crate) traces: Traces, - /// Prover inputs containing RLP data, in reverse order so that the next input can be obtained - /// via `pop()`. + /// Prover inputs containing RLP data, in reverse order so that the next + /// input can be obtained via `pop()`. pub(crate) rlp_prover_inputs: Vec, pub(crate) withdrawal_prover_inputs: Vec, - /// The state trie only stores state keys, which are hashes of addresses, but sometimes it is - /// useful to see the actual addresses for debugging. Here we store the mapping for all known - /// addresses. + /// The state trie only stores state keys, which are hashes of addresses, + /// but sometimes it is useful to see the actual addresses for + /// debugging. Here we store the mapping for all known addresses. pub(crate) state_key_to_address: HashMap, - /// Prover inputs containing the result of a MODMUL operation, in little-endian order (so that - /// inputs are obtained in big-endian order via `pop()`). Contains both the remainder and the - /// quotient, in that order. + /// Prover inputs containing the result of a MODMUL operation, in + /// little-endian order (so that inputs are obtained in big-endian order + /// via `pop()`). Contains both the remainder and the quotient, in that + /// order. pub(crate) bignum_modmul_result_limbs: Vec, /// Pointers, within the `TrieData` segment, of the three MPTs. pub(crate) trie_root_ptrs: TrieRootPtrs, - /// A hash map where the key is a context in the user's code and the value is the set of - /// jump destinations with its corresponding "proof". A "proof" for a jump destination is - /// either 0 or an address i > 32 in the code (not necessarily pointing to an opcode) such that - /// for every j in [i, i+32] it holds that code[j] < 0x7f - j + i. + /// A hash map where the key is a context in the user's code and the value + /// is the set of jump destinations with its corresponding "proof". A + /// "proof" for a jump destination is either 0 or an address i > 32 in + /// the code (not necessarily pointing to an opcode) such that for every + /// j in [i, i+32] it holds that code[j] < 0x7f - j + i. pub(crate) jumpdest_table: Option>>, } @@ -103,8 +105,8 @@ impl GenerationState { Ok(state) } - /// Updates `program_counter`, and potentially adds some extra handling if we're jumping to a - /// special location. + /// Updates `program_counter`, and potentially adds some extra handling if + /// we're jumping to a special location. pub(crate) fn jump_to(&mut self, dst: usize) -> Result<(), ProgramError> { self.registers.program_counter = dst; if dst == KERNEL.global_labels["observe_new_address"] { @@ -121,18 +123,20 @@ impl GenerationState { Ok(()) } - /// Observe the given address, so that we will be able to recognize the associated state key. - /// This is just for debugging purposes. + /// Observe the given address, so that we will be able to recognize the + /// associated state key. This is just for debugging purposes. pub(crate) fn observe_address(&mut self, address: Address) { let state_key = keccak(address.0); self.state_key_to_address.insert(state_key, address); } /// Observe the given code hash and store the associated code. - /// When called, the code corresponding to `codehash` should be stored in the return data. + /// When called, the code corresponding to `codehash` should be stored in + /// the return data. pub(crate) fn observe_contract(&mut self, codehash: H256) -> Result<(), ProgramError> { if self.inputs.contract_code.contains_key(&codehash) { - return Ok(()); // Return early if the code hash has already been observed. + return Ok(()); // Return early if the code hash has already been + // observed. } let ctx = self.registers.context; @@ -192,8 +196,8 @@ impl GenerationState { } } -/// Withdrawals prover input array is of the form `[addr0, amount0, ..., addrN, amountN, U256::MAX, U256::MAX]`. -/// Returns the reversed array. +/// Withdrawals prover input array is of the form `[addr0, amount0, ..., addrN, +/// amountN, U256::MAX, U256::MAX]`. Returns the reversed array. pub(crate) fn all_withdrawals_prover_inputs_reversed(withdrawals: &[(Address, U256)]) -> Vec { let mut withdrawal_prover_inputs = withdrawals .iter() diff --git a/src/generation/trie_extractor.rs b/evm/src/generation/trie_extractor.rs similarity index 98% rename from src/generation/trie_extractor.rs rename to evm/src/generation/trie_extractor.rs index 4d3a745a1..66af5361f 100644 --- a/src/generation/trie_extractor.rs +++ b/evm/src/generation/trie_extractor.rs @@ -1,4 +1,5 @@ -//! Code for extracting trie data after witness generation. This is intended only for debugging. +//! Code for extracting trie data after witness generation. This is intended +//! only for debugging. use std::collections::HashMap; @@ -13,7 +14,8 @@ use crate::util::{u256_to_bool, u256_to_h160, u256_to_u8, u256_to_usize}; use crate::witness::errors::ProgramError; use crate::witness::memory::{MemoryAddress, MemoryState}; -/// Account data as it's stored in the state trie, with a pointer to the storage trie. +/// Account data as it's stored in the state trie, with a pointer to the storage +/// trie. #[derive(Debug)] pub(crate) struct AccountTrieRecord { pub(crate) nonce: u64, @@ -129,7 +131,8 @@ pub(crate) fn read_receipt_trie_value( .iter() .map(|&x| u256_to_u8(x)) .collect::>()?; - // We read the number of logs at position `2 + 256 + 1`, and skip over the next element before parsing the logs. + // We read the number of logs at position `2 + 256 + 1`, and skip over the next + // element before parsing the logs. let logs = read_logs(u256_to_usize(slice[2 + 256 + 1])?, &slice[2 + 256 + 3..])?; Ok(( diff --git a/src/get_challenges.rs b/evm/src/get_challenges.rs similarity index 97% rename from src/get_challenges.rs rename to evm/src/get_challenges.rs index 2a783b940..5cf8579b7 100644 --- a/src/get_challenges.rs +++ b/evm/src/get_challenges.rs @@ -65,6 +65,9 @@ fn observe_block_metadata< challenger.observe_element(basefee.0); challenger.observe_element(basefee.1); challenger.observe_element(u256_to_u32(block_metadata.block_gas_used)?); + let blob_basefee = u256_to_u64(block_metadata.block_blob_base_fee)?; + challenger.observe_element(blob_basefee.0); + challenger.observe_element(blob_basefee.1); for i in 0..8 { challenger.observe_elements(&u256_limbs(block_metadata.block_bloom[i])); } @@ -91,6 +94,7 @@ fn observe_block_metadata_target< challenger.observe_element(block_metadata.block_chain_id); challenger.observe_elements(&block_metadata.block_base_fee); challenger.observe_element(block_metadata.block_gas_used); + challenger.observe_elements(&block_metadata.block_blob_base_fee); challenger.observe_elements(&block_metadata.block_bloom); } diff --git a/src/keccak/columns.rs b/evm/src/keccak/columns.rs similarity index 100% rename from src/keccak/columns.rs rename to evm/src/keccak/columns.rs diff --git a/src/keccak/constants.rs b/evm/src/keccak/constants.rs similarity index 100% rename from src/keccak/constants.rs rename to evm/src/keccak/constants.rs diff --git a/src/keccak/keccak_stark.rs b/evm/src/keccak/keccak_stark.rs similarity index 99% rename from src/keccak/keccak_stark.rs rename to evm/src/keccak/keccak_stark.rs index fc27086ae..969a0357f 100644 --- a/src/keccak/keccak_stark.rs +++ b/evm/src/keccak/keccak_stark.rs @@ -64,8 +64,9 @@ pub(crate) struct KeccakStark { } impl, const D: usize> KeccakStark { - /// Generate the rows of the trace. Note that this does not generate the permuted columns used - /// in our lookup arguments, as those are computed after transposing to column-wise form. + /// Generate the rows of the trace. Note that this does not generate the + /// permuted columns used in our lookup arguments, as those are computed + /// after transposing to column-wise form. fn generate_trace_rows( &self, inputs_and_timestamps: Vec<([u64; NUM_INPUTS], usize)>, diff --git a/src/keccak/logic.rs b/evm/src/keccak/logic.rs similarity index 100% rename from src/keccak/logic.rs rename to evm/src/keccak/logic.rs diff --git a/src/keccak/mod.rs b/evm/src/keccak/mod.rs similarity index 100% rename from src/keccak/mod.rs rename to evm/src/keccak/mod.rs diff --git a/src/keccak/round_flags.rs b/evm/src/keccak/round_flags.rs similarity index 100% rename from src/keccak/round_flags.rs rename to evm/src/keccak/round_flags.rs diff --git a/src/keccak_sponge/columns.rs b/evm/src/keccak_sponge/columns.rs similarity index 87% rename from src/keccak_sponge/columns.rs rename to evm/src/keccak_sponge/columns.rs index b35ff1fa1..6f602ab02 100644 --- a/src/keccak_sponge/columns.rs +++ b/evm/src/keccak_sponge/columns.rs @@ -4,7 +4,8 @@ use core::ops::Range; use crate::util::{indices_arr, transmute_no_compile_time_size_checks}; -/// Total number of sponge bytes: number of rate bytes + number of capacity bytes. +/// Total number of sponge bytes: number of rate bytes + number of capacity +/// bytes. pub(crate) const KECCAK_WIDTH_BYTES: usize = 200; /// Total number of 32-bit limbs in the sponge. pub(crate) const KECCAK_WIDTH_U32S: usize = KECCAK_WIDTH_BYTES / 4; @@ -28,8 +29,8 @@ pub(crate) const KECCAK_DIGEST_U32S: usize = KECCAK_DIGEST_BYTES / 4; #[repr(C)] #[derive(Eq, PartialEq, Debug)] pub(crate) struct KeccakSpongeColumnsView { - /// 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. + /// 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, /// The context of the base address at which we will read the input block. @@ -42,11 +43,13 @@ pub(crate) struct KeccakSpongeColumnsView { /// The timestamp at which inputs should be read from memory. pub timestamp: T, - /// The number of input bytes that have already been absorbed prior to this block. + /// 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 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], @@ -54,27 +57,32 @@ pub(crate) struct KeccakSpongeColumnsView { /// The initial rate part of the sponge, at the start of this step. pub original_rate_u32s: [T; KECCAK_RATE_U32S], - /// The capacity part 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. + /// 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, encoded as 32-bit chunks, 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 - /// permutation is applied, minus the first limbs where the digest is extracted from. - /// Those missing limbs can be recomputed from their corresponding bytes stored in + /// The entire state (rate + capacity) of the sponge, encoded as 32-bit + /// chunks, after the permutation is applied, minus the first limbs + /// where the digest is extracted from. Those missing limbs can be + /// recomputed from their corresponding bytes stored in /// `updated_digest_state_bytes`. pub partial_updated_state_u32s: [T; KECCAK_WIDTH_MINUS_DIGEST_U32S], - /// The first part of the state of the sponge, seen as bytes, after the permutation is applied. - /// This also represents the output digest of the Keccak sponge during the squeezing phase. + /// The first part of the state of the sponge, seen as bytes, after the + /// permutation is applied. This also represents the output digest of + /// the Keccak sponge during the squeezing phase. pub updated_digest_state_bytes: [T; KECCAK_DIGEST_BYTES], - /// The counter column (used for the range check) starts from 0 and increments. + /// The counter column (used for the range check) starts from 0 and + /// increments. pub range_counter: T, /// The frequencies column used in logUp. pub rc_frequencies: T, diff --git a/src/keccak_sponge/keccak_sponge_stark.rs b/evm/src/keccak_sponge/keccak_sponge_stark.rs similarity index 94% rename from src/keccak_sponge/keccak_sponge_stark.rs rename to evm/src/keccak_sponge/keccak_sponge_stark.rs index 04b1bca63..667d37ef8 100644 --- a/src/keccak_sponge/keccak_sponge_stark.rs +++ b/evm/src/keccak_sponge/keccak_sponge_stark.rs @@ -64,9 +64,9 @@ pub(crate) fn ctl_looked_data() -> Vec> { res } -/// Creates the vector of `Columns` corresponding to the inputs of the Keccak sponge. -/// This is used to check that the inputs of the sponge correspond to the inputs -/// given by `KeccakStark`. +/// Creates the vector of `Columns` corresponding to the inputs of the Keccak +/// sponge. This is used to check that the inputs of the sponge correspond to +/// the inputs given by `KeccakStark`. pub(crate) fn ctl_looking_keccak_inputs() -> Vec> { let cols = KECCAK_SPONGE_COL_MAP; let mut res: Vec<_> = Column::singles( @@ -82,9 +82,9 @@ pub(crate) fn ctl_looking_keccak_inputs() -> Vec> { res } -/// Creates the vector of `Columns` corresponding to the outputs of the Keccak sponge. -/// This is used to check that the outputs of the sponge correspond to the outputs -/// given by `KeccakStark`. +/// Creates the vector of `Columns` corresponding to the outputs of the Keccak +/// sponge. This is used to check that the outputs of the sponge correspond to +/// the outputs given by `KeccakStark`. pub(crate) fn ctl_looking_keccak_outputs() -> Vec> { let cols = KECCAK_SPONGE_COL_MAP; @@ -106,7 +106,8 @@ pub(crate) fn ctl_looking_keccak_outputs() -> Vec> { res } -/// Creates the vector of `Columns` corresponding to the address and value of the byte being read from memory. +/// Creates the vector of `Columns` corresponding to the address and value of +/// the byte being read from memory. pub(crate) fn ctl_looking_memory(i: usize) -> Vec> { let cols = KECCAK_SPONGE_COL_MAP; @@ -157,15 +158,17 @@ pub(crate) fn ctl_looking_logic(i: usize) -> Vec> { Column::constant(F::from_canonical_u8(0x18)), // 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. + // 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. + // 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::()) @@ -186,8 +189,8 @@ pub(crate) fn ctl_looking_logic(i: usize) -> Vec> { /// CTL filter for the final block rows of the `KeccakSponge` table. pub(crate) fn ctl_looked_filter() -> Filter { - // The CPU table is only interested in our final-block rows, since those contain the final - // sponge output. + // The CPU table is only interested in our final-block rows, since those contain + // the final sponge output. Filter::new_simple(Column::sum(KECCAK_SPONGE_COL_MAP.is_final_input_len)) } @@ -235,7 +238,8 @@ pub(crate) struct KeccakSpongeOp { pub(crate) input: Vec, } -/// Structure representing the `KeccakSponge` STARK, which carries out the sponge permutation. +/// Structure representing the `KeccakSponge` STARK, which carries out the +/// sponge permutation. #[derive(Copy, Clone, Default)] pub(crate) struct KeccakSpongeStark { f: PhantomData, @@ -289,8 +293,9 @@ impl, const D: usize> KeccakSpongeStark { } /// Generates the rows associated to a given operation: - /// Performs a Keccak sponge permutation and fills the STARK's rows accordingly. - /// The number of rows is the number of input chunks of size `KECCAK_RATE_BYTES`. + /// Performs a Keccak sponge permutation and fills the STARK's rows + /// accordingly. The number of rows is the number of input chunks of + /// size `KECCAK_RATE_BYTES`. fn generate_rows_for_op(&self, op: KeccakSpongeOp) -> Vec<[F; NUM_KECCAK_SPONGE_COLUMNS]> { let mut rows = Vec::with_capacity(op.input.len() / KECCAK_RATE_BYTES + 1); @@ -308,8 +313,9 @@ impl, const D: usize> KeccakSpongeStark { ); // We update the state limbs for the next block absorption. - // The first `KECCAK_DIGEST_U32s` limbs are stored as bytes after the computation, - // so we recompute the corresponding `u32` and update the first state limbs. + // The first `KECCAK_DIGEST_U32s` limbs are stored as bytes after the + // computation, so we recompute the corresponding `u32` and update + // the first state limbs. sponge_state[..KECCAK_DIGEST_U32S] .iter_mut() .zip(row.updated_digest_state_bytes.chunks_exact(4)) @@ -321,8 +327,8 @@ impl, const D: usize> KeccakSpongeStark { .sum(); }); - // The rest of the bytes are already stored in the expected form, so we can directly - // update the state with the stored values. + // The rest of the bytes are already stored in the expected form, so we can + // directly update the state with the stored values. sponge_state[KECCAK_DIGEST_U32S..] .iter_mut() .zip(row.partial_updated_state_u32s) @@ -399,9 +405,9 @@ impl, const D: usize> KeccakSpongeStark { row } - /// Generate fields that are common to both full-input-block rows and final-block rows. - /// Also updates the sponge state with a single absorption. - /// Given a state S = R || C and a block input B, + /// Generate fields that are common to both full-input-block rows and + /// final-block rows. Also updates the sponge state with a single + /// absorption. Given a state S = R || C and a block input B, /// - R is updated with R XOR B, /// - S is replaced by keccakf_u32s(S). fn generate_common_fields( @@ -555,7 +561,8 @@ impl, const D: usize> Stark for KeccakSpongeS let range_max = P::Scalar::from_canonical_u64((BYTE_RANGE_MAX - 1) as u64); yield_constr.constraint_last_row(rc1 - range_max); - // Each flag (full-input block, final block or implied dummy flag) must be boolean. + // Each flag (full-input block, final block or implied dummy flag) must be + // boolean. let is_full_input_block = local_values.is_full_input_block; yield_constr.constraint(is_full_input_block * (is_full_input_block - P::ONES)); @@ -566,10 +573,12 @@ impl, const D: usize> Stark for KeccakSpongeS yield_constr.constraint(is_final_len * (is_final_len - P::ONES)); } - // Ensure that full-input block and final block flags are not set to 1 at the same time. + // Ensure that full-input block and final block flags are not set to 1 at the + // same time. yield_constr.constraint(is_final_block * is_full_input_block); - // If this is the first row, the original sponge state should be 0 and already_absorbed_bytes = 0. + // If this is the first row, the original sponge state should be 0 and + // already_absorbed_bytes = 0. let already_absorbed_bytes = local_values.already_absorbed_bytes; yield_constr.constraint_first_row(already_absorbed_bytes); for &original_rate_elem in local_values.original_rate_u32s.iter() { @@ -579,7 +588,8 @@ impl, const D: usize> Stark for KeccakSpongeS yield_constr.constraint_first_row(original_capacity_elem); } - // If this is a final block, the next row's original sponge state should be 0 and already_absorbed_bytes = 0. + // If this is a final block, the next row's original sponge state should be 0 + // and already_absorbed_bytes = 0. yield_constr.constraint_transition(is_final_block * next_values.already_absorbed_bytes); for &original_rate_elem in next_values.original_rate_u32s.iter() { yield_constr.constraint_transition(is_final_block * original_rate_elem); @@ -588,7 +598,8 @@ impl, const D: usize> Stark for KeccakSpongeS yield_constr.constraint_transition(is_final_block * original_capacity_elem); } - // If this is a full-input block, the next row's address, time and len must match as well as its timestamp. + // If this is a full-input block, the next row's address, time and len must + // match as well as its timestamp. yield_constr.constraint_transition( is_full_input_block * (local_values.context - next_values.context), ); @@ -601,7 +612,8 @@ impl, const D: usize> Stark for KeccakSpongeS is_full_input_block * (local_values.timestamp - next_values.timestamp), ); - // If this is a full-input block, the next row's "before" should match our "after" state. + // If this is a full-input block, the next row's "before" should match our + // "after" state. for (current_bytes_after, next_before) in local_values .updated_digest_state_bytes .chunks_exact(4) @@ -631,14 +643,16 @@ impl, const D: usize> Stark for KeccakSpongeS yield_constr.constraint_transition(is_full_input_block * (next_before - current_after)); } - // If this is a full-input block, the next row's already_absorbed_bytes should be ours plus `KECCAK_RATE_BYTES`. + // If this is a full-input block, the next row's already_absorbed_bytes should + // be ours plus `KECCAK_RATE_BYTES`. yield_constr.constraint_transition( is_full_input_block * (already_absorbed_bytes + P::from(FE::from_canonical_usize(KECCAK_RATE_BYTES)) - next_values.already_absorbed_bytes), ); - // 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. + // 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. let is_dummy = P::ONES - is_full_input_block - is_final_block; let next_is_final_block: P = next_values.is_final_input_len.iter().copied().sum(); yield_constr.constraint_transition( @@ -675,7 +689,8 @@ impl, const D: usize> Stark for KeccakSpongeS let t = builder.sub_extension(rc1, range_max); yield_constr.constraint_last_row(builder, t); - // Each flag (full-input block, final block or implied dummy flag) must be boolean. + // Each flag (full-input block, final block or implied dummy flag) must be + // boolean. let is_full_input_block = local_values.is_full_input_block; let constraint = builder.mul_sub_extension( is_full_input_block, @@ -693,11 +708,13 @@ impl, const D: usize> Stark for KeccakSpongeS yield_constr.constraint(builder, constraint); } - // Ensure that full-input block and final block flags are not set to 1 at the same time. + // Ensure that full-input block and final block flags are not set to 1 at the + // same time. let constraint = builder.mul_extension(is_final_block, is_full_input_block); yield_constr.constraint(builder, constraint); - // If this is the first row, the original sponge state should be 0 and already_absorbed_bytes = 0. + // If this is the first row, the original sponge state should be 0 and + // already_absorbed_bytes = 0. let already_absorbed_bytes = local_values.already_absorbed_bytes; yield_constr.constraint_first_row(builder, already_absorbed_bytes); for &original_rate_elem in local_values.original_rate_u32s.iter() { @@ -707,7 +724,8 @@ impl, const D: usize> Stark for KeccakSpongeS yield_constr.constraint_first_row(builder, original_capacity_elem); } - // If this is a final block, the next row's original sponge state should be 0 and already_absorbed_bytes = 0. + // If this is a final block, the next row's original sponge state should be 0 + // and already_absorbed_bytes = 0. let constraint = builder.mul_extension(is_final_block, next_values.already_absorbed_bytes); yield_constr.constraint_transition(builder, constraint); for &original_rate_elem in next_values.original_rate_u32s.iter() { @@ -719,7 +737,8 @@ impl, const D: usize> Stark for KeccakSpongeS yield_constr.constraint_transition(builder, constraint); } - // If this is a full-input block, the next row's address, time and len must match as well as its timestamp. + // If this is a full-input block, the next row's address, time and len must + // match as well as its timestamp. let context_diff = builder.sub_extension(local_values.context, next_values.context); let constraint = builder.mul_extension(is_full_input_block, context_diff); yield_constr.constraint_transition(builder, constraint); @@ -736,7 +755,8 @@ impl, const D: usize> Stark for KeccakSpongeS let constraint = builder.mul_extension(is_full_input_block, timestamp_diff); yield_constr.constraint_transition(builder, constraint); - // If this is a full-input block, the next row's "before" should match our "after" state. + // If this is a full-input block, the next row's "before" should match our + // "after" state. for (current_bytes_after, next_before) in local_values .updated_digest_state_bytes .chunks_exact(4) @@ -774,7 +794,8 @@ impl, const D: usize> Stark for KeccakSpongeS yield_constr.constraint_transition(builder, constraint); } - // If this is a full-input block, the next row's already_absorbed_bytes should be ours plus `KECCAK_RATE_BYTES`. + // If this is a full-input block, the next row's already_absorbed_bytes should + // be ours plus `KECCAK_RATE_BYTES`. let absorbed_bytes = builder.add_const_extension( already_absorbed_bytes, F::from_canonical_usize(KECCAK_RATE_BYTES), @@ -784,7 +805,8 @@ impl, const D: usize> Stark for KeccakSpongeS let constraint = builder.mul_extension(is_full_input_block, absorbed_diff); yield_constr.constraint_transition(builder, constraint); - // 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. + // 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. let is_dummy = { let tmp = builder.sub_extension(one, is_final_block); builder.sub_extension(tmp, is_full_input_block) diff --git a/evm/src/keccak_sponge/mod.rs b/evm/src/keccak_sponge/mod.rs new file mode 100644 index 000000000..ff431de7e --- /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/src/lib.rs b/evm/src/lib.rs similarity index 62% rename from src/lib.rs rename to evm/src/lib.rs index 5741c4419..11f5c7402 100644 --- a/src/lib.rs +++ b/evm/src/lib.rs @@ -1,15 +1,17 @@ //! An implementation of a Type 1 zk-EVM by Polygon Zero. //! //! Following the [zk-EVM classification of V. Buterin](https://vitalik.eth.limo/general/2022/08/04/zkevm.html), -//! the plonky2_evm crate aims at providing an efficient solution for the problem of generating cryptographic -//! proofs of Ethereum-like transactions with *full Ethereum capability*. +//! the plonky2_evm crate aims at providing an efficient solution for the +//! problem of generating cryptographic proofs of Ethereum-like transactions +//! with *full Ethereum capability*. //! -//! To this end, the plonky2 zk-EVM is tailored for an AIR-based STARK system satisfying degree 3 constraints, -//! with support for recursive aggregation leveraging plonky2 circuits with FRI-based plonkish arithmetization. +//! To this end, the plonky2 zk-EVM is tailored for an AIR-based STARK system +//! satisfying degree 3 constraints, with support for recursive aggregation +//! leveraging plonky2 circuits with FRI-based plonkish arithmetization. //! These circuits require a one-time, offline preprocessing phase. -//! See the [`fixed_recursive_verifier`] module for more details on how this works. -//! These preprocessed circuits are gathered within the [`AllRecursiveCircuits`] prover state, -//! and can be generated as such: +//! See the [`fixed_recursive_verifier`] module for more details on how this +//! works. These preprocessed circuits are gathered within the +//! [`AllRecursiveCircuits`] prover state, and can be generated as such: //! //! ```ignore //! // Specify the base field to use. @@ -34,10 +36,11 @@ //! //! # Inputs type //! -//! Transactions need to be processed into an Intermediary Representation (IR) format for the prover -//! to be able to generate proofs of valid state transition. This involves passing the encoded transaction, -//! the header of the block in which it was included, some information on the state prior execution -//! of this transaction, etc. +//! Transactions need to be processed into an Intermediary Representation (IR) +//! format for the prover to be able to generate proofs of valid state +//! transition. This involves passing the encoded transaction, the header of the +//! block in which it was included, some information on the state prior +//! execution of this transaction, etc. //! This intermediary representation is called [`GenerationInputs`]. //! //! @@ -45,8 +48,9 @@ //! //! ## Transaction proofs //! -//! To generate a proof for a transaction, given its [`GenerationInputs`] and an [`AllRecursiveCircuits`] -//! prover state, one can simply call the [prove_root](AllRecursiveCircuits::prove_root) method. +//! To generate a proof for a transaction, given its [`GenerationInputs`] and an +//! [`AllRecursiveCircuits`] prover state, one can simply call the +//! [prove_root](AllRecursiveCircuits::prove_root) method. //! //! ```ignore //! let mut timing = TimingTree::new("prove", log::Level::Debug); @@ -55,9 +59,10 @@ //! prover_state.prove_root(all_stark, config, inputs, &mut timing, kill_signal); //! ``` //! -//! This outputs a transaction proof and its associated public values. These are necessary during the -//! aggregation levels (see below). If one were to miss the public values, they are also retrievable directly -//! from the proof's encoded public inputs, as such: +//! This outputs a transaction proof and its associated public values. These are +//! necessary during the aggregation levels (see below). If one were to miss the +//! public values, they are also retrievable directly from the proof's encoded +//! public inputs, as such: //! //! ```ignore //! let public_values = PublicValues::from_public_inputs(&proof.public_inputs); @@ -65,12 +70,14 @@ //! //! ## Aggregation proofs //! -//! Because the plonky2 zkEVM generates proofs on a transaction basis, we then need to aggregate them for succinct -//! verification. This is done in a binary tree fashion, where each inner node proof verifies two children proofs, -//! through the [prove_aggregation](AllRecursiveCircuits::prove_aggregation) method. -//! Note that the tree does *not* need to be complete, as this aggregation process can take as inputs both regular -//! transaction proofs and aggregation proofs. We only need to specify for each child if it is an aggregation proof -//! or a regular one. +//! Because the plonky2 zkEVM generates proofs on a transaction basis, we then +//! need to aggregate them for succinct verification. This is done in a binary +//! tree fashion, where each inner node proof verifies two children proofs, +//! through the [prove_aggregation](AllRecursiveCircuits::prove_aggregation) +//! method. Note that the tree does *not* need to be complete, as this +//! aggregation process can take as inputs both regular transaction proofs and +//! aggregation proofs. We only need to specify for each child if it is an +//! aggregation proof or a regular one. //! //! ```ignore //! let (proof_1, pv_1) = @@ -89,16 +96,21 @@ //! prover_state.prove_aggregation(true, agg_proof_1_2, pv_1_2, false, proof_3, pv_3); //! ``` //! -//! **Note**: The proofs provided to the [prove_aggregation](AllRecursiveCircuits::prove_aggregation) method *MUST* have contiguous states. -//! Trying to combine `proof_1` and `proof_3` from the example above would fail. +//! **Note**: The proofs provided to the +//! [prove_aggregation](AllRecursiveCircuits::prove_aggregation) method *MUST* +//! have contiguous states. Trying to combine `proof_1` and `proof_3` from the +//! example above would fail. //! //! ## Block proofs //! -//! Once all transactions of a block have been proven and we are left with a single aggregation proof and its public values, -//! we can then wrap it into a final block proof, attesting validity of the entire block. -//! This [prove_block](AllRecursiveCircuits::prove_block) method accepts an optional previous block proof as argument, -//! which will then try combining the previously proven block with the current one, generating a validity proof for both. -//! Applying this process from genesis would yield a single proof attesting correctness of the entire chain. +//! Once all transactions of a block have been proven and we are left with a +//! single aggregation proof and its public values, we can then wrap it into a +//! final block proof, attesting validity of the entire block. +//! This [prove_block](AllRecursiveCircuits::prove_block) method accepts an +//! optional previous block proof as argument, which will then try combining the +//! previously proven block with the current one, generating a validity proof +//! for both. Applying this process from genesis would yield a single proof +//! attesting correctness of the entire chain. //! //! ```ignore //! let previous_block_proof = { ... }; @@ -108,15 +120,18 @@ //! //! ### Checkpoint heights //! -//! The process of always providing a previous block proof when generating a proof for the current block may yield some -//! undesirable issues. For this reason, the plonky2 zk-EVM supports checkpoint heights. At given block heights, -//! the prover does not have to pass a previous block proof. This would in practice correspond to block heights at which -//! a proof has been generated and sent to L1 for settlement. +//! The process of always providing a previous block proof when generating a +//! proof for the current block may yield some undesirable issues. For this +//! reason, the plonky2 zk-EVM supports checkpoint heights. At given block +//! heights, the prover does not have to pass a previous block proof. This would +//! in practice correspond to block heights at which a proof has been generated +//! and sent to L1 for settlement. //! -//! The only requirement when generating a block proof without passing a previous one as argument is to have the -//! `checkpoint_state_trie_root` metadata in the `PublicValues` of the final aggregation proof be matching the state -//! trie before applying all the included transactions. If this condition is not met, the prover will fail to generate -//! a valid proof. +//! The only requirement when generating a block proof without passing a +//! previous one as argument is to have the `checkpoint_state_trie_root` +//! metadata in the `PublicValues` of the final aggregation proof be matching +//! the state trie before applying all the included transactions. If this +//! condition is not met, the prover will fail to generate a valid proof. //! //! //! ```ignore @@ -126,11 +141,14 @@ //! //! # Prover state serialization //! -//! Because the recursive circuits only need to be generated once, they can be saved to disk once the preprocessing phase -//! completed successfully, and deserialized on-demand. -//! The plonky2 zk-EVM provides serialization methods to convert the entire prover state to a vector of bytes, and vice-versa. -//! This requires the use of custom serializers for gates and generators for proper recursive circuit encoding. This crate provides -//! default serializers supporting all custom gates and associated generators defined within the [`plonky2`] crate. +//! Because the recursive circuits only need to be generated once, they can be +//! saved to disk once the preprocessing phase completed successfully, and +//! deserialized on-demand. The plonky2 zk-EVM provides serialization methods to +//! convert the entire prover state to a vector of bytes, and vice-versa. +//! This requires the use of custom serializers for gates and generators for +//! proper recursive circuit encoding. This crate provides default serializers +//! supporting all custom gates and associated generators defined within the +//! [`plonky2`] crate. //! //! ```ignore //! let prover_state = AllRecursiveCircuits::::new(...); @@ -155,8 +173,9 @@ //! assert_eq!(prover_state, recovered_prover_state); //! ``` //! -//! Note that an entire prover state built with wide ranges may be particularly large (up to ~25 GB), hence serialization methods, -//! while faster than doing another preprocessing, may take some non-negligible time. +//! Note that an entire prover state built with wide ranges may be particularly +//! large (up to ~25 GB), hence serialization methods, while faster than doing +//! another preprocessing, may take some non-negligible time. #![cfg_attr(docsrs, feature(doc_cfg))] #![allow(clippy::needless_range_loop)] diff --git a/src/logic.rs b/evm/src/logic.rs similarity index 96% rename from src/logic.rs rename to evm/src/logic.rs index d07a6e3d1..c5f952465 100644 --- a/src/logic.rs +++ b/evm/src/logic.rs @@ -23,9 +23,11 @@ use crate::util::{limb_from_bits_le, limb_from_bits_le_recursive}; /// 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. +/// 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. +/// Number of field elements needed to store each input/output at the specified +/// packing. const PACKED_LEN: usize = ceil_div_usize(VAL_BITS, PACKED_LIMB_BITS); /// `LogicStark` columns. @@ -63,7 +65,8 @@ pub(crate) mod columns { pub(crate) const NUM_COLUMNS: usize = RESULT.end; } -/// Creates the vector of `Columns` corresponding to the opcode, the two inputs and the output of the logic operation. +/// Creates the vector of `Columns` corresponding to the opcode, the two inputs +/// and the output of the logic operation. pub(crate) fn ctl_data() -> Vec> { // We scale each filter flag with the associated opcode value. // If a logic operation is happening on the CPU side, the CTL @@ -125,8 +128,8 @@ pub(crate) struct Operation { } impl Operation { - /// Computes the expected result of an operator with the two provided inputs, - /// and returns the associated logic `Operation`. + /// Computes the expected result of an operator with the two provided + /// inputs, and returns the associated logic `Operation`. pub(crate) fn new(operator: Op, input0: U256, input1: U256) -> Self { let result = operator.result(input0, input1); Operation { @@ -137,7 +140,8 @@ impl Operation { } } - /// Given an `Operation`, fills a row with the corresponding flag, inputs and output. + /// Given an `Operation`, fills a row with the corresponding flag, inputs + /// and output. fn into_row(self) -> [F; NUM_COLUMNS] { let Operation { operator, @@ -172,7 +176,8 @@ impl LogicStark { min_rows: usize, timing: &mut TimingTree, ) -> Vec> { - // First, turn all provided operations into rows in `LogicStark`, and pad if necessary. + // First, turn all provided operations into rows in `LogicStark`, and pad if + // necessary. let trace_rows = timed!( timing, "generate trace rows", @@ -187,8 +192,9 @@ impl LogicStark { trace_polys } - /// Generate the `LogicStark` traces based on the provided vector of operations. - /// The trace is padded to a power of two with all-zero rows. + /// Generate the `LogicStark` traces based on the provided vector of + /// operations. The trace is padded to a power of two with all-zero + /// rows. fn generate_trace_rows( &self, operations: Vec, @@ -242,8 +248,8 @@ impl, const D: usize> Stark for LogicStark sum_coeff = 0, and_coeff = 1` + // The result will be `in0 OP in1 = sum_coeff * (in0 + in1) + and_coeff * (in0 + // AND in1)`. `AND => sum_coeff = 0, and_coeff = 1` // `OR => sum_coeff = 1, and_coeff = -1` // `XOR => sum_coeff = 1, and_coeff = -2` let sum_coeff = is_or + is_xor; @@ -301,8 +307,8 @@ impl, const D: usize> Stark for LogicStark sum_coeff = 0, and_coeff = 1` + // The result will be `in0 OP in1 = sum_coeff * (in0 + in1) + and_coeff * (in0 + // AND in1)`. `AND => sum_coeff = 0, and_coeff = 1` // `OR => sum_coeff = 1, and_coeff = -1` // `XOR => sum_coeff = 1, and_coeff = -2` let sum_coeff = builder.add_extension(is_or, is_xor); diff --git a/src/memory/columns.rs b/evm/src/memory/columns.rs similarity index 88% rename from src/memory/columns.rs rename to evm/src/memory/columns.rs index 2010bf33e..28fc7943f 100644 --- a/src/memory/columns.rs +++ b/evm/src/memory/columns.rs @@ -6,9 +6,10 @@ use crate::memory::VALUE_LIMBS; /// 1 if this is an actual memory operation, or 0 if it's a padding row. pub(crate) const FILTER: usize = 0; /// Each memory operation is associated to a unique timestamp. -/// For a given memory operation `op_i`, its timestamp is computed as `C * N + i` -/// where `C` is the CPU clock at that time, `N` is the number of general memory channels, -/// and `i` is the index of the memory channel at which the memory operation is performed. +/// For a given memory operation `op_i`, its timestamp is computed as `C * N + +/// i` where `C` is the CPU clock at that time, `N` is the number of general +/// memory channels, and `i` is the index of the memory channel at which the +/// memory operation is performed. pub(crate) const TIMESTAMP: usize = FILTER + 1; /// 1 if this is a read operation, 0 if it is a write one. pub(crate) const IS_READ: usize = TIMESTAMP + 1; @@ -29,8 +30,8 @@ pub(crate) const fn value_limb(i: usize) -> usize { // Flags to indicate whether this part of the address differs from the next row, // and the previous parts do not differ. -// That is, e.g., `SEGMENT_FIRST_CHANGE` is `F::ONE` iff `ADDR_CONTEXT` is the same in this -// row and the next, but `ADDR_SEGMENT` is not. +// That is, e.g., `SEGMENT_FIRST_CHANGE` is `F::ONE` iff `ADDR_CONTEXT` is the +// same in this row and the next, but `ADDR_SEGMENT` is not. 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; diff --git a/src/memory/memory_stark.rs b/evm/src/memory/memory_stark.rs similarity index 89% rename from src/memory/memory_stark.rs rename to evm/src/memory/memory_stark.rs index d8a818ff8..66f1ca42c 100644 --- a/src/memory/memory_stark.rs +++ b/evm/src/memory/memory_stark.rs @@ -52,9 +52,10 @@ pub(crate) struct MemoryStark { } impl MemoryOp { - /// Generate a row for a given memory operation. Note that this does not generate columns which - /// depend on the next operation, such as `CONTEXT_FIRST_CHANGE`; those are generated later. - /// It also does not generate columns such as `COUNTER`, which are generated later, after the + /// Generate a row for a given memory operation. Note that this does not + /// generate columns which depend on the next operation, such as + /// `CONTEXT_FIRST_CHANGE`; those are generated later. It also does not + /// generate columns such as `COUNTER`, which are generated later, after the /// trace has been transposed into column-major form. fn into_row(self) -> [F; NUM_COLUMNS] { let mut row = [F::ZERO; NUM_COLUMNS]; @@ -76,7 +77,8 @@ impl MemoryOp { } } -/// Generates the `_FIRST_CHANGE` columns and the `RANGE_CHECK` column in the trace. +/// Generates the `_FIRST_CHANGE` columns and the `RANGE_CHECK` column in the +/// trace. pub(crate) fn generate_first_change_flags_and_rc( trace_rows: &mut [[F; NUM_COLUMNS]], ) { @@ -132,8 +134,8 @@ pub(crate) fn generate_first_change_flags_and_rc( } impl, const D: usize> MemoryStark { - /// Generate most of the trace rows. Excludes a few columns like `COUNTER`, which are generated - /// later, after transposing to column-major form. + /// Generate most of the trace rows. Excludes a few columns like `COUNTER`, + /// which are generated later, after transposing to column-major form. fn generate_trace_row_major(&self, mut memory_ops: Vec) -> Vec<[F; NUM_COLUMNS]> { // fill_gaps expects an ordered list of operations. memory_ops.sort_by_key(MemoryOp::sorting_key); @@ -141,7 +143,8 @@ impl, const D: usize> MemoryStark { Self::pad_memory_ops(&mut memory_ops); - // fill_gaps may have added operations at the end which break the order, so sort again. + // fill_gaps may have added operations at the end which break the order, so sort + // again. memory_ops.sort_by_key(MemoryOp::sorting_key); let mut trace_rows = memory_ops @@ -152,8 +155,8 @@ impl, const D: usize> MemoryStark { trace_rows } - /// Generates the `COUNTER`, `RANGE_CHECK` and `FREQUENCIES` columns, given a - /// trace in column-major form. + /// Generates the `COUNTER`, `RANGE_CHECK` and `FREQUENCIES` columns, given + /// a trace in column-major form. fn generate_trace_col_major(trace_col_vecs: &mut [Vec]) { let height = trace_col_vecs[0].len(); trace_col_vecs[COUNTER] = (0..height).map(|i| F::from_canonical_usize(i)).collect(); @@ -164,38 +167,42 @@ impl, const D: usize> MemoryStark { if (trace_col_vecs[CONTEXT_FIRST_CHANGE][i] == F::ONE) || (trace_col_vecs[SEGMENT_FIRST_CHANGE][i] == F::ONE) { - // CONTEXT_FIRST_CHANGE and SEGMENT_FIRST_CHANGE should be 0 at the last row, so the index - // should never be out of bounds. + // CONTEXT_FIRST_CHANGE and SEGMENT_FIRST_CHANGE should be 0 at the last row, so + // the index should never be out of bounds. let x_fo = trace_col_vecs[ADDR_VIRTUAL][i + 1].to_canonical_u64() as usize; trace_col_vecs[FREQUENCIES][x_fo] += F::ONE; } } } - /// This memory STARK orders rows by `(context, segment, virt, timestamp)`. To enforce the - /// ordering, it range checks the delta of the first field that changed. + /// This memory STARK orders rows by `(context, segment, virt, timestamp)`. + /// To enforce the ordering, it range checks the delta of the first + /// field that changed. /// - /// This method adds some dummy operations to ensure that none of these range checks will be too - /// large, i.e. that they will all be smaller than the number of rows, allowing them to be - /// checked easily with a single lookup. + /// This method adds some dummy operations to ensure that none of these + /// range checks will be too large, i.e. that they will all be smaller + /// than the number of rows, allowing them to be checked easily with a + /// single lookup. /// - /// For example, say there are 32 memory operations, and a particular address is accessed at - /// timestamps 20 and 100. 80 would fail the range check, so this method would add two dummy - /// reads to the same address, say at timestamps 50 and 80. + /// For example, say there are 32 memory operations, and a particular + /// address is accessed at timestamps 20 and 100. 80 would fail the + /// range check, so this method would add two dummy reads to the same + /// address, say at timestamps 50 and 80. fn fill_gaps(memory_ops: &mut Vec) { let max_rc = memory_ops.len().next_power_of_two() - 1; for (mut curr, mut next) in memory_ops.clone().into_iter().tuple_windows() { if curr.address.context != next.address.context || curr.address.segment != next.address.segment { - // We won't bother to check if there's a large context gap, because there can't be - // more than 500 contexts or so, as explained here: + // We won't bother to check if there's a large context gap, because there can't + // be more than 500 contexts or so, as explained here: // https://notes.ethereum.org/@vbuterin/proposals_to_adjust_memory_gas_costs - // Similarly, the number of possible segments is a small constant, so any gap must - // be small. max_rc will always be much larger, as just bootloading the kernel will - // trigger thousands of memory operations. - // However, we do check that the first address accessed is range-checkable. If not, - // we could start at a negative address and cheat. + // Similarly, the number of possible segments is a small constant, so any gap + // must be small. max_rc will always be much larger, as just + // bootloading the kernel will trigger thousands of memory + // operations. However, we do check that the first address + // accessed is range-checkable. If not, we could start at a + // negative address and cheat. while next.address.virt > max_rc { let mut dummy_address = next.address; dummy_address.virt -= max_rc; @@ -225,8 +232,8 @@ impl, const D: usize> MemoryStark { fn pad_memory_ops(memory_ops: &mut Vec) { let last_op = *memory_ops.last().expect("No memory ops?"); - // We essentially repeat the last operation until our operation list has the desired size, - // with a few changes: + // We essentially repeat the last operation until our operation list has the + // desired size, with a few changes: // - We change its filter to 0 to indicate that this is a dummy operation. // - We make sure it's a read, since dummy operations must be reads. let padding_op = MemoryOp { @@ -309,8 +316,9 @@ impl, const D: usize> Stark for MemoryStark, const D: usize> Stark for MemoryStark, const D: usize> Stark for MemoryStark, const D: usize> Stark for MemoryStark, const D: usize> Stark for MemoryStark, const D: usize> Stark for MemoryStark, const D: usize> Stark for MemoryStark, const D: usize> Stark for MemoryStark, const D: usize> Stark for MemoryStark usize { @@ -115,6 +124,7 @@ impl Segment { Self::TouchedAddresses, Self::ContextCheckpoints, Self::BlockHashes, + Self::CreatedContracts, ] } @@ -155,6 +165,7 @@ impl Segment { Segment::TouchedAddresses => "SEGMENT_TOUCHED_ADDRESSES", Segment::ContextCheckpoints => "SEGMENT_CONTEXT_CHECKPOINTS", Segment::BlockHashes => "SEGMENT_BLOCK_HASHES", + Segment::CreatedContracts => "SEGMENT_CREATED_CONTRACTS", } } @@ -194,6 +205,7 @@ impl Segment { Segment::TouchedAddresses => 256, Segment::ContextCheckpoints => 256, Segment::BlockHashes => 256, + Segment::CreatedContracts => 256, } } diff --git a/src/proof.rs b/evm/src/proof.rs similarity index 90% rename from src/proof.rs rename to evm/src/proof.rs index bc70dbb85..e5100b26e 100644 --- a/src/proof.rs +++ b/evm/src/proof.rs @@ -13,11 +13,12 @@ use starky::proof::{MultiProof, StarkProofChallenges}; use crate::all_stark::NUM_TABLES; use crate::util::{get_h160, get_h256, h2u}; -/// A STARK proof for each table, plus some metadata used to create recursive wrapper proofs. +/// A STARK proof for each table, plus some metadata used to create recursive +/// wrapper proofs. #[derive(Debug, Clone)] pub struct AllProof, C: GenericConfig, const D: usize> { - /// A multi-proof containing all proofs for the different STARK modules and their - /// cross-table lookup challenges. + /// A multi-proof containing all proofs for the different STARK modules and + /// their cross-table lookup challenges. pub multi_proof: MultiProof, /// Public memory values used for the recursive proofs. pub public_values: PublicValues, @@ -122,7 +123,8 @@ impl TrieRoots { } } -// There should be 256 previous hashes stored, so the default should also contain 256 values. +// There should be 256 previous hashes stored, so the default should also +// contain 256 values. impl Default for BlockHashes { fn default() -> Self { Self { @@ -136,12 +138,13 @@ impl Default for BlockHashes { /// The proofs across consecutive blocks ensure that these values /// are consistent (i.e. shifted by one to the left). /// -/// When the block number is less than 256, dummy values, i.e. `H256::default()`, -/// should be used for the additional block hashes. +/// When the block number is less than 256, dummy values, i.e. +/// `H256::default()`, should be used for the additional block hashes. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct BlockHashes { - /// The previous 256 hashes to the current block. The leftmost hash, i.e. `prev_hashes[0]`, - /// is the oldest, and the rightmost, i.e. `prev_hashes[255]` is the hash of the parent block. + /// The previous 256 hashes to the current block. The leftmost hash, i.e. + /// `prev_hashes[0]`, is the oldest, and the rightmost, i.e. + /// `prev_hashes[255]` is the hash of the parent block. pub prev_hashes: Vec, // The hash of the current block. pub cur_hash: H256, @@ -182,6 +185,8 @@ pub struct BlockMetadata { pub block_base_fee: U256, /// The total gas used in this block. It must fit in a `u32`. pub block_gas_used: U256, + /// The blob base fee. It must fit in a `u64`. + pub block_blob_base_fee: U256, /// The block bloom of this block, represented as the consecutive /// 32-byte chunks of a block's final bloom filter string. pub block_bloom: [U256; 8], @@ -201,7 +206,10 @@ impl BlockMetadata { let block_base_fee = (pis[18].to_canonical_u64() + (pis[19].to_canonical_u64() << 32)).into(); let block_gas_used = pis[20].to_canonical_u64().into(); - let block_bloom = core::array::from_fn(|i| h2u(get_h256(&pis[21 + 8 * i..29 + 8 * i]))); + let block_blob_base_fee = + (pis[21].to_canonical_u64() + (pis[22].to_canonical_u64() << 32)).into(); + let block_bloom = + core::array::from_fn(|i| h2u(get_h256(&pis[23 + 8 * i..23 + 8 * (i + 1)]))); Self { block_beneficiary, @@ -213,27 +221,29 @@ impl BlockMetadata { block_chain_id, block_base_fee, block_gas_used, + block_blob_base_fee, block_bloom, } } } -/// Additional block data that are specific to the local transaction being proven, -/// unlike `BlockMetadata`. +/// Additional block data that are specific to the local transaction being +/// proven, unlike `BlockMetadata`. #[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)] pub struct ExtraBlockData { /// The state trie digest of the checkpoint block. pub checkpoint_state_trie_root: H256, - /// The transaction count prior execution of the local state transition, starting - /// at 0 for the initial transaction of a block. + /// The transaction count prior execution of the local state transition, + /// starting at 0 for the initial transaction of a block. pub txn_number_before: U256, /// The transaction count after execution of the local state transition. pub txn_number_after: U256, - /// The accumulated gas used prior execution of the local state transition, starting - /// at 0 for the initial transaction of a block. + /// The accumulated gas used prior execution of the local state transition, + /// starting at 0 for the initial transaction of a block. pub gas_used_before: U256, - /// The accumulated gas used after execution of the local state transition. It should - /// match the `block_gas_used` value after execution of the last transaction in a block. + /// The accumulated gas used after execution of the local state transition. + /// It should match the `block_gas_used` value after execution of the + /// last transaction in a block. pub gas_used_after: U256, } @@ -258,7 +268,8 @@ impl ExtraBlockData { } /// Memory values which are public. -/// Note: All the larger integers are encoded with 32-bit limbs in little-endian order. +/// Note: All the larger integers are encoded with 32-bit limbs in little-endian +/// order. #[derive(Eq, PartialEq, Debug)] pub struct PublicValuesTarget { /// Trie hashes before the execution of the local state transition. @@ -306,6 +317,7 @@ impl PublicValuesTarget { block_chain_id, block_base_fee, block_gas_used, + block_blob_base_fee, block_bloom, } = self.block_metadata; @@ -318,6 +330,7 @@ impl PublicValuesTarget { buffer.write_target(block_chain_id)?; buffer.write_target_array(&block_base_fee)?; buffer.write_target(block_gas_used)?; + buffer.write_target_array(&block_blob_base_fee)?; buffer.write_target_array(&block_bloom)?; let BlockHashesTarget { @@ -367,6 +380,7 @@ impl PublicValuesTarget { block_chain_id: buffer.read_target()?, block_base_fee: buffer.read_target_array()?, block_gas_used: buffer.read_target()?, + block_blob_base_fee: buffer.read_target_array()?, block_bloom: buffer.read_target_array()?, }; @@ -473,7 +487,8 @@ impl PublicValuesTarget { } /// Circuit version of `TrieRoots`. -/// `Target`s for trie hashes. Since a `Target` holds a 32-bit limb, each hash requires 8 `Target`s. +/// `Target`s for trie hashes. Since a `Target` holds a 32-bit limb, each hash +/// requires 8 `Target`s. #[derive(Eq, PartialEq, Debug, Copy, Clone)] pub struct TrieRootsTarget { /// Targets for the state trie hash. @@ -489,8 +504,9 @@ impl TrieRootsTarget { pub(crate) const HASH_SIZE: usize = 8; pub(crate) const SIZE: usize = Self::HASH_SIZE * 3; - /// Extracts trie hash `Target`s for all tries from the provided public input `Target`s. - /// The provided `pis` should start with the trie hashes. + /// Extracts trie hash `Target`s for all tries from the provided public + /// input `Target`s. The provided `pis` should start with the trie + /// hashes. pub(crate) fn from_public_inputs(pis: &[Target]) -> Self { let state_root = pis[0..8].try_into().unwrap(); let transactions_root = pis[8..16].try_into().unwrap(); @@ -557,24 +573,26 @@ pub struct BlockMetadataTarget { pub(crate) block_difficulty: Target, /// `Target`s for the `mix_hash` value of this block. pub(crate) block_random: [Target; 8], - /// `Target`s for the gas limit of this block. + /// `Target` for the gas limit of this block. pub(crate) block_gaslimit: Target, /// `Target` for the chain id of this block. pub(crate) block_chain_id: Target, /// `Target`s for the base fee of this block. pub(crate) block_base_fee: [Target; 2], - /// `Target`s for the gas used of this block. + /// `Target` for the gas used of this block. pub(crate) block_gas_used: Target, + /// `Target`s for the blob base fee of this block. + pub(crate) block_blob_base_fee: [Target; 2], /// `Target`s for the block bloom of this block. pub(crate) block_bloom: [Target; 64], } impl BlockMetadataTarget { /// Number of `Target`s required for the block metadata. - pub(crate) const SIZE: usize = 85; + pub(crate) const SIZE: usize = 87; - /// Extracts block metadata `Target`s from the provided public input `Target`s. - /// The provided `pis` should start with the block metadata. + /// Extracts block metadata `Target`s from the provided public input + /// `Target`s. The provided `pis` should start with the block metadata. pub(crate) fn from_public_inputs(pis: &[Target]) -> Self { let block_beneficiary = pis[0..5].try_into().unwrap(); let block_timestamp = pis[5]; @@ -585,7 +603,8 @@ impl BlockMetadataTarget { let block_chain_id = pis[17]; let block_base_fee = pis[18..20].try_into().unwrap(); let block_gas_used = pis[20]; - let block_bloom = pis[21..85].try_into().unwrap(); + let block_blob_base_fee = pis[21..23].try_into().unwrap(); + let block_bloom = pis[23..87].try_into().unwrap(); Self { block_beneficiary, @@ -597,6 +616,7 @@ impl BlockMetadataTarget { block_chain_id, block_base_fee, block_gas_used, + block_blob_base_fee, block_bloom, } } @@ -629,6 +649,13 @@ impl BlockMetadataTarget { builder.select(condition, bm0.block_base_fee[i], bm1.block_base_fee[i]) }), block_gas_used: builder.select(condition, bm0.block_gas_used, bm1.block_gas_used), + block_blob_base_fee: core::array::from_fn(|i| { + builder.select( + condition, + bm0.block_blob_base_fee[i], + bm1.block_blob_base_fee[i], + ) + }), block_bloom: core::array::from_fn(|i| { builder.select(condition, bm0.block_bloom[i], bm1.block_bloom[i]) }), @@ -656,6 +683,9 @@ impl BlockMetadataTarget { builder.connect(bm0.block_base_fee[i], bm1.block_base_fee[i]) } builder.connect(bm0.block_gas_used, bm1.block_gas_used); + for i in 0..2 { + builder.connect(bm0.block_blob_base_fee[i], bm1.block_blob_base_fee[i]) + } for i in 0..64 { builder.connect(bm0.block_bloom[i], bm1.block_bloom[i]) } @@ -663,17 +693,18 @@ impl BlockMetadataTarget { } /// Circuit version of `BlockHashes`. -/// `Target`s for the user-provided previous 256 block hashes and current block hash. -/// Each block hash requires 8 `Target`s. +/// `Target`s for the user-provided previous 256 block hashes and current block +/// hash. Each block hash requires 8 `Target`s. /// The proofs across consecutive blocks ensure that these values /// are consistent (i.e. shifted by eight `Target`s to the left). /// -/// When the block number is less than 256, dummy values, i.e. `H256::default()`, -/// should be used for the additional block hashes. +/// When the block number is less than 256, dummy values, i.e. +/// `H256::default()`, should be used for the additional block hashes. #[derive(Eq, PartialEq, Debug, Copy, Clone)] pub struct BlockHashesTarget { - /// `Target`s for the previous 256 hashes to the current block. The leftmost hash, i.e. `prev_hashes[0..8]`, - /// is the oldest, and the rightmost, i.e. `prev_hashes[255 * 7..255 * 8]` is the hash of the parent block. + /// `Target`s for the previous 256 hashes to the current block. The leftmost + /// hash, i.e. `prev_hashes[0..8]`, is the oldest, and the rightmost, + /// i.e. `prev_hashes[255 * 7..255 * 8]` is the hash of the parent block. pub(crate) prev_hashes: [Target; 2048], // `Target` for the hash of the current block. pub(crate) cur_hash: [Target; 8], @@ -683,8 +714,9 @@ impl BlockHashesTarget { /// Number of `Target`s required for previous and current block hashes. pub(crate) const SIZE: usize = 2056; - /// Extracts the previous and current block hash `Target`s from the public input `Target`s. - /// The provided `pis` should start with the block hashes. + /// Extracts the previous and current block hash `Target`s from the public + /// input `Target`s. The provided `pis` should start with the block + /// hashes. pub(crate) fn from_public_inputs(pis: &[Target]) -> Self { Self { prev_hashes: pis[0..2048].try_into().unwrap(), @@ -726,22 +758,24 @@ impl BlockHashesTarget { } /// Circuit version of `ExtraBlockData`. -/// Additional block data that are specific to the local transaction being proven, -/// unlike `BlockMetadata`. +/// Additional block data that are specific to the local transaction being +/// proven, unlike `BlockMetadata`. #[derive(Eq, PartialEq, Debug, Copy, Clone)] pub struct ExtraBlockDataTarget { /// `Target`s for the state trie digest of the checkpoint block. pub checkpoint_state_trie_root: [Target; 8], - /// `Target` for the transaction count prior execution of the local state transition, starting - /// at 0 for the initial trnasaction of a block. + /// `Target` for the transaction count prior execution of the local state + /// transition, starting at 0 for the initial trnasaction of a block. pub txn_number_before: Target, - /// `Target` for the transaction count after execution of the local state transition. + /// `Target` for the transaction count after execution of the local state + /// transition. pub txn_number_after: Target, - /// `Target` for the accumulated gas used prior execution of the local state transition, starting - /// at 0 for the initial transaction of a block. + /// `Target` for the accumulated gas used prior execution of the local state + /// transition, starting at 0 for the initial transaction of a block. pub gas_used_before: Target, - /// `Target` for the accumulated gas used after execution of the local state transition. It should - /// match the `block_gas_used` value after execution of the last transaction in a block. + /// `Target` for the accumulated gas used after execution of the local state + /// transition. It should match the `block_gas_used` value after + /// execution of the last transaction in a block. pub gas_used_after: Target, } @@ -794,7 +828,8 @@ impl ExtraBlockDataTarget { } } - /// Connects the extra block data in `ed0` with the extra block data in `ed1`. + /// Connects the extra block data in `ed0` with the extra block data in + /// `ed1`. pub(crate) fn connect, const D: usize>( builder: &mut CircuitBuilder, ed0: Self, diff --git a/src/prover.rs b/evm/src/prover.rs similarity index 96% rename from src/prover.rs rename to evm/src/prover.rs index 8f11c112b..e1b0f15f8 100644 --- a/src/prover.rs +++ b/evm/src/prover.rs @@ -77,7 +77,8 @@ where let rate_bits = config.fri_config.rate_bits; let cap_height = config.fri_config.cap_height; - // For each STARK, we compute the polynomial commitments for the polynomials interpolating its trace. + // For each STARK, we compute the polynomial commitments for the polynomials + // interpolating its trace. let trace_commitments = timed!( timing, "compute all trace commitments", @@ -114,7 +115,8 @@ where observe_public_values::(&mut challenger, &public_values) .map_err(|_| anyhow::Error::msg("Invalid conversion of public values."))?; - // For each STARK, compute its cross-table lookup Z polynomials and get the associated `CtlData`. + // For each STARK, compute its cross-table lookup Z polynomials and get the + // associated `CtlData`. let (ctl_challenges, ctl_data_per_table) = timed!( timing, "compute CTL data", @@ -143,7 +145,8 @@ where )? ); - // This is an expensive check, hence is only run when `debug_assertions` are enabled. + // This is an expensive check, hence is only run when `debug_assertions` are + // enabled. #[cfg(debug_assertions)] { let mut extra_values = HashMap::new(); @@ -168,8 +171,9 @@ where } /// Generates a proof for each STARK. -/// At this stage, we have computed the trace polynomials commitments for the various STARKs, -/// and we have the cross-table lookup data for each table, including the associated challenges. +/// At this stage, we have computed the trace polynomials commitments for the +/// various STARKs, and we have the cross-table lookup data for each table, +/// including the associated challenges. /// - `trace_poly_values` are the trace values for each STARK. /// - `trace_commitments` are the trace polynomials commitments for each STARK. /// - `ctl_data_per_table` group all the cross-table lookup data for each STARK. @@ -348,9 +352,9 @@ where }) } -/// Utility method that checks whether a kill signal has been emitted by one of the workers, -/// which will result in an early abort for all the other processes involved in the same set -/// of transactions. +/// Utility method that checks whether a kill signal has been emitted by one of +/// the workers, which will result in an early abort for all the other processes +/// involved in the same set of transactions. pub fn check_abort_signal(abort_signal: Option>) -> Result<()> { if let Some(signal) = abort_signal { if signal.load(Ordering::Relaxed) { diff --git a/src/recursive_verifier.rs b/evm/src/recursive_verifier.rs similarity index 96% rename from src/recursive_verifier.rs rename to evm/src/recursive_verifier.rs index f3a8e1db4..e63d4731b 100644 --- a/src/recursive_verifier.rs +++ b/evm/src/recursive_verifier.rs @@ -312,9 +312,10 @@ where } } -/// Add gates that are sometimes used by recursive circuits, even if it's not actually used by this -/// particular recursive circuit. This is done for uniformity. We sometimes want all recursion -/// circuits to have the same gate set, so that we can do 1-of-n conditional recursion efficiently. +/// Add gates that are sometimes used by recursive circuits, even if it's not +/// actually used by this particular recursive circuit. This is done for +/// uniformity. We sometimes want all recursion circuits to have the same gate +/// set, so that we can do 1-of-n conditional recursion efficiently. pub(crate) fn add_common_recursion_gates, const D: usize>( builder: &mut CircuitBuilder, ) { @@ -375,7 +376,9 @@ pub(crate) fn get_memory_extra_looking_sum_circuit, ), ]; - let beneficiary_random_base_fee_cur_hash_fields: [(GlobalMetadata, &[Target]); 4] = [ + // This contains the `block_beneficiary`, `block_random`, `block_base_fee`, + // `block_blob_base_fee` as well as `cur_hash`. + let block_fields_arrays: [(GlobalMetadata, &[Target]); 5] = [ ( GlobalMetadata::BlockBeneficiary, &public_values.block_metadata.block_beneficiary, @@ -388,6 +391,10 @@ pub(crate) fn get_memory_extra_looking_sum_circuit, GlobalMetadata::BlockBaseFee, &public_values.block_metadata.block_base_fee, ), + ( + GlobalMetadata::BlockBlobBaseFee, + &public_values.block_metadata.block_blob_base_fee, + ), ( GlobalMetadata::BlockCurrentHash, &public_values.block_hashes.cur_hash, @@ -408,7 +415,7 @@ pub(crate) fn get_memory_extra_looking_sum_circuit, ); }); - beneficiary_random_base_fee_cur_hash_fields.map(|(field, targets)| { + block_fields_arrays.map(|(field, targets)| { sum = add_data_write( builder, challenge, @@ -592,6 +599,7 @@ pub(crate) fn add_virtual_block_metadata, const D: let block_chain_id = builder.add_virtual_public_input(); let block_base_fee = builder.add_virtual_public_input_arr(); let block_gas_used = builder.add_virtual_public_input(); + let block_blob_base_fee = builder.add_virtual_public_input_arr(); let block_bloom = builder.add_virtual_public_input_arr(); BlockMetadataTarget { block_beneficiary, @@ -603,6 +611,7 @@ pub(crate) fn add_virtual_block_metadata, const D: block_chain_id, block_base_fee, block_gas_used, + block_blob_base_fee, block_bloom, } } @@ -772,6 +781,10 @@ where block_metadata_target.block_gas_used, u256_to_u32(block_metadata.block_gas_used)?, ); + // Blobbasefee fits in 2 limbs + let blob_basefee = u256_to_u64(block_metadata.block_blob_base_fee)?; + witness.set_target(block_metadata_target.block_blob_base_fee[0], blob_basefee.0); + witness.set_target(block_metadata_target.block_blob_base_fee[1], blob_basefee.1); let mut block_bloom_limbs = [F::ZERO; 64]; for (i, limbs) in block_bloom_limbs.chunks_exact_mut(8).enumerate() { limbs.copy_from_slice(&u256_limbs(block_metadata.block_bloom[i])); diff --git a/src/util.rs b/evm/src/util.rs similarity index 97% rename from src/util.rs rename to evm/src/util.rs index fdb5a98c3..1a721a370 100644 --- a/src/util.rs +++ b/evm/src/util.rs @@ -15,14 +15,16 @@ use crate::witness::errors::ProgramError; /// Construct an integer from its constituent bits (in little-endian order) pub(crate) fn limb_from_bits_le(iter: impl IntoIterator) -> P { - // TODO: This is technically wrong, as 1 << i won't be canonical for all fields... + // TODO: This is technically wrong, as 1 << i won't be canonical for all + // fields... iter.into_iter() .enumerate() .map(|(i, bit)| bit * P::Scalar::from_canonical_u64(1 << i)) .sum() } -/// Construct an integer from its constituent bits (in little-endian order): recursive edition +/// Construct an integer from its constituent bits (in little-endian order): +/// recursive edition pub(crate) fn limb_from_bits_le_recursive, const D: usize>( builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, iter: impl IntoIterator>, @@ -30,7 +32,8 @@ pub(crate) fn limb_from_bits_le_recursive, const D: iter.into_iter() .enumerate() .fold(builder.zero_extension(), |acc, (i, bit)| { - // TODO: This is technically wrong, as 1 << i won't be canonical for all fields... + // TODO: This is technically wrong, as 1 << i won't be canonical for all + // fields... builder.mul_const_add_extension(F::from_canonical_u64(1 << i), bit, acc) }) } @@ -58,17 +61,20 @@ pub(crate) fn u256_to_u64(u256: U256) -> Result<(F, F), ProgramError> )) } -/// Safe alternative to `U256::as_usize()`, which errors in case of overflow instead of panicking. +/// Safe alternative to `U256::as_usize()`, which errors in case of overflow +/// instead of panicking. pub(crate) fn u256_to_usize(u256: U256) -> Result { u256.try_into().map_err(|_| ProgramError::IntegerTooLarge) } -/// Converts a `U256` to a `u8`, erroring in case of overflow instead of panicking. +/// Converts a `U256` to a `u8`, erroring in case of overflow instead of +/// panicking. pub(crate) fn u256_to_u8(u256: U256) -> Result { u256.try_into().map_err(|_| ProgramError::IntegerTooLarge) } -/// Converts a `U256` to a `bool`, erroring in case of overflow instead of panicking. +/// Converts a `U256` to a `bool`, erroring in case of overflow instead of +/// panicking. pub(crate) fn u256_to_bool(u256: U256) -> Result { if u256 == U256::zero() { Ok(false) @@ -79,7 +85,8 @@ pub(crate) fn u256_to_bool(u256: U256) -> Result { } } -/// Converts a `U256` to a `H160`, erroring in case of overflow instead of panicking. +/// Converts a `U256` to a `H160`, erroring in case of overflow instead of +/// panicking. pub(crate) fn u256_to_h160(u256: U256) -> Result { if u256.bits() / 8 > 20 { return Err(ProgramError::IntegerTooLarge); diff --git a/src/verifier.rs b/evm/src/verifier.rs similarity index 96% rename from src/verifier.rs rename to evm/src/verifier.rs index fd2af8638..2e40a177c 100644 --- a/src/verifier.rs +++ b/evm/src/verifier.rs @@ -135,7 +135,8 @@ where ) } -/// Computes the extra product to multiply to the looked value. It contains memory operations not in the CPU trace: +/// Computes the extra product to multiply to the looked value. It contains +/// memory operations not in the CPU trace: /// - block metadata writes, /// - trie roots writes. pub(crate) fn get_memory_extra_looking_sum( @@ -189,6 +190,10 @@ where GlobalMetadata::BlockGasUsed, public_values.block_metadata.block_gas_used, ), + ( + GlobalMetadata::BlockBlobBaseFee, + public_values.block_metadata.block_blob_base_fee, + ), ( GlobalMetadata::TxnNumberBefore, public_values.extra_block_data.txn_number_before, @@ -236,7 +241,8 @@ where let segment = F::from_canonical_usize(Segment::GlobalMetadata.unscale()); fields.map(|(field, val)| { - // These fields are already scaled by their segment, and are in context 0 (kernel). + // These fields are already scaled by their segment, and are in context 0 + // (kernel). sum = add_data_write(challenge, segment, sum, field.unscale(), val) }); @@ -284,8 +290,8 @@ where pub(crate) mod debug_utils { use super::*; - /// Output all the extra memory rows that don't appear in the CPU trace but are - /// necessary to correctly check the MemoryStark CTL. + /// Output all the extra memory rows that don't appear in the CPU trace but + /// are necessary to correctly check the MemoryStark CTL. pub(crate) fn get_memory_extra_looking_values( public_values: &PublicValues, ) -> Vec> @@ -334,6 +340,10 @@ pub(crate) mod debug_utils { GlobalMetadata::BlockGasUsed, public_values.block_metadata.block_gas_used, ), + ( + GlobalMetadata::BlockBlobBaseFee, + public_values.block_metadata.block_blob_base_fee, + ), ( GlobalMetadata::TxnNumberBefore, public_values.extra_block_data.txn_number_before, diff --git a/src/witness/errors.rs b/evm/src/witness/errors.rs similarity index 100% rename from src/witness/errors.rs rename to evm/src/witness/errors.rs diff --git a/src/witness/gas.rs b/evm/src/witness/gas.rs similarity index 100% rename from src/witness/gas.rs rename to evm/src/witness/gas.rs diff --git a/src/witness/memory.rs b/evm/src/witness/memory.rs similarity index 98% rename from src/witness/memory.rs rename to evm/src/witness/memory.rs index e6cb14f98..4da4203c0 100644 --- a/src/witness/memory.rs +++ b/evm/src/witness/memory.rs @@ -72,8 +72,8 @@ impl MemoryAddress { } /// Creates a new `MemoryAddress` from a bundled address fitting a `U256`. - /// It will recover the virtual offset as the lowest 32-bit limb, the segment - /// as the next limb, and the context as the next one. + /// It will recover the virtual offset as the lowest 32-bit limb, the + /// segment as the next limb, and the context as the next one. pub(crate) fn new_bundle(addr: U256) -> Result { let virt = addr.low_u32().into(); let segment = (addr >> SEGMENT_SCALING_FACTOR).low_u32().into(); @@ -95,7 +95,8 @@ pub(crate) enum MemoryOpKind { #[derive(Clone, Copy, Debug)] pub(crate) struct MemoryOp { - /// true if this is an actual memory operation, or false if it's a padding row. + /// true if this is an actual memory operation, or false if it's a padding + /// row. pub filter: bool, pub timestamp: usize, pub address: MemoryAddress, diff --git a/src/witness/mod.rs b/evm/src/witness/mod.rs similarity index 100% rename from src/witness/mod.rs rename to evm/src/witness/mod.rs diff --git a/src/witness/operation.rs b/evm/src/witness/operation.rs similarity index 96% rename from src/witness/operation.rs rename to evm/src/witness/operation.rs index 4e6271d3b..78e158ab2 100644 --- a/src/witness/operation.rs +++ b/evm/src/witness/operation.rs @@ -62,9 +62,10 @@ pub(crate) enum Operation { // the segment and virtual address components in a single U256 word. pub(crate) const CONTEXT_SCALING_FACTOR: usize = 64; -/// Adds a CPU row filled with the two inputs and the output of a logic operation. -/// Generates a new logic operation and adds it to the vector of operation in `LogicStark`. -/// Adds three memory read operations to `MemoryStark`: for the two inputs and the output. +/// Adds a CPU row filled with the two inputs and the output of a logic +/// operation. Generates a new logic operation and adds it to the vector of +/// operation in `LogicStark`. Adds three memory read operations to +/// `MemoryStark`: for the two inputs and the output. pub(crate) fn generate_binary_logic_op( op: logic::Op, state: &mut GenerationState, @@ -340,7 +341,8 @@ pub(crate) fn generate_get_context( state: &mut GenerationState, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { - // Same logic as push_with_write, but we have to use channel 3 for stack constraint reasons. + // Same logic as push_with_write, but we have to use channel 3 for stack + // constraint reasons. let write = if state.registers.stack_len == 0 { None } else { @@ -380,9 +382,10 @@ pub(crate) fn generate_set_context( let old_sp_addr = MemoryAddress::new(old_ctx, Segment::ContextMetadata, sp_field); let new_sp_addr = MemoryAddress::new(new_ctx, Segment::ContextMetadata, sp_field); - // This channel will hold in limb 0 and 1 the one-limb value of two separate memory operations: - // the old stack pointer write and the new stack pointer read. - // Channels only matter for time stamps: the write must happen before the read. + // This channel will hold in limb 0 and 1 the one-limb value of two separate + // memory operations: the old stack pointer write and the new stack pointer + // read. Channels only matter for time stamps: the write must happen before + // the read. let log_write_old_sp = mem_write_log(GeneralPurpose(1), old_sp_addr, state, sp_to_save); let (new_sp, log_read_new_sp) = if old_ctx == new_ctx { let op = MemoryOp::new( @@ -398,7 +401,7 @@ pub(crate) fn generate_set_context( }; // If the new stack isn't empty, read stack_top from memory. - let new_sp = u256_to_usize(new_sp)?; + let new_sp = new_sp.as_usize(); if new_sp > 0 { // Set up columns to disable the channel if it *is* empty. let new_sp_field = F::from_canonical_usize(new_sp); @@ -445,8 +448,8 @@ pub(crate) fn generate_push( let initial_offset = state.registers.program_counter + 1; let base_address = MemoryAddress::new(code_context, Segment::Code, initial_offset); - // First read val without going through `mem_read_with_log` type methods, so we can pass it - // to stack_push_log_and_fill. + // First read val without going through `mem_read_with_log` type methods, so we + // can pass it to stack_push_log_and_fill. let bytes = (0..num_bytes) .map(|i| { state @@ -502,8 +505,9 @@ pub(crate) fn generate_dup( state.registers.stack_len - 1 - n as usize, ); - // If n = 0, we read a value that hasn't been written to memory: the corresponding write - // is buffered in the mem_ops queue, but hasn't been applied yet. + // If n = 0, we read a value that hasn't been written to memory: the + // corresponding write is buffered in the mem_ops queue, but hasn't been + // applied yet. let (val, log_read) = if n == 0 { let op = MemoryOp::new( MemoryChannel::GeneralPurpose(2), @@ -616,7 +620,8 @@ fn append_shift( let (_, read) = mem_read_gp_with_log_and_fill(LOOKUP_CHANNEL, lookup_addr, state, &mut row); state.traces.push_memory(read); } else { - // The shift constraints still expect the address to be set, even though no read will occur. + // The shift constraints still expect the address to be set, even though no read + // will occur. let channel = &mut row.mem_channels[LOOKUP_CHANNEL]; channel.addr_context = F::from_canonical_usize(lookup_addr.context); channel.addr_segment = F::from_canonical_usize(lookup_addr.segment); @@ -738,9 +743,10 @@ pub(crate) fn generate_syscall( U256::from(opcode), syscall_info, ); - // Set registers before pushing to the stack; in particular, we need to set kernel mode so we - // can't incorrectly trigger a stack overflow. However, note that we have to do it _after_ we - // make `syscall_info`, which should contain the old values. + // Set registers before pushing to the stack; in particular, we need to set + // kernel mode so we can't incorrectly trigger a stack overflow. However, + // note that we have to do it _after_ we make `syscall_info`, which should + // contain the old values. state.registers.program_counter = new_program_counter; state.registers.is_kernel = true; state.registers.gas_used = 0; @@ -810,8 +816,9 @@ pub(crate) fn generate_mload_general( mem_read_gp_with_log_and_fill(1, MemoryAddress::new_bundle(addr)?, state, &mut row); push_no_write(state, val); - // Because MLOAD_GENERAL performs 1 pop and 1 push, it does not make use of the `stack_inv_aux` general columns. - // We hence can set the diff to 2 (instead of 1) so that the stack constraint for MSTORE_GENERAL applies to both + // Because MLOAD_GENERAL performs 1 pop and 1 push, it does not make use of the + // `stack_inv_aux` general columns. We hence can set the diff to 2 (instead + // of 1) so that the stack constraint for MSTORE_GENERAL applies to both // operations, which are combined into a single CPU flag. let diff = row.stack_len - F::TWO; if let Some(inv) = diff.try_inverse() { @@ -986,9 +993,10 @@ pub(crate) fn generate_exception( opcode, exc_info, ); - // Set registers before pushing to the stack; in particular, we need to set kernel mode so we - // can't incorrectly trigger a stack overflow. However, note that we have to do it _after_ we - // make `exc_info`, which should contain the old values. + // Set registers before pushing to the stack; in particular, we need to set + // kernel mode so we can't incorrectly trigger a stack overflow. However, + // note that we have to do it _after_ we make `exc_info`, which should + // contain the old values. state.registers.program_counter = new_program_counter; state.registers.is_kernel = true; state.registers.gas_used = 0; diff --git a/src/witness/state.rs b/evm/src/witness/state.rs similarity index 100% rename from src/witness/state.rs rename to evm/src/witness/state.rs diff --git a/src/witness/traces.rs b/evm/src/witness/traces.rs similarity index 100% rename from src/witness/traces.rs rename to evm/src/witness/traces.rs diff --git a/src/witness/transition.rs b/evm/src/witness/transition.rs similarity index 97% rename from src/witness/transition.rs rename to evm/src/witness/transition.rs index aed6ff539..d0215e3e3 100644 --- a/src/witness/transition.rs +++ b/evm/src/witness/transition.rs @@ -108,6 +108,7 @@ pub(crate) fn decode(registers: RegistersState, opcode: u8) -> Result Ok(Operation::Syscall(opcode, 0, true)), // SELFBALANCE (0x48, _) => Ok(Operation::Syscall(opcode, 0, true)), // BASEFEE (0x49, true) => Ok(Operation::ProverInput), + (0x4a, _) => Ok(Operation::Syscall(opcode, 0, true)), // BLOBBASEFEE (0x50, _) => Ok(Operation::Pop), (0x51, _) => Ok(Operation::Syscall(opcode, 1, false)), // MLOAD (0x52, _) => Ok(Operation::Syscall(opcode, 2, false)), // MSTORE @@ -120,6 +121,7 @@ pub(crate) fn decode(registers: RegistersState, opcode: u8) -> Result Ok(Operation::Syscall(opcode, 0, true)), // MSIZE (0x5a, _) => Ok(Operation::Syscall(opcode, 0, true)), // GAS (0x5b, _) => Ok(Operation::Jumpdest), + (0x5e, _) => Ok(Operation::Syscall(opcode, 3, false)), // MCOPY (0x5f..=0x7f, _) => Ok(Operation::Push(opcode - 0x5f)), (0x80..=0x8f, _) => Ok(Operation::Dup(opcode & 0xf)), (0x90..=0x9f, _) => Ok(Operation::Swap(opcode & 0xf)), @@ -184,7 +186,8 @@ fn fill_op_flag(op: Operation, row: &mut CpuColumnsView) { } = F::ONE; } -// Equal to the number of pops if an operation pops without pushing, and `None` otherwise. +// Equal to the number of pops if an operation pops without pushing, and `None` +// otherwise. const fn get_op_special_length(op: Operation) -> Option { let behavior_opt = match op { Operation::Push(0) | Operation::Pc => STACK_BEHAVIORS.pc_push0, @@ -223,8 +226,9 @@ const fn get_op_special_length(op: Operation) -> Option { } } -// These operations might trigger a stack overflow, typically those pushing without popping. -// Kernel-only pushing instructions aren't considered; they can't overflow. +// These operations might trigger a stack overflow, typically those pushing +// without popping. Kernel-only pushing instructions aren't considered; they +// can't overflow. const fn might_overflow_op(op: Operation) -> bool { match op { Operation::Push(1..) | Operation::ProverInput => MIGHT_OVERFLOW.push_prover_input, @@ -319,9 +323,10 @@ fn perform_op( Ok(op) } -/// Row that has the correct values for system registers and the code channel, but is otherwise -/// blank. It fulfills the constraints that are common to successful operations and the exception -/// operation. It also returns the opcode. +/// Row that has the correct values for system registers and the code channel, +/// but is otherwise blank. It fulfills the constraints that are common to +/// successful operations and the exception operation. It also returns the +/// opcode. fn base_row(state: &mut GenerationState) -> (CpuColumnsView, u8) { let mut row: CpuColumnsView = CpuColumnsView::default(); row.clock = F::from_canonical_usize(state.traces.clock()); @@ -402,8 +407,8 @@ fn try_perform_instruction( fill_stack_fields(state, &mut row)?; - // Might write in general CPU columns when it shouldn't, but the correct values will - // overwrite these ones during the op generation. + // Might write in general CPU columns when it shouldn't, but the correct values + // will overwrite these ones during the op generation. if let Some(special_len) = get_op_special_length(op) { let special_len = F::from_canonical_usize(special_len); let diff = row.stack_len - special_len; diff --git a/src/witness/util.rs b/evm/src/witness/util.rs similarity index 99% rename from src/witness/util.rs rename to evm/src/witness/util.rs index 5f3980939..940e5d095 100644 --- a/src/witness/util.rs +++ b/evm/src/witness/util.rs @@ -67,13 +67,15 @@ pub(crate) fn fill_channel_with_value(row: &mut CpuColumnsView, n: } } -/// Pushes without writing in memory. This happens in opcodes where a push immediately follows a pop. +/// Pushes without writing in memory. This happens in opcodes where a push +/// immediately follows a pop. pub(crate) fn push_no_write(state: &mut GenerationState, val: U256) { state.registers.stack_top = val; state.registers.stack_len += 1; } -/// Pushes and (maybe) writes the previous stack top in memory. This happens in opcodes which only push. +/// Pushes and (maybe) writes the previous stack top in memory. This happens in +/// opcodes which only push. pub(crate) fn push_with_write( state: &mut GenerationState, row: &mut CpuColumnsView, diff --git a/tests/add11_yml.rs b/evm/tests/add11_yml.rs similarity index 99% rename from tests/add11_yml.rs rename to evm/tests/add11_yml.rs index 51da107c5..8ec5560f6 100644 --- a/tests/add11_yml.rs +++ b/evm/tests/add11_yml.rs @@ -86,6 +86,7 @@ fn add11_yml() -> anyhow::Result<()> { block_chain_id: 1.into(), block_base_fee: 0xa.into(), block_gas_used: 0xa868u64.into(), + block_blob_base_fee: 0x2.into(), block_bloom: [0.into(); 8], }; diff --git a/tests/basic_smart_contract.rs b/evm/tests/basic_smart_contract.rs similarity index 99% rename from tests/basic_smart_contract.rs rename to evm/tests/basic_smart_contract.rs index 69c90988c..c09eed6f3 100644 --- a/tests/basic_smart_contract.rs +++ b/evm/tests/basic_smart_contract.rs @@ -113,6 +113,7 @@ fn test_basic_smart_contract() -> anyhow::Result<()> { block_gas_used: gas_used.into(), block_bloom: [0.into(); 8], block_base_fee: 0xa.into(), + block_blob_base_fee: 0x2.into(), block_random: Default::default(), }; diff --git a/tests/empty_txn_list.rs b/evm/tests/empty_txn_list.rs similarity index 97% rename from tests/empty_txn_list.rs rename to evm/tests/empty_txn_list.rs index 8f482f72d..fa3fe5f4c 100644 --- a/tests/empty_txn_list.rs +++ b/evm/tests/empty_txn_list.rs @@ -74,7 +74,8 @@ fn test_empty_txn_list() -> anyhow::Result<()> { // Initialize the preprocessed circuits for the zkEVM. let all_circuits = AllRecursiveCircuits::::new( &all_stark, - &[16..17, 9..11, 12..13, 14..15, 9..11, 12..13, 17..18], // Minimal ranges to prove an empty list + &[16..17, 9..11, 12..13, 14..15, 9..11, 12..13, 17..18], /* Minimal ranges to prove an + * empty list */ &config, ); @@ -140,7 +141,8 @@ fn test_empty_txn_list() -> anyhow::Result<()> { let retrieved_public_values = PublicValues::from_public_inputs(&block_proof.public_inputs); assert_eq!(retrieved_public_values, block_public_values); - // Get the verifier associated to these preprocessed circuits, and have it verify the block_proof. + // Get the verifier associated to these preprocessed circuits, and have it + // verify the block_proof. let verifier = all_circuits.final_verifier_data(); verifier.verify(block_proof) } diff --git a/tests/erc20.rs b/evm/tests/erc20.rs similarity index 99% rename from tests/erc20.rs rename to evm/tests/erc20.rs index 430da14d5..243b5f3e2 100644 --- a/tests/erc20.rs +++ b/evm/tests/erc20.rs @@ -92,6 +92,7 @@ fn test_erc20() -> anyhow::Result<()> { block_chain_id: 1.into(), block_base_fee: 0xa.into(), block_gas_used: gas_used, + block_blob_base_fee: 0x2.into(), block_bloom: bloom, }; diff --git a/tests/erc721.rs b/evm/tests/erc721.rs similarity index 99% rename from tests/erc721.rs rename to evm/tests/erc721.rs index 4dfed2495..898ced005 100644 --- a/tests/erc721.rs +++ b/evm/tests/erc721.rs @@ -41,8 +41,9 @@ type C = KeccakGoldilocksConfig; /// } /// ``` /// -/// The transaction calls the `safeTransferFrom` function to transfer token `1337` from address -/// `0x5B38Da6a701c568545dCfcB03FcB875f56beddC4` to address `0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2`. +/// The transaction calls the `safeTransferFrom` function to transfer token +/// `1337` from address `0x5B38Da6a701c568545dCfcB03FcB875f56beddC4` to address +/// `0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2`. #[test] fn test_erc721() -> anyhow::Result<()> { init_logger(); @@ -155,6 +156,7 @@ fn test_erc721() -> anyhow::Result<()> { block_chain_id: 1.into(), block_base_fee: 0xa.into(), block_gas_used: gas_used, + block_blob_base_fee: 0x2.into(), block_bloom: bloom.try_into().unwrap(), }; diff --git a/tests/log_opcode.rs b/evm/tests/log_opcode.rs similarity index 97% rename from tests/log_opcode.rs rename to evm/tests/log_opcode.rs index a95473fcb..74f2571f1 100644 --- a/tests/log_opcode.rs +++ b/evm/tests/log_opcode.rs @@ -46,7 +46,9 @@ fn test_log_opcodes() -> anyhow::Result<()> { let sender_nibbles = Nibbles::from_bytes_be(sender_state_key.as_bytes()).unwrap(); let to_nibbles = Nibbles::from_bytes_be(to_hashed.as_bytes()).unwrap(); - // For the first code transaction code, we consider two LOG opcodes. The first deals with 0 topics and empty data. The second deals with two topics, and data of length 5, stored in memory. + // For the first code transaction code, we consider two LOG opcodes. The first + // deals with 0 topics and empty data. The second deals with two topics, and + // data of length 5, stored in memory. let code = [ 0x64, 0xA1, 0xB2, 0xC3, 0xD4, 0xE5, 0x60, 0x0, 0x52, // MSTORE(0x0, 0xA1B2C3D4E5) 0x60, 0x0, 0x60, 0x0, 0xA0, // LOG0(0x0, 0x0) @@ -88,7 +90,8 @@ fn test_log_opcodes() -> anyhow::Result<()> { state_trie_before.insert(sender_nibbles, rlp::encode(&sender_account_before).to_vec()); state_trie_before.insert(to_nibbles, rlp::encode(&to_account_before).to_vec()); - // We now add two receipts with logs and data. This updates the receipt trie as well. + // We now add two receipts with logs and data. This updates the receipt trie as + // well. let log_0 = LogRlp { address: hex!("7ef66b77759e12Caf3dDB3E4AFF524E577C59D8D").into(), topics: vec![ @@ -108,7 +111,8 @@ fn test_log_opcodes() -> anyhow::Result<()> { logs: vec![log_0], }; - // Insert the first receipt into the initial receipt trie. The initial receipts trie has an initial node with a random nibble. + // Insert the first receipt into the initial receipt trie. The initial receipts + // trie has an initial node with a random nibble. let mut receipts_trie = HashedPartialTrie::from(Node::Empty); receipts_trie.insert( Nibbles::from_str("0x1337").unwrap(), @@ -136,6 +140,7 @@ fn test_log_opcodes() -> anyhow::Result<()> { block_chain_id: 1.into(), block_base_fee: 0xa.into(), block_gas_used: 0.into(), + block_blob_base_fee: 0x2.into(), block_bloom: [0.into(); 8], }; @@ -143,8 +148,8 @@ fn test_log_opcodes() -> anyhow::Result<()> { contract_code.insert(keccak(vec![]), vec![]); contract_code.insert(code_hash, code.to_vec()); - // Update the state and receipt tries after the transaction, so that we have the correct expected tries: - // Update accounts + // Update the state and receipt tries after the transaction, so that we have the + // correct expected tries: Update accounts let beneficiary_account_after = AccountRlp { nonce: 1.into(), ..AccountRlp::default() @@ -172,8 +177,8 @@ fn test_log_opcodes() -> anyhow::Result<()> { let second_log = LogRlp { address: to.into(), topics: vec![ - hex!("0000000000000000000000000000000000000000000000000000000000000062").into(), // dec: 98 - hex!("0000000000000000000000000000000000000000000000000000000000000063").into(), // dec: 99 + hex!("0000000000000000000000000000000000000000000000000000000000000062").into(), /* dec: 98 */ + hex!("0000000000000000000000000000000000000000000000000000000000000063").into(), /* dec: 99 */ ], data: hex!("a1b2c3d4e5").to_vec().into(), }; @@ -305,7 +310,8 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { ..AccountRlp::default() }; - // In the first transaction, the sender account sends `txn_value` to `to_account`. + // In the first transaction, the sender account sends `txn_value` to + // `to_account`. let gas_price = 10; let txn_value = 0xau64; let mut state_trie_before = HashedPartialTrie::from(Node::Empty); @@ -339,6 +345,7 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { block_chain_id: 1.into(), block_base_fee: 0xa.into(), block_gas_used: (22570 + 21000).into(), + block_blob_base_fee: 0x2.into(), block_bloom: [ 0.into(), 0.into(), @@ -450,10 +457,12 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { timing.filter(Duration::from_millis(100)).print(); all_circuits.verify_root(root_proof_first.clone())?; - // The gas used and transaction number are fed to the next transaction, so the two proofs can be correctly aggregated. + // The gas used and transaction number are fed to the next transaction, so the + // two proofs can be correctly aggregated. let gas_used_second = public_values_first.extra_block_data.gas_used_after; - // Prove second transaction. In this second transaction, the code with logs is executed. + // Prove second transaction. In this second transaction, the code with logs is + // executed. let state_trie_before = expected_state_trie_after; @@ -472,8 +481,8 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { contract_code.insert(keccak(vec![]), vec![]); contract_code.insert(code_hash, code.to_vec()); - // Update the state and receipt tries after the transaction, so that we have the correct expected tries: - // Update accounts. + // Update the state and receipt tries after the transaction, so that we have the + // correct expected tries: Update accounts. let beneficiary_account_after = AccountRlp { nonce: 1.into(), ..AccountRlp::default() @@ -506,8 +515,8 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { let second_log = LogRlp { address: to.into(), topics: vec![ - hex!("0000000000000000000000000000000000000000000000000000000000000062").into(), // dec: 98 - hex!("0000000000000000000000000000000000000000000000000000000000000063").into(), // dec: 99 + hex!("0000000000000000000000000000000000000000000000000000000000000062").into(), /* dec: 98 */ + hex!("0000000000000000000000000000000000000000000000000000000000000063").into(), /* dec: 99 */ ], data: hex!("a1b2c3d4e5").to_vec().into(), }; @@ -655,7 +664,8 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { /// Values taken from the block 1000000 of Goerli: https://goerli.etherscan.io/txs?block=1000000 #[test] fn test_txn_and_receipt_trie_hash() -> anyhow::Result<()> { - // This test checks that inserting into the transaction and receipt `HashedPartialTrie`s works as expected. + // This test checks that inserting into the transaction and receipt + // `HashedPartialTrie`s works as expected. let mut example_txn_trie = HashedPartialTrie::from(Node::Empty); // We consider two transactions, with one log each. diff --git a/tests/self_balance_gas_cost.rs b/evm/tests/self_balance_gas_cost.rs similarity index 99% rename from tests/self_balance_gas_cost.rs rename to evm/tests/self_balance_gas_cost.rs index d759387cb..57d5a862c 100644 --- a/tests/self_balance_gas_cost.rs +++ b/evm/tests/self_balance_gas_cost.rs @@ -102,6 +102,7 @@ fn self_balance_gas_cost() -> anyhow::Result<()> { block_gas_used: gas_used.into(), block_bloom: [0.into(); 8], block_base_fee: 0xa.into(), + block_blob_base_fee: 0x2.into(), block_random: Default::default(), }; diff --git a/tests/selfdestruct.rs b/evm/tests/selfdestruct.rs similarity index 90% rename from tests/selfdestruct.rs rename to evm/tests/selfdestruct.rs index 87b39e307..3be5f358d 100644 --- a/tests/selfdestruct.rs +++ b/evm/tests/selfdestruct.rs @@ -80,10 +80,11 @@ fn test_selfdestruct() -> anyhow::Result<()> { block_chain_id: 1.into(), block_base_fee: 0xa.into(), block_gas_used: 26002.into(), + block_blob_base_fee: 0x2.into(), block_bloom: [0.into(); 8], }; - let contract_code = [(keccak(&code), code), (keccak([]), vec![])].into(); + let contract_code = [(keccak(&code), code.clone()), (keccak([]), vec![])].into(); let expected_state_trie_after: HashedPartialTrie = { let mut state_trie_after = HashedPartialTrie::from(Node::Empty); @@ -94,6 +95,16 @@ fn test_selfdestruct() -> anyhow::Result<()> { code_hash: keccak([]), }; state_trie_after.insert(sender_nibbles, rlp::encode(&sender_account_after).to_vec()); + + // EIP-6780: The account won't be deleted because it wasn't created during this + // transaction. + let to_account_before = AccountRlp { + nonce: 12.into(), + balance: 0.into(), + storage_root: HashedPartialTrie::from(Node::Empty).hash(), + code_hash: keccak(&code), + }; + state_trie_after.insert(to_nibbles, rlp::encode(&to_account_before).to_vec()); state_trie_after }; diff --git a/tests/simple_transfer.rs b/evm/tests/simple_transfer.rs similarity index 99% rename from tests/simple_transfer.rs rename to evm/tests/simple_transfer.rs index cd17fdaed..36476187a 100644 --- a/tests/simple_transfer.rs +++ b/evm/tests/simple_transfer.rs @@ -75,6 +75,7 @@ fn test_simple_transfer() -> anyhow::Result<()> { block_chain_id: 1.into(), block_base_fee: 0xa.into(), block_gas_used: 21032.into(), + block_blob_base_fee: 0x2.into(), block_bloom: [0.into(); 8], }; diff --git a/tests/withdrawals.rs b/evm/tests/withdrawals.rs similarity index 100% rename from tests/withdrawals.rs rename to evm/tests/withdrawals.rs diff --git a/field/.cargo/katex-header.html b/field/.cargo/katex-header.html new file mode 100644 index 000000000..20723b5d2 --- /dev/null +++ b/field/.cargo/katex-header.html @@ -0,0 +1 @@ +../../.cargo/katex-header.html \ No newline at end of file diff --git a/field/Cargo.toml b/field/Cargo.toml new file mode 100644 index 000000000..72408c494 --- /dev/null +++ b/field/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "plonky2_field" +description = "Finite field arithmetic" +version = "0.1.1" +license = "MIT OR Apache-2.0" +authors = ["Daniel Lubarov ", "William Borgeaud ", "Jacqueline Nabaglo ", "Hamish Ivey-Law "] +edition = "2021" + +[dependencies] +anyhow = { version = "1.0.40", default-features = false } +itertools = { version = "0.11.0", default-features = false, features = ["use_alloc"] } +num = { version = "0.4", default-features = false, features = ["alloc", "rand"] } +plonky2_util = { path = "../util", default-features = false } +rand = { version = "0.8.5", default-features = false, features = ["getrandom"] } +serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } +static_assertions = { version = "1.1.0", default-features = false } +unroll = { version = "0.1.5", default-features = false } + +# Display math equations properly in documentation +[package.metadata.docs.rs] +rustdoc-args = ["--html-in-header", ".cargo/katex-header.html"] diff --git a/field/LICENSE-APACHE b/field/LICENSE-APACHE new file mode 100644 index 000000000..1e5006dc1 --- /dev/null +++ b/field/LICENSE-APACHE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + diff --git a/field/LICENSE-MIT b/field/LICENSE-MIT new file mode 100644 index 000000000..86d690b22 --- /dev/null +++ b/field/LICENSE-MIT @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2022 The Plonky2 Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/field/README.md b/field/README.md new file mode 100644 index 000000000..bb4e2d8a9 --- /dev/null +++ b/field/README.md @@ -0,0 +1,13 @@ +## License + +Licensed under either of + +* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/field/src/arch/mod.rs b/field/src/arch/mod.rs new file mode 100644 index 000000000..832557efd --- /dev/null +++ b/field/src/arch/mod.rs @@ -0,0 +1,2 @@ +#[cfg(target_arch = "x86_64")] +pub mod x86_64; diff --git a/field/src/arch/x86_64/avx2_goldilocks_field.rs b/field/src/arch/x86_64/avx2_goldilocks_field.rs new file mode 100644 index 000000000..2c9995919 --- /dev/null +++ b/field/src/arch/x86_64/avx2_goldilocks_field.rs @@ -0,0 +1,689 @@ +use core::arch::x86_64::*; +use core::fmt; +use core::fmt::{Debug, Formatter}; +use core::iter::{Product, Sum}; +use core::mem::transmute; +use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + +use crate::goldilocks_field::GoldilocksField; +use crate::ops::Square; +use crate::packed::PackedField; +use crate::types::{Field, Field64}; + +/// AVX2 Goldilocks Field +/// +/// Ideally `Avx2GoldilocksField` would wrap `__m256i`. Unfortunately, `__m256i` +/// has an alignment of 32B, which would preclude us from casting +/// `[GoldilocksField; 4]` (alignment 8B) to `Avx2GoldilocksField`. We need to +/// ensure that `Avx2GoldilocksField` has the same alignment as +/// `GoldilocksField`. Thus we wrap `[GoldilocksField; 4]` and use the `new` and +/// `get` methods to convert to and from `__m256i`. +#[derive(Copy, Clone)] +#[repr(transparent)] +pub struct Avx2GoldilocksField(pub [GoldilocksField; 4]); + +impl Avx2GoldilocksField { + #[inline] + fn new(x: __m256i) -> Self { + unsafe { transmute(x) } + } + #[inline] + fn get(&self) -> __m256i { + unsafe { transmute(*self) } + } +} + +impl Add for Avx2GoldilocksField { + type Output = Self; + #[inline] + fn add(self, rhs: Self) -> Self { + Self::new(unsafe { add(self.get(), rhs.get()) }) + } +} +impl Add for Avx2GoldilocksField { + type Output = Self; + #[inline] + fn add(self, rhs: GoldilocksField) -> Self { + self + Self::from(rhs) + } +} +impl Add for GoldilocksField { + type Output = Avx2GoldilocksField; + #[inline] + fn add(self, rhs: Self::Output) -> Self::Output { + Self::Output::from(self) + rhs + } +} +impl AddAssign for Avx2GoldilocksField { + #[inline] + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs; + } +} +impl AddAssign for Avx2GoldilocksField { + #[inline] + fn add_assign(&mut self, rhs: GoldilocksField) { + *self = *self + rhs; + } +} + +impl Debug for Avx2GoldilocksField { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "({:?})", self.get()) + } +} + +impl Default for Avx2GoldilocksField { + #[inline] + fn default() -> Self { + Self::ZEROS + } +} + +impl Div for Avx2GoldilocksField { + type Output = Self; + #[allow(clippy::suspicious_arithmetic_impl)] + #[inline] + fn div(self, rhs: GoldilocksField) -> Self { + self * rhs.inverse() + } +} +impl DivAssign for Avx2GoldilocksField { + #[allow(clippy::suspicious_op_assign_impl)] + #[inline] + fn div_assign(&mut self, rhs: GoldilocksField) { + *self *= rhs.inverse(); + } +} + +impl From for Avx2GoldilocksField { + fn from(x: GoldilocksField) -> Self { + Self([x; 4]) + } +} + +impl Mul for Avx2GoldilocksField { + type Output = Self; + #[inline] + fn mul(self, rhs: Self) -> Self { + Self::new(unsafe { mul(self.get(), rhs.get()) }) + } +} +impl Mul for Avx2GoldilocksField { + type Output = Self; + #[inline] + fn mul(self, rhs: GoldilocksField) -> Self { + self * Self::from(rhs) + } +} +impl Mul for GoldilocksField { + type Output = Avx2GoldilocksField; + #[inline] + fn mul(self, rhs: Avx2GoldilocksField) -> Self::Output { + Self::Output::from(self) * rhs + } +} +impl MulAssign for Avx2GoldilocksField { + #[inline] + fn mul_assign(&mut self, rhs: Self) { + *self = *self * rhs; + } +} +impl MulAssign for Avx2GoldilocksField { + #[inline] + fn mul_assign(&mut self, rhs: GoldilocksField) { + *self = *self * rhs; + } +} + +impl Neg for Avx2GoldilocksField { + type Output = Self; + #[inline] + fn neg(self) -> Self { + Self::new(unsafe { neg(self.get()) }) + } +} + +impl Product for Avx2GoldilocksField { + #[inline] + fn product>(iter: I) -> Self { + iter.reduce(|x, y| x * y).unwrap_or(Self::ONES) + } +} + +unsafe impl PackedField for Avx2GoldilocksField { + const WIDTH: usize = 4; + + type Scalar = GoldilocksField; + + const ZEROS: Self = Self([GoldilocksField::ZERO; 4]); + const ONES: Self = Self([GoldilocksField::ONE; 4]); + + #[inline] + fn from_slice(slice: &[Self::Scalar]) -> &Self { + assert_eq!(slice.len(), Self::WIDTH); + unsafe { &*slice.as_ptr().cast() } + } + #[inline] + fn from_slice_mut(slice: &mut [Self::Scalar]) -> &mut Self { + assert_eq!(slice.len(), Self::WIDTH); + unsafe { &mut *slice.as_mut_ptr().cast() } + } + #[inline] + fn as_slice(&self) -> &[Self::Scalar] { + &self.0[..] + } + #[inline] + fn as_slice_mut(&mut self) -> &mut [Self::Scalar] { + &mut self.0[..] + } + + #[inline] + fn interleave(&self, other: Self, block_len: usize) -> (Self, Self) { + let (v0, v1) = (self.get(), other.get()); + let (res0, res1) = match block_len { + 1 => unsafe { interleave1(v0, v1) }, + 2 => unsafe { interleave2(v0, v1) }, + 4 => (v0, v1), + _ => panic!("unsupported block_len"), + }; + (Self::new(res0), Self::new(res1)) + } +} + +impl Square for Avx2GoldilocksField { + #[inline] + fn square(&self) -> Self { + Self::new(unsafe { square(self.get()) }) + } +} + +impl Sub for Avx2GoldilocksField { + type Output = Self; + #[inline] + fn sub(self, rhs: Self) -> Self { + Self::new(unsafe { sub(self.get(), rhs.get()) }) + } +} +impl Sub for Avx2GoldilocksField { + type Output = Self; + #[inline] + fn sub(self, rhs: GoldilocksField) -> Self { + self - Self::from(rhs) + } +} +impl Sub for GoldilocksField { + type Output = Avx2GoldilocksField; + #[inline] + fn sub(self, rhs: Avx2GoldilocksField) -> Self::Output { + Self::Output::from(self) - rhs + } +} +impl SubAssign for Avx2GoldilocksField { + #[inline] + fn sub_assign(&mut self, rhs: Self) { + *self = *self - rhs; + } +} +impl SubAssign for Avx2GoldilocksField { + #[inline] + fn sub_assign(&mut self, rhs: GoldilocksField) { + *self = *self - rhs; + } +} + +impl Sum for Avx2GoldilocksField { + #[inline] + fn sum>(iter: I) -> Self { + iter.reduce(|x, y| x + y).unwrap_or(Self::ZEROS) + } +} + +// Resources: +// 1. Intel Intrinsics Guide for explanation of each intrinsic: https://software.intel.com/sites/landingpage/IntrinsicsGuide/ +// 2. uops.info lists micro-ops for each instruction: https://uops.info/table.html +// 3. Intel optimization manual for introduction to x86 vector extensions and best practices: +// https://software.intel.com/content/www/us/en/develop/download/intel-64-and-ia-32-architectures-optimization-reference-manual.html + +// Preliminary knowledge: +// 1. Vector code usually avoids branching. Instead of branches, we can do input +// selection with _mm256_blendv_epi8 or similar instruction. If all we're +// doing is conditionally zeroing a vector element then _mm256_and_si256 or +// _mm256_andnot_si256 may be used and are cheaper. +// +// 2. AVX does not support addition with carry but 128-bit (2-word) addition can +// be easily emulated. The method recognizes that for a + b overflowed iff (a +// + b) < a: i. res_lo = a_lo + b_lo ii. carry_mask = res_lo < a_lo iii. +// res_hi = a_hi + b_hi - carry_mask Notice that carry_mask is subtracted, +// not added. This is because AVX comparison instructions return -1 (all bits +// 1) for true and 0 for false. +// +// 3. AVX does not have unsigned 64-bit comparisons. Those can be emulated with +// signed comparisons by recognizing that a __m256i { + _mm256_xor_si256(x, SIGN_BIT) +} + +/// Convert to canonical representation. +/// The argument is assumed to be shifted by 1 << 63 (i.e. x_s = x + 1<<63, +/// where x is the field value). The returned value is similarly shifted by 1 +/// << 63 (i.e. we return y_s = y + (1<<63), where 0 <= y < FIELD_ORDER). +#[inline] +unsafe fn canonicalize_s(x_s: __m256i) -> __m256i { + // If x >= FIELD_ORDER then corresponding mask bits are all 0; otherwise all 1. + let mask = _mm256_cmpgt_epi64(SHIFTED_FIELD_ORDER, x_s); + // wrapback_amt is -FIELD_ORDER if mask is 0; otherwise 0. + let wrapback_amt = _mm256_andnot_si256(mask, EPSILON); + _mm256_add_epi64(x_s, wrapback_amt) +} + +/// Addition u64 + u64 -> u64. Assumes that x + y < 2^64 + FIELD_ORDER. The +/// second argument is pre-shifted by 1 << 63. The result is similarly shifted. +#[inline] +unsafe fn add_no_double_overflow_64_64s_s(x: __m256i, y_s: __m256i) -> __m256i { + let res_wrapped_s = _mm256_add_epi64(x, y_s); + let mask = _mm256_cmpgt_epi64(y_s, res_wrapped_s); // -1 if overflowed else 0. + let wrapback_amt = _mm256_srli_epi64::<32>(mask); // -FIELD_ORDER if overflowed else 0. + _mm256_add_epi64(res_wrapped_s, wrapback_amt) +} + +#[inline] +unsafe fn add(x: __m256i, y: __m256i) -> __m256i { + let y_s = shift(y); + let res_s = add_no_double_overflow_64_64s_s(x, canonicalize_s(y_s)); + shift(res_s) +} + +#[inline] +unsafe fn sub(x: __m256i, y: __m256i) -> __m256i { + let mut y_s = shift(y); + y_s = canonicalize_s(y_s); + let x_s = shift(x); + let mask = _mm256_cmpgt_epi64(y_s, x_s); // -1 if sub will underflow (y > x) else 0. + let wrapback_amt = _mm256_srli_epi64::<32>(mask); // -FIELD_ORDER if underflow else 0. + let res_wrapped = _mm256_sub_epi64(x_s, y_s); + _mm256_sub_epi64(res_wrapped, wrapback_amt) +} + +#[inline] +unsafe fn neg(y: __m256i) -> __m256i { + let y_s = shift(y); + _mm256_sub_epi64(SHIFTED_FIELD_ORDER, canonicalize_s(y_s)) +} + +/// Full 64-bit by 64-bit multiplication. This emulated multiplication is 1.33x +/// slower than the scalar instruction, but may be worth it if we want our data +/// to live in vector registers. +#[inline] +unsafe fn mul64_64(x: __m256i, y: __m256i) -> (__m256i, __m256i) { + // We want to move the high 32 bits to the low position. The multiplication + // instruction ignores the high 32 bits, so it's ok to just duplicate it + // into the low position. This duplication can be done on port 5; bitshifts + // run on ports 0 and 1, competing with multiplication. This instruction + // is only provided for 32-bit floats, not integers. Idk why Intel makes the + // distinction; the casts are free and it guarantees that the exact bit pattern + // is preserved. Using a swizzle instruction of the wrong domain (float vs + // int) does not increase latency since Haswell. + let x_hi = _mm256_castps_si256(_mm256_movehdup_ps(_mm256_castsi256_ps(x))); + let y_hi = _mm256_castps_si256(_mm256_movehdup_ps(_mm256_castsi256_ps(y))); + + // All four pairwise multiplications + let mul_ll = _mm256_mul_epu32(x, y); + let mul_lh = _mm256_mul_epu32(x, y_hi); + let mul_hl = _mm256_mul_epu32(x_hi, y); + let mul_hh = _mm256_mul_epu32(x_hi, y_hi); + + // Bignum addition + // Extract high 32 bits of mul_ll and add to mul_hl. This cannot overflow. + let mul_ll_hi = _mm256_srli_epi64::<32>(mul_ll); + let t0 = _mm256_add_epi64(mul_hl, mul_ll_hi); + // Extract low 32 bits of t0 and add to mul_lh. Again, this cannot overflow. + // Also, extract high 32 bits of t0 and add to mul_hh. + let t0_lo = _mm256_and_si256(t0, EPSILON); + let t0_hi = _mm256_srli_epi64::<32>(t0); + let t1 = _mm256_add_epi64(mul_lh, t0_lo); + let t2 = _mm256_add_epi64(mul_hh, t0_hi); + // Lastly, extract the high 32 bits of t1 and add to t2. + let t1_hi = _mm256_srli_epi64::<32>(t1); + let res_hi = _mm256_add_epi64(t2, t1_hi); + + // Form res_lo by combining the low half of mul_ll with the low half of t1 + // (shifted into high position). + let t1_lo = _mm256_castps_si256(_mm256_moveldup_ps(_mm256_castsi256_ps(t1))); + let res_lo = _mm256_blend_epi32::<0xaa>(mul_ll, t1_lo); + + (res_hi, res_lo) +} + +/// Full 64-bit squaring. This routine is 1.2x faster than the scalar +/// instruction. +#[inline] +unsafe fn square64(x: __m256i) -> (__m256i, __m256i) { + // Get high 32 bits of x. See comment in mul64_64_s. + let x_hi = _mm256_castps_si256(_mm256_movehdup_ps(_mm256_castsi256_ps(x))); + + // All pairwise multiplications. + let mul_ll = _mm256_mul_epu32(x, x); + let mul_lh = _mm256_mul_epu32(x, x_hi); + let mul_hh = _mm256_mul_epu32(x_hi, x_hi); + + // Bignum addition, but mul_lh is shifted by 33 bits (not 32). + let mul_ll_hi = _mm256_srli_epi64::<33>(mul_ll); + let t0 = _mm256_add_epi64(mul_lh, mul_ll_hi); + let t0_hi = _mm256_srli_epi64::<31>(t0); + let res_hi = _mm256_add_epi64(mul_hh, t0_hi); + + // Form low result by adding the mul_ll and the low 31 bits of mul_lh (shifted + // to the high position). + let mul_lh_lo = _mm256_slli_epi64::<33>(mul_lh); + let res_lo = _mm256_add_epi64(mul_ll, mul_lh_lo); + + (res_hi, res_lo) +} + +/// Goldilocks addition of a "small" number. `x_s` is pre-shifted by 2**63. `y` +/// is assumed to be <= `0xffffffff00000000`. The result is shifted by 2**63. +#[inline] +unsafe fn add_small_64s_64_s(x_s: __m256i, y: __m256i) -> __m256i { + let res_wrapped_s = _mm256_add_epi64(x_s, y); + // 32-bit compare is faster than 64-bit. It's safe as long as x > res_wrapped + // iff x >> 32 > res_wrapped >> 32. The case of x >> 32 > res_wrapped >> 32 + // is trivial and so is <. The case where x >> 32 = res_wrapped >> 32 + // remains. If x >> 32 = res_wrapped >> 32, then y >> 32 = 0xffffffff and + // the addition of the low 32 bits generated a carry. This can never occur if y + // <= 0xffffffff00000000: if y >> 32 = 0xffffffff, then no carry can occur. + let mask = _mm256_cmpgt_epi32(x_s, res_wrapped_s); // -1 if overflowed else 0. + // The mask contains 0xffffffff in the high 32 bits if wraparound occurred and 0 + // otherwise. + let wrapback_amt = _mm256_srli_epi64::<32>(mask); // -FIELD_ORDER if overflowed else 0. + _mm256_add_epi64(res_wrapped_s, wrapback_amt) +} + +/// Goldilocks subtraction of a "small" number. `x_s` is pre-shifted by 2**63. +/// `y` is assumed to be <= `0xffffffff00000000`. The result is shifted by +/// 2**63. +#[inline] +unsafe fn sub_small_64s_64_s(x_s: __m256i, y: __m256i) -> __m256i { + let res_wrapped_s = _mm256_sub_epi64(x_s, y); + // 32-bit compare is faster than 64-bit. It's safe as long as res_wrapped > x + // iff res_wrapped >> 32 > x >> 32. The case of res_wrapped >> 32 > x >> 32 + // is trivial and so is <. The case where res_wrapped >> 32 = x >> 32 + // remains. If res_wrapped >> 32 = x >> 32, then y >> 32 = 0xffffffff and + // the subtraction of the low 32 bits generated a borrow. This can never occur + // if y <= 0xffffffff00000000: if y >> 32 = 0xffffffff, then no borrow can + // occur. + let mask = _mm256_cmpgt_epi32(res_wrapped_s, x_s); // -1 if underflowed else 0. + // The mask contains 0xffffffff in the high 32 bits if wraparound occurred and 0 + // otherwise. + let wrapback_amt = _mm256_srli_epi64::<32>(mask); // -FIELD_ORDER if underflowed else 0. + _mm256_sub_epi64(res_wrapped_s, wrapback_amt) +} + +#[inline] +unsafe fn reduce128(x: (__m256i, __m256i)) -> __m256i { + let (hi0, lo0) = x; + let lo0_s = shift(lo0); + let hi_hi0 = _mm256_srli_epi64::<32>(hi0); + let lo1_s = sub_small_64s_64_s(lo0_s, hi_hi0); + let t1 = _mm256_mul_epu32(hi0, EPSILON); + let lo2_s = add_small_64s_64_s(lo1_s, t1); + shift(lo2_s) +} + +/// Multiply two integers modulo FIELD_ORDER. +#[inline] +unsafe fn mul(x: __m256i, y: __m256i) -> __m256i { + reduce128(mul64_64(x, y)) +} + +/// Square an integer modulo FIELD_ORDER. +#[inline] +unsafe fn square(x: __m256i) -> __m256i { + reduce128(square64(x)) +} + +#[inline] +unsafe fn interleave1(x: __m256i, y: __m256i) -> (__m256i, __m256i) { + let a = _mm256_unpacklo_epi64(x, y); + let b = _mm256_unpackhi_epi64(x, y); + (a, b) +} + +#[inline] +unsafe fn interleave2(x: __m256i, y: __m256i) -> (__m256i, __m256i) { + let y_lo = _mm256_castsi256_si128(y); // This has 0 cost. + + // 1 places y_lo in the high half of x; 0 would place it in the lower half. + let a = _mm256_inserti128_si256::<1>(x, y_lo); + // NB: _mm256_permute2x128_si256 could be used here as well but + // _mm256_inserti128_si256 has lower latency on Zen 3 processors. + + // Each nibble of the constant has the following semantics: + // 0 => src1[low 128 bits] + // 1 => src1[high 128 bits] + // 2 => src2[low 128 bits] + // 3 => src2[high 128 bits] + // The low (resp. high) nibble chooses the low (resp. high) 128 bits of the + // result. + let b = _mm256_permute2x128_si256::<0x31>(x, y); + + (a, b) +} + +#[cfg(test)] +mod tests { + use crate::arch::x86_64::avx2_goldilocks_field::Avx2GoldilocksField; + use crate::goldilocks_field::GoldilocksField; + use crate::ops::Square; + use crate::packed::PackedField; + use crate::types::Field; + + fn test_vals_a() -> [GoldilocksField; 4] { + [ + GoldilocksField::from_noncanonical_u64(14479013849828404771), + GoldilocksField::from_noncanonical_u64(9087029921428221768), + GoldilocksField::from_noncanonical_u64(2441288194761790662), + GoldilocksField::from_noncanonical_u64(5646033492608483824), + ] + } + fn test_vals_b() -> [GoldilocksField; 4] { + [ + GoldilocksField::from_noncanonical_u64(17891926589593242302), + GoldilocksField::from_noncanonical_u64(11009798273260028228), + GoldilocksField::from_noncanonical_u64(2028722748960791447), + GoldilocksField::from_noncanonical_u64(7929433601095175579), + ] + } + + #[test] + fn test_add() { + let a_arr = test_vals_a(); + let b_arr = test_vals_b(); + + let packed_a = *Avx2GoldilocksField::from_slice(&a_arr); + let packed_b = *Avx2GoldilocksField::from_slice(&b_arr); + let packed_res = packed_a + packed_b; + let arr_res = packed_res.as_slice(); + + let expected = a_arr.iter().zip(b_arr).map(|(&a, b)| a + b); + for (exp, &res) in expected.zip(arr_res) { + assert_eq!(res, exp); + } + } + + #[test] + fn test_mul() { + let a_arr = test_vals_a(); + let b_arr = test_vals_b(); + + let packed_a = *Avx2GoldilocksField::from_slice(&a_arr); + let packed_b = *Avx2GoldilocksField::from_slice(&b_arr); + let packed_res = packed_a * packed_b; + let arr_res = packed_res.as_slice(); + + let expected = a_arr.iter().zip(b_arr).map(|(&a, b)| a * b); + for (exp, &res) in expected.zip(arr_res) { + assert_eq!(res, exp); + } + } + + #[test] + fn test_square() { + let a_arr = test_vals_a(); + + let packed_a = *Avx2GoldilocksField::from_slice(&a_arr); + let packed_res = packed_a.square(); + let arr_res = packed_res.as_slice(); + + let expected = a_arr.iter().map(|&a| a.square()); + for (exp, &res) in expected.zip(arr_res) { + assert_eq!(res, exp); + } + } + + #[test] + fn test_neg() { + let a_arr = test_vals_a(); + + let packed_a = *Avx2GoldilocksField::from_slice(&a_arr); + let packed_res = -packed_a; + let arr_res = packed_res.as_slice(); + + let expected = a_arr.iter().map(|&a| -a); + for (exp, &res) in expected.zip(arr_res) { + assert_eq!(res, exp); + } + } + + #[test] + fn test_sub() { + let a_arr = test_vals_a(); + let b_arr = test_vals_b(); + + let packed_a = *Avx2GoldilocksField::from_slice(&a_arr); + let packed_b = *Avx2GoldilocksField::from_slice(&b_arr); + let packed_res = packed_a - packed_b; + let arr_res = packed_res.as_slice(); + + let expected = a_arr.iter().zip(b_arr).map(|(&a, b)| a - b); + for (exp, &res) in expected.zip(arr_res) { + assert_eq!(res, exp); + } + } + + #[test] + fn test_interleave_is_involution() { + let a_arr = test_vals_a(); + let b_arr = test_vals_b(); + + let packed_a = *Avx2GoldilocksField::from_slice(&a_arr); + let packed_b = *Avx2GoldilocksField::from_slice(&b_arr); + { + // Interleave, then deinterleave. + let (x, y) = packed_a.interleave(packed_b, 1); + let (res_a, res_b) = x.interleave(y, 1); + assert_eq!(res_a.as_slice(), a_arr); + assert_eq!(res_b.as_slice(), b_arr); + } + { + let (x, y) = packed_a.interleave(packed_b, 2); + let (res_a, res_b) = x.interleave(y, 2); + assert_eq!(res_a.as_slice(), a_arr); + assert_eq!(res_b.as_slice(), b_arr); + } + { + let (x, y) = packed_a.interleave(packed_b, 4); + let (res_a, res_b) = x.interleave(y, 4); + assert_eq!(res_a.as_slice(), a_arr); + assert_eq!(res_b.as_slice(), b_arr); + } + } + + #[allow(clippy::zero_prefixed_literal)] + #[test] + fn test_interleave() { + let in_a: [GoldilocksField; 4] = [ + GoldilocksField::from_noncanonical_u64(00), + GoldilocksField::from_noncanonical_u64(01), + GoldilocksField::from_noncanonical_u64(02), + GoldilocksField::from_noncanonical_u64(03), + ]; + let in_b: [GoldilocksField; 4] = [ + GoldilocksField::from_noncanonical_u64(10), + GoldilocksField::from_noncanonical_u64(11), + GoldilocksField::from_noncanonical_u64(12), + GoldilocksField::from_noncanonical_u64(13), + ]; + let int1_a: [GoldilocksField; 4] = [ + GoldilocksField::from_noncanonical_u64(00), + GoldilocksField::from_noncanonical_u64(10), + GoldilocksField::from_noncanonical_u64(02), + GoldilocksField::from_noncanonical_u64(12), + ]; + let int1_b: [GoldilocksField; 4] = [ + GoldilocksField::from_noncanonical_u64(01), + GoldilocksField::from_noncanonical_u64(11), + GoldilocksField::from_noncanonical_u64(03), + GoldilocksField::from_noncanonical_u64(13), + ]; + let int2_a: [GoldilocksField; 4] = [ + GoldilocksField::from_noncanonical_u64(00), + GoldilocksField::from_noncanonical_u64(01), + GoldilocksField::from_noncanonical_u64(10), + GoldilocksField::from_noncanonical_u64(11), + ]; + let int2_b: [GoldilocksField; 4] = [ + GoldilocksField::from_noncanonical_u64(02), + GoldilocksField::from_noncanonical_u64(03), + GoldilocksField::from_noncanonical_u64(12), + GoldilocksField::from_noncanonical_u64(13), + ]; + + let packed_a = *Avx2GoldilocksField::from_slice(&in_a); + let packed_b = *Avx2GoldilocksField::from_slice(&in_b); + { + let (x1, y1) = packed_a.interleave(packed_b, 1); + assert_eq!(x1.as_slice(), int1_a); + assert_eq!(y1.as_slice(), int1_b); + } + { + let (x2, y2) = packed_a.interleave(packed_b, 2); + assert_eq!(x2.as_slice(), int2_a); + assert_eq!(y2.as_slice(), int2_b); + } + { + let (x4, y4) = packed_a.interleave(packed_b, 4); + assert_eq!(x4.as_slice(), in_a); + assert_eq!(y4.as_slice(), in_b); + } + } +} diff --git a/field/src/arch/x86_64/avx512_goldilocks_field.rs b/field/src/arch/x86_64/avx512_goldilocks_field.rs new file mode 100644 index 000000000..97d79a6a0 --- /dev/null +++ b/field/src/arch/x86_64/avx512_goldilocks_field.rs @@ -0,0 +1,650 @@ +use core::arch::x86_64::*; +use core::fmt; +use core::fmt::{Debug, Formatter}; +use core::iter::{Product, Sum}; +use core::mem::transmute; +use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + +use crate::goldilocks_field::GoldilocksField; +use crate::ops::Square; +use crate::packed::PackedField; +use crate::types::{Field, Field64}; + +/// AVX512 Goldilocks Field +/// +/// Ideally `Avx512GoldilocksField` would wrap `__m512i`. Unfortunately, +/// `__m512i` has an alignment of 64B, which would preclude us from casting +/// `[GoldilocksField; 8]` (alignment 8B) to `Avx512GoldilocksField`. We need to +/// ensure that `Avx512GoldilocksField` has the same alignment as +/// `GoldilocksField`. Thus we wrap `[GoldilocksField; 8]` and use the `new` and +/// `get` methods to convert to and from `__m512i`. +#[derive(Copy, Clone)] +#[repr(transparent)] +pub struct Avx512GoldilocksField(pub [GoldilocksField; 8]); + +impl Avx512GoldilocksField { + #[inline] + fn new(x: __m512i) -> Self { + unsafe { transmute(x) } + } + #[inline] + fn get(&self) -> __m512i { + unsafe { transmute(*self) } + } +} + +unsafe impl PackedField for Avx512GoldilocksField { + const WIDTH: usize = 8; + + type Scalar = GoldilocksField; + + const ZEROS: Self = Self([GoldilocksField::ZERO; 8]); + const ONES: Self = Self([GoldilocksField::ONE; 8]); + + #[inline] + fn from_slice(slice: &[Self::Scalar]) -> &Self { + assert_eq!(slice.len(), Self::WIDTH); + unsafe { &*slice.as_ptr().cast() } + } + #[inline] + fn from_slice_mut(slice: &mut [Self::Scalar]) -> &mut Self { + assert_eq!(slice.len(), Self::WIDTH); + unsafe { &mut *slice.as_mut_ptr().cast() } + } + #[inline] + fn as_slice(&self) -> &[Self::Scalar] { + &self.0[..] + } + #[inline] + fn as_slice_mut(&mut self) -> &mut [Self::Scalar] { + &mut self.0[..] + } + + #[inline] + fn interleave(&self, other: Self, block_len: usize) -> (Self, Self) { + let (v0, v1) = (self.get(), other.get()); + let (res0, res1) = match block_len { + 1 => unsafe { interleave1(v0, v1) }, + 2 => unsafe { interleave2(v0, v1) }, + 4 => unsafe { interleave4(v0, v1) }, + 8 => (v0, v1), + _ => panic!("unsupported block_len"), + }; + (Self::new(res0), Self::new(res1)) + } +} + +impl Add for Avx512GoldilocksField { + type Output = Self; + #[inline] + fn add(self, rhs: Self) -> Self { + Self::new(unsafe { add(self.get(), rhs.get()) }) + } +} +impl Add for Avx512GoldilocksField { + type Output = Self; + #[inline] + fn add(self, rhs: GoldilocksField) -> Self { + self + Self::from(rhs) + } +} +impl Add for GoldilocksField { + type Output = Avx512GoldilocksField; + #[inline] + fn add(self, rhs: Self::Output) -> Self::Output { + Self::Output::from(self) + rhs + } +} +impl AddAssign for Avx512GoldilocksField { + #[inline] + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs; + } +} +impl AddAssign for Avx512GoldilocksField { + #[inline] + fn add_assign(&mut self, rhs: GoldilocksField) { + *self = *self + rhs; + } +} + +impl Debug for Avx512GoldilocksField { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "({:?})", self.get()) + } +} + +impl Default for Avx512GoldilocksField { + #[inline] + fn default() -> Self { + Self::ZEROS + } +} + +impl Div for Avx512GoldilocksField { + type Output = Self; + #[inline] + fn div(self, rhs: GoldilocksField) -> Self { + self * rhs.inverse() + } +} +impl DivAssign for Avx512GoldilocksField { + #[inline] + fn div_assign(&mut self, rhs: GoldilocksField) { + *self *= rhs.inverse(); + } +} + +impl From for Avx512GoldilocksField { + fn from(x: GoldilocksField) -> Self { + Self([x; 8]) + } +} + +impl Mul for Avx512GoldilocksField { + type Output = Self; + #[inline] + fn mul(self, rhs: Self) -> Self { + Self::new(unsafe { mul(self.get(), rhs.get()) }) + } +} +impl Mul for Avx512GoldilocksField { + type Output = Self; + #[inline] + fn mul(self, rhs: GoldilocksField) -> Self { + self * Self::from(rhs) + } +} +impl Mul for GoldilocksField { + type Output = Avx512GoldilocksField; + #[inline] + fn mul(self, rhs: Avx512GoldilocksField) -> Self::Output { + Self::Output::from(self) * rhs + } +} +impl MulAssign for Avx512GoldilocksField { + #[inline] + fn mul_assign(&mut self, rhs: Self) { + *self = *self * rhs; + } +} +impl MulAssign for Avx512GoldilocksField { + #[inline] + fn mul_assign(&mut self, rhs: GoldilocksField) { + *self = *self * rhs; + } +} + +impl Neg for Avx512GoldilocksField { + type Output = Self; + #[inline] + fn neg(self) -> Self { + Self::new(unsafe { neg(self.get()) }) + } +} + +impl Product for Avx512GoldilocksField { + #[inline] + fn product>(iter: I) -> Self { + iter.reduce(|x, y| x * y).unwrap_or(Self::ONES) + } +} + +impl Square for Avx512GoldilocksField { + #[inline] + fn square(&self) -> Self { + Self::new(unsafe { square(self.get()) }) + } +} + +impl Sub for Avx512GoldilocksField { + type Output = Self; + #[inline] + fn sub(self, rhs: Self) -> Self { + Self::new(unsafe { sub(self.get(), rhs.get()) }) + } +} +impl Sub for Avx512GoldilocksField { + type Output = Self; + #[inline] + fn sub(self, rhs: GoldilocksField) -> Self { + self - Self::from(rhs) + } +} +impl Sub for GoldilocksField { + type Output = Avx512GoldilocksField; + #[inline] + fn sub(self, rhs: Avx512GoldilocksField) -> Self::Output { + Self::Output::from(self) - rhs + } +} +impl SubAssign for Avx512GoldilocksField { + #[inline] + fn sub_assign(&mut self, rhs: Self) { + *self = *self - rhs; + } +} +impl SubAssign for Avx512GoldilocksField { + #[inline] + fn sub_assign(&mut self, rhs: GoldilocksField) { + *self = *self - rhs; + } +} + +impl Sum for Avx512GoldilocksField { + #[inline] + fn sum>(iter: I) -> Self { + iter.reduce(|x, y| x + y).unwrap_or(Self::ZEROS) + } +} + +const FIELD_ORDER: __m512i = unsafe { transmute([GoldilocksField::ORDER; 8]) }; +const EPSILON: __m512i = unsafe { transmute([GoldilocksField::ORDER.wrapping_neg(); 8]) }; + +#[inline] +unsafe fn canonicalize(x: __m512i) -> __m512i { + let mask = _mm512_cmpge_epu64_mask(x, FIELD_ORDER); + _mm512_mask_sub_epi64(x, mask, x, FIELD_ORDER) +} + +#[inline] +unsafe fn add_no_double_overflow_64_64(x: __m512i, y: __m512i) -> __m512i { + let res_wrapped = _mm512_add_epi64(x, y); + let mask = _mm512_cmplt_epu64_mask(res_wrapped, y); // mask set if add overflowed + let res = _mm512_mask_sub_epi64(res_wrapped, mask, res_wrapped, FIELD_ORDER); + res +} + +#[inline] +unsafe fn sub_no_double_overflow_64_64(x: __m512i, y: __m512i) -> __m512i { + let mask = _mm512_cmplt_epu64_mask(x, y); // mask set if sub will underflow (x < y) + let res_wrapped = _mm512_sub_epi64(x, y); + let res = _mm512_mask_add_epi64(res_wrapped, mask, res_wrapped, FIELD_ORDER); + res +} + +#[inline] +unsafe fn add(x: __m512i, y: __m512i) -> __m512i { + add_no_double_overflow_64_64(x, canonicalize(y)) +} + +#[inline] +unsafe fn sub(x: __m512i, y: __m512i) -> __m512i { + sub_no_double_overflow_64_64(x, canonicalize(y)) +} + +#[inline] +unsafe fn neg(y: __m512i) -> __m512i { + _mm512_sub_epi64(FIELD_ORDER, canonicalize(y)) +} + +const LO_32_BITS_MASK: __mmask16 = unsafe { transmute(0b0101010101010101u16) }; + +#[inline] +unsafe fn mul64_64(x: __m512i, y: __m512i) -> (__m512i, __m512i) { + // We want to move the high 32 bits to the low position. The multiplication + // instruction ignores the high 32 bits, so it's ok to just duplicate it + // into the low position. This duplication can be done on port 5; bitshifts + // run on port 0, competing with multiplication. This instruction is only + // provided for 32-bit floats, not integers. Idk why Intel makes the + // distinction; the casts are free and it guarantees that the exact bit pattern + // is preserved. Using a swizzle instruction of the wrong domain (float vs + // int) does not increase latency since Haswell. + let x_hi = _mm512_castps_si512(_mm512_movehdup_ps(_mm512_castsi512_ps(x))); + let y_hi = _mm512_castps_si512(_mm512_movehdup_ps(_mm512_castsi512_ps(y))); + + // All four pairwise multiplications + let mul_ll = _mm512_mul_epu32(x, y); + let mul_lh = _mm512_mul_epu32(x, y_hi); + let mul_hl = _mm512_mul_epu32(x_hi, y); + let mul_hh = _mm512_mul_epu32(x_hi, y_hi); + + // Bignum addition + // Extract high 32 bits of mul_ll and add to mul_hl. This cannot overflow. + let mul_ll_hi = _mm512_srli_epi64::<32>(mul_ll); + let t0 = _mm512_add_epi64(mul_hl, mul_ll_hi); + // Extract low 32 bits of t0 and add to mul_lh. Again, this cannot overflow. + // Also, extract high 32 bits of t0 and add to mul_hh. + let t0_lo = _mm512_and_si512(t0, EPSILON); + let t0_hi = _mm512_srli_epi64::<32>(t0); + let t1 = _mm512_add_epi64(mul_lh, t0_lo); + let t2 = _mm512_add_epi64(mul_hh, t0_hi); + // Lastly, extract the high 32 bits of t1 and add to t2. + let t1_hi = _mm512_srli_epi64::<32>(t1); + let res_hi = _mm512_add_epi64(t2, t1_hi); + + // Form res_lo by combining the low half of mul_ll with the low half of t1 + // (shifted into high position). + let t1_lo = _mm512_castps_si512(_mm512_moveldup_ps(_mm512_castsi512_ps(t1))); + let res_lo = _mm512_mask_blend_epi32(LO_32_BITS_MASK, t1_lo, mul_ll); + + (res_hi, res_lo) +} + +#[inline] +unsafe fn square64(x: __m512i) -> (__m512i, __m512i) { + // Get high 32 bits of x. See comment in mul64_64_s. + let x_hi = _mm512_castps_si512(_mm512_movehdup_ps(_mm512_castsi512_ps(x))); + + // All pairwise multiplications. + let mul_ll = _mm512_mul_epu32(x, x); + let mul_lh = _mm512_mul_epu32(x, x_hi); + let mul_hh = _mm512_mul_epu32(x_hi, x_hi); + + // Bignum addition, but mul_lh is shifted by 33 bits (not 32). + let mul_ll_hi = _mm512_srli_epi64::<33>(mul_ll); + let t0 = _mm512_add_epi64(mul_lh, mul_ll_hi); + let t0_hi = _mm512_srli_epi64::<31>(t0); + let res_hi = _mm512_add_epi64(mul_hh, t0_hi); + + // Form low result by adding the mul_ll and the low 31 bits of mul_lh (shifted + // to the high position). + let mul_lh_lo = _mm512_slli_epi64::<33>(mul_lh); + let res_lo = _mm512_add_epi64(mul_ll, mul_lh_lo); + + (res_hi, res_lo) +} + +#[inline] +unsafe fn reduce128(x: (__m512i, __m512i)) -> __m512i { + let (hi0, lo0) = x; + let hi_hi0 = _mm512_srli_epi64::<32>(hi0); + let lo1 = sub_no_double_overflow_64_64(lo0, hi_hi0); + let t1 = _mm512_mul_epu32(hi0, EPSILON); + let lo2 = add_no_double_overflow_64_64(lo1, t1); + lo2 +} + +#[inline] +unsafe fn mul(x: __m512i, y: __m512i) -> __m512i { + reduce128(mul64_64(x, y)) +} + +#[inline] +unsafe fn square(x: __m512i) -> __m512i { + reduce128(square64(x)) +} + +#[inline] +unsafe fn interleave1(x: __m512i, y: __m512i) -> (__m512i, __m512i) { + let a = _mm512_unpacklo_epi64(x, y); + let b = _mm512_unpackhi_epi64(x, y); + (a, b) +} + +const INTERLEAVE2_IDX_A: __m512i = unsafe { + transmute([ + 0o00u64, 0o01u64, 0o10u64, 0o11u64, 0o04u64, 0o05u64, 0o14u64, 0o15u64, + ]) +}; +const INTERLEAVE2_IDX_B: __m512i = unsafe { + transmute([ + 0o02u64, 0o03u64, 0o12u64, 0o13u64, 0o06u64, 0o07u64, 0o16u64, 0o17u64, + ]) +}; + +#[inline] +unsafe fn interleave2(x: __m512i, y: __m512i) -> (__m512i, __m512i) { + let a = _mm512_permutex2var_epi64(x, INTERLEAVE2_IDX_A, y); + let b = _mm512_permutex2var_epi64(x, INTERLEAVE2_IDX_B, y); + (a, b) +} + +#[inline] +unsafe fn interleave4(x: __m512i, y: __m512i) -> (__m512i, __m512i) { + let a = _mm512_shuffle_i64x2::<0x44>(x, y); + let b = _mm512_shuffle_i64x2::<0xee>(x, y); + (a, b) +} + +#[cfg(test)] +mod tests { + use crate::arch::x86_64::avx512_goldilocks_field::Avx512GoldilocksField; + use crate::goldilocks_field::GoldilocksField; + use crate::ops::Square; + use crate::packed::PackedField; + use crate::types::Field; + + fn test_vals_a() -> [GoldilocksField; 8] { + [ + GoldilocksField::from_noncanonical_u64(14479013849828404771), + GoldilocksField::from_noncanonical_u64(9087029921428221768), + GoldilocksField::from_noncanonical_u64(2441288194761790662), + GoldilocksField::from_noncanonical_u64(5646033492608483824), + GoldilocksField::from_noncanonical_u64(2779181197214900072), + GoldilocksField::from_noncanonical_u64(2989742820063487116), + GoldilocksField::from_noncanonical_u64(727880025589250743), + GoldilocksField::from_noncanonical_u64(3803926346107752679), + ] + } + fn test_vals_b() -> [GoldilocksField; 8] { + [ + GoldilocksField::from_noncanonical_u64(17891926589593242302), + GoldilocksField::from_noncanonical_u64(11009798273260028228), + GoldilocksField::from_noncanonical_u64(2028722748960791447), + GoldilocksField::from_noncanonical_u64(7929433601095175579), + GoldilocksField::from_noncanonical_u64(6632528436085461172), + GoldilocksField::from_noncanonical_u64(2145438710786785567), + GoldilocksField::from_noncanonical_u64(11821483668392863016), + GoldilocksField::from_noncanonical_u64(15638272883309521929), + ] + } + + #[test] + fn test_add() { + let a_arr = test_vals_a(); + let b_arr = test_vals_b(); + + let packed_a = *Avx512GoldilocksField::from_slice(&a_arr); + let packed_b = *Avx512GoldilocksField::from_slice(&b_arr); + let packed_res = packed_a + packed_b; + let arr_res = packed_res.as_slice(); + + let expected = a_arr.iter().zip(b_arr).map(|(&a, b)| a + b); + for (exp, &res) in expected.zip(arr_res) { + assert_eq!(res, exp); + } + } + + #[test] + fn test_mul() { + let a_arr = test_vals_a(); + let b_arr = test_vals_b(); + + let packed_a = *Avx512GoldilocksField::from_slice(&a_arr); + let packed_b = *Avx512GoldilocksField::from_slice(&b_arr); + let packed_res = packed_a * packed_b; + let arr_res = packed_res.as_slice(); + + let expected = a_arr.iter().zip(b_arr).map(|(&a, b)| a * b); + for (exp, &res) in expected.zip(arr_res) { + assert_eq!(res, exp); + } + } + + #[test] + fn test_square() { + let a_arr = test_vals_a(); + + let packed_a = *Avx512GoldilocksField::from_slice(&a_arr); + let packed_res = packed_a.square(); + let arr_res = packed_res.as_slice(); + + let expected = a_arr.iter().map(|&a| a.square()); + for (exp, &res) in expected.zip(arr_res) { + assert_eq!(res, exp); + } + } + + #[test] + fn test_neg() { + let a_arr = test_vals_a(); + + let packed_a = *Avx512GoldilocksField::from_slice(&a_arr); + let packed_res = -packed_a; + let arr_res = packed_res.as_slice(); + + let expected = a_arr.iter().map(|&a| -a); + for (exp, &res) in expected.zip(arr_res) { + assert_eq!(res, exp); + } + } + + #[test] + fn test_sub() { + let a_arr = test_vals_a(); + let b_arr = test_vals_b(); + + let packed_a = *Avx512GoldilocksField::from_slice(&a_arr); + let packed_b = *Avx512GoldilocksField::from_slice(&b_arr); + let packed_res = packed_a - packed_b; + let arr_res = packed_res.as_slice(); + + let expected = a_arr.iter().zip(b_arr).map(|(&a, b)| a - b); + for (exp, &res) in expected.zip(arr_res) { + assert_eq!(res, exp); + } + } + + #[test] + fn test_interleave_is_involution() { + let a_arr = test_vals_a(); + let b_arr = test_vals_b(); + + let packed_a = *Avx512GoldilocksField::from_slice(&a_arr); + let packed_b = *Avx512GoldilocksField::from_slice(&b_arr); + { + // Interleave, then deinterleave. + let (x, y) = packed_a.interleave(packed_b, 1); + let (res_a, res_b) = x.interleave(y, 1); + assert_eq!(res_a.as_slice(), a_arr); + assert_eq!(res_b.as_slice(), b_arr); + } + { + let (x, y) = packed_a.interleave(packed_b, 2); + let (res_a, res_b) = x.interleave(y, 2); + assert_eq!(res_a.as_slice(), a_arr); + assert_eq!(res_b.as_slice(), b_arr); + } + { + let (x, y) = packed_a.interleave(packed_b, 4); + let (res_a, res_b) = x.interleave(y, 4); + assert_eq!(res_a.as_slice(), a_arr); + assert_eq!(res_b.as_slice(), b_arr); + } + { + let (x, y) = packed_a.interleave(packed_b, 8); + let (res_a, res_b) = x.interleave(y, 8); + assert_eq!(res_a.as_slice(), a_arr); + assert_eq!(res_b.as_slice(), b_arr); + } + } + + #[test] + fn test_interleave() { + let in_a: [GoldilocksField; 8] = [ + GoldilocksField::from_noncanonical_u64(00), + GoldilocksField::from_noncanonical_u64(01), + GoldilocksField::from_noncanonical_u64(02), + GoldilocksField::from_noncanonical_u64(03), + GoldilocksField::from_noncanonical_u64(04), + GoldilocksField::from_noncanonical_u64(05), + GoldilocksField::from_noncanonical_u64(06), + GoldilocksField::from_noncanonical_u64(07), + ]; + let in_b: [GoldilocksField; 8] = [ + GoldilocksField::from_noncanonical_u64(10), + GoldilocksField::from_noncanonical_u64(11), + GoldilocksField::from_noncanonical_u64(12), + GoldilocksField::from_noncanonical_u64(13), + GoldilocksField::from_noncanonical_u64(14), + GoldilocksField::from_noncanonical_u64(15), + GoldilocksField::from_noncanonical_u64(16), + GoldilocksField::from_noncanonical_u64(17), + ]; + let int1_a: [GoldilocksField; 8] = [ + GoldilocksField::from_noncanonical_u64(00), + GoldilocksField::from_noncanonical_u64(10), + GoldilocksField::from_noncanonical_u64(02), + GoldilocksField::from_noncanonical_u64(12), + GoldilocksField::from_noncanonical_u64(04), + GoldilocksField::from_noncanonical_u64(14), + GoldilocksField::from_noncanonical_u64(06), + GoldilocksField::from_noncanonical_u64(16), + ]; + let int1_b: [GoldilocksField; 8] = [ + GoldilocksField::from_noncanonical_u64(01), + GoldilocksField::from_noncanonical_u64(11), + GoldilocksField::from_noncanonical_u64(03), + GoldilocksField::from_noncanonical_u64(13), + GoldilocksField::from_noncanonical_u64(05), + GoldilocksField::from_noncanonical_u64(15), + GoldilocksField::from_noncanonical_u64(07), + GoldilocksField::from_noncanonical_u64(17), + ]; + let int2_a: [GoldilocksField; 8] = [ + GoldilocksField::from_noncanonical_u64(00), + GoldilocksField::from_noncanonical_u64(01), + GoldilocksField::from_noncanonical_u64(10), + GoldilocksField::from_noncanonical_u64(11), + GoldilocksField::from_noncanonical_u64(04), + GoldilocksField::from_noncanonical_u64(05), + GoldilocksField::from_noncanonical_u64(14), + GoldilocksField::from_noncanonical_u64(15), + ]; + let int2_b: [GoldilocksField; 8] = [ + GoldilocksField::from_noncanonical_u64(02), + GoldilocksField::from_noncanonical_u64(03), + GoldilocksField::from_noncanonical_u64(12), + GoldilocksField::from_noncanonical_u64(13), + GoldilocksField::from_noncanonical_u64(06), + GoldilocksField::from_noncanonical_u64(07), + GoldilocksField::from_noncanonical_u64(16), + GoldilocksField::from_noncanonical_u64(17), + ]; + let int4_a: [GoldilocksField; 8] = [ + GoldilocksField::from_noncanonical_u64(00), + GoldilocksField::from_noncanonical_u64(01), + GoldilocksField::from_noncanonical_u64(02), + GoldilocksField::from_noncanonical_u64(03), + GoldilocksField::from_noncanonical_u64(10), + GoldilocksField::from_noncanonical_u64(11), + GoldilocksField::from_noncanonical_u64(12), + GoldilocksField::from_noncanonical_u64(13), + ]; + let int4_b: [GoldilocksField; 8] = [ + GoldilocksField::from_noncanonical_u64(04), + GoldilocksField::from_noncanonical_u64(05), + GoldilocksField::from_noncanonical_u64(06), + GoldilocksField::from_noncanonical_u64(07), + GoldilocksField::from_noncanonical_u64(14), + GoldilocksField::from_noncanonical_u64(15), + GoldilocksField::from_noncanonical_u64(16), + GoldilocksField::from_noncanonical_u64(17), + ]; + + let packed_a = *Avx512GoldilocksField::from_slice(&in_a); + let packed_b = *Avx512GoldilocksField::from_slice(&in_b); + { + let (x1, y1) = packed_a.interleave(packed_b, 1); + assert_eq!(x1.as_slice(), int1_a); + assert_eq!(y1.as_slice(), int1_b); + } + { + let (x2, y2) = packed_a.interleave(packed_b, 2); + assert_eq!(x2.as_slice(), int2_a); + assert_eq!(y2.as_slice(), int2_b); + } + { + let (x4, y4) = packed_a.interleave(packed_b, 4); + assert_eq!(x4.as_slice(), int4_a); + assert_eq!(y4.as_slice(), int4_b); + } + { + let (x8, y8) = packed_a.interleave(packed_b, 8); + assert_eq!(x8.as_slice(), in_a); + assert_eq!(y8.as_slice(), in_b); + } + } +} diff --git a/field/src/arch/x86_64/mod.rs b/field/src/arch/x86_64/mod.rs new file mode 100644 index 000000000..326deb784 --- /dev/null +++ b/field/src/arch/x86_64/mod.rs @@ -0,0 +1,20 @@ +#[cfg(all( + target_feature = "avx2", + not(all( + target_feature = "avx512bw", + target_feature = "avx512cd", + target_feature = "avx512dq", + target_feature = "avx512f", + target_feature = "avx512vl" + )) +))] +pub mod avx2_goldilocks_field; + +#[cfg(all( + target_feature = "avx512bw", + target_feature = "avx512cd", + target_feature = "avx512dq", + target_feature = "avx512f", + target_feature = "avx512vl" +))] +pub mod avx512_goldilocks_field; diff --git a/field/src/batch_util.rs b/field/src/batch_util.rs new file mode 100644 index 000000000..ab7ee3d50 --- /dev/null +++ b/field/src/batch_util.rs @@ -0,0 +1,65 @@ +use crate::packable::Packable; +use crate::packed::PackedField; +use crate::types::Field; + +const fn pack_with_leftovers_split_point(slice: &[P::Scalar]) -> usize { + let n = slice.len(); + let n_leftover = n % P::WIDTH; + n - n_leftover +} + +fn pack_slice_with_leftovers(slice: &[P::Scalar]) -> (&[P], &[P::Scalar]) { + let split_point = pack_with_leftovers_split_point::

(slice); + let (slice_packable, slice_leftovers) = slice.split_at(split_point); + let slice_packed = P::pack_slice(slice_packable); + (slice_packed, slice_leftovers) +} + +fn pack_slice_with_leftovers_mut( + slice: &mut [P::Scalar], +) -> (&mut [P], &mut [P::Scalar]) { + let split_point = pack_with_leftovers_split_point::

(slice); + let (slice_packable, slice_leftovers) = slice.split_at_mut(split_point); + let slice_packed = P::pack_slice_mut(slice_packable); + (slice_packed, slice_leftovers) +} + +/// Elementwise inplace multiplication of two slices of field elements. +/// Implementation be faster than the trivial for loop. +pub fn batch_multiply_inplace(out: &mut [F], a: &[F]) { + let n = out.len(); + assert_eq!(n, a.len(), "both arrays must have the same length"); + + // Split out slice of vectors, leaving leftovers as scalars + let (out_packed, out_leftovers) = + pack_slice_with_leftovers_mut::<::Packing>(out); + let (a_packed, a_leftovers) = pack_slice_with_leftovers::<::Packing>(a); + + // Multiply packed and the leftovers + for (x_out, x_a) in out_packed.iter_mut().zip(a_packed) { + *x_out *= *x_a; + } + for (x_out, x_a) in out_leftovers.iter_mut().zip(a_leftovers) { + *x_out *= *x_a; + } +} + +/// Elementwise inplace addition of two slices of field elements. +/// Implementation be faster than the trivial for loop. +pub fn batch_add_inplace(out: &mut [F], a: &[F]) { + let n = out.len(); + assert_eq!(n, a.len(), "both arrays must have the same length"); + + // Split out slice of vectors, leaving leftovers as scalars + let (out_packed, out_leftovers) = + pack_slice_with_leftovers_mut::<::Packing>(out); + let (a_packed, a_leftovers) = pack_slice_with_leftovers::<::Packing>(a); + + // Add packed and the leftovers + for (x_out, x_a) in out_packed.iter_mut().zip(a_packed) { + *x_out += *x_a; + } + for (x_out, x_a) in out_leftovers.iter_mut().zip(a_leftovers) { + *x_out += *x_a; + } +} diff --git a/field/src/cosets.rs b/field/src/cosets.rs new file mode 100644 index 000000000..8c8f2416e --- /dev/null +++ b/field/src/cosets.rs @@ -0,0 +1,55 @@ +use alloc::vec::Vec; + +use num::bigint::BigUint; + +use crate::types::Field; + +/// Finds a set of shifts that result in unique cosets for the multiplicative +/// subgroup of size `2^subgroup_bits`. +pub fn get_unique_coset_shifts(subgroup_size: usize, num_shifts: usize) -> Vec { + // From Lagrange's theorem. + let num_cosets = (F::order() - 1u32) / (subgroup_size as u32); + assert!( + BigUint::from(num_shifts) <= num_cosets, + "The subgroup does not have enough distinct cosets" + ); + + // Let g be a generator of the entire multiplicative group. Let n be the order + // of the subgroup. The subgroup can be written as . We can + // use g^0, ..., g^(num_shifts - 1) as our shifts, since g^i + // are distinct cosets provided i < |F*| / n, which we checked. + F::MULTIPLICATIVE_GROUP_GENERATOR + .powers() + .take(num_shifts) + .collect() +} + +#[cfg(test)] +mod tests { + use std::collections::HashSet; + + use crate::cosets::get_unique_coset_shifts; + use crate::goldilocks_field::GoldilocksField; + use crate::types::Field; + + #[test] + fn distinct_cosets() { + type F = GoldilocksField; + const SUBGROUP_BITS: usize = 5; + const NUM_SHIFTS: usize = 50; + + let generator = F::primitive_root_of_unity(SUBGROUP_BITS); + let subgroup_size = 1 << SUBGROUP_BITS; + + let shifts = get_unique_coset_shifts::(subgroup_size, NUM_SHIFTS); + + let mut union = HashSet::new(); + for shift in shifts { + let coset = F::cyclic_subgroup_coset_known_order(generator, shift, subgroup_size); + assert!( + coset.into_iter().all(|x| union.insert(x)), + "Duplicate element!" + ); + } + } +} diff --git a/field/src/extension/algebra.rs b/field/src/extension/algebra.rs new file mode 100644 index 000000000..86c8cda38 --- /dev/null +++ b/field/src/extension/algebra.rs @@ -0,0 +1,295 @@ +use alloc::vec::Vec; +use core::fmt::{self, Debug, Display, Formatter}; +use core::iter::{Product, Sum}; +use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + +use crate::extension::OEF; + +/// Let `F_D` be the optimal extension field `F[X]/(X^D-W)`. Then +/// `ExtensionAlgebra` is the quotient `F_D[X]/(X^D-W)`. +/// It's a `D`-dimensional algebra over `F_D` useful to lift the multiplication +/// over `F_D` to a multiplication over `(F_D)^D`. +#[derive(Copy, Clone)] +pub struct ExtensionAlgebra, const D: usize>(pub [F; D]); + +impl, const D: usize> ExtensionAlgebra { + pub const ZERO: Self = Self([F::ZERO; D]); + + pub fn one() -> Self { + F::ONE.into() + } + + pub const fn from_basefield_array(arr: [F; D]) -> Self { + Self(arr) + } + + pub const fn to_basefield_array(self) -> [F; D] { + self.0 + } + + pub fn scalar_mul(&self, scalar: F) -> Self { + let mut res = self.0; + res.iter_mut().for_each(|x| { + *x *= scalar; + }); + Self(res) + } +} + +impl, const D: usize> From for ExtensionAlgebra { + fn from(x: F) -> Self { + let mut arr = [F::ZERO; D]; + arr[0] = x; + Self(arr) + } +} + +impl, const D: usize> Display for ExtensionAlgebra { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "({})", self.0[0])?; + for i in 1..D { + write!(f, " + ({})*b^{i}", self.0[i])?; + } + Ok(()) + } +} + +impl, const D: usize> Debug for ExtensionAlgebra { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Display::fmt(self, f) + } +} + +impl, const D: usize> Neg for ExtensionAlgebra { + type Output = Self; + + #[inline] + fn neg(self) -> Self { + let mut arr = self.0; + arr.iter_mut().for_each(|x| *x = -*x); + Self(arr) + } +} + +impl, const D: usize> Add for ExtensionAlgebra { + type Output = Self; + + #[inline] + fn add(self, rhs: Self) -> Self { + let mut arr = self.0; + arr.iter_mut().zip(&rhs.0).for_each(|(x, &y)| *x += y); + Self(arr) + } +} + +impl, const D: usize> AddAssign for ExtensionAlgebra { + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs; + } +} + +impl, const D: usize> Sum for ExtensionAlgebra { + fn sum>(iter: I) -> Self { + iter.fold(Self::ZERO, |acc, x| acc + x) + } +} + +impl, const D: usize> Sub for ExtensionAlgebra { + type Output = Self; + + #[inline] + fn sub(self, rhs: Self) -> Self { + let mut arr = self.0; + arr.iter_mut().zip(&rhs.0).for_each(|(x, &y)| *x -= y); + Self(arr) + } +} + +impl, const D: usize> SubAssign for ExtensionAlgebra { + #[inline] + fn sub_assign(&mut self, rhs: Self) { + *self = *self - rhs; + } +} + +impl, const D: usize> Mul for ExtensionAlgebra { + type Output = Self; + + #[inline] + fn mul(self, rhs: Self) -> Self { + let mut res = [F::ZERO; D]; + let w = F::from_basefield(F::W); + for i in 0..D { + for j in 0..D { + res[(i + j) % D] += if i + j < D { + self.0[i] * rhs.0[j] + } else { + w * self.0[i] * rhs.0[j] + } + } + } + Self(res) + } +} + +impl, const D: usize> MulAssign for ExtensionAlgebra { + #[inline] + fn mul_assign(&mut self, rhs: Self) { + *self = *self * rhs; + } +} + +impl, const D: usize> Product for ExtensionAlgebra { + fn product>(iter: I) -> Self { + iter.fold(Self::one(), |acc, x| acc * x) + } +} + +/// A polynomial in coefficient form. +#[derive(Clone, Debug)] +pub struct PolynomialCoeffsAlgebra, const D: usize> { + pub(crate) coeffs: Vec>, +} + +impl, const D: usize> PolynomialCoeffsAlgebra { + pub fn new(coeffs: Vec>) -> Self { + PolynomialCoeffsAlgebra { coeffs } + } + + pub fn eval(&self, x: ExtensionAlgebra) -> ExtensionAlgebra { + self.coeffs + .iter() + .rev() + .fold(ExtensionAlgebra::ZERO, |acc, &c| acc * x + c) + } + + /// Evaluate the polynomial at a point given its powers. The first power is + /// the point itself, not 1. + pub fn eval_with_powers(&self, powers: &[ExtensionAlgebra]) -> ExtensionAlgebra { + debug_assert_eq!(self.coeffs.len(), powers.len() + 1); + let acc = self.coeffs[0]; + self.coeffs[1..] + .iter() + .zip(powers) + .fold(acc, |acc, (&x, &c)| acc + c * x) + } + + pub fn eval_base(&self, x: F) -> ExtensionAlgebra { + self.coeffs + .iter() + .rev() + .fold(ExtensionAlgebra::ZERO, |acc, &c| acc.scalar_mul(x) + c) + } + + /// Evaluate the polynomial at a point given its powers. The first power is + /// the point itself, not 1. + pub fn eval_base_with_powers(&self, powers: &[F]) -> ExtensionAlgebra { + debug_assert_eq!(self.coeffs.len(), powers.len() + 1); + let acc = self.coeffs[0]; + self.coeffs[1..] + .iter() + .zip(powers) + .fold(acc, |acc, (&x, &c)| acc + x.scalar_mul(c)) + } +} + +#[cfg(test)] +mod tests { + use alloc::vec::Vec; + + use itertools::Itertools; + + use crate::extension::algebra::ExtensionAlgebra; + use crate::extension::{Extendable, FieldExtension}; + use crate::goldilocks_field::GoldilocksField; + use crate::types::{Field, Sample}; + + /// Tests that the multiplication on the extension algebra lifts that of the + /// field extension. + fn test_extension_algebra, const D: usize>() { + #[derive(Copy, Clone, Debug)] + enum ZeroOne { + Zero, + One, + } + + let to_field = |zo: &ZeroOne| match zo { + ZeroOne::Zero => F::ZERO, + ZeroOne::One => F::ONE, + }; + let to_fields = |x: &[ZeroOne], y: &[ZeroOne]| -> (F::Extension, F::Extension) { + let mut arr0 = [F::ZERO; D]; + let mut arr1 = [F::ZERO; D]; + arr0.copy_from_slice(&x.iter().map(to_field).collect::>()); + arr1.copy_from_slice(&y.iter().map(to_field).collect::>()); + ( + >::Extension::from_basefield_array(arr0), + >::Extension::from_basefield_array(arr1), + ) + }; + + // Standard MLE formula. + let selector = |xs: Vec, ts: &[F::Extension]| -> F::Extension { + (0..2 * D) + .map(|i| match xs[i] { + ZeroOne::Zero => F::Extension::ONE - ts[i], + ZeroOne::One => ts[i], + }) + .product() + }; + + let mul_mle = |ts: Vec| -> [F::Extension; D] { + let mut ans = [F::Extension::ZERO; D]; + for xs in (0..2 * D) + .map(|_| vec![ZeroOne::Zero, ZeroOne::One]) + .multi_cartesian_product() + { + let (a, b) = to_fields(&xs[..D], &xs[D..]); + let c = a * b; + let res = selector(xs, &ts); + for i in 0..D { + ans[i] += res.scalar_mul(c.to_basefield_array()[i]); + } + } + ans + }; + + let ts = F::Extension::rand_vec(2 * D); + let mut arr0 = [F::Extension::ZERO; D]; + let mut arr1 = [F::Extension::ZERO; D]; + arr0.copy_from_slice(&ts[..D]); + arr1.copy_from_slice(&ts[D..]); + let x = ExtensionAlgebra::from_basefield_array(arr0); + let y = ExtensionAlgebra::from_basefield_array(arr1); + let z = x * y; + + assert_eq!(z.0, mul_mle(ts)); + } + + mod base { + use super::*; + + #[test] + fn test_algebra() { + test_extension_algebra::(); + } + } + + mod quadratic { + use super::*; + + #[test] + fn test_algebra() { + test_extension_algebra::(); + } + } + + mod quartic { + use super::*; + + #[test] + fn test_algebra() { + test_extension_algebra::(); + } + } +} diff --git a/field/src/extension/mod.rs b/field/src/extension/mod.rs new file mode 100644 index 000000000..abcab30f2 --- /dev/null +++ b/field/src/extension/mod.rs @@ -0,0 +1,150 @@ +use alloc::vec::Vec; + +use crate::types::Field; + +pub mod algebra; +pub mod quadratic; +pub mod quartic; +pub mod quintic; + +/// Optimal extension field trait. +/// A degree `d` field extension is optimal if there exists a base field element +/// `W`, such that the extension is `F[X]/(X^d-W)`. +#[allow(clippy::upper_case_acronyms)] +pub trait OEF: FieldExtension { + // Element W of BaseField, such that `X^d - W` is irreducible over BaseField. + const W: Self::BaseField; + + // Element of BaseField such that DTH_ROOT^D == 1. Implementers + // should set this to W^((p - 1)/D), where W is as above and p is + // the order of the BaseField. + const DTH_ROOT: Self::BaseField; +} + +impl OEF<1> for F { + const W: Self::BaseField = F::ONE; + const DTH_ROOT: Self::BaseField = F::ONE; +} + +pub trait Frobenius: OEF { + /// FrobeniusField automorphisms: x -> x^p, where p is the order of + /// BaseField. + fn frobenius(&self) -> Self { + self.repeated_frobenius(1) + } + + /// Repeated Frobenius automorphisms: x -> x^(p^count). + /// + /// Follows precomputation suggestion in Section 11.3.3 of the + /// Handbook of Elliptic and Hyperelliptic Curve Cryptography. + fn repeated_frobenius(&self, count: usize) -> Self { + if count == 0 { + return *self; + } else if count >= D { + // x |-> x^(p^D) is the identity, so x^(p^count) == + // x^(p^(count % D)) + return self.repeated_frobenius(count % D); + } + let arr = self.to_basefield_array(); + + // z0 = DTH_ROOT^count = W^(k * count) where k = floor((p^D-1)/D) + let mut z0 = Self::DTH_ROOT; + for _ in 1..count { + z0 *= Self::DTH_ROOT; + } + + let mut res = [Self::BaseField::ZERO; D]; + for (i, z) in z0.powers().take(D).enumerate() { + res[i] = arr[i] * z; + } + + Self::from_basefield_array(res) + } +} + +pub trait Extendable: Field + Sized { + type Extension: Field + OEF + Frobenius + From; + + const W: Self; + + const DTH_ROOT: Self; + + /// Chosen so that when raised to the power `(p^D - 1) >> + /// F::Extension::TWO_ADICITY)` we obtain F::EXT_POWER_OF_TWO_GENERATOR. + const EXT_MULTIPLICATIVE_GROUP_GENERATOR: [Self; D]; + + /// Chosen so that when raised to the power + /// `1<<(Self::TWO_ADICITY-Self::BaseField::TWO_ADICITY)`, + /// we get `Self::BaseField::POWER_OF_TWO_GENERATOR`. This makes + /// `primitive_root_of_unity` coherent with the base field which implies + /// that the FFT commutes with field inclusion. + const EXT_POWER_OF_TWO_GENERATOR: [Self; D]; +} + +impl + FieldExtension<1, BaseField = F>> Extendable<1> for F { + type Extension = F; + 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]; +} + +pub trait FieldExtension: Field { + type BaseField: Field; + + fn to_basefield_array(&self) -> [Self::BaseField; D]; + + fn from_basefield_array(arr: [Self::BaseField; D]) -> Self; + + fn from_basefield(x: Self::BaseField) -> Self; + + fn is_in_basefield(&self) -> bool { + self.to_basefield_array()[1..].iter().all(|x| x.is_zero()) + } + + fn scalar_mul(&self, scalar: Self::BaseField) -> Self { + let mut res = self.to_basefield_array(); + res.iter_mut().for_each(|x| { + *x *= scalar; + }); + Self::from_basefield_array(res) + } +} + +impl FieldExtension<1> for F { + type BaseField = F; + + fn to_basefield_array(&self) -> [Self::BaseField; 1] { + [*self] + } + + fn from_basefield_array(arr: [Self::BaseField; 1]) -> Self { + arr[0] + } + + fn from_basefield(x: Self::BaseField) -> Self { + x + } +} + +/// Flatten the slice by sending every extension field element to its D-sized +/// canonical representation. +pub fn flatten(l: &[F::Extension]) -> Vec +where + F: Extendable, +{ + l.iter() + .flat_map(|x| x.to_basefield_array().to_vec()) + .collect() +} + +/// Batch every D-sized chunks into extension field elements. +pub fn unflatten(l: &[F]) -> Vec +where + F: Extendable, +{ + debug_assert_eq!(l.len() % D, 0); + l.chunks_exact(D) + .map(|c| F::Extension::from_basefield_array(c.to_vec().try_into().unwrap())) + .collect() +} diff --git a/field/src/extension/quadratic.rs b/field/src/extension/quadratic.rs new file mode 100644 index 000000000..7e250ccbb --- /dev/null +++ b/field/src/extension/quadratic.rs @@ -0,0 +1,252 @@ +use core::fmt::{self, Debug, Display, Formatter}; +use core::iter::{Product, Sum}; +use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + +use num::bigint::BigUint; +use serde::{Deserialize, Serialize}; + +use crate::extension::{Extendable, FieldExtension, Frobenius, OEF}; +use crate::ops::Square; +use crate::types::{Field, Sample}; + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct QuadraticExtension>(pub [F; 2]); + +impl> Default for QuadraticExtension { + fn default() -> Self { + Self::ZERO + } +} + +impl> OEF<2> for QuadraticExtension { + const W: F = F::W; + const DTH_ROOT: F = F::DTH_ROOT; +} + +impl> Frobenius<2> for QuadraticExtension {} + +impl> FieldExtension<2> for QuadraticExtension { + type BaseField = F; + + fn to_basefield_array(&self) -> [F; 2] { + self.0 + } + + fn from_basefield_array(arr: [F; 2]) -> Self { + Self(arr) + } + + fn from_basefield(x: F) -> Self { + x.into() + } +} + +impl> From for QuadraticExtension { + fn from(x: F) -> Self { + Self([x, F::ZERO]) + } +} + +impl> Sample for QuadraticExtension { + #[inline] + fn sample(rng: &mut R) -> Self + where + R: rand::RngCore + ?Sized, + { + Self([F::sample(rng), F::sample(rng)]) + } +} + +impl> Field for QuadraticExtension { + const ZERO: Self = Self([F::ZERO; 2]); + const ONE: Self = Self([F::ONE, F::ZERO]); + const TWO: Self = Self([F::TWO, F::ZERO]); + const NEG_ONE: Self = Self([F::NEG_ONE, F::ZERO]); + + // `p^2 - 1 = (p - 1)(p + 1)`. The `p - 1` term has a two-adicity of + // `F::TWO_ADICITY`. As long as `F::TWO_ADICITY >= 2`, `p` can be written as + // `4n + 1`, so `p + 1` can be written as `2(2n + 1)`, which has a 2-adicity + // of 1. + const TWO_ADICITY: usize = F::TWO_ADICITY + 1; + const CHARACTERISTIC_TWO_ADICITY: usize = F::CHARACTERISTIC_TWO_ADICITY; + + const MULTIPLICATIVE_GROUP_GENERATOR: Self = Self(F::EXT_MULTIPLICATIVE_GROUP_GENERATOR); + const POWER_OF_TWO_GENERATOR: Self = Self(F::EXT_POWER_OF_TWO_GENERATOR); + + const BITS: usize = F::BITS * 2; + + fn order() -> BigUint { + F::order() * F::order() + } + fn characteristic() -> BigUint { + F::characteristic() + } + + // Algorithm 11.3.4 in Handbook of Elliptic and Hyperelliptic Curve + // Cryptography. + fn try_inverse(&self) -> Option { + if self.is_zero() { + return None; + } + + let a_pow_r_minus_1 = self.frobenius(); + let a_pow_r = a_pow_r_minus_1 * *self; + debug_assert!(FieldExtension::<2>::is_in_basefield(&a_pow_r)); + + Some(FieldExtension::<2>::scalar_mul( + &a_pow_r_minus_1, + a_pow_r.0[0].inverse(), + )) + } + + fn from_noncanonical_biguint(n: BigUint) -> Self { + F::from_noncanonical_biguint(n).into() + } + + fn from_canonical_u64(n: u64) -> Self { + F::from_canonical_u64(n).into() + } + + fn from_noncanonical_u128(n: u128) -> Self { + F::from_noncanonical_u128(n).into() + } + + fn from_noncanonical_i64(n: i64) -> Self { + F::from_noncanonical_i64(n).into() + } + + fn from_noncanonical_u64(n: u64) -> Self { + F::from_noncanonical_u64(n).into() + } +} + +impl> Display for QuadraticExtension { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{} + {}*a", self.0[0], self.0[1]) + } +} + +impl> Debug for QuadraticExtension { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Display::fmt(self, f) + } +} + +impl> Neg for QuadraticExtension { + type Output = Self; + + #[inline] + fn neg(self) -> Self { + Self([-self.0[0], -self.0[1]]) + } +} + +impl> Add for QuadraticExtension { + type Output = Self; + + #[inline] + fn add(self, rhs: Self) -> Self { + Self([self.0[0] + rhs.0[0], self.0[1] + rhs.0[1]]) + } +} + +impl> AddAssign for QuadraticExtension { + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs; + } +} + +impl> Sum for QuadraticExtension { + fn sum>(iter: I) -> Self { + iter.fold(Self::ZERO, |acc, x| acc + x) + } +} + +impl> Sub for QuadraticExtension { + type Output = Self; + + #[inline] + fn sub(self, rhs: Self) -> Self { + Self([self.0[0] - rhs.0[0], self.0[1] - rhs.0[1]]) + } +} + +impl> SubAssign for QuadraticExtension { + #[inline] + fn sub_assign(&mut self, rhs: Self) { + *self = *self - rhs; + } +} + +impl> Mul for QuadraticExtension { + type Output = Self; + + #[inline] + default fn mul(self, rhs: Self) -> Self { + let Self([a0, a1]) = self; + let Self([b0, b1]) = rhs; + + let c0 = a0 * b0 + >::W * a1 * b1; + let c1 = a0 * b1 + a1 * b0; + + Self([c0, c1]) + } +} + +impl> MulAssign for QuadraticExtension { + #[inline] + fn mul_assign(&mut self, rhs: Self) { + *self = *self * rhs; + } +} + +impl> Square for QuadraticExtension { + #[inline(always)] + fn square(&self) -> Self { + // Specialising mul reduces the computation of c1 from 2 muls + // and one add to one mul and a shift + + let Self([a0, a1]) = *self; + + let c0 = a0.square() + >::W * a1.square(); + let c1 = a0 * a1.double(); + + Self([c0, c1]) + } +} + +impl> Product for QuadraticExtension { + fn product>(iter: I) -> Self { + iter.fold(Self::ONE, |acc, x| acc * x) + } +} + +impl> Div for QuadraticExtension { + type Output = Self; + + #[allow(clippy::suspicious_arithmetic_impl)] + fn div(self, rhs: Self) -> Self::Output { + self * rhs.inverse() + } +} + +impl> DivAssign for QuadraticExtension { + fn div_assign(&mut self, rhs: Self) { + *self = *self / rhs; + } +} + +#[cfg(test)] +mod tests { + mod goldilocks { + use crate::{test_field_arithmetic, test_field_extension}; + + test_field_extension!(crate::goldilocks_field::GoldilocksField, 2); + test_field_arithmetic!( + crate::extension::quadratic::QuadraticExtension< + crate::goldilocks_field::GoldilocksField, + > + ); + } +} diff --git a/field/src/extension/quartic.rs b/field/src/extension/quartic.rs new file mode 100644 index 000000000..2427d015c --- /dev/null +++ b/field/src/extension/quartic.rs @@ -0,0 +1,278 @@ +use core::fmt::{self, Debug, Display, Formatter}; +use core::iter::{Product, Sum}; +use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + +use num::bigint::BigUint; +use num::traits::Pow; +use serde::{Deserialize, Serialize}; + +use crate::extension::{Extendable, FieldExtension, Frobenius, OEF}; +use crate::ops::Square; +use crate::types::{Field, Sample}; + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct QuarticExtension>(pub [F; 4]); + +impl> Default for QuarticExtension { + fn default() -> Self { + Self::ZERO + } +} + +impl> OEF<4> for QuarticExtension { + const W: F = F::W; + const DTH_ROOT: F = F::DTH_ROOT; +} + +impl> Frobenius<4> for QuarticExtension {} + +impl> FieldExtension<4> for QuarticExtension { + type BaseField = F; + + fn to_basefield_array(&self) -> [F; 4] { + self.0 + } + + fn from_basefield_array(arr: [F; 4]) -> Self { + Self(arr) + } + + fn from_basefield(x: F) -> Self { + x.into() + } +} + +impl> From for QuarticExtension { + fn from(x: F) -> Self { + Self([x, F::ZERO, F::ZERO, F::ZERO]) + } +} + +impl> Sample for QuarticExtension { + #[inline] + fn sample(rng: &mut R) -> Self + where + R: rand::RngCore + ?Sized, + { + Self::from_basefield_array([ + F::sample(rng), + F::sample(rng), + F::sample(rng), + F::sample(rng), + ]) + } +} + +impl> Field for QuarticExtension { + const ZERO: Self = Self([F::ZERO; 4]); + const ONE: Self = Self([F::ONE, F::ZERO, F::ZERO, F::ZERO]); + const TWO: Self = Self([F::TWO, F::ZERO, F::ZERO, F::ZERO]); + const NEG_ONE: Self = Self([F::NEG_ONE, F::ZERO, F::ZERO, F::ZERO]); + + // `p^4 - 1 = (p - 1)(p + 1)(p^2 + 1)`. The `p - 1` term has a two-adicity of + // `F::TWO_ADICITY`. As long as `F::TWO_ADICITY >= 2`, `p` can be written as + // `4n + 1`, so `p + 1` can be written as `2(2n + 1)`, which has a 2-adicity + // of 1. A similar argument can show that `p^2 + 1` also has a 2-adicity of + // 1. + const TWO_ADICITY: usize = F::TWO_ADICITY + 2; + const CHARACTERISTIC_TWO_ADICITY: usize = F::CHARACTERISTIC_TWO_ADICITY; + + const MULTIPLICATIVE_GROUP_GENERATOR: Self = Self(F::EXT_MULTIPLICATIVE_GROUP_GENERATOR); + const POWER_OF_TWO_GENERATOR: Self = Self(F::EXT_POWER_OF_TWO_GENERATOR); + + const BITS: usize = F::BITS * 4; + + fn order() -> BigUint { + F::order().pow(4u32) + } + fn characteristic() -> BigUint { + F::characteristic() + } + + // Algorithm 11.3.4 in Handbook of Elliptic and Hyperelliptic Curve + // Cryptography. + fn try_inverse(&self) -> Option { + if self.is_zero() { + return None; + } + + let a_pow_p = self.frobenius(); + let a_pow_p_plus_1 = a_pow_p * *self; + let a_pow_p3_plus_p2 = a_pow_p_plus_1.repeated_frobenius(2); + let a_pow_r_minus_1 = a_pow_p3_plus_p2 * a_pow_p; + let a_pow_r = a_pow_r_minus_1 * *self; + debug_assert!(FieldExtension::<4>::is_in_basefield(&a_pow_r)); + + Some(FieldExtension::<4>::scalar_mul( + &a_pow_r_minus_1, + a_pow_r.0[0].inverse(), + )) + } + + fn from_noncanonical_biguint(n: BigUint) -> Self { + F::from_noncanonical_biguint(n).into() + } + + fn from_canonical_u64(n: u64) -> Self { + F::from_canonical_u64(n).into() + } + + fn from_noncanonical_u128(n: u128) -> Self { + F::from_noncanonical_u128(n).into() + } + + fn from_noncanonical_i64(n: i64) -> Self { + F::from_noncanonical_i64(n).into() + } + + fn from_noncanonical_u64(n: u64) -> Self { + F::from_noncanonical_u64(n).into() + } +} + +impl> Display for QuarticExtension { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "{} + {}*a + {}*a^2 + {}*a^3", + self.0[0], self.0[1], self.0[2], self.0[3] + ) + } +} + +impl> Debug for QuarticExtension { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Display::fmt(self, f) + } +} + +impl> Neg for QuarticExtension { + type Output = Self; + + #[inline] + fn neg(self) -> Self { + Self([-self.0[0], -self.0[1], -self.0[2], -self.0[3]]) + } +} + +impl> Add for QuarticExtension { + type Output = Self; + + #[inline] + fn add(self, rhs: Self) -> Self { + Self([ + self.0[0] + rhs.0[0], + self.0[1] + rhs.0[1], + self.0[2] + rhs.0[2], + self.0[3] + rhs.0[3], + ]) + } +} + +impl> AddAssign for QuarticExtension { + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs; + } +} + +impl> Sum for QuarticExtension { + fn sum>(iter: I) -> Self { + iter.fold(Self::ZERO, |acc, x| acc + x) + } +} + +impl> Sub for QuarticExtension { + type Output = Self; + + #[inline] + fn sub(self, rhs: Self) -> Self { + Self([ + self.0[0] - rhs.0[0], + self.0[1] - rhs.0[1], + self.0[2] - rhs.0[2], + self.0[3] - rhs.0[3], + ]) + } +} + +impl> SubAssign for QuarticExtension { + #[inline] + fn sub_assign(&mut self, rhs: Self) { + *self = *self - rhs; + } +} + +impl> Mul for QuarticExtension { + type Output = Self; + + #[inline] + default fn mul(self, rhs: Self) -> Self { + let Self([a0, a1, a2, a3]) = self; + let Self([b0, b1, b2, b3]) = rhs; + + let c0 = a0 * b0 + >::W * (a1 * b3 + a2 * b2 + a3 * b1); + let c1 = a0 * b1 + a1 * b0 + >::W * (a2 * b3 + a3 * b2); + let c2 = a0 * b2 + a1 * b1 + a2 * b0 + >::W * a3 * b3; + let c3 = a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0; + + Self([c0, c1, c2, c3]) + } +} + +impl> MulAssign for QuarticExtension { + #[inline] + fn mul_assign(&mut self, rhs: Self) { + *self = *self * rhs; + } +} + +impl> Square for QuarticExtension { + #[inline(always)] + fn square(&self) -> Self { + let Self([a0, a1, a2, a3]) = *self; + let w = >::W; + + let c0 = a0.square() + w * (a1 * a3.double() + a2.square()); + let c1 = (a0 * a1 + w * a2 * a3).double(); + let c2 = a0 * a2.double() + a1.square() + w * a3.square(); + let c3 = (a0 * a3 + a1 * a2).double(); + + Self([c0, c1, c2, c3]) + } +} + +impl> Product for QuarticExtension { + fn product>(iter: I) -> Self { + iter.fold(Self::ONE, |acc, x| acc * x) + } +} + +impl> Div for QuarticExtension { + type Output = Self; + + #[allow(clippy::suspicious_arithmetic_impl)] + fn div(self, rhs: Self) -> Self::Output { + self * rhs.inverse() + } +} + +impl> DivAssign for QuarticExtension { + fn div_assign(&mut self, rhs: Self) { + *self = *self / rhs; + } +} + +#[cfg(test)] +mod tests { + mod goldilocks { + use crate::{test_field_arithmetic, test_field_extension}; + + test_field_extension!(crate::goldilocks_field::GoldilocksField, 4); + test_field_arithmetic!( + crate::extension::quartic::QuarticExtension< + crate::goldilocks_field::GoldilocksField, + > + ); + } +} diff --git a/field/src/extension/quintic.rs b/field/src/extension/quintic.rs new file mode 100644 index 000000000..c9d0603f6 --- /dev/null +++ b/field/src/extension/quintic.rs @@ -0,0 +1,292 @@ +use core::fmt::{self, Debug, Display, Formatter}; +use core::iter::{Product, Sum}; +use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + +use num::bigint::BigUint; +use num::traits::Pow; +use serde::{Deserialize, Serialize}; + +use crate::extension::{Extendable, FieldExtension, Frobenius, OEF}; +use crate::ops::Square; +use crate::types::{Field, Sample}; + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct QuinticExtension>(pub [F; 5]); + +impl> Default for QuinticExtension { + fn default() -> Self { + Self::ZERO + } +} + +impl> OEF<5> for QuinticExtension { + const W: F = F::W; + const DTH_ROOT: F = F::DTH_ROOT; +} + +impl> Frobenius<5> for QuinticExtension {} + +impl> FieldExtension<5> for QuinticExtension { + type BaseField = F; + + fn to_basefield_array(&self) -> [F; 5] { + self.0 + } + + fn from_basefield_array(arr: [F; 5]) -> Self { + Self(arr) + } + + fn from_basefield(x: F) -> Self { + x.into() + } +} + +impl> From for QuinticExtension { + fn from(x: F) -> Self { + Self([x, F::ZERO, F::ZERO, F::ZERO, F::ZERO]) + } +} + +impl> Sample for QuinticExtension { + #[inline] + fn sample(rng: &mut R) -> Self + where + R: rand::RngCore + ?Sized, + { + Self::from_basefield_array([ + F::sample(rng), + F::sample(rng), + F::sample(rng), + F::sample(rng), + F::sample(rng), + ]) + } +} + +impl> Field for QuinticExtension { + const ZERO: Self = Self([F::ZERO; 5]); + const ONE: Self = Self([F::ONE, F::ZERO, F::ZERO, F::ZERO, F::ZERO]); + const TWO: Self = Self([F::TWO, F::ZERO, F::ZERO, F::ZERO, F::ZERO]); + const NEG_ONE: Self = Self([F::NEG_ONE, F::ZERO, F::ZERO, F::ZERO, F::ZERO]); + + // `p^5 - 1 = (p - 1)(p^4 + p^3 + p^2 + p + 1)`. The `p - 1` term + // has a two-adicity of `F::TWO_ADICITY` and the term `p^4 + p^3 + + // p^2 + p + 1` is odd since it is the sum of an odd number of odd + // terms. Hence the two-adicity of `p^5 - 1` is the same as for + // `p - 1`. + const TWO_ADICITY: usize = F::TWO_ADICITY; + const CHARACTERISTIC_TWO_ADICITY: usize = F::CHARACTERISTIC_TWO_ADICITY; + + const MULTIPLICATIVE_GROUP_GENERATOR: Self = Self(F::EXT_MULTIPLICATIVE_GROUP_GENERATOR); + const POWER_OF_TWO_GENERATOR: Self = Self(F::EXT_POWER_OF_TWO_GENERATOR); + + const BITS: usize = F::BITS * 5; + + fn order() -> BigUint { + F::order().pow(5u32) + } + fn characteristic() -> BigUint { + F::characteristic() + } + + // Algorithm 11.3.4 in Handbook of Elliptic and Hyperelliptic Curve + // Cryptography. + fn try_inverse(&self) -> Option { + if self.is_zero() { + return None; + } + + // Writing 'a' for self: + let d = self.frobenius(); // d = a^p + let e = d * d.frobenius(); // e = a^(p + p^2) + let f = e * e.repeated_frobenius(2); // f = a^(p + p^2 + p^3 + p^4) + + // f contains a^(r-1) and a^r is in the base field. + debug_assert!(FieldExtension::<5>::is_in_basefield(&(*self * f))); + + // g = a^r is in the base field, so only compute that + // coefficient rather than the full product. The equation is + // extracted from Mul::mul(...) below. + let Self([a0, a1, a2, a3, a4]) = *self; + let Self([b0, b1, b2, b3, b4]) = f; + let g = a0 * b0 + >::W * (a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1); + + Some(FieldExtension::<5>::scalar_mul(&f, g.inverse())) + } + + fn from_noncanonical_biguint(n: BigUint) -> Self { + F::from_noncanonical_biguint(n).into() + } + + fn from_canonical_u64(n: u64) -> Self { + F::from_canonical_u64(n).into() + } + + fn from_noncanonical_u128(n: u128) -> Self { + F::from_noncanonical_u128(n).into() + } + + fn from_noncanonical_i64(n: i64) -> Self { + F::from_noncanonical_i64(n).into() + } + + fn from_noncanonical_u64(n: u64) -> Self { + F::from_noncanonical_u64(n).into() + } +} + +impl> Display for QuinticExtension { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "{} + {}*a + {}*a^2 + {}*a^3 + {}*a^4", + self.0[0], self.0[1], self.0[2], self.0[3], self.0[4] + ) + } +} + +impl> Debug for QuinticExtension { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Display::fmt(self, f) + } +} + +impl> Neg for QuinticExtension { + type Output = Self; + + #[inline] + fn neg(self) -> Self { + Self([-self.0[0], -self.0[1], -self.0[2], -self.0[3], -self.0[4]]) + } +} + +impl> Add for QuinticExtension { + type Output = Self; + + #[inline] + fn add(self, rhs: Self) -> Self { + Self([ + self.0[0] + rhs.0[0], + self.0[1] + rhs.0[1], + self.0[2] + rhs.0[2], + self.0[3] + rhs.0[3], + self.0[4] + rhs.0[4], + ]) + } +} + +impl> AddAssign for QuinticExtension { + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs; + } +} + +impl> Sum for QuinticExtension { + fn sum>(iter: I) -> Self { + iter.fold(Self::ZERO, |acc, x| acc + x) + } +} + +impl> Sub for QuinticExtension { + type Output = Self; + + #[inline] + fn sub(self, rhs: Self) -> Self { + Self([ + self.0[0] - rhs.0[0], + self.0[1] - rhs.0[1], + self.0[2] - rhs.0[2], + self.0[3] - rhs.0[3], + self.0[4] - rhs.0[4], + ]) + } +} + +impl> SubAssign for QuinticExtension { + #[inline] + fn sub_assign(&mut self, rhs: Self) { + *self = *self - rhs; + } +} + +impl> Mul for QuinticExtension { + type Output = Self; + + #[inline] + default fn mul(self, rhs: Self) -> Self { + let Self([a0, a1, a2, a3, a4]) = self; + let Self([b0, b1, b2, b3, b4]) = rhs; + let w = >::W; + + let c0 = a0 * b0 + w * (a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1); + let c1 = a0 * b1 + a1 * b0 + w * (a2 * b4 + a3 * b3 + a4 * b2); + let c2 = a0 * b2 + a1 * b1 + a2 * b0 + w * (a3 * b4 + a4 * b3); + let c3 = a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0 + w * a4 * b4; + let c4 = a0 * b4 + a1 * b3 + a2 * b2 + a3 * b1 + a4 * b0; + + Self([c0, c1, c2, c3, c4]) + } +} + +impl> MulAssign for QuinticExtension { + #[inline] + fn mul_assign(&mut self, rhs: Self) { + *self = *self * rhs; + } +} + +impl> Square for QuinticExtension { + #[inline(always)] + fn square(&self) -> Self { + let Self([a0, a1, a2, a3, a4]) = *self; + let w = >::W; + let double_w = >::W.double(); + + let c0 = a0.square() + double_w * (a1 * a4 + a2 * a3); + let double_a0 = a0.double(); + let c1 = double_a0 * a1 + double_w * a2 * a4 + w * a3 * a3; + let c2 = double_a0 * a2 + a1 * a1 + double_w * a4 * a3; + let double_a1 = a1.double(); + let c3 = double_a0 * a3 + double_a1 * a2 + w * a4 * a4; + let c4 = double_a0 * a4 + double_a1 * a3 + a2 * a2; + + Self([c0, c1, c2, c3, c4]) + } +} + +impl> Product for QuinticExtension { + fn product>(iter: I) -> Self { + iter.fold(Self::ONE, |acc, x| acc * x) + } +} + +impl> Div for QuinticExtension { + type Output = Self; + + #[allow(clippy::suspicious_arithmetic_impl)] + fn div(self, rhs: Self) -> Self::Output { + self * rhs.inverse() + } +} + +impl> DivAssign for QuinticExtension { + fn div_assign(&mut self, rhs: Self) { + *self = *self / rhs; + } +} + +#[cfg(test)] +mod tests { + mod goldilocks { + use crate::{test_field_arithmetic, test_field_extension}; + + test_field_extension!(crate::goldilocks_field::GoldilocksField, 5); + test_field_arithmetic!( + crate::extension::quintic::QuinticExtension< + crate::goldilocks_field::GoldilocksField, + > + ); + } +} diff --git a/field/src/fft.rs b/field/src/fft.rs new file mode 100644 index 000000000..dee681a3e --- /dev/null +++ b/field/src/fft.rs @@ -0,0 +1,288 @@ +use alloc::vec::Vec; +use core::cmp::{max, min}; + +use plonky2_util::{log2_strict, reverse_index_bits_in_place}; +use unroll::unroll_for_loops; + +use crate::packable::Packable; +use crate::packed::PackedField; +use crate::polynomial::{PolynomialCoeffs, PolynomialValues}; +use crate::types::Field; + +pub type FftRootTable = Vec>; + +pub fn fft_root_table(n: usize) -> FftRootTable { + let lg_n = log2_strict(n); + // bases[i] = g^2^i, for i = 0, ..., lg_n - 1 + let mut bases = Vec::with_capacity(lg_n); + let mut base = F::primitive_root_of_unity(lg_n); + bases.push(base); + for _ in 1..lg_n { + base = base.square(); // base = g^2^_ + bases.push(base); + } + + let mut root_table = Vec::with_capacity(lg_n); + for lg_m in 1..=lg_n { + let half_m = 1 << (lg_m - 1); + let base = bases[lg_n - lg_m]; + let root_row = base.powers().take(half_m.max(2)).collect(); + root_table.push(root_row); + } + root_table +} + +#[inline] +fn fft_dispatch( + input: &mut [F], + zero_factor: Option, + root_table: Option<&FftRootTable>, +) { + let computed_root_table = if root_table.is_some() { + None + } else { + Some(fft_root_table(input.len())) + }; + let used_root_table = root_table.or(computed_root_table.as_ref()).unwrap(); + + fft_classic(input, zero_factor.unwrap_or(0), used_root_table); +} + +#[inline] +pub fn fft(poly: PolynomialCoeffs) -> PolynomialValues { + fft_with_options(poly, None, None) +} + +#[inline] +pub fn fft_with_options( + poly: PolynomialCoeffs, + zero_factor: Option, + root_table: Option<&FftRootTable>, +) -> PolynomialValues { + let PolynomialCoeffs { coeffs: mut buffer } = poly; + fft_dispatch(&mut buffer, zero_factor, root_table); + PolynomialValues::new(buffer) +} + +#[inline] +pub fn ifft(poly: PolynomialValues) -> PolynomialCoeffs { + ifft_with_options(poly, None, None) +} + +pub fn ifft_with_options( + poly: PolynomialValues, + zero_factor: Option, + root_table: Option<&FftRootTable>, +) -> PolynomialCoeffs { + let n = poly.len(); + let lg_n = log2_strict(n); + let n_inv = F::inverse_2exp(lg_n); + + let PolynomialValues { values: mut buffer } = poly; + fft_dispatch(&mut buffer, zero_factor, root_table); + + // We reverse all values except the first, and divide each by n. + buffer[0] *= n_inv; + buffer[n / 2] *= n_inv; + for i in 1..(n / 2) { + let j = n - i; + let coeffs_i = buffer[j] * n_inv; + let coeffs_j = buffer[i] * n_inv; + buffer[i] = coeffs_i; + buffer[j] = coeffs_j; + } + PolynomialCoeffs { coeffs: buffer } +} + +/// Generic FFT implementation that works with both scalar and packed inputs. +#[unroll_for_loops] +fn fft_classic_simd( + values: &mut [P::Scalar], + r: usize, + lg_n: usize, + root_table: &FftRootTable, +) { + let lg_packed_width = log2_strict(P::WIDTH); // 0 when P is a scalar. + let packed_values = P::pack_slice_mut(values); + let packed_n = packed_values.len(); + debug_assert!(packed_n == 1 << (lg_n - lg_packed_width)); + + // Want the below for loop to unroll, hence the need for a literal. + // This loop will not run when P is a scalar. + assert!(lg_packed_width <= 4); + for lg_half_m in 0..4 { + if (r..min(lg_n, lg_packed_width)).contains(&lg_half_m) { + // Intuitively, we split values into m slices: subarr[0], ..., subarr[m - 1]. + // Each of those slices is split into two halves: subarr[j].left, + // subarr[j].right. We do (subarr[j].left[k], subarr[j].right[k]) + // := f(subarr[j].left[k], subarr[j].right[k], omega[k]), + // where f(u, v, omega) = (u + omega * v, u - omega * v). + let half_m = 1 << lg_half_m; + + // Set omega to root_table[lg_half_m][0..half_m] but repeated. + let mut omega = P::default(); + for (j, omega_j) in omega.as_slice_mut().iter_mut().enumerate() { + *omega_j = root_table[lg_half_m][j % half_m]; + } + + for k in (0..packed_n).step_by(2) { + // We have two vectors and want to do math on pairs of adjacent elements (or for + // lg_half_m > 0, pairs of adjacent blocks of elements). .interleave does the + // appropriate shuffling and is its own inverse. + let (u, v) = packed_values[k].interleave(packed_values[k + 1], half_m); + let t = omega * v; + (packed_values[k], packed_values[k + 1]) = (u + t).interleave(u - t, half_m); + } + } + } + + // We've already done the first lg_packed_width (if they were required) + // iterations. + let s = max(r, lg_packed_width); + + for lg_half_m in s..lg_n { + let lg_m = lg_half_m + 1; + let m = 1 << lg_m; // Subarray size (in field elements). + let packed_m = m >> lg_packed_width; // Subarray size (in vectors). + let half_packed_m = packed_m / 2; + debug_assert!(half_packed_m != 0); + + // omega values for this iteration, as slice of vectors + let omega_table = P::pack_slice(&root_table[lg_half_m][..]); + for k in (0..packed_n).step_by(packed_m) { + for j in 0..half_packed_m { + let omega = omega_table[j]; + let t = omega * packed_values[k + half_packed_m + j]; + let u = packed_values[k + j]; + packed_values[k + j] = u + t; + packed_values[k + half_packed_m + j] = u - t; + } + } + } +} + +/// FFT implementation based on Section 32.3 of "Introduction to +/// Algorithms" by Cormen et al. +/// +/// The parameter r signifies that the first 1/2^r of the entries of +/// input may be non-zero, but the last 1 - 1/2^r entries are +/// definitely zero. +pub(crate) fn fft_classic(values: &mut [F], r: usize, root_table: &FftRootTable) { + reverse_index_bits_in_place(values); + + let n = values.len(); + let lg_n = log2_strict(n); + + if root_table.len() != lg_n { + panic!( + "Expected root table of length {}, but it was {}.", + lg_n, + root_table.len() + ); + } + + // After reverse_index_bits, the only non-zero elements of values + // are at indices i*2^r for i = 0..n/2^r. The loop below copies + // the value at i*2^r to the positions [i*2^r + 1, i*2^r + 2, ..., + // (i+1)*2^r - 1]; i.e. it replaces the 2^r - 1 zeros following + // element i*2^r with the value at i*2^r. This corresponds to the + // first r rounds of the FFT when there are 2^r zeros at the end + // of the original input. + if r > 0 { + // if r == 0 then this loop is a noop. + let mask = !((1 << r) - 1); + for i in 0..n { + values[i] = values[i & mask]; + } + } + + let lg_packed_width = log2_strict(::Packing::WIDTH); + if lg_n <= lg_packed_width { + // Need the slice to be at least the width of two packed vectors for the + // vectorized version to work. Do this tiny problem in scalar. + fft_classic_simd::(values, r, lg_n, root_table); + } else { + fft_classic_simd::<::Packing>(values, r, lg_n, root_table); + } +} + +#[cfg(test)] +mod tests { + use alloc::vec::Vec; + + use plonky2_util::{log2_ceil, log2_strict}; + + use crate::fft::{fft, fft_with_options, ifft}; + use crate::goldilocks_field::GoldilocksField; + use crate::polynomial::{PolynomialCoeffs, PolynomialValues}; + use crate::types::Field; + + #[test] + fn fft_and_ifft() { + type F = GoldilocksField; + let degree = 200usize; + let degree_padded = degree.next_power_of_two(); + + // Create a vector of coeffs; the first degree of them are + // "random", the last degree_padded-degree of them are zero. + let coeffs = (0..degree) + .map(|i| F::from_canonical_usize(i * 1337 % 100)) + .chain(core::iter::repeat(F::ZERO).take(degree_padded - degree)) + .collect::>(); + assert_eq!(coeffs.len(), degree_padded); + let coefficients = PolynomialCoeffs { coeffs }; + + let points = fft(coefficients.clone()); + assert_eq!(points, evaluate_naive(&coefficients)); + + let interpolated_coefficients = ifft(points); + for i in 0..degree { + assert_eq!(interpolated_coefficients.coeffs[i], coefficients.coeffs[i]); + } + for i in degree..degree_padded { + assert_eq!(interpolated_coefficients.coeffs[i], F::ZERO); + } + + for r in 0..4 { + // expand coefficients by factor 2^r by filling with zeros + let zero_tail = coefficients.lde(r); + assert_eq!( + fft(zero_tail.clone()), + fft_with_options(zero_tail, Some(r), None) + ); + } + } + + fn evaluate_naive(coefficients: &PolynomialCoeffs) -> PolynomialValues { + let degree = coefficients.len(); + let degree_padded = 1 << log2_ceil(degree); + + let coefficients_padded = coefficients.padded(degree_padded); + evaluate_naive_power_of_2(&coefficients_padded) + } + + fn evaluate_naive_power_of_2( + coefficients: &PolynomialCoeffs, + ) -> PolynomialValues { + let degree = coefficients.len(); + let degree_log = log2_strict(degree); + + let subgroup = F::two_adic_subgroup(degree_log); + + let values = subgroup + .into_iter() + .map(|x| evaluate_at_naive(coefficients, x)) + .collect(); + PolynomialValues::new(values) + } + + fn evaluate_at_naive(coefficients: &PolynomialCoeffs, point: F) -> F { + let mut sum = F::ZERO; + let mut point_power = F::ONE; + for &c in &coefficients.coeffs { + sum += c * point_power; + point_power *= point; + } + sum + } +} diff --git a/field/src/field_testing.rs b/field/src/field_testing.rs new file mode 100644 index 000000000..16dd87f49 --- /dev/null +++ b/field/src/field_testing.rs @@ -0,0 +1,206 @@ +use crate::extension::{Extendable, Frobenius}; +use crate::ops::Square; +use crate::types::{Field, Sample}; + +#[macro_export] +macro_rules! test_field_arithmetic { + ($field:ty) => { + mod field_arithmetic { + use alloc::vec::Vec; + + use num::bigint::BigUint; + use rand::rngs::OsRng; + use rand::{Rng, RngCore}; + use $crate::types::{Field, Sample}; + + #[test] + fn modular_reduction() { + let mut rng = OsRng; + for _ in 0..10 { + let x_lo = rng.next_u64(); + let x_hi = rng.next_u32(); + let x = (x_lo as u128) + ((x_hi as u128) << 64); + let a = <$field>::from_noncanonical_u128(x); + let b = <$field>::from_noncanonical_u96((x_lo, x_hi)); + assert_eq!(a, b); + } + } + + #[test] + fn batch_inversion() { + for n in 0..20 { + let xs = (1..=n as u64) + .map(|i| <$field>::from_canonical_u64(i)) + .collect::>(); + let invs = <$field>::batch_multiplicative_inverse(&xs); + assert_eq!(invs.len(), n); + for (x, inv) in xs.into_iter().zip(invs) { + assert_eq!(x * inv, <$field>::ONE); + } + } + } + + #[test] + fn primitive_root_order() { + let max_power = 8.min(<$field>::TWO_ADICITY); + for n_power in 0..max_power { + let root = <$field>::primitive_root_of_unity(n_power); + let order = <$field>::generator_order(root); + assert_eq!(order, 1 << n_power, "2^{}'th primitive root", n_power); + } + } + + #[test] + fn negation() { + type F = $field; + + for x in [F::ZERO, F::ONE, F::TWO, F::NEG_ONE] { + assert_eq!(x + -x, F::ZERO); + } + } + + #[test] + fn exponentiation() { + type F = $field; + + assert_eq!(F::ZERO.exp_u64(0), ::ONE); + assert_eq!(F::ONE.exp_u64(0), ::ONE); + assert_eq!(F::TWO.exp_u64(0), ::ONE); + + assert_eq!(F::ZERO.exp_u64(1), ::ZERO); + assert_eq!(F::ONE.exp_u64(1), ::ONE); + assert_eq!(F::TWO.exp_u64(1), ::TWO); + + assert_eq!(F::ZERO.kth_root_u64(1), ::ZERO); + assert_eq!(F::ONE.kth_root_u64(1), ::ONE); + assert_eq!(F::TWO.kth_root_u64(1), ::TWO); + + for power in 1..10 { + if F::is_monomial_permutation_u64(power) { + let x = F::rand(); + assert_eq!(x.exp_u64(power).kth_root_u64(power), x); + } + } + } + + #[test] + fn exponentiation_large() { + type F = $field; + + let mut rng = OsRng; + + let base = F::rand(); + let pow = BigUint::from(rng.gen::()); + let cycles = rng.gen::(); + let mul_group_order = F::order() - 1u32; + let big_pow = &pow + &mul_group_order * cycles; + let big_pow_wrong = &pow + &mul_group_order * cycles + 1u32; + + assert_eq!(base.exp_biguint(&pow), base.exp_biguint(&big_pow)); + assert_ne!(base.exp_biguint(&pow), base.exp_biguint(&big_pow_wrong)); + } + + #[test] + fn inverses() { + type F = $field; + + let x = F::rand(); + let x1 = x.inverse(); + let x2 = x1.inverse(); + let x3 = x2.inverse(); + + assert_eq!(x, x2); + assert_eq!(x1, x3); + } + } + }; +} + +#[allow(clippy::eq_op)] +pub(crate) fn test_add_neg_sub_mul, const D: usize>() { + let x = BF::Extension::rand(); + let y = BF::Extension::rand(); + let z = BF::Extension::rand(); + assert_eq!(x + (-x), BF::Extension::ZERO); + assert_eq!(-x, BF::Extension::ZERO - x); + assert_eq!(x + x, x * BF::Extension::TWO); + assert_eq!(x * (-x), -x.square()); + assert_eq!(x + y, y + x); + assert_eq!(x * y, y * x); + assert_eq!(x * (y * z), (x * y) * z); + assert_eq!(x - (y + z), (x - y) - z); + assert_eq!((x + y) - z, x + (y - z)); + assert_eq!(x * (y + z), x * y + x * z); +} + +pub(crate) fn test_inv_div, const D: usize>() { + let x = BF::Extension::rand(); + let y = BF::Extension::rand(); + let z = BF::Extension::rand(); + assert_eq!(x * x.inverse(), BF::Extension::ONE); + assert_eq!(x.inverse() * x, BF::Extension::ONE); + assert_eq!(x.square().inverse(), x.inverse().square()); + assert_eq!((x / y) * y, x); + assert_eq!(x / (y * z), (x / y) / z); + assert_eq!((x * y) / z, x * (y / z)); +} + +pub(crate) fn test_frobenius, const D: usize>() { + let x = BF::Extension::rand(); + assert_eq!(x.exp_biguint(&BF::order()), x.frobenius()); + for count in 2..D { + assert_eq!( + x.repeated_frobenius(count), + (0..count).fold(x, |acc, _| acc.frobenius()) + ); + } +} + +pub(crate) fn test_field_order, const D: usize>() { + let x = BF::Extension::rand(); + assert_eq!( + x.exp_biguint(&(BF::Extension::order() - 1u8)), + BF::Extension::ONE + ); +} + +pub(crate) fn test_power_of_two_gen, const D: usize>() { + assert_eq!( + BF::Extension::MULTIPLICATIVE_GROUP_GENERATOR + .exp_biguint(&(BF::Extension::order() >> BF::Extension::TWO_ADICITY)), + BF::Extension::POWER_OF_TWO_GENERATOR, + ); + assert_eq!( + BF::Extension::POWER_OF_TWO_GENERATOR + .exp_u64(1 << (BF::Extension::TWO_ADICITY - BF::TWO_ADICITY)), + BF::POWER_OF_TWO_GENERATOR.into() + ); +} + +#[macro_export] +macro_rules! test_field_extension { + ($field:ty, $d:expr) => { + mod field_extension { + #[test] + fn test_add_neg_sub_mul() { + $crate::field_testing::test_add_neg_sub_mul::<$field, $d>(); + } + #[test] + fn test_inv_div() { + $crate::field_testing::test_inv_div::<$field, $d>(); + } + #[test] + fn test_frobenius() { + $crate::field_testing::test_frobenius::<$field, $d>(); + } + #[test] + fn test_field_order() { + $crate::field_testing::test_field_order::<$field, $d>(); + } + #[test] + fn test_power_of_two_gen() { + $crate::field_testing::test_power_of_two_gen::<$field, $d>(); + } + } + }; +} diff --git a/field/src/goldilocks_extensions.rs b/field/src/goldilocks_extensions.rs new file mode 100644 index 000000000..85eec543c --- /dev/null +++ b/field/src/goldilocks_extensions.rs @@ -0,0 +1,497 @@ +use core::ops::Mul; + +use static_assertions::const_assert; + +use crate::extension::quadratic::QuadraticExtension; +use crate::extension::quartic::QuarticExtension; +use crate::extension::quintic::QuinticExtension; +use crate::extension::{Extendable, Frobenius}; +use crate::goldilocks_field::{reduce160, GoldilocksField}; +use crate::types::Field; + +impl Frobenius<1> for GoldilocksField {} + +impl Extendable<2> for GoldilocksField { + type Extension = QuadraticExtension; + + // Verifiable in Sage with + // `R. = GF(p)[]; assert (x^2 - 7).is_irreducible()`. + const W: Self = Self(7); + + // DTH_ROOT = W^((ORDER - 1)/2) + const DTH_ROOT: Self = Self(18446744069414584320); + + const EXT_MULTIPLICATIVE_GROUP_GENERATOR: [Self; 2] = + [Self(18081566051660590251), Self(16121475356294670766)]; + + const EXT_POWER_OF_TWO_GENERATOR: [Self; 2] = [Self(0), Self(15659105665374529263)]; +} + +impl Mul for QuadraticExtension { + #[inline] + fn mul(self, rhs: Self) -> Self { + let Self([a0, a1]) = self; + let Self([b0, b1]) = rhs; + let c = ext2_mul([a0.0, a1.0], [b0.0, b1.0]); + Self(c) + } +} + +impl Extendable<4> for GoldilocksField { + type Extension = QuarticExtension; + + const W: Self = Self(7); + + // DTH_ROOT = W^((ORDER - 1)/4) + const DTH_ROOT: Self = Self(281474976710656); + + const EXT_MULTIPLICATIVE_GROUP_GENERATOR: [Self; 4] = [ + Self(5024755240244648895), + Self(13227474371289740625), + Self(3912887029498544536), + Self(3900057112666848848), + ]; + + const EXT_POWER_OF_TWO_GENERATOR: [Self; 4] = + [Self(0), Self(0), Self(0), Self(12587610116473453104)]; +} + +impl Mul for QuarticExtension { + #[inline] + fn mul(self, rhs: Self) -> Self { + let Self([a0, a1, a2, a3]) = self; + let Self([b0, b1, b2, b3]) = rhs; + let c = ext4_mul([a0.0, a1.0, a2.0, a3.0], [b0.0, b1.0, b2.0, b3.0]); + Self(c) + } +} + +impl Extendable<5> for GoldilocksField { + type Extension = QuinticExtension; + + const W: Self = Self(3); + + // DTH_ROOT = W^((ORDER - 1)/5) + const DTH_ROOT: Self = Self(1041288259238279555); + + const EXT_MULTIPLICATIVE_GROUP_GENERATOR: [Self; 5] = [ + Self(2899034827742553394), + Self(13012057356839176729), + Self(14593811582388663055), + Self(7722900811313895436), + Self(4557222484695340057), + ]; + + const EXT_POWER_OF_TWO_GENERATOR: [Self; 5] = [ + Self::POWER_OF_TWO_GENERATOR, + Self(0), + Self(0), + Self(0), + Self(0), + ]; +} + +impl Mul for QuinticExtension { + #[inline] + fn mul(self, rhs: Self) -> Self { + let Self([a0, a1, a2, a3, a4]) = self; + let Self([b0, b1, b2, b3, b4]) = rhs; + let c = ext5_mul( + [a0.0, a1.0, a2.0, a3.0, a4.0], + [b0.0, b1.0, b2.0, b3.0, b4.0], + ); + Self(c) + } +} + +/* + * The functions extD_add_prods[0-4] are helper functions for + * computing products for extensions of degree D over the Goldilocks + * field. They are faster than the generic method because all + * reductions are delayed until the end which means only one per + * result coefficient is necessary. + */ + +/// Return `a`, `b` such that `a + b*2^128 = 3*(x + y*2^128)` with `a < 2^128` +/// and `b < 2^32`. +#[inline(always)] +const 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 + y*2^128)` with `a < 2^128` +/// and `b < 2^32`. +#[inline(always)] +const fn u160_times_7(x: u128, y: u32) -> (u128, u32) { + let (d, br) = (x << 3).overflowing_sub(x); + // NB: subtracting the borrow can't underflow + (d, 7 * y + (x >> (128 - 3)) as u32 - br as u32) +} + +/* + * Quadratic multiplication and squaring + */ + +#[inline(always)] +fn ext2_add_prods0(a: &[u64; 2], b: &[u64; 2]) -> GoldilocksField { + // Computes a0 * b0 + W * a1 * b1; + let [a0, a1] = *a; + let [b0, b1] = *b; + + let cy; + + // W * a1 * b1 + let (mut cumul_lo, mut cumul_hi) = u160_times_7((a1 as u128) * (b1 as u128), 0u32); + + // a0 * b0 + (cumul_lo, cy) = cumul_lo.overflowing_add((a0 as u128) * (b0 as u128)); + cumul_hi += cy as u32; + + unsafe { reduce160(cumul_lo, cumul_hi) } +} + +#[inline(always)] +fn ext2_add_prods1(a: &[u64; 2], b: &[u64; 2]) -> GoldilocksField { + // Computes a0 * b1 + a1 * b0; + let [a0, a1] = *a; + let [b0, b1] = *b; + + let cy; + + // a0 * b1 + let mut cumul_lo = (a0 as u128) * (b1 as u128); + + // a1 * b0 + (cumul_lo, cy) = cumul_lo.overflowing_add((a1 as u128) * (b0 as u128)); + let cumul_hi = cy as u32; + + unsafe { reduce160(cumul_lo, cumul_hi) } +} + +/// Multiply a and b considered as elements of GF(p^2). +#[inline(always)] +pub(crate) fn ext2_mul(a: [u64; 2], b: [u64; 2]) -> [GoldilocksField; 2] { + // The code in ext2_add_prods[01] assumes the quadratic extension + // generator is 7. + const_assert!(>::W.0 == 7u64); + + let c0 = ext2_add_prods0(&a, &b); + let c1 = ext2_add_prods1(&a, &b); + [c0, c1] +} + +/* + * Quartic multiplication and squaring + */ + +#[inline(always)] +fn ext4_add_prods0(a: &[u64; 4], b: &[u64; 4]) -> GoldilocksField { + // Computes c0 = a0 * b0 + W * (a1 * b3 + a2 * b2 + a3 * b1) + + let [a0, a1, a2, a3] = *a; + let [b0, b1, b2, b3] = *b; + + let mut cy; + + // a1 * b3 + let mut cumul_lo = (a1 as u128) * (b3 as u128); + + // a2 * b2 + (cumul_lo, cy) = cumul_lo.overflowing_add((a2 as u128) * (b2 as u128)); + let mut cumul_hi = cy as u32; + + // a3 * b1 + (cumul_lo, cy) = cumul_lo.overflowing_add((a3 as u128) * (b1 as u128)); + cumul_hi += cy as u32; + + // * W + (cumul_lo, cumul_hi) = u160_times_7(cumul_lo, cumul_hi); + + // a0 * b0 + (cumul_lo, cy) = cumul_lo.overflowing_add((a0 as u128) * (b0 as u128)); + cumul_hi += cy as u32; + + unsafe { reduce160(cumul_lo, cumul_hi) } +} + +#[inline(always)] +fn ext4_add_prods1(a: &[u64; 4], b: &[u64; 4]) -> GoldilocksField { + // Computes c1 = a0 * b1 + a1 * b0 + W * (a2 * b3 + a3 * b2); + + let [a0, a1, a2, a3] = *a; + let [b0, b1, b2, b3] = *b; + + let mut cy; + + // a2 * b3 + let mut cumul_lo = (a2 as u128) * (b3 as u128); + + // a3 * b2 + (cumul_lo, cy) = cumul_lo.overflowing_add((a3 as u128) * (b2 as u128)); + let mut cumul_hi = cy as u32; + + // * W + (cumul_lo, cumul_hi) = u160_times_7(cumul_lo, cumul_hi); + + // a0 * b1 + (cumul_lo, cy) = cumul_lo.overflowing_add((a0 as u128) * (b1 as u128)); + cumul_hi += cy as u32; + + // a1 * b0 + (cumul_lo, cy) = cumul_lo.overflowing_add((a1 as u128) * (b0 as u128)); + cumul_hi += cy as u32; + + unsafe { reduce160(cumul_lo, cumul_hi) } +} + +#[inline(always)] +fn ext4_add_prods2(a: &[u64; 4], b: &[u64; 4]) -> GoldilocksField { + // Computes c2 = a0 * b2 + a1 * b1 + a2 * b0 + W * a3 * b3; + + let [a0, a1, a2, a3] = *a; + let [b0, b1, b2, b3] = *b; + + let mut cy; + + // W * a3 * b3 + let (mut cumul_lo, mut cumul_hi) = u160_times_7((a3 as u128) * (b3 as u128), 0u32); + + // a0 * b2 + (cumul_lo, cy) = cumul_lo.overflowing_add((a0 as u128) * (b2 as u128)); + cumul_hi += cy as u32; + + // a1 * b1 + (cumul_lo, cy) = cumul_lo.overflowing_add((a1 as u128) * (b1 as u128)); + cumul_hi += cy as u32; + + // a2 * b0 + (cumul_lo, cy) = cumul_lo.overflowing_add((a2 as u128) * (b0 as u128)); + cumul_hi += cy as u32; + + unsafe { reduce160(cumul_lo, cumul_hi) } +} + +#[inline(always)] +fn ext4_add_prods3(a: &[u64; 4], b: &[u64; 4]) -> GoldilocksField { + // Computes c3 = a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0; + + let [a0, a1, a2, a3] = *a; + let [b0, b1, b2, b3] = *b; + + let mut cy; + + // a0 * b3 + let mut cumul_lo = (a0 as u128) * (b3 as u128); + + // a1 * b2 + (cumul_lo, cy) = cumul_lo.overflowing_add((a1 as u128) * (b2 as u128)); + let mut cumul_hi = cy as u32; + + // a2 * b1 + (cumul_lo, cy) = cumul_lo.overflowing_add((a2 as u128) * (b1 as u128)); + cumul_hi += cy as u32; + + // a3 * b0 + (cumul_lo, cy) = cumul_lo.overflowing_add((a3 as u128) * (b0 as u128)); + cumul_hi += cy as u32; + + unsafe { reduce160(cumul_lo, cumul_hi) } +} + +/// Multiply a and b considered as elements of GF(p^4). +#[inline(always)] +pub(crate) fn ext4_mul(a: [u64; 4], b: [u64; 4]) -> [GoldilocksField; 4] { + // The code in ext4_add_prods[0-3] assumes the quartic extension + // generator is 7. + const_assert!(>::W.0 == 7u64); + + let c0 = ext4_add_prods0(&a, &b); + let c1 = ext4_add_prods1(&a, &b); + let c2 = ext4_add_prods2(&a, &b); + let c3 = ext4_add_prods3(&a, &b); + [c0, c1, c2, c3] +} + +/* + * Quintic multiplication and squaring + */ + +#[inline(always)] +fn ext5_add_prods0(a: &[u64; 5], b: &[u64; 5]) -> GoldilocksField { + // Computes c0 = a0 * b0 + W * (a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1) + + let [a0, a1, a2, a3, a4] = *a; + let [b0, b1, b2, b3, b4] = *b; + + let mut cy; + + // a1 * b4 + let mut cumul_lo = (a1 as u128) * (b4 as u128); + + // a2 * b3 + (cumul_lo, cy) = cumul_lo.overflowing_add((a2 as u128) * (b3 as u128)); + let mut cumul_hi = cy as u32; + + // a3 * b2 + (cumul_lo, cy) = cumul_lo.overflowing_add((a3 as u128) * (b2 as u128)); + cumul_hi += cy as u32; + + // a4 * b1 + (cumul_lo, cy) = cumul_lo.overflowing_add((a4 as u128) * (b1 as u128)); + cumul_hi += cy as u32; + + // * W + (cumul_lo, cumul_hi) = u160_times_3(cumul_lo, cumul_hi); + + // a0 * b0 + (cumul_lo, cy) = cumul_lo.overflowing_add((a0 as u128) * (b0 as u128)); + cumul_hi += cy as u32; + + unsafe { reduce160(cumul_lo, cumul_hi) } +} + +#[inline(always)] +fn ext5_add_prods1(a: &[u64; 5], b: &[u64; 5]) -> GoldilocksField { + // Computes c1 = a0 * b1 + a1 * b0 + W * (a2 * b4 + a3 * b3 + a4 * b2); + + let [a0, a1, a2, a3, a4] = *a; + let [b0, b1, b2, b3, b4] = *b; + + let mut cy; + + // a2 * b4 + let mut cumul_lo = (a2 as u128) * (b4 as u128); + + // a3 * b3 + (cumul_lo, cy) = cumul_lo.overflowing_add((a3 as u128) * (b3 as u128)); + let mut cumul_hi = cy as u32; + + // a4 * b2 + (cumul_lo, cy) = cumul_lo.overflowing_add((a4 as u128) * (b2 as u128)); + cumul_hi += cy as u32; + + // * W + (cumul_lo, cumul_hi) = u160_times_3(cumul_lo, cumul_hi); + + // a0 * b1 + (cumul_lo, cy) = cumul_lo.overflowing_add((a0 as u128) * (b1 as u128)); + cumul_hi += cy as u32; + + // a1 * b0 + (cumul_lo, cy) = cumul_lo.overflowing_add((a1 as u128) * (b0 as u128)); + cumul_hi += cy as u32; + + unsafe { reduce160(cumul_lo, cumul_hi) } +} + +#[inline(always)] +fn ext5_add_prods2(a: &[u64; 5], b: &[u64; 5]) -> GoldilocksField { + // Computes c2 = a0 * b2 + a1 * b1 + a2 * b0 + W * (a3 * b4 + a4 * b3); + + let [a0, a1, a2, a3, a4] = *a; + let [b0, b1, b2, b3, b4] = *b; + + let mut cy; + + // a3 * b4 + let mut cumul_lo = (a3 as u128) * (b4 as u128); + + // a4 * b3 + (cumul_lo, cy) = cumul_lo.overflowing_add((a4 as u128) * (b3 as u128)); + let mut cumul_hi = cy as u32; + + // * W + (cumul_lo, cumul_hi) = u160_times_3(cumul_lo, cumul_hi); + + // a0 * b2 + (cumul_lo, cy) = cumul_lo.overflowing_add((a0 as u128) * (b2 as u128)); + cumul_hi += cy as u32; + + // a1 * b1 + (cumul_lo, cy) = cumul_lo.overflowing_add((a1 as u128) * (b1 as u128)); + cumul_hi += cy as u32; + + // a2 * b0 + (cumul_lo, cy) = cumul_lo.overflowing_add((a2 as u128) * (b0 as u128)); + cumul_hi += cy as u32; + + unsafe { reduce160(cumul_lo, cumul_hi) } +} + +#[inline(always)] +fn ext5_add_prods3(a: &[u64; 5], b: &[u64; 5]) -> GoldilocksField { + // Computes c3 = a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0 + W * a4 * b4; + + let [a0, a1, a2, a3, a4] = *a; + let [b0, b1, b2, b3, b4] = *b; + + let mut cy; + + // W * a4 * b4 + let (mut cumul_lo, mut cumul_hi) = u160_times_3((a4 as u128) * (b4 as u128), 0u32); + + // a0 * b3 + (cumul_lo, cy) = cumul_lo.overflowing_add((a0 as u128) * (b3 as u128)); + cumul_hi += cy as u32; + + // a1 * b2 + (cumul_lo, cy) = cumul_lo.overflowing_add((a1 as u128) * (b2 as u128)); + cumul_hi += cy as u32; + + // a2 * b1 + (cumul_lo, cy) = cumul_lo.overflowing_add((a2 as u128) * (b1 as u128)); + cumul_hi += cy as u32; + + // a3 * b0 + (cumul_lo, cy) = cumul_lo.overflowing_add((a3 as u128) * (b0 as u128)); + cumul_hi += cy as u32; + + unsafe { reduce160(cumul_lo, cumul_hi) } +} + +#[inline(always)] +fn ext5_add_prods4(a: &[u64; 5], b: &[u64; 5]) -> GoldilocksField { + // Computes c4 = a0 * b4 + a1 * b3 + a2 * b2 + a3 * b1 + a4 * b0; + + let [a0, a1, a2, a3, a4] = *a; + let [b0, b1, b2, b3, b4] = *b; + + let mut cy; + + // a0 * b4 + let mut cumul_lo = (a0 as u128) * (b4 as u128); + + // a1 * b3 + (cumul_lo, cy) = cumul_lo.overflowing_add((a1 as u128) * (b3 as u128)); + let mut cumul_hi = cy as u32; + + // a2 * b2 + (cumul_lo, cy) = cumul_lo.overflowing_add((a2 as u128) * (b2 as u128)); + cumul_hi += cy as u32; + + // a3 * b1 + (cumul_lo, cy) = cumul_lo.overflowing_add((a3 as u128) * (b1 as u128)); + cumul_hi += cy as u32; + + // a4 * b0 + (cumul_lo, cy) = cumul_lo.overflowing_add((a4 as u128) * (b0 as u128)); + cumul_hi += cy as u32; + + unsafe { reduce160(cumul_lo, cumul_hi) } +} + +/// Multiply a and b considered as elements of GF(p^5). +#[inline(always)] +pub(crate) fn ext5_mul(a: [u64; 5], b: [u64; 5]) -> [GoldilocksField; 5] { + // The code in ext5_add_prods[0-4] assumes the quintic extension + // generator is 3. + const_assert!(>::W.0 == 3u64); + + let c0 = ext5_add_prods0(&a, &b); + let c1 = ext5_add_prods1(&a, &b); + let c2 = ext5_add_prods2(&a, &b); + let c3 = ext5_add_prods3(&a, &b); + let c4 = ext5_add_prods4(&a, &b); + [c0, c1, c2, c3, c4] +} diff --git a/field/src/goldilocks_field.rs b/field/src/goldilocks_field.rs new file mode 100644 index 000000000..8d61734b7 --- /dev/null +++ b/field/src/goldilocks_field.rs @@ -0,0 +1,468 @@ +use core::fmt::{self, Debug, Display, Formatter}; +use core::hash::{Hash, Hasher}; +use core::iter::{Product, Sum}; +use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + +use num::{BigUint, Integer, ToPrimitive}; +use plonky2_util::{assume, branch_hint}; +use serde::{Deserialize, Serialize}; + +use crate::ops::Square; +use crate::types::{Field, Field64, PrimeField, PrimeField64, Sample}; + +const EPSILON: u64 = (1 << 32) - 1; + +/// A field selected to have fast reduction. +/// +/// Its order is 2^64 - 2^32 + 1. +/// ```ignore +/// P = 2**64 - EPSILON +/// = 2**64 - 2**32 + 1 +/// = 2**32 * (2**32 - 1) + 1 +/// ``` +#[derive(Copy, Clone, Serialize, Deserialize)] +#[repr(transparent)] +pub struct GoldilocksField(pub u64); + +impl Default for GoldilocksField { + fn default() -> Self { + Self::ZERO + } +} + +impl PartialEq for GoldilocksField { + fn eq(&self, other: &Self) -> bool { + self.to_canonical_u64() == other.to_canonical_u64() + } +} + +impl Eq for GoldilocksField {} + +impl Hash for GoldilocksField { + fn hash(&self, state: &mut H) { + state.write_u64(self.to_canonical_u64()) + } +} + +impl Display for GoldilocksField { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Display::fmt(&self.to_canonical_u64(), f) + } +} + +impl Debug for GoldilocksField { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Debug::fmt(&self.to_canonical_u64(), f) + } +} + +impl Sample for GoldilocksField { + #[inline] + fn sample(rng: &mut R) -> Self + where + R: rand::RngCore + ?Sized, + { + use rand::Rng; + Self::from_canonical_u64(rng.gen_range(0..Self::ORDER)) + } +} + +impl Field for GoldilocksField { + const ZERO: Self = Self(0); + const ONE: Self = Self(1); + const TWO: Self = Self(2); + const NEG_ONE: Self = Self(Self::ORDER - 1); + + const TWO_ADICITY: usize = 32; + const CHARACTERISTIC_TWO_ADICITY: usize = Self::TWO_ADICITY; + + // Sage: `g = GF(p).multiplicative_generator()` + const MULTIPLICATIVE_GROUP_GENERATOR: Self = Self(7); + + // Sage: + // ``` + // g_2 = g^((p - 1) / 2^32) + // g_2.multiplicative_order().factor() + // ``` + const POWER_OF_TWO_GENERATOR: Self = Self(1753635133440165772); + + const BITS: usize = 64; + + fn order() -> BigUint { + Self::ORDER.into() + } + fn characteristic() -> BigUint { + Self::order() + } + + /// Returns the inverse of the field element, using Fermat's little theorem. + /// The inverse of `a` is computed as `a^(p-2)`, where `p` is the prime + /// order of the field. + /// + /// Mathematically, this is equivalent to: + /// $a^(p-1) = 1 (mod p)$ + /// $a^(p-2) * a = 1 (mod p)$ + /// Therefore $a^(p-2) = a^-1 (mod p)$ + /// + /// The following code has been adapted from + /// winterfell/math/src/field/f64/mod.rs located at . + fn try_inverse(&self) -> Option { + if self.is_zero() { + return None; + } + + // compute base^(P - 2) using 72 multiplications + // The exponent P - 2 is represented in binary as: + // 0b1111111111111111111111111111111011111111111111111111111111111111 + + // compute base^11 + let t2 = self.square() * *self; + + // compute base^111 + let t3 = t2.square() * *self; + + // compute base^111111 (6 ones) + // repeatedly square t3 3 times and multiply by t3 + let t6 = exp_acc::<3>(t3, t3); + + // compute base^111111111111 (12 ones) + // repeatedly square t6 6 times and multiply by t6 + let t12 = exp_acc::<6>(t6, t6); + + // compute base^111111111111111111111111 (24 ones) + // repeatedly square t12 12 times and multiply by t12 + let t24 = exp_acc::<12>(t12, t12); + + // compute base^1111111111111111111111111111111 (31 ones) + // repeatedly square t24 6 times and multiply by t6 first. then square t30 and + // multiply by base + let t30 = exp_acc::<6>(t24, t6); + let t31 = t30.square() * *self; + + // compute base^111111111111111111111111111111101111111111111111111111111111111 + // repeatedly square t31 32 times and multiply by t31 + let t63 = exp_acc::<32>(t31, t31); + + // compute base^1111111111111111111111111111111011111111111111111111111111111111 + Some(t63.square() * *self) + } + + fn from_noncanonical_biguint(n: BigUint) -> Self { + Self(n.mod_floor(&Self::order()).to_u64().unwrap()) + } + + #[inline(always)] + fn from_canonical_u64(n: u64) -> Self { + debug_assert!(n < Self::ORDER); + Self(n) + } + + fn from_noncanonical_u96((n_lo, n_hi): (u64, u32)) -> Self { + reduce96((n_lo, n_hi)) + } + + fn from_noncanonical_u128(n: u128) -> Self { + reduce128(n) + } + + #[inline] + fn from_noncanonical_u64(n: u64) -> Self { + Self(n) + } + + #[inline] + fn from_noncanonical_i64(n: i64) -> Self { + Self::from_canonical_u64(if n < 0 { + // If n < 0, then this is guaranteed to overflow since + // both arguments have their high bit set, so the result + // is in the canonical range. + Self::ORDER.wrapping_add(n as u64) + } else { + n as u64 + }) + } + + #[inline] + fn multiply_accumulate(&self, x: Self, y: Self) -> Self { + // u64 + u64 * u64 cannot overflow. + reduce128((self.0 as u128) + (x.0 as u128) * (y.0 as u128)) + } +} + +impl PrimeField for GoldilocksField { + fn to_canonical_biguint(&self) -> BigUint { + self.to_canonical_u64().into() + } +} + +impl Field64 for GoldilocksField { + const ORDER: u64 = 0xFFFFFFFF00000001; + + #[inline] + unsafe fn add_canonical_u64(&self, rhs: u64) -> Self { + let (res_wrapped, carry) = self.0.overflowing_add(rhs); + // Add EPSILON * carry cannot overflow unless rhs is not in canonical form. + Self(res_wrapped + EPSILON * (carry as u64)) + } + + #[inline] + unsafe fn sub_canonical_u64(&self, rhs: u64) -> Self { + let (res_wrapped, borrow) = self.0.overflowing_sub(rhs); + // Sub EPSILON * carry cannot underflow unless rhs is not in canonical form. + Self(res_wrapped - EPSILON * (borrow as u64)) + } +} + +impl PrimeField64 for GoldilocksField { + #[inline] + fn to_canonical_u64(&self) -> u64 { + let mut c = self.0; + // We only need one condition subtraction, since 2 * ORDER would not fit in a + // u64. + if c >= Self::ORDER { + c -= Self::ORDER; + } + c + } + + #[inline(always)] + fn to_noncanonical_u64(&self) -> u64 { + self.0 + } +} + +impl Neg for GoldilocksField { + type Output = Self; + + #[inline] + fn neg(self) -> Self { + if self.is_zero() { + Self::ZERO + } else { + Self(Self::ORDER - self.to_canonical_u64()) + } + } +} + +impl Add for GoldilocksField { + type Output = Self; + + #[inline] + #[allow(clippy::suspicious_arithmetic_impl)] + fn add(self, rhs: Self) -> Self { + let (sum, over) = self.0.overflowing_add(rhs.0); + let (mut sum, over) = sum.overflowing_add((over as u64) * EPSILON); + if over { + // NB: self.0 > Self::ORDER && rhs.0 > Self::ORDER is necessary but not + // sufficient for double-overflow. + // This assume does two things: + // 1. If compiler knows that either self.0 or rhs.0 <= ORDER, then it can skip + // this check. + // 2. Hints to the compiler how rare this double-overflow is (thus handled + // better with a branch). + assume(self.0 > Self::ORDER && rhs.0 > Self::ORDER); + branch_hint(); + sum += EPSILON; // Cannot overflow. + } + Self(sum) + } +} + +impl AddAssign for GoldilocksField { + #[inline] + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs; + } +} + +impl Sum for GoldilocksField { + fn sum>(iter: I) -> Self { + iter.fold(Self::ZERO, |acc, x| acc + x) + } +} + +impl Sub for GoldilocksField { + type Output = Self; + + #[inline] + #[allow(clippy::suspicious_arithmetic_impl)] + fn sub(self, rhs: Self) -> Self { + let (diff, under) = self.0.overflowing_sub(rhs.0); + let (mut diff, under) = diff.overflowing_sub((under as u64) * EPSILON); + if under { + // NB: self.0 < EPSILON - 1 && rhs.0 > Self::ORDER is necessary but not + // sufficient for double-underflow. + // This assume does two things: + // 1. If compiler knows that either self.0 >= EPSILON - 1 or rhs.0 <= ORDER, + // then it can skip this check. + // 2. Hints to the compiler how rare this double-underflow is (thus handled + // better with a branch). + assume(self.0 < EPSILON - 1 && rhs.0 > Self::ORDER); + branch_hint(); + diff -= EPSILON; // Cannot underflow. + } + Self(diff) + } +} + +impl SubAssign for GoldilocksField { + #[inline] + fn sub_assign(&mut self, rhs: Self) { + *self = *self - rhs; + } +} + +impl Mul for GoldilocksField { + type Output = Self; + + #[inline] + fn mul(self, rhs: Self) -> Self { + reduce128((self.0 as u128) * (rhs.0 as u128)) + } +} + +impl MulAssign for GoldilocksField { + #[inline] + fn mul_assign(&mut self, rhs: Self) { + *self = *self * rhs; + } +} + +impl Product for GoldilocksField { + fn product>(iter: I) -> Self { + iter.fold(Self::ONE, |acc, x| acc * x) + } +} + +impl Div for GoldilocksField { + type Output = Self; + + #[allow(clippy::suspicious_arithmetic_impl)] + fn div(self, rhs: Self) -> Self::Output { + self * rhs.inverse() + } +} + +impl DivAssign for GoldilocksField { + fn div_assign(&mut self, rhs: Self) { + *self = *self / rhs; + } +} + +/// Fast addition modulo ORDER for x86-64. +/// This function is marked unsafe for the following reasons: +/// - It is only correct if x + y < 2**64 + ORDER = 0x1ffffffff00000001. +/// - It is only faster in some circumstances. In particular, on x86 it +/// overwrites both inputs in the registers, so its use is not recommended +/// when either input will be used again. +#[inline(always)] +#[cfg(target_arch = "x86_64")] +unsafe fn add_no_canonicalize_trashing_input(x: u64, y: u64) -> u64 { + let res_wrapped: u64; + let adjustment: u64; + core::arch::asm!( + "add {0}, {1}", + // Trick. The carry flag is set iff the addition overflowed. + // sbb x, y does x := x - y - CF. In our case, x and y are both {1:e}, so it simply does + // {1:e} := 0xffffffff on overflow and {1:e} := 0 otherwise. {1:e} is the low 32 bits of + // {1}; the high 32-bits are zeroed on write. In the end, we end up with 0xffffffff in {1} + // on overflow; this happens be EPSILON. + // Note that the CPU does not realize that the result of sbb x, x does not actually depend + // on x. We must write the result to a register that we know to be ready. We have a + // dependency on {1} anyway, so let's use it. + "sbb {1:e}, {1:e}", + inlateout(reg) x => res_wrapped, + inlateout(reg) y => adjustment, + options(pure, nomem, nostack), + ); + assume(x != 0 || (res_wrapped == y && adjustment == 0)); + assume(y != 0 || (res_wrapped == x && adjustment == 0)); + // Add EPSILON == subtract ORDER. + // Cannot overflow unless the assumption if x + y < 2**64 + ORDER is incorrect. + res_wrapped + adjustment +} + +#[inline(always)] +#[cfg(not(target_arch = "x86_64"))] +const unsafe fn add_no_canonicalize_trashing_input(x: u64, y: u64) -> u64 { + let (res_wrapped, carry) = x.overflowing_add(y); + // Below cannot overflow unless the assumption if x + y < 2**64 + ORDER is + // incorrect. + res_wrapped + EPSILON * (carry as u64) +} + +/// Reduces to a 64-bit value. The result might not be in canonical form; it +/// could be in between the field order and `2^64`. +#[inline] +fn reduce96((x_lo, x_hi): (u64, u32)) -> GoldilocksField { + let t1 = x_hi as u64 * EPSILON; + let t2 = unsafe { add_no_canonicalize_trashing_input(x_lo, t1) }; + GoldilocksField(t2) +} + +/// Reduces to a 64-bit value. The result might not be in canonical form; it +/// could be in between the field order and `2^64`. +#[inline] +fn reduce128(x: u128) -> GoldilocksField { + let (x_lo, x_hi) = split(x); // This is a no-op + let x_hi_hi = x_hi >> 32; + let x_hi_lo = x_hi & EPSILON; + + let (mut t0, borrow) = x_lo.overflowing_sub(x_hi_hi); + if borrow { + branch_hint(); // A borrow is exceedingly rare. It is faster to branch. + t0 -= EPSILON; // Cannot underflow. + } + let t1 = x_hi_lo * EPSILON; + let t2 = unsafe { add_no_canonicalize_trashing_input(t0, t1) }; + GoldilocksField(t2) +} + +#[inline] +const fn split(x: u128) -> (u64, u64) { + (x as u64, (x >> 64) as u64) +} + +/// Reduce the value x_lo + x_hi * 2^128 to an element in the +/// Goldilocks field. +/// +/// This function is marked 'unsafe' because correctness relies on the +/// unchecked assumption that x < 2^160 - 2^128 + 2^96. Further, +/// performance may degrade as x_hi increases beyond 2**40 or so. +#[inline(always)] +pub(crate) unsafe fn reduce160(x_lo: u128, x_hi: u32) -> GoldilocksField { + let x_hi = (x_lo >> 96) as u64 + ((x_hi as u64) << 32); // shld to form x_hi + let x_mid = (x_lo >> 64) as u32; // shr to form x_mid + let x_lo = x_lo as u64; + + // sub + jc (should fuse) + let (mut t0, borrow) = x_lo.overflowing_sub(x_hi); + if borrow { + // The maximum possible value of x is (2^64 - 1)^2 * 4 * 7 < 2^133, + // so x_hi < 2^37. A borrow will happen roughly one in 134 million + // times, so it's best to branch. + branch_hint(); + // NB: this assumes that x < 2^160 - 2^128 + 2^96. + t0 -= EPSILON; // Cannot underflow if x_hi is canonical. + } + // imul + let t1 = (x_mid as u64) * EPSILON; + // add, sbb, add + let t2 = add_no_canonicalize_trashing_input(t0, t1); + GoldilocksField(t2) +} + +/// Squares the base N number of times and multiplies the result by the tail +/// value. +#[inline(always)] +fn exp_acc(base: GoldilocksField, tail: GoldilocksField) -> GoldilocksField { + base.exp_power_of_2(N) * tail +} + +#[cfg(test)] +mod tests { + use crate::{test_field_arithmetic, test_prime_field_arithmetic}; + + test_prime_field_arithmetic!(crate::goldilocks_field::GoldilocksField); + test_field_arithmetic!(crate::goldilocks_field::GoldilocksField); +} diff --git a/field/src/interpolation.rs b/field/src/interpolation.rs new file mode 100644 index 000000000..cf57f8732 --- /dev/null +++ b/field/src/interpolation.rs @@ -0,0 +1,149 @@ +use alloc::vec::Vec; + +use plonky2_util::log2_ceil; + +use crate::fft::ifft; +use crate::polynomial::{PolynomialCoeffs, PolynomialValues}; +use crate::types::Field; + +/// Computes the unique degree < n interpolant of an arbitrary list of n (point, +/// value) pairs. +/// +/// Note that the implementation assumes that `F` is two-adic, in particular +/// that `2^{F::TWO_ADICITY} >= points.len()`. This leads to a simple FFT-based +/// implementation. +pub fn interpolant(points: &[(F, F)]) -> PolynomialCoeffs { + let n = points.len(); + let n_log = log2_ceil(n); + + let subgroup = F::two_adic_subgroup(n_log); + let barycentric_weights = barycentric_weights(points); + let subgroup_evals = subgroup + .into_iter() + .map(|x| interpolate(points, x, &barycentric_weights)) + .collect(); + + let mut coeffs = ifft(PolynomialValues::new(subgroup_evals)); + coeffs.trim(); + coeffs +} + +/// Interpolate the polynomial defined by an arbitrary set of (point, value) +/// pairs at the given point `x`. +pub fn interpolate(points: &[(F, F)], x: F, barycentric_weights: &[F]) -> F { + // If x is in the list of points, the Lagrange formula would divide by zero. + for &(x_i, y_i) in points { + if x_i == x { + return y_i; + } + } + + let l_x: F = points.iter().map(|&(x_i, _y_i)| x - x_i).product(); + + let sum = (0..points.len()) + .map(|i| { + let x_i = points[i].0; + let y_i = points[i].1; + let w_i = barycentric_weights[i]; + w_i / (x - x_i) * y_i + }) + .sum(); + + l_x * sum +} + +pub fn barycentric_weights(points: &[(F, F)]) -> Vec { + let n = points.len(); + F::batch_multiplicative_inverse( + &(0..n) + .map(|i| { + (0..n) + .filter(|&j| j != i) + .map(|j| points[i].0 - points[j].0) + .product::() + }) + .collect::>(), + ) +} + +/// Interpolate the linear polynomial passing through `points` on `x`. +pub fn interpolate2(points: [(F, F); 2], x: F) -> F { + // a0 -> a1 + // b0 -> b1 + // x -> a1 + (x-a0)*(b1-a1)/(b0-a0) + let (a0, a1) = points[0]; + let (b0, b1) = points[1]; + assert_ne!(a0, b0); + a1 + (x - a0) * (b1 - a1) / (b0 - a0) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::extension::quartic::QuarticExtension; + use crate::goldilocks_field::GoldilocksField; + use crate::polynomial::PolynomialCoeffs; + use crate::types::{Field, Sample}; + + #[test] + fn interpolant_random() { + type F = GoldilocksField; + + for deg in 0..10 { + let domain = F::rand_vec(deg); + let coeffs = F::rand_vec(deg); + let coeffs = PolynomialCoeffs { coeffs }; + + let points = eval_naive(&coeffs, &domain); + assert_eq!(interpolant(&points), coeffs); + } + } + + #[test] + fn interpolant_random_roots_of_unity() { + type F = GoldilocksField; + + for deg_log in 0..4 { + let deg = 1 << deg_log; + let domain = F::two_adic_subgroup(deg_log); + let coeffs = F::rand_vec(deg); + let coeffs = PolynomialCoeffs { coeffs }; + + let points = eval_naive(&coeffs, &domain); + assert_eq!(interpolant(&points), coeffs); + } + } + + #[test] + fn interpolant_random_overspecified() { + type F = GoldilocksField; + + for deg in 0..10 { + let points = deg + 5; + let domain = F::rand_vec(points); + let coeffs = F::rand_vec(deg); + let coeffs = PolynomialCoeffs { coeffs }; + + let points = eval_naive(&coeffs, &domain); + assert_eq!(interpolant(&points), coeffs); + } + } + + fn eval_naive(coeffs: &PolynomialCoeffs, domain: &[F]) -> Vec<(F, F)> { + domain.iter().map(|&x| (x, coeffs.eval(x))).collect() + } + + #[test] + fn test_interpolate2() { + type F = QuarticExtension; + let points = [(F::rand(), F::rand()), (F::rand(), F::rand())]; + let x = F::rand(); + + let ev0 = interpolant(&points).eval(x); + let ev1 = interpolate(&points, x, &barycentric_weights(&points)); + let ev2 = interpolate2(points, x); + + assert_eq!(ev0, ev1); + assert_eq!(ev0, ev2); + } +} diff --git a/field/src/lib.rs b/field/src/lib.rs new file mode 100644 index 000000000..c35441bdb --- /dev/null +++ b/field/src/lib.rs @@ -0,0 +1,33 @@ +#![allow(incomplete_features)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::type_complexity)] +#![allow(clippy::len_without_is_empty)] +#![allow(clippy::needless_range_loop)] +#![feature(specialization)] +#![cfg_attr(not(test), no_std)] + +extern crate alloc; + +pub(crate) mod arch; + +pub mod batch_util; +pub mod cosets; +pub mod extension; +pub mod fft; +pub mod goldilocks_extensions; +pub mod goldilocks_field; +pub mod interpolation; +pub mod ops; +pub mod packable; +pub mod packed; +pub mod polynomial; +pub mod secp256k1_base; +pub mod secp256k1_scalar; +pub mod types; +pub mod zero_poly_coset; + +#[cfg(test)] +mod field_testing; + +#[cfg(test)] +mod prime_field_testing; diff --git a/field/src/ops.rs b/field/src/ops.rs new file mode 100644 index 000000000..ce05ea78b --- /dev/null +++ b/field/src/ops.rs @@ -0,0 +1,11 @@ +use core::ops::Mul; + +pub trait Square { + fn square(&self) -> Self; +} + +impl + Copy> Square for F { + default fn square(&self) -> Self { + *self * *self + } +} diff --git a/field/src/packable.rs b/field/src/packable.rs new file mode 100644 index 000000000..a54b0b35f --- /dev/null +++ b/field/src/packable.rs @@ -0,0 +1,41 @@ +use crate::packed::PackedField; +use crate::types::Field; + +/// Points us to the default packing for a particular field. There may me +/// multiple choices of PackedField for a particular Field (e.g. every Field is +/// also a PackedField), but this is the recommended one. The recommended +/// packing varies by target_arch and target_feature. +pub trait Packable: Field { + type Packing: PackedField; +} + +impl Packable for F { + default type Packing = Self; +} + +#[cfg(all( + target_arch = "x86_64", + target_feature = "avx2", + not(all( + target_feature = "avx512bw", + target_feature = "avx512cd", + target_feature = "avx512dq", + target_feature = "avx512f", + target_feature = "avx512vl" + )) +))] +impl Packable for crate::goldilocks_field::GoldilocksField { + type Packing = crate::arch::x86_64::avx2_goldilocks_field::Avx2GoldilocksField; +} + +#[cfg(all( + target_arch = "x86_64", + target_feature = "avx512bw", + target_feature = "avx512cd", + target_feature = "avx512dq", + target_feature = "avx512f", + target_feature = "avx512vl" +))] +impl Packable for crate::goldilocks_field::GoldilocksField { + type Packing = crate::arch::x86_64::avx512_goldilocks_field::Avx512GoldilocksField; +} diff --git a/field/src/packed.rs b/field/src/packed.rs new file mode 100644 index 000000000..d7fcff857 --- /dev/null +++ b/field/src/packed.rs @@ -0,0 +1,128 @@ +use core::fmt::Debug; +use core::iter::{Product, Sum}; +use core::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub, SubAssign}; +use core::slice; + +use crate::ops::Square; +use crate::types::Field; + +/// # Safety +/// - WIDTH is assumed to be a power of 2. +/// - If P implements PackedField then P must be castable to/from [P::Scalar; +/// P::WIDTH] without UB. +pub unsafe trait PackedField: + 'static + + Add + + Add + + AddAssign + + AddAssign + + Copy + + Debug + + Default + + From + // TODO: Implement packed / packed division + + Div + + Mul + + Mul + + MulAssign + + MulAssign + + Square + + Neg + + Product + + Send + + Sub + + Sub + + SubAssign + + SubAssign + + Sum + + Sync +where + Self::Scalar: Add, + Self::Scalar: Mul, + Self::Scalar: Sub, +{ + type Scalar: Field; + + const WIDTH: usize; + const ZEROS: Self; + const ONES: Self; + + fn from_slice(slice: &[Self::Scalar]) -> &Self; + fn from_slice_mut(slice: &mut [Self::Scalar]) -> &mut Self; + fn as_slice(&self) -> &[Self::Scalar]; + fn as_slice_mut(&mut self) -> &mut [Self::Scalar]; + + /// Take interpret two vectors as chunks of block_len elements. Unpack and interleave those + /// chunks. This is best seen with an example. If we have: + /// A = [x0, y0, x1, y1], + /// B = [x2, y2, x3, y3], + /// then + /// interleave(A, B, 1) = ([x0, x2, x1, x3], [y0, y2, y1, y3]). + /// Pairs that were adjacent in the input are at corresponding positions in the output. + /// r lets us set the size of chunks we're interleaving. If we set block_len = 2, then for + /// A = [x0, x1, y0, y1], + /// B = [x2, x3, y2, y3], + /// we obtain + /// interleave(A, B, block_len) = ([x0, x1, x2, x3], [y0, y1, y2, y3]). + /// We can also think about this as stacking the vectors, dividing them into 2x2 matrices, and + /// transposing those matrices. + /// When block_len = WIDTH, this operation is a no-op. block_len must divide WIDTH. Since + /// WIDTH is specified to be a power of 2, block_len must also be a power of 2. It cannot be 0 + /// and it cannot be > WIDTH. + fn interleave(&self, other: Self, block_len: usize) -> (Self, Self); + + fn pack_slice(buf: &[Self::Scalar]) -> &[Self] { + assert!( + buf.len() % Self::WIDTH == 0, + "Slice length (got {}) must be a multiple of packed field width ({}).", + buf.len(), + Self::WIDTH + ); + let buf_ptr = buf.as_ptr().cast::(); + let n = buf.len() / Self::WIDTH; + unsafe { slice::from_raw_parts(buf_ptr, n) } + } + fn pack_slice_mut(buf: &mut [Self::Scalar]) -> &mut [Self] { + assert!( + buf.len() % Self::WIDTH == 0, + "Slice length (got {}) must be a multiple of packed field width ({}).", + buf.len(), + Self::WIDTH + ); + let buf_ptr = buf.as_mut_ptr().cast::(); + let n = buf.len() / Self::WIDTH; + unsafe { slice::from_raw_parts_mut(buf_ptr, n) } + } + + fn doubles(&self) -> Self { + *self * Self::Scalar::TWO + } +} + +unsafe impl PackedField for F { + type Scalar = Self; + + const WIDTH: usize = 1; + const ZEROS: Self = F::ZERO; + const ONES: Self = F::ONE; + + fn from_slice(slice: &[Self::Scalar]) -> &Self { + &slice[0] + } + fn from_slice_mut(slice: &mut [Self::Scalar]) -> &mut Self { + &mut slice[0] + } + fn as_slice(&self) -> &[Self::Scalar] { + slice::from_ref(self) + } + fn as_slice_mut(&mut self) -> &mut [Self::Scalar] { + slice::from_mut(self) + } + + fn interleave(&self, other: Self, block_len: usize) -> (Self, Self) { + match block_len { + 1 => (*self, other), + _ => panic!("unsupported block length"), + } + } +} diff --git a/field/src/polynomial/division.rs b/field/src/polynomial/division.rs new file mode 100644 index 000000000..d34c04006 --- /dev/null +++ b/field/src/polynomial/division.rs @@ -0,0 +1,161 @@ +use alloc::vec; +use alloc::vec::Vec; + +use plonky2_util::log2_ceil; + +use crate::polynomial::PolynomialCoeffs; +use crate::types::Field; + +impl PolynomialCoeffs { + /// Polynomial division. + /// Returns `(q, r)`, the quotient and remainder of the polynomial division + /// of `a` by `b`. + pub fn div_rem(&self, b: &Self) -> (Self, Self) { + let (a_degree_plug_1, b_degree_plus_1) = (self.degree_plus_one(), b.degree_plus_one()); + if a_degree_plug_1 == 0 { + (Self::zero(1), Self::empty()) + } else if b_degree_plus_1 == 0 { + panic!("Division by zero polynomial"); + } else if a_degree_plug_1 < b_degree_plus_1 { + (Self::zero(1), self.clone()) + } else if b_degree_plus_1 == 1 { + (self * b.coeffs[0].inverse(), Self::empty()) + } else { + let rev_b = b.rev(); + let rev_b_inv = rev_b.inv_mod_xn(a_degree_plug_1 - b_degree_plus_1 + 1); + let rhs: Self = self.rev().coeffs[..=a_degree_plug_1 - b_degree_plus_1] + .to_vec() + .into(); + let rev_q: Self = (&rev_b_inv * &rhs).coeffs[..=a_degree_plug_1 - b_degree_plus_1] + .to_vec() + .into(); + let mut q = rev_q.rev(); + let qb = &q * b; + let mut r = self - &qb; + q.trim(); + r.trim(); + (q, r) + } + } + + /// Polynomial long division. + /// Returns `(q, r)`, the quotient and remainder of the polynomial division + /// of `a` by `b`. Generally slower that the equivalent function + /// `Polynomial::polynomial_division`. + pub fn div_rem_long_division(&self, b: &Self) -> (Self, Self) { + let b = b.trimmed(); + + let (a_degree_plus_1, b_degree_plus_1) = (self.degree_plus_one(), b.degree_plus_one()); + if a_degree_plus_1 == 0 { + (Self::zero(1), Self::empty()) + } else if b_degree_plus_1 == 0 { + panic!("Division by zero polynomial"); + } else if a_degree_plus_1 < b_degree_plus_1 { + (Self::zero(1), self.clone()) + } else { + // Now we know that self.degree() >= divisor.degree(); + let mut quotient = Self::zero(a_degree_plus_1 - b_degree_plus_1 + 1); + let mut remainder = self.clone(); + // Can unwrap here because we know self is not zero. + let divisor_leading_inv = b.lead().inverse(); + while !remainder.is_zero() && remainder.degree_plus_one() >= b_degree_plus_1 { + let cur_q_coeff = remainder.lead() * divisor_leading_inv; + let cur_q_degree = remainder.degree_plus_one() - b_degree_plus_1; + quotient.coeffs[cur_q_degree] = cur_q_coeff; + + for (i, &div_coeff) in b.coeffs.iter().enumerate() { + remainder.coeffs[cur_q_degree + i] -= cur_q_coeff * div_coeff; + } + remainder.trim(); + } + (quotient, remainder) + } + } + + /// Let `self=p(X)`, this returns `(p(X)-p(z))/(X-z)`. + /// See + pub fn divide_by_linear(&self, z: F) -> PolynomialCoeffs { + let mut bs = self + .coeffs + .iter() + .rev() + .scan(F::ZERO, |acc, &c| { + *acc = *acc * z + c; + Some(*acc) + }) + .collect::>(); + bs.pop(); + bs.reverse(); + Self { coeffs: bs } + } + + /// Computes the inverse of `self` modulo `x^n`. + pub fn inv_mod_xn(&self, n: usize) -> Self { + assert!(n > 0, "`n` needs to be nonzero"); + assert!(self.coeffs[0].is_nonzero(), "Inverse doesn't exist."); + + // If polynomial is constant, return the inverse of the constant. + if self.degree_plus_one() == 1 { + return Self::new(vec![self.coeffs[0].inverse()]); + } + + let h = if self.len() < n { + self.padded(n) + } else { + self.clone() + }; + + let mut a = Self::empty(); + a.coeffs.push(h.coeffs[0].inverse()); + for i in 0..log2_ceil(n) { + let l = 1 << i; + let h0 = h.coeffs[..l].to_vec().into(); + let mut h1: Self = h.coeffs[l..].to_vec().into(); + let mut c = &a * &h0; + if l == c.len() { + c = Self::zero(1); + } else { + c.coeffs.drain(0..l); + } + h1.trim(); + let mut tmp = &a * &h1; + tmp = &tmp + &c; + tmp.coeffs.iter_mut().for_each(|x| *x = -(*x)); + tmp.trim(); + let mut b = &a * &tmp; + b.trim(); + if b.len() > l { + b.coeffs.drain(l..); + } + a.coeffs.extend_from_slice(&b.coeffs); + } + a.coeffs.drain(n..); + a + } +} + +#[cfg(test)] +mod tests { + use rand::rngs::OsRng; + use rand::Rng; + + use crate::extension::quartic::QuarticExtension; + use crate::goldilocks_field::GoldilocksField; + use crate::polynomial::PolynomialCoeffs; + use crate::types::{Field, Sample}; + + #[test] + fn test_division_by_linear() { + type F = QuarticExtension; + let n = OsRng.gen_range(1..1000); + let poly = PolynomialCoeffs::new(F::rand_vec(n)); + let z = F::rand(); + let ev = poly.eval(z); + + let quotient = poly.divide_by_linear(z); + assert_eq!( + poly, + &("ient * &vec![-z, F::ONE].into()) + &vec![ev].into() // `quotient * (X-z) + ev` + ); + } +} diff --git a/field/src/polynomial/mod.rs b/field/src/polynomial/mod.rs new file mode 100644 index 000000000..784cef85f --- /dev/null +++ b/field/src/polynomial/mod.rs @@ -0,0 +1,677 @@ +pub(crate) mod division; + +use alloc::vec; +use alloc::vec::Vec; +use core::cmp::max; +use core::iter::Sum; +use core::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign}; + +use anyhow::{ensure, Result}; +use itertools::Itertools; +use plonky2_util::log2_strict; +use serde::{Deserialize, Serialize}; + +use crate::extension::{Extendable, FieldExtension}; +use crate::fft::{fft, fft_with_options, ifft, FftRootTable}; +use crate::types::Field; + +/// A polynomial in point-value form. +/// +/// The points are implicitly `g^i`, where `g` generates the subgroup whose size +/// equals the number of points. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct PolynomialValues { + pub values: Vec, +} + +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 } + } + + pub fn constant(value: F, len: usize) -> Self { + Self::new(vec![value; len]) + } + + pub fn zero(len: usize) -> Self { + 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); + result.values[index] = F::ONE; + result + } + + /// The number of values stored. + pub fn len(&self) -> usize { + self.values.len() + } + + pub fn ifft(self) -> PolynomialCoeffs { + ifft(self) + } + + /// Returns the polynomial whose evaluation on the coset `shift*H` is + /// `self`. + pub fn coset_ifft(self, shift: F) -> PolynomialCoeffs { + let mut shifted_coeffs = self.ifft(); + shifted_coeffs + .coeffs + .iter_mut() + .zip(shift.inverse().powers()) + .for_each(|(c, r)| { + *c *= r; + }); + shifted_coeffs + } + + pub fn lde_multiple(polys: Vec, rate_bits: usize) -> Vec { + polys.into_iter().map(|p| p.lde(rate_bits)).collect() + } + + pub fn lde(self, rate_bits: usize) -> Self { + let coeffs = ifft(self).lde(rate_bits); + fft_with_options(coeffs, Some(rate_bits), None) + } + + /// Low-degree extend `Self` (seen as evaluations over the subgroup) onto a + /// coset. + pub fn lde_onto_coset(self, rate_bits: usize) -> Self { + let coeffs = ifft(self).lde(rate_bits); + coeffs.coset_fft_with_options(F::coset_shift(), Some(rate_bits), None) + } + + pub fn degree(&self) -> usize { + self.degree_plus_one() + .checked_sub(1) + .expect("deg(0) is undefined") + } + + pub fn degree_plus_one(&self) -> usize { + self.clone().ifft().degree_plus_one() + } + + /// Adds `rhs * rhs_weight` to `self`. Assumes `self.len() == rhs.len()`. + pub fn add_assign_scaled(&mut self, rhs: &Self, rhs_weight: F) { + self.values + .iter_mut() + .zip_eq(&rhs.values) + .for_each(|(self_v, rhs_v)| *self_v += *rhs_v * rhs_weight) + } +} + +impl From> for PolynomialValues { + fn from(values: Vec) -> Self { + Self::new(values) + } +} + +/// A polynomial in coefficient form. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct PolynomialCoeffs { + pub coeffs: Vec, +} + +impl PolynomialCoeffs { + pub fn new(coeffs: Vec) -> Self { + PolynomialCoeffs { coeffs } + } + + /// The empty list of coefficients, which is the smallest encoding of the + /// zero polynomial. + pub fn empty() -> Self { + Self::new(Vec::new()) + } + + pub fn zero(len: usize) -> Self { + Self::new(vec![F::ZERO; len]) + } + + pub fn is_zero(&self) -> bool { + self.coeffs.iter().all(|x| x.is_zero()) + } + + /// The number of coefficients. This does not filter out any zero + /// coefficients, so it is not necessarily related to the degree. + pub fn len(&self) -> usize { + self.coeffs.len() + } + + pub fn log_len(&self) -> usize { + log2_strict(self.len()) + } + + pub fn chunks(&self, chunk_size: usize) -> Vec { + self.coeffs + .chunks(chunk_size) + .map(|chunk| PolynomialCoeffs::new(chunk.to_vec())) + .collect() + } + + pub fn eval(&self, x: F) -> F { + self.coeffs + .iter() + .rev() + .fold(F::ZERO, |acc, &c| acc * x + c) + } + + /// Evaluate the polynomial at a point given its powers. The first power is + /// the point itself, not 1. + pub fn eval_with_powers(&self, powers: &[F]) -> F { + debug_assert_eq!(self.coeffs.len(), powers.len() + 1); + let acc = self.coeffs[0]; + self.coeffs[1..] + .iter() + .zip(powers) + .fold(acc, |acc, (&x, &c)| acc + c * x) + } + + pub fn eval_base(&self, x: F::BaseField) -> F + where + F: FieldExtension, + { + self.coeffs + .iter() + .rev() + .fold(F::ZERO, |acc, &c| acc.scalar_mul(x) + c) + } + + /// Evaluate the polynomial at a point given its powers. The first power is + /// the point itself, not 1. + pub fn eval_base_with_powers(&self, powers: &[F::BaseField]) -> F + where + F: FieldExtension, + { + debug_assert_eq!(self.coeffs.len(), powers.len() + 1); + let acc = self.coeffs[0]; + self.coeffs[1..] + .iter() + .zip(powers) + .fold(acc, |acc, (&x, &c)| acc + x.scalar_mul(c)) + } + + pub fn lde_multiple(polys: Vec<&Self>, rate_bits: usize) -> Vec { + polys.into_iter().map(|p| p.lde(rate_bits)).collect() + } + + pub fn lde(&self, rate_bits: usize) -> Self { + self.padded(self.len() << rate_bits) + } + + pub fn pad(&mut self, new_len: usize) -> Result<()> { + ensure!( + new_len >= self.len(), + "Trying to pad a polynomial of length {} to a length of {}.", + self.len(), + new_len + ); + self.coeffs.resize(new_len, F::ZERO); + Ok(()) + } + + pub fn padded(&self, new_len: usize) -> Self { + let mut poly = self.clone(); + poly.pad(new_len).unwrap(); + poly + } + + /// Removes any leading zero coefficients. + pub fn trim(&mut self) { + self.coeffs.truncate(self.degree_plus_one()); + } + + /// Removes some leading zero coefficients, such that a desired length is + /// reached. Fails if a nonzero coefficient is encountered before then. + pub fn trim_to_len(&mut self, len: usize) -> Result<()> { + ensure!(self.len() >= len); + ensure!(self.coeffs[len..].iter().all(F::is_zero)); + self.coeffs.truncate(len); + Ok(()) + } + + /// Removes any leading zero coefficients. + pub fn trimmed(&self) -> Self { + let coeffs = self.coeffs[..self.degree_plus_one()].to_vec(); + Self { coeffs } + } + + /// Degree of the polynomial + 1, or 0 for a polynomial with no non-zero + /// coefficients. + pub fn degree_plus_one(&self) -> usize { + (0usize..self.len()) + .rev() + .find(|&i| self.coeffs[i].is_nonzero()) + .map_or(0, |i| i + 1) + } + + /// Leading coefficient. + pub fn lead(&self) -> F { + self.coeffs + .iter() + .rev() + .find(|x| x.is_nonzero()) + .map_or(F::ZERO, |x| *x) + } + + /// Reverse the order of the coefficients, not taking into account the + /// leading zero coefficients. + pub(crate) fn rev(&self) -> Self { + Self::new(self.trimmed().coeffs.into_iter().rev().collect()) + } + + pub fn fft(self) -> PolynomialValues { + fft(self) + } + + pub fn fft_with_options( + self, + zero_factor: Option, + root_table: Option<&FftRootTable>, + ) -> PolynomialValues { + fft_with_options(self, zero_factor, root_table) + } + + /// Returns the evaluation of the polynomial on the coset `shift*H`. + pub fn coset_fft(&self, shift: F) -> PolynomialValues { + self.coset_fft_with_options(shift, None, None) + } + + /// Returns the evaluation of the polynomial on the coset `shift*H`. + pub fn coset_fft_with_options( + &self, + shift: F, + zero_factor: Option, + root_table: Option<&FftRootTable>, + ) -> PolynomialValues { + let modified_poly: Self = shift + .powers() + .zip(&self.coeffs) + .map(|(r, &c)| r * c) + .collect::>() + .into(); + modified_poly.fft_with_options(zero_factor, root_table) + } + + pub fn to_extension(&self) -> PolynomialCoeffs + where + F: Extendable, + { + PolynomialCoeffs::new(self.coeffs.iter().map(|&c| c.into()).collect()) + } + + pub fn mul_extension(&self, rhs: F::Extension) -> PolynomialCoeffs + where + F: Extendable, + { + PolynomialCoeffs::new(self.coeffs.iter().map(|&c| rhs.scalar_mul(c)).collect()) + } +} + +impl PartialEq for PolynomialCoeffs { + fn eq(&self, other: &Self) -> bool { + let max_terms = self.coeffs.len().max(other.coeffs.len()); + for i in 0..max_terms { + let self_i = self.coeffs.get(i).cloned().unwrap_or(F::ZERO); + let other_i = other.coeffs.get(i).cloned().unwrap_or(F::ZERO); + if self_i != other_i { + return false; + } + } + true + } +} + +impl Eq for PolynomialCoeffs {} + +impl From> for PolynomialCoeffs { + fn from(coeffs: Vec) -> Self { + Self::new(coeffs) + } +} + +impl Add for &PolynomialCoeffs { + type Output = PolynomialCoeffs; + + fn add(self, rhs: Self) -> Self::Output { + let len = max(self.len(), rhs.len()); + let a = self.padded(len).coeffs; + let b = rhs.padded(len).coeffs; + let coeffs = a.into_iter().zip(b).map(|(x, y)| x + y).collect(); + PolynomialCoeffs::new(coeffs) + } +} + +impl Sum for PolynomialCoeffs { + fn sum>(iter: I) -> Self { + iter.fold(Self::empty(), |acc, p| &acc + &p) + } +} + +impl Sub for &PolynomialCoeffs { + type Output = PolynomialCoeffs; + + fn sub(self, rhs: Self) -> Self::Output { + let len = max(self.len(), rhs.len()); + let mut coeffs = self.padded(len).coeffs; + for (i, &c) in rhs.coeffs.iter().enumerate() { + coeffs[i] -= c; + } + PolynomialCoeffs::new(coeffs) + } +} + +impl AddAssign for PolynomialCoeffs { + fn add_assign(&mut self, rhs: Self) { + let len = max(self.len(), rhs.len()); + self.coeffs.resize(len, F::ZERO); + for (l, r) in self.coeffs.iter_mut().zip(rhs.coeffs) { + *l += r; + } + } +} + +impl AddAssign<&Self> for PolynomialCoeffs { + fn add_assign(&mut self, rhs: &Self) { + let len = max(self.len(), rhs.len()); + self.coeffs.resize(len, F::ZERO); + for (l, &r) in self.coeffs.iter_mut().zip(&rhs.coeffs) { + *l += r; + } + } +} + +impl SubAssign for PolynomialCoeffs { + fn sub_assign(&mut self, rhs: Self) { + let len = max(self.len(), rhs.len()); + self.coeffs.resize(len, F::ZERO); + for (l, r) in self.coeffs.iter_mut().zip(rhs.coeffs) { + *l -= r; + } + } +} + +impl SubAssign<&Self> for PolynomialCoeffs { + fn sub_assign(&mut self, rhs: &Self) { + let len = max(self.len(), rhs.len()); + self.coeffs.resize(len, F::ZERO); + for (l, &r) in self.coeffs.iter_mut().zip(&rhs.coeffs) { + *l -= r; + } + } +} + +impl Mul for &PolynomialCoeffs { + type Output = PolynomialCoeffs; + + fn mul(self, rhs: F) -> Self::Output { + let coeffs = self.coeffs.iter().map(|&x| rhs * x).collect(); + PolynomialCoeffs::new(coeffs) + } +} + +impl MulAssign for PolynomialCoeffs { + fn mul_assign(&mut self, rhs: F) { + self.coeffs.iter_mut().for_each(|x| *x *= rhs); + } +} + +impl Mul for &PolynomialCoeffs { + type Output = PolynomialCoeffs; + + #[allow(clippy::suspicious_arithmetic_impl)] + fn mul(self, rhs: Self) -> Self::Output { + let new_len = (self.len() + rhs.len()).next_power_of_two(); + let a = self.padded(new_len); + let b = rhs.padded(new_len); + let a_evals = a.fft(); + let b_evals = b.fft(); + + let mul_evals: Vec = a_evals + .values + .into_iter() + .zip(b_evals.values) + .map(|(pa, pb)| pa * pb) + .collect(); + ifft(mul_evals.into()) + } +} + +#[cfg(test)] +mod tests { + use std::time::Instant; + + use rand::rngs::OsRng; + use rand::Rng; + + use super::*; + use crate::goldilocks_field::GoldilocksField; + use crate::types::Sample; + + #[test] + fn test_trimmed() { + type F = GoldilocksField; + + assert_eq!( + PolynomialCoeffs:: { coeffs: vec![] }.trimmed(), + PolynomialCoeffs:: { coeffs: vec![] } + ); + assert_eq!( + PolynomialCoeffs:: { + coeffs: vec![F::ZERO] + } + .trimmed(), + PolynomialCoeffs:: { coeffs: vec![] } + ); + assert_eq!( + PolynomialCoeffs:: { + coeffs: vec![F::ONE, F::TWO, F::ZERO, F::ZERO] + } + .trimmed(), + PolynomialCoeffs:: { + coeffs: vec![F::ONE, F::TWO] + } + ); + } + + #[test] + fn test_coset_fft() { + type F = GoldilocksField; + + let k = 8; + let n = 1 << k; + let poly = PolynomialCoeffs::new(F::rand_vec(n)); + let shift = F::rand(); + let coset_evals = poly.coset_fft(shift).values; + + let generator = F::primitive_root_of_unity(k); + let naive_coset_evals = F::cyclic_subgroup_coset_known_order(generator, shift, n) + .into_iter() + .map(|x| poly.eval(x)) + .collect::>(); + assert_eq!(coset_evals, naive_coset_evals); + + let ifft_coeffs = PolynomialValues::new(coset_evals).coset_ifft(shift); + assert_eq!(poly, ifft_coeffs); + } + + #[test] + fn test_coset_ifft() { + type F = GoldilocksField; + + let k = 8; + let n = 1 << k; + let evals = PolynomialValues::new(F::rand_vec(n)); + let shift = F::rand(); + let coeffs = evals.clone().coset_ifft(shift); + + let generator = F::primitive_root_of_unity(k); + let naive_coset_evals = F::cyclic_subgroup_coset_known_order(generator, shift, n) + .into_iter() + .map(|x| coeffs.eval(x)) + .collect::>(); + assert_eq!(evals, naive_coset_evals.into()); + + let fft_evals = coeffs.coset_fft(shift); + assert_eq!(evals, fft_evals); + } + + #[test] + fn test_polynomial_multiplication() { + type F = GoldilocksField; + let mut rng = OsRng; + let (a_deg, b_deg) = (rng.gen_range(1..10_000), rng.gen_range(1..10_000)); + let a = PolynomialCoeffs::new(F::rand_vec(a_deg)); + let b = PolynomialCoeffs::new(F::rand_vec(b_deg)); + let m1 = &a * &b; + let m2 = &a * &b; + for _ in 0..1000 { + let x = F::rand(); + assert_eq!(m1.eval(x), a.eval(x) * b.eval(x)); + assert_eq!(m2.eval(x), a.eval(x) * b.eval(x)); + } + } + + #[test] + fn test_inv_mod_xn() { + type F = GoldilocksField; + let mut rng = OsRng; + let a_deg = rng.gen_range(0..1_000); + let n = rng.gen_range(1..1_000); + let mut a = PolynomialCoeffs::new(F::rand_vec(a_deg + 1)); + if a.coeffs[0].is_zero() { + a.coeffs[0] = F::ONE; // First coefficient needs to be nonzero. + } + let b = a.inv_mod_xn(n); + let mut m = &a * &b; + m.coeffs.truncate(n); + m.trim(); + assert_eq!( + m, + PolynomialCoeffs::new(vec![F::ONE]), + "a: {:#?}, b:{:#?}, n:{:#?}, m:{:#?}", + a, + b, + n, + m + ); + } + + #[test] + fn test_polynomial_long_division() { + type F = GoldilocksField; + let mut rng = OsRng; + let (a_deg, b_deg) = (rng.gen_range(1..10_000), rng.gen_range(1..10_000)); + let a = PolynomialCoeffs::new(F::rand_vec(a_deg)); + let b = PolynomialCoeffs::new(F::rand_vec(b_deg)); + let (q, r) = a.div_rem_long_division(&b); + for _ in 0..1000 { + let x = F::rand(); + assert_eq!(a.eval(x), b.eval(x) * q.eval(x) + r.eval(x)); + } + } + + #[test] + fn test_polynomial_division() { + type F = GoldilocksField; + let mut rng = OsRng; + let (a_deg, b_deg) = (rng.gen_range(1..10_000), rng.gen_range(1..10_000)); + let a = PolynomialCoeffs::new(F::rand_vec(a_deg)); + let b = PolynomialCoeffs::new(F::rand_vec(b_deg)); + let (q, r) = a.div_rem(&b); + for _ in 0..1000 { + let x = F::rand(); + assert_eq!(a.eval(x), b.eval(x) * q.eval(x) + r.eval(x)); + } + } + + #[test] + fn test_polynomial_division_by_constant() { + type F = GoldilocksField; + let mut rng = OsRng; + let a_deg = rng.gen_range(1..10_000); + let a = PolynomialCoeffs::new(F::rand_vec(a_deg)); + let b = PolynomialCoeffs::from(vec![F::rand()]); + let (q, r) = a.div_rem(&b); + for _ in 0..1000 { + let x = F::rand(); + assert_eq!(a.eval(x), b.eval(x) * q.eval(x) + r.eval(x)); + } + } + + // Test to see which polynomial division method is faster for divisions of the + // type `(X^n - 1)/(X - a) + #[test] + fn test_division_linear() { + type F = GoldilocksField; + let mut rng = OsRng; + let l = 14; + let n = 1 << l; + let g = F::primitive_root_of_unity(l); + let xn_minus_one = { + let mut xn_min_one_vec = vec![F::ZERO; n + 1]; + xn_min_one_vec[n] = F::ONE; + xn_min_one_vec[0] = F::NEG_ONE; + PolynomialCoeffs::new(xn_min_one_vec) + }; + + let a = g.exp_u64(rng.gen_range(0..(n as u64))); + let denom = PolynomialCoeffs::new(vec![-a, F::ONE]); + let now = Instant::now(); + xn_minus_one.div_rem(&denom); + println!("Division time: {:?}", now.elapsed()); + let now = Instant::now(); + xn_minus_one.div_rem_long_division(&denom); + println!("Division time: {:?}", now.elapsed()); + } + + #[test] + fn eq() { + type F = GoldilocksField; + assert_eq!( + PolynomialCoeffs::::new(vec![]), + PolynomialCoeffs::new(vec![]) + ); + assert_eq!( + PolynomialCoeffs::::new(vec![F::ZERO]), + PolynomialCoeffs::new(vec![F::ZERO]) + ); + assert_eq!( + PolynomialCoeffs::::new(vec![]), + PolynomialCoeffs::new(vec![F::ZERO]) + ); + assert_eq!( + PolynomialCoeffs::::new(vec![F::ZERO]), + PolynomialCoeffs::new(vec![]) + ); + assert_eq!( + PolynomialCoeffs::::new(vec![F::ZERO]), + PolynomialCoeffs::new(vec![F::ZERO, F::ZERO]) + ); + assert_eq!( + PolynomialCoeffs::::new(vec![F::ONE]), + PolynomialCoeffs::new(vec![F::ONE, F::ZERO]) + ); + assert_ne!( + PolynomialCoeffs::::new(vec![]), + PolynomialCoeffs::new(vec![F::ONE]) + ); + assert_ne!( + PolynomialCoeffs::::new(vec![F::ZERO]), + PolynomialCoeffs::new(vec![F::ZERO, F::ONE]) + ); + assert_ne!( + PolynomialCoeffs::::new(vec![F::ZERO]), + PolynomialCoeffs::new(vec![F::ONE, F::ZERO]) + ); + } +} diff --git a/field/src/prime_field_testing.rs b/field/src/prime_field_testing.rs new file mode 100644 index 000000000..d1f693d47 --- /dev/null +++ b/field/src/prime_field_testing.rs @@ -0,0 +1,183 @@ +use alloc::vec::Vec; + +use crate::types::PrimeField64; + +/// Generates a series of non-negative integers less than `modulus` which cover +/// a range of interesting test values. +pub fn test_inputs(modulus: u64) -> Vec { + const CHUNK_SIZE: u64 = 10; + + (0..CHUNK_SIZE) + .chain((1 << 31) - CHUNK_SIZE..(1 << 31) + CHUNK_SIZE) + .chain((1 << 32) - CHUNK_SIZE..(1 << 32) + CHUNK_SIZE) + .chain((1 << 63) - CHUNK_SIZE..(1 << 63) + CHUNK_SIZE) + .chain(modulus - CHUNK_SIZE..modulus) + .filter(|&x| x < modulus) + .collect() +} + +/// Apply the unary functions `op` and `expected_op` +/// coordinate-wise to the inputs from `test_inputs(modulus, +/// word_bits)` and panic if the two resulting vectors differ. +pub fn run_unaryop_test_cases(op: UnaryOp, expected_op: ExpectedOp) +where + F: PrimeField64, + UnaryOp: Fn(F) -> F, + ExpectedOp: Fn(u64) -> u64, +{ + let inputs = test_inputs(F::ORDER); + let expected: Vec<_> = inputs.iter().map(|&x| expected_op(x)).collect(); + let output: Vec<_> = inputs + .iter() + .cloned() + .map(|x| op(F::from_canonical_u64(x)).to_canonical_u64()) + .collect(); + // Compare expected outputs with actual outputs + for i in 0..inputs.len() { + assert_eq!( + output[i], expected[i], + "Expected {}, got {} for input {}", + expected[i], output[i], inputs[i] + ); + } +} + +/// Apply the binary functions `op` and `expected_op` to each pair of inputs. +pub fn run_binaryop_test_cases(op: BinaryOp, expected_op: ExpectedOp) +where + F: PrimeField64, + BinaryOp: Fn(F, F) -> F, + ExpectedOp: Fn(u64, u64) -> u64, +{ + let inputs = test_inputs(F::ORDER); + + for &lhs in &inputs { + for &rhs in &inputs { + let lhs_f = F::from_canonical_u64(lhs); + let rhs_f = F::from_canonical_u64(rhs); + let actual = op(lhs_f, rhs_f).to_canonical_u64(); + let expected = expected_op(lhs, rhs); + assert_eq!( + actual, expected, + "Expected {}, got {} for inputs ({}, {})", + expected, actual, lhs, rhs + ); + } + } +} + +#[macro_export] +macro_rules! test_prime_field_arithmetic { + ($field:ty) => { + mod prime_field_arithmetic { + use core::ops::{Add, Mul, Neg, Sub}; + + use $crate::ops::Square; + use $crate::types::{Field, Field64}; + + #[test] + fn arithmetic_addition() { + let modulus = <$field>::ORDER; + $crate::prime_field_testing::run_binaryop_test_cases(<$field>::add, |x, y| { + ((x as u128 + y as u128) % (modulus as u128)) as u64 + }) + } + + #[test] + fn arithmetic_subtraction() { + let modulus = <$field>::ORDER; + $crate::prime_field_testing::run_binaryop_test_cases(<$field>::sub, |x, y| { + if x >= y { + x - y + } else { + modulus - y + x + } + }) + } + + #[test] + fn arithmetic_negation() { + let modulus = <$field>::ORDER; + $crate::prime_field_testing::run_unaryop_test_cases(<$field>::neg, |x| { + if x == 0 { + 0 + } else { + modulus - x + } + }) + } + + #[test] + fn arithmetic_multiplication() { + let modulus = <$field>::ORDER; + $crate::prime_field_testing::run_binaryop_test_cases(<$field>::mul, |x, y| { + ((x as u128) * (y as u128) % (modulus as u128)) as u64 + }) + } + + #[test] + fn arithmetic_square() { + let modulus = <$field>::ORDER; + $crate::prime_field_testing::run_unaryop_test_cases( + |x: $field| x.square(), + |x| ((x as u128 * x as u128) % (modulus as u128)) as u64, + ) + } + + #[test] + fn inversion() { + let zero = <$field>::ZERO; + let one = <$field>::ONE; + let modulus = <$field>::ORDER; + + assert_eq!(zero.try_inverse(), None); + + let inputs = $crate::prime_field_testing::test_inputs(modulus); + + for x in inputs { + if x != 0 { + let x = <$field>::from_canonical_u64(x); + let inv = x.inverse(); + assert_eq!(x * inv, one); + } + } + } + + #[test] + fn inverse_2exp() { + type F = $field; + + let v = ::TWO_ADICITY; + + for e in [0, 1, 2, 3, 4, v - 2, v - 1, v, v + 1, v + 2, 123 * v] { + let x = F::TWO.exp_u64(e as u64); + let y = F::inverse_2exp(e); + assert_eq!(x * y, F::ONE); + } + } + + #[test] + fn subtraction_double_wraparound() { + type F = $field; + + let (a, b) = (F::from_canonical_u64((F::ORDER + 1u64) / 2u64), F::TWO); + let x = a * b; + assert_eq!(x, F::ONE); + assert_eq!(F::ZERO - x, F::NEG_ONE); + } + + #[test] + fn addition_double_wraparound() { + type F = $field; + + let a = F::from_canonical_u64(u64::MAX - F::ORDER); + let b = F::NEG_ONE; + + let c = (a + a) + (b + b); + let d = (a + b) + (a + b); + + assert_eq!(c, d); + } + } + }; +} diff --git a/field/src/secp256k1_base.rs b/field/src/secp256k1_base.rs new file mode 100644 index 000000000..6632a7f83 --- /dev/null +++ b/field/src/secp256k1_base.rs @@ -0,0 +1,271 @@ +use alloc::vec::Vec; +use core::fmt::{self, Debug, Display, Formatter}; +use core::hash::{Hash, Hasher}; +use core::iter::{Product, Sum}; +use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + +use itertools::Itertools; +use num::bigint::BigUint; +use num::{Integer, One}; +use serde::{Deserialize, Serialize}; + +use crate::types::{Field, PrimeField, Sample}; + +/// The base field of the secp256k1 elliptic curve. +/// +/// Its order is +/// ```ignore +/// P = 2**256 - 2**32 - 2**9 - 2**8 - 2**7 - 2**6 - 2**4 - 1 +/// ``` +#[derive(Copy, Clone, Serialize, Deserialize)] +pub struct Secp256K1Base(pub [u64; 4]); + +fn biguint_from_array(arr: [u64; 4]) -> BigUint { + BigUint::from_slice(&[ + arr[0] as u32, + (arr[0] >> 32) as u32, + arr[1] as u32, + (arr[1] >> 32) as u32, + arr[2] as u32, + (arr[2] >> 32) as u32, + arr[3] as u32, + (arr[3] >> 32) as u32, + ]) +} + +impl Default for Secp256K1Base { + fn default() -> Self { + Self::ZERO + } +} + +impl PartialEq for Secp256K1Base { + fn eq(&self, other: &Self) -> bool { + self.to_canonical_biguint() == other.to_canonical_biguint() + } +} + +impl Eq for Secp256K1Base {} + +impl Hash for Secp256K1Base { + fn hash(&self, state: &mut H) { + self.to_canonical_biguint().hash(state) + } +} + +impl Display for Secp256K1Base { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Display::fmt(&self.to_canonical_biguint(), f) + } +} + +impl Debug for Secp256K1Base { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Debug::fmt(&self.to_canonical_biguint(), f) + } +} + +impl Sample for Secp256K1Base { + #[inline] + fn sample(rng: &mut R) -> Self + where + R: rand::RngCore + ?Sized, + { + use num::bigint::RandBigInt; + Self::from_noncanonical_biguint(rng.gen_biguint_below(&Self::order())) + } +} + +impl Field for Secp256K1Base { + const ZERO: Self = Self([0; 4]); + const ONE: Self = Self([1, 0, 0, 0]); + const TWO: Self = Self([2, 0, 0, 0]); + const NEG_ONE: Self = Self([ + 0xFFFFFFFEFFFFFC2E, + 0xFFFFFFFFFFFFFFFF, + 0xFFFFFFFFFFFFFFFF, + 0xFFFFFFFFFFFFFFFF, + ]); + + const TWO_ADICITY: usize = 1; + const CHARACTERISTIC_TWO_ADICITY: usize = Self::TWO_ADICITY; + + // Sage: `g = GF(p).multiplicative_generator()` + const MULTIPLICATIVE_GROUP_GENERATOR: Self = Self([5, 0, 0, 0]); + + // Sage: `g_2 = g^((p - 1) / 2)` + const POWER_OF_TWO_GENERATOR: Self = Self::NEG_ONE; + + const BITS: usize = 256; + + fn order() -> BigUint { + BigUint::from_slice(&[ + 0xFFFFFC2F, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, + ]) + } + fn characteristic() -> BigUint { + Self::order() + } + + fn try_inverse(&self) -> Option { + if self.is_zero() { + return None; + } + + // Fermat's Little Theorem + Some(self.exp_biguint(&(Self::order() - BigUint::one() - BigUint::one()))) + } + + fn from_noncanonical_biguint(val: BigUint) -> Self { + Self( + val.to_u64_digits() + .into_iter() + .pad_using(4, |_| 0) + .collect::>()[..] + .try_into() + .expect("error converting to u64 array"), + ) + } + + #[inline] + fn from_canonical_u64(n: u64) -> Self { + Self([n, 0, 0, 0]) + } + + #[inline] + fn from_noncanonical_u128(n: u128) -> Self { + Self([n as u64, (n >> 64) as u64, 0, 0]) + } + + #[inline] + fn from_noncanonical_u96(n: (u64, u32)) -> Self { + Self([n.0, n.1 as u64, 0, 0]) + } + + fn from_noncanonical_i64(n: i64) -> Self { + let f = Self::from_canonical_u64(n.unsigned_abs()); + if n < 0 { + -f + } else { + f + } + } + + fn from_noncanonical_u64(n: u64) -> Self { + Self::from_canonical_u64(n) + } +} + +impl PrimeField for Secp256K1Base { + fn to_canonical_biguint(&self) -> BigUint { + let mut result = biguint_from_array(self.0); + if result >= Self::order() { + result -= Self::order(); + } + result + } +} + +impl Neg for Secp256K1Base { + type Output = Self; + + #[inline] + fn neg(self) -> Self { + if self.is_zero() { + Self::ZERO + } else { + Self::from_noncanonical_biguint(Self::order() - self.to_canonical_biguint()) + } + } +} + +impl Add for Secp256K1Base { + type Output = Self; + + #[inline] + fn add(self, rhs: Self) -> Self { + let mut result = self.to_canonical_biguint() + rhs.to_canonical_biguint(); + if result >= Self::order() { + result -= Self::order(); + } + Self::from_noncanonical_biguint(result) + } +} + +impl AddAssign for Secp256K1Base { + #[inline] + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs; + } +} + +impl Sum for Secp256K1Base { + fn sum>(iter: I) -> Self { + iter.fold(Self::ZERO, |acc, x| acc + x) + } +} + +impl Sub for Secp256K1Base { + type Output = Self; + + #[inline] + #[allow(clippy::suspicious_arithmetic_impl)] + fn sub(self, rhs: Self) -> Self { + self + -rhs + } +} + +impl SubAssign for Secp256K1Base { + #[inline] + fn sub_assign(&mut self, rhs: Self) { + *self = *self - rhs; + } +} + +impl Mul for Secp256K1Base { + type Output = Self; + + #[inline] + fn mul(self, rhs: Self) -> Self { + Self::from_noncanonical_biguint( + (self.to_canonical_biguint() * rhs.to_canonical_biguint()).mod_floor(&Self::order()), + ) + } +} + +impl MulAssign for Secp256K1Base { + #[inline] + fn mul_assign(&mut self, rhs: Self) { + *self = *self * rhs; + } +} + +impl Product for Secp256K1Base { + #[inline] + fn product>(iter: I) -> Self { + iter.reduce(|acc, x| acc * x).unwrap_or(Self::ONE) + } +} + +impl Div for Secp256K1Base { + type Output = Self; + + #[allow(clippy::suspicious_arithmetic_impl)] + fn div(self, rhs: Self) -> Self::Output { + self * rhs.inverse() + } +} + +impl DivAssign for Secp256K1Base { + fn div_assign(&mut self, rhs: Self) { + *self = *self / rhs; + } +} + +#[cfg(test)] +mod tests { + use crate::test_field_arithmetic; + + test_field_arithmetic!(crate::secp256k1_base::Secp256K1Base); +} diff --git a/field/src/secp256k1_scalar.rs b/field/src/secp256k1_scalar.rs new file mode 100644 index 000000000..3ca5b6ba2 --- /dev/null +++ b/field/src/secp256k1_scalar.rs @@ -0,0 +1,279 @@ +use alloc::vec::Vec; +use core::fmt::{self, Debug, Display, Formatter}; +use core::hash::{Hash, Hasher}; +use core::iter::{Product, Sum}; +use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + +use itertools::Itertools; +use num::bigint::BigUint; +use num::{Integer, One}; +use serde::{Deserialize, Serialize}; + +use crate::types::{Field, PrimeField, Sample}; + +/// The base field of the secp256k1 elliptic curve. +/// +/// Its order is +/// ```ignore +/// P = 0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141 +/// = 115792089237316195423570985008687907852837564279074904382605163141518161494337 +/// = 2**256 - 432420386565659656852420866394968145599 +/// ``` +#[derive(Copy, Clone, Serialize, Deserialize)] +pub struct Secp256K1Scalar(pub [u64; 4]); + +fn biguint_from_array(arr: [u64; 4]) -> BigUint { + BigUint::from_slice(&[ + arr[0] as u32, + (arr[0] >> 32) as u32, + arr[1] as u32, + (arr[1] >> 32) as u32, + arr[2] as u32, + (arr[2] >> 32) as u32, + arr[3] as u32, + (arr[3] >> 32) as u32, + ]) +} + +impl Default for Secp256K1Scalar { + fn default() -> Self { + Self::ZERO + } +} + +impl PartialEq for Secp256K1Scalar { + fn eq(&self, other: &Self) -> bool { + self.to_canonical_biguint() == other.to_canonical_biguint() + } +} + +impl Eq for Secp256K1Scalar {} + +impl Hash for Secp256K1Scalar { + fn hash(&self, state: &mut H) { + self.to_canonical_biguint().hash(state) + } +} + +impl Display for Secp256K1Scalar { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Display::fmt(&self.to_canonical_biguint(), f) + } +} + +impl Debug for Secp256K1Scalar { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Debug::fmt(&self.to_canonical_biguint(), f) + } +} + +impl Sample for Secp256K1Scalar { + #[inline] + fn sample(rng: &mut R) -> Self + where + R: rand::RngCore + ?Sized, + { + use num::bigint::RandBigInt; + Self::from_noncanonical_biguint(rng.gen_biguint_below(&Self::order())) + } +} + +impl Field for Secp256K1Scalar { + const ZERO: Self = Self([0; 4]); + const ONE: Self = Self([1, 0, 0, 0]); + const TWO: Self = Self([2, 0, 0, 0]); + const NEG_ONE: Self = Self([ + 0xBFD25E8CD0364140, + 0xBAAEDCE6AF48A03B, + 0xFFFFFFFFFFFFFFFE, + 0xFFFFFFFFFFFFFFFF, + ]); + + const TWO_ADICITY: usize = 6; + const CHARACTERISTIC_TWO_ADICITY: usize = Self::TWO_ADICITY; + + // Sage: `g = GF(p).multiplicative_generator()` + const MULTIPLICATIVE_GROUP_GENERATOR: Self = Self([7, 0, 0, 0]); + + // Sage: `g_2 = power_mod(g, (p - 1) // 2^6), p)` + // 5480320495727936603795231718619559942670027629901634955707709633242980176626 + const POWER_OF_TWO_GENERATOR: Self = Self([ + 0x992f4b5402b052f2, + 0x98BDEAB680756045, + 0xDF9879A3FBC483A8, + 0xC1DC060E7A91986, + ]); + + const BITS: usize = 256; + + fn order() -> BigUint { + BigUint::from_slice(&[ + 0xD0364141, 0xBFD25E8C, 0xAF48A03B, 0xBAAEDCE6, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, + ]) + } + fn characteristic() -> BigUint { + Self::order() + } + + fn try_inverse(&self) -> Option { + if self.is_zero() { + return None; + } + + // Fermat's Little Theorem + Some(self.exp_biguint(&(Self::order() - BigUint::one() - BigUint::one()))) + } + + fn from_noncanonical_biguint(val: BigUint) -> Self { + Self( + val.to_u64_digits() + .into_iter() + .pad_using(4, |_| 0) + .collect::>()[..] + .try_into() + .expect("error converting to u64 array"), + ) + } + + #[inline] + fn from_canonical_u64(n: u64) -> Self { + Self([n, 0, 0, 0]) + } + + #[inline] + fn from_noncanonical_u128(n: u128) -> Self { + Self([n as u64, (n >> 64) as u64, 0, 0]) + } + + #[inline] + fn from_noncanonical_u96(n: (u64, u32)) -> Self { + Self([n.0, n.1 as u64, 0, 0]) + } + + fn from_noncanonical_i64(n: i64) -> Self { + let f = Self::from_canonical_u64(n.unsigned_abs()); + if n < 0 { + -f + } else { + f + } + } + + fn from_noncanonical_u64(n: u64) -> Self { + Self::from_canonical_u64(n) + } +} + +impl PrimeField for Secp256K1Scalar { + fn to_canonical_biguint(&self) -> BigUint { + let mut result = biguint_from_array(self.0); + if result >= Self::order() { + result -= Self::order(); + } + result + } +} + +impl Neg for Secp256K1Scalar { + type Output = Self; + + #[inline] + fn neg(self) -> Self { + if self.is_zero() { + Self::ZERO + } else { + Self::from_noncanonical_biguint(Self::order() - self.to_canonical_biguint()) + } + } +} + +impl Add for Secp256K1Scalar { + type Output = Self; + + #[inline] + fn add(self, rhs: Self) -> Self { + let mut result = self.to_canonical_biguint() + rhs.to_canonical_biguint(); + if result >= Self::order() { + result -= Self::order(); + } + Self::from_noncanonical_biguint(result) + } +} + +impl AddAssign for Secp256K1Scalar { + #[inline] + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs; + } +} + +impl Sum for Secp256K1Scalar { + fn sum>(iter: I) -> Self { + iter.fold(Self::ZERO, |acc, x| acc + x) + } +} + +impl Sub for Secp256K1Scalar { + type Output = Self; + + #[inline] + #[allow(clippy::suspicious_arithmetic_impl)] + fn sub(self, rhs: Self) -> Self { + self + -rhs + } +} + +impl SubAssign for Secp256K1Scalar { + #[inline] + fn sub_assign(&mut self, rhs: Self) { + *self = *self - rhs; + } +} + +impl Mul for Secp256K1Scalar { + type Output = Self; + + #[inline] + fn mul(self, rhs: Self) -> Self { + Self::from_noncanonical_biguint( + (self.to_canonical_biguint() * rhs.to_canonical_biguint()).mod_floor(&Self::order()), + ) + } +} + +impl MulAssign for Secp256K1Scalar { + #[inline] + fn mul_assign(&mut self, rhs: Self) { + *self = *self * rhs; + } +} + +impl Product for Secp256K1Scalar { + #[inline] + fn product>(iter: I) -> Self { + iter.reduce(|acc, x| acc * x).unwrap_or(Self::ONE) + } +} + +impl Div for Secp256K1Scalar { + type Output = Self; + + #[allow(clippy::suspicious_arithmetic_impl)] + fn div(self, rhs: Self) -> Self::Output { + self * rhs.inverse() + } +} + +impl DivAssign for Secp256K1Scalar { + fn div_assign(&mut self, rhs: Self) { + *self = *self / rhs; + } +} + +#[cfg(test)] +mod tests { + use crate::test_field_arithmetic; + + test_field_arithmetic!(crate::secp256k1_scalar::Secp256K1Scalar); +} diff --git a/field/src/types.rs b/field/src/types.rs new file mode 100644 index 000000000..78d33a460 --- /dev/null +++ b/field/src/types.rs @@ -0,0 +1,610 @@ +use alloc::vec; +use alloc::vec::Vec; +use core::fmt::{Debug, Display}; +use core::hash::Hash; +use core::iter::{Product, Sum}; +use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + +use num::bigint::BigUint; +use num::{Integer, One, ToPrimitive, Zero}; +use plonky2_util::bits_u64; +use rand::rngs::OsRng; +use serde::de::DeserializeOwned; +use serde::Serialize; + +use crate::extension::Frobenius; +use crate::ops::Square; + +/// Sampling +pub trait Sample: Sized { + /// Samples a single value using `rng`. + fn sample(rng: &mut R) -> Self + where + R: rand::RngCore + ?Sized; + + /// Samples a single value using the [`OsRng`]. + #[inline] + fn rand() -> Self { + Self::sample(&mut OsRng) + } + + /// Samples a [`Vec`] of values of length `n` using [`OsRng`]. + #[inline] + fn rand_vec(n: usize) -> Vec { + (0..n).map(|_| Self::rand()).collect() + } + + /// Samples an array of values of length `N` using [`OsRng`]. + #[inline] + fn rand_array() -> [Self; N] { + Self::rand_vec(N) + .try_into() + .ok() + .expect("This conversion can never fail.") + } +} + +/// A finite field. +pub trait Field: + 'static + + Copy + + Eq + + Hash + + Neg + + Add + + AddAssign + + Sum + + Sub + + SubAssign + + Mul + + MulAssign + + Square + + Product + + Div + + DivAssign + + Debug + + Default + + Display + + Sample + + Send + + Sync + + Serialize + + DeserializeOwned +{ + const ZERO: Self; + const ONE: Self; + const TWO: Self; + const NEG_ONE: Self; + + /// The 2-adicity of this field's multiplicative group. + const TWO_ADICITY: usize; + + /// The field's characteristic and it's 2-adicity. + /// Set to `None` when the characteristic doesn't fit in a u64. + const CHARACTERISTIC_TWO_ADICITY: usize; + + /// Generator of the entire multiplicative group, i.e. all non-zero + /// elements. + const MULTIPLICATIVE_GROUP_GENERATOR: Self; + /// Generator of a multiplicative subgroup of order `2^TWO_ADICITY`. + const POWER_OF_TWO_GENERATOR: Self; + + /// The bit length of the field order. + const BITS: usize; + + fn order() -> BigUint; + fn characteristic() -> BigUint; + + #[inline] + fn is_zero(&self) -> bool { + *self == Self::ZERO + } + + #[inline] + fn is_nonzero(&self) -> bool { + *self != Self::ZERO + } + + #[inline] + fn is_one(&self) -> bool { + *self == Self::ONE + } + + #[inline] + fn double(&self) -> Self { + *self + *self + } + + #[inline] + fn cube(&self) -> Self { + self.square() * *self + } + + fn triple(&self) -> Self { + *self * (Self::ONE + Self::TWO) + } + + /// Compute the multiplicative inverse of this field element. + fn try_inverse(&self) -> Option; + + fn inverse(&self) -> Self { + self.try_inverse().expect("Tried to invert zero") + } + + fn batch_multiplicative_inverse(x: &[Self]) -> Vec { + // This is Montgomery's trick. At a high level, we invert the product of the + // given field elements, then derive the individual inverses from that + // via multiplication. + + // The usual Montgomery trick involves calculating an array of cumulative + // products, resulting in a long dependency chain. To increase + // instruction-level parallelism, we compute WIDTH separate cumulative + // product arrays that only meet at the end. + + // Higher WIDTH increases instruction-level parallelism, but too high a value + // will cause us to run out of registers. + const WIDTH: usize = 4; + // JN note: WIDTH is 4. The code is specialized to this value and will need + // modification if it is changed. I tried to make it more generic, but Rust's + // const generics are not yet good enough. + + // Handle special cases. Paradoxically, below is repetitive but concise. + // The branches should be very predictable. + let n = x.len(); + if n == 0 { + return Vec::new(); + } else if n == 1 { + return vec![x[0].inverse()]; + } else if n == 2 { + let x01 = x[0] * x[1]; + let x01inv = x01.inverse(); + return vec![x01inv * x[1], x01inv * x[0]]; + } else if n == 3 { + let x01 = x[0] * x[1]; + let x012 = x01 * x[2]; + let x012inv = x012.inverse(); + let x01inv = x012inv * x[2]; + return vec![x01inv * x[1], x01inv * x[0], x012inv * x01]; + } + debug_assert!(n >= WIDTH); + + // Buf is reused for a few things to save allocations. + // Fill buf with cumulative product of x, only taking every 4th value. + // Concretely, buf will be [ + // x[0], x[1], x[2], x[3], + // x[0] * x[4], x[1] * x[5], x[2] * x[6], x[3] * x[7], + // x[0] * x[4] * x[8], x[1] * x[5] * x[9], x[2] * x[6] * x[10], x[3] * x[7] * + // x[11], ... + // ]. + // If n is not a multiple of WIDTH, the result is truncated from the end. For + // example, for n == 5, we get [x[0], x[1], x[2], x[3], x[0] * x[4]]. + let mut buf: Vec = Vec::with_capacity(n); + // cumul_prod holds the last WIDTH elements of buf. This is redundant, but it's + // how we convince LLVM to keep the values in the registers. + let mut cumul_prod: [Self; WIDTH] = x[..WIDTH].try_into().unwrap(); + buf.extend(cumul_prod); + for (i, &xi) in x[WIDTH..].iter().enumerate() { + cumul_prod[i % WIDTH] *= xi; + buf.push(cumul_prod[i % WIDTH]); + } + debug_assert_eq!(buf.len(), n); + + let mut a_inv = { + // This is where the four dependency chains meet. + // Take the last four elements of buf and invert them all. + let c01 = cumul_prod[0] * cumul_prod[1]; + let c23 = cumul_prod[2] * cumul_prod[3]; + let c0123 = c01 * c23; + let c0123inv = c0123.inverse(); + let c01inv = c0123inv * c23; + let c23inv = c0123inv * c01; + [ + c01inv * cumul_prod[1], + c01inv * cumul_prod[0], + c23inv * cumul_prod[3], + c23inv * cumul_prod[2], + ] + }; + + for i in (WIDTH..n).rev() { + // buf[i - WIDTH] has not been written to by this loop, so it equals + // x[i % WIDTH] * x[i % WIDTH + WIDTH] * ... * x[i - WIDTH]. + buf[i] = buf[i - WIDTH] * a_inv[i % WIDTH]; + // buf[i] now holds the inverse of x[i]. + a_inv[i % WIDTH] *= x[i]; + } + for i in (0..WIDTH).rev() { + buf[i] = a_inv[i]; + } + + for (&bi, &xi) in buf.iter().zip(x) { + // Sanity check only. + debug_assert_eq!(bi * xi, Self::ONE); + } + + buf + } + + /// Compute the inverse of 2^exp in this field. + #[inline] + fn inverse_2exp(exp: usize) -> Self { + // Let p = char(F). Since 2^exp is in the prime subfield, i.e. an + // element of GF_p, its inverse must be as well. Thus we may add + // multiples of p without changing the result. In particular, + // 2^-exp = 2^-exp - p 2^-exp + // = 2^-exp (1 - p) + // = p - (p - 1) / 2^exp + + // If this field's two adicity, t, is at least exp, then 2^exp divides + // p - 1, so this division can be done with a simple bit shift. If + // exp > t, we repeatedly multiply by 2^-t and reduce exp until it's in + // the right range. + + if let Some(p) = Self::characteristic().to_u64() { + // NB: The only reason this is split into two cases is to save + // the multiplication (and possible calculation of + // inverse_2_pow_adicity) in the usual case that exp <= + // TWO_ADICITY. Can remove the branch and simplify if that + // saving isn't worth it. + + if exp > Self::CHARACTERISTIC_TWO_ADICITY { + // NB: This should be a compile-time constant + let inverse_2_pow_adicity: Self = + Self::from_canonical_u64(p - ((p - 1) >> Self::CHARACTERISTIC_TWO_ADICITY)); + + let mut res = inverse_2_pow_adicity; + let mut e = exp - Self::CHARACTERISTIC_TWO_ADICITY; + + while e > Self::CHARACTERISTIC_TWO_ADICITY { + res *= inverse_2_pow_adicity; + e -= Self::CHARACTERISTIC_TWO_ADICITY; + } + res * Self::from_canonical_u64(p - ((p - 1) >> e)) + } else { + Self::from_canonical_u64(p - ((p - 1) >> exp)) + } + } else { + Self::TWO.inverse().exp_u64(exp as u64) + } + } + + fn primitive_root_of_unity(n_log: usize) -> Self { + assert!(n_log <= Self::TWO_ADICITY); + let base = Self::POWER_OF_TWO_GENERATOR; + base.exp_power_of_2(Self::TWO_ADICITY - n_log) + } + + /// Computes a multiplicative subgroup whose order is known in advance. + fn cyclic_subgroup_known_order(generator: Self, order: usize) -> Vec { + generator.powers().take(order).collect() + } + + /// Computes the subgroup generated by the root of unity of a given order + /// generated by `Self::primitive_root_of_unity`. + fn two_adic_subgroup(n_log: usize) -> Vec { + let generator = Self::primitive_root_of_unity(n_log); + generator.powers().take(1 << n_log).collect() + } + + fn cyclic_subgroup_unknown_order(generator: Self) -> Vec { + let mut subgroup = Vec::new(); + for power in generator.powers() { + if power.is_one() && !subgroup.is_empty() { + break; + } + subgroup.push(power); + } + subgroup + } + + fn generator_order(generator: Self) -> usize { + generator.powers().skip(1).position(|y| y.is_one()).unwrap() + 1 + } + + /// Computes a coset of a multiplicative subgroup whose order is known in + /// advance. + fn cyclic_subgroup_coset_known_order(generator: Self, shift: Self, order: usize) -> Vec { + let subgroup = Self::cyclic_subgroup_known_order(generator, order); + subgroup.into_iter().map(|x| x * shift).collect() + } + + /// 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. + fn from_canonical_u64(n: u64) -> Self; + + /// Returns `n`. Assumes that `n` is already in canonical form, i.e. `n < + /// Self::order()`. + // TODO: Should probably be unsafe. + fn from_canonical_u32(n: u32) -> Self { + Self::from_canonical_u64(n as u64) + } + + /// Returns `n`. Assumes that `n` is already in canonical form, i.e. `n < + /// Self::order()`. + // TODO: Should probably be unsafe. + fn from_canonical_u16(n: u16) -> Self { + Self::from_canonical_u64(n as u64) + } + + /// Returns `n`. Assumes that `n` is already in canonical form, i.e. `n < + /// Self::order()`. + // TODO: Should probably be unsafe. + fn from_canonical_u8(n: u8) -> Self { + Self::from_canonical_u64(n as u64) + } + + /// Returns `n`. Assumes that `n` is already in canonical form, i.e. `n < + /// Self::order()`. + // TODO: Should probably be unsafe. + fn from_canonical_usize(n: usize) -> Self { + Self::from_canonical_u64(n as u64) + } + + fn from_bool(b: bool) -> Self { + Self::from_canonical_u64(b as u64) + } + + /// Returns `n % Self::characteristic()`. + fn from_noncanonical_u128(n: u128) -> Self; + + /// Returns `x % Self::CHARACTERISTIC`. + fn from_noncanonical_u64(n: u64) -> Self; + + /// Returns `n` as an element of this field. + fn from_noncanonical_i64(n: i64) -> Self; + + /// Returns `n % Self::characteristic()`. May be cheaper than + /// from_noncanonical_u128 when we know that `n < 2 ** 96`. + #[inline] + fn from_noncanonical_u96((n_lo, n_hi): (u64, u32)) -> Self { + // Default implementation. + let n: u128 = ((n_hi as u128) << 64) + (n_lo as u128); + Self::from_noncanonical_u128(n) + } + + fn exp_power_of_2(&self, power_log: usize) -> Self { + let mut res = *self; + for _ in 0..power_log { + res = res.square(); + } + res + } + + fn exp_u64(&self, power: u64) -> Self { + let mut current = *self; + let mut product = Self::ONE; + + for j in 0..bits_u64(power) { + if (power >> j & 1) != 0 { + product *= current; + } + current = current.square(); + } + product + } + + fn exp_biguint(&self, power: &BigUint) -> Self { + let mut result = Self::ONE; + for &digit in power.to_u64_digits().iter().rev() { + result = result.exp_power_of_2(64); + result *= self.exp_u64(digit); + } + result + } + + /// Returns whether `x^power` is a permutation of this field. + fn is_monomial_permutation_u64(power: u64) -> bool { + match power { + 0 => false, + 1 => true, + _ => (Self::order() - 1u32).gcd(&BigUint::from(power)).is_one(), + } + } + + fn kth_root_u64(&self, k: u64) -> Self { + let p = Self::order(); + let p_minus_1 = &p - 1u32; + debug_assert!( + Self::is_monomial_permutation_u64(k), + "Not a permutation of this field" + ); + + // By Fermat's little theorem, x^p = x and x^(p - 1) = 1, so x^(p + n(p - 1)) = + // x for any n. Our assumption that the k'th root operation is a + // permutation implies gcd(p - 1, k) = 1, so there exists some n such + // that p + n(p - 1) is a multiple of k. Once we find such an n, + // we can rewrite the above as + // x^((p + n(p - 1))/k)^k = x, + // implying that x^((p + n(p - 1))/k) is a k'th root of x. + for n in 0..k { + let numerator = &p + &p_minus_1 * n; + if (&numerator % k).is_zero() { + let power = (numerator / k) % p_minus_1; + return self.exp_biguint(&power); + } + } + panic!( + "x^{} and x^(1/{}) are not permutations of this field, or we have a bug!", + k, k + ); + } + + fn cube_root(&self) -> Self { + self.kth_root_u64(3) + } + + fn powers(&self) -> Powers { + Powers { + base: *self, + current: Self::ONE, + } + } + + /// Representative `g` of the coset used in FRI, so that LDEs in FRI are + /// done over `gH`. + fn coset_shift() -> Self { + Self::MULTIPLICATIVE_GROUP_GENERATOR + } + + /// Equivalent to *self + x * y, but may be cheaper. + #[inline] + fn multiply_accumulate(&self, x: Self, y: Self) -> Self { + // Default implementation. + *self + x * y + } +} + +pub trait PrimeField: Field { + fn to_canonical_biguint(&self) -> BigUint; + + fn is_quadratic_residue(&self) -> bool { + if self.is_zero() { + return true; + } + // This is based on Euler's criterion. + let power = Self::NEG_ONE.to_canonical_biguint() / 2u8; + let exp = self.exp_biguint(&power); + if exp == Self::ONE { + return true; + } + if exp == Self::NEG_ONE { + return false; + } + panic!("Unreachable") + } + + fn sqrt(&self) -> Option { + if self.is_zero() { + Some(*self) + } else if self.is_quadratic_residue() { + let t = (Self::order() - BigUint::from(1u32)) + / (BigUint::from(2u32).pow(Self::TWO_ADICITY as u32)); + let mut z = Self::POWER_OF_TWO_GENERATOR; + let mut w = self.exp_biguint(&((t - BigUint::from(1u32)) / BigUint::from(2u32))); + let mut x = w * *self; + let mut b = x * w; + + let mut v = Self::TWO_ADICITY; + + while !b.is_one() { + let mut k = 0usize; + let mut b2k = b; + while !b2k.is_one() { + b2k = b2k * b2k; + k += 1; + } + let j = v - k - 1; + w = z; + for _ in 0..j { + w = w * w; + } + + z = w * w; + b *= z; + x *= w; + v = k; + } + Some(x) + } else { + None + } + } +} + +/// A finite field of order less than 2^64. +pub trait Field64: Field { + const ORDER: u64; + + /// Returns `n` as an element of this field. Assumes that `0 <= n < + /// Self::ORDER`. + // TODO: Move to `Field`. + // TODO: Should probably be unsafe. + #[inline] + fn from_canonical_i64(n: i64) -> Self { + Self::from_canonical_u64(n as u64) + } + + #[inline] + // TODO: Move to `Field`. + fn add_one(&self) -> Self { + unsafe { self.add_canonical_u64(1) } + } + + #[inline] + // TODO: Move to `Field`. + fn sub_one(&self) -> Self { + unsafe { self.sub_canonical_u64(1) } + } + + /// # Safety + /// Equivalent to *self + Self::from_canonical_u64(rhs), but may be cheaper. + /// The caller must ensure that 0 <= rhs < Self::ORDER. The function may + /// return incorrect results if this precondition is not met. It is + /// marked unsafe for this reason. + // TODO: Move to `Field`. + #[inline] + unsafe fn add_canonical_u64(&self, rhs: u64) -> Self { + // Default implementation. + *self + Self::from_canonical_u64(rhs) + } + + /// # Safety + /// Equivalent to *self - Self::from_canonical_u64(rhs), but may be cheaper. + /// The caller must ensure that 0 <= rhs < Self::ORDER. The function may + /// return incorrect results if this precondition is not met. It is + /// marked unsafe for this reason. + // TODO: Move to `Field`. + #[inline] + unsafe fn sub_canonical_u64(&self, rhs: u64) -> Self { + // Default implementation. + *self - Self::from_canonical_u64(rhs) + } +} + +/// A finite field of prime order less than 2^64. +pub trait PrimeField64: PrimeField + Field64 { + fn to_canonical_u64(&self) -> u64; + + fn to_noncanonical_u64(&self) -> u64; + + #[inline(always)] + fn to_canonical(&self) -> Self { + Self::from_canonical_u64(self.to_canonical_u64()) + } +} + +/// An iterator over the powers of a certain base element `b`: `b^0, b^1, b^2, +/// ...`. +#[derive(Clone)] +pub struct Powers { + base: F, + current: F, +} + +impl Iterator for Powers { + type Item = F; + + fn next(&mut self) -> Option { + let result = self.current; + self.current *= self.base; + Some(result) + } +} + +impl Powers { + /// Apply the Frobenius automorphism `k` times. + pub fn repeated_frobenius(self, k: usize) -> Self + where + F: Frobenius, + { + let Self { base, current } = self; + Self { + base: base.repeated_frobenius(k), + current: current.repeated_frobenius(k), + } + } +} diff --git a/field/src/zero_poly_coset.rs b/field/src/zero_poly_coset.rs new file mode 100644 index 000000000..305058897 --- /dev/null +++ b/field/src/zero_poly_coset.rs @@ -0,0 +1,62 @@ +use alloc::vec::Vec; + +use crate::packed::PackedField; +use crate::types::Field; + +/// Precomputations of the evaluation of `Z_H(X) = X^n - 1` on a coset `gK` with +/// `H <= K`. +pub struct ZeroPolyOnCoset { + /// `n = |H|`. + n: F, + /// `rate = |K|/|H|`. + rate: usize, + /// Holds `g^n * (w^n)^i - 1 = g^n * v^i - 1` for `i in 0..rate`, with `w` a + /// generator of `K` and `v` a `rate`-primitive root of unity. + evals: Vec, + /// Holds the multiplicative inverses of `evals`. + inverses: Vec, +} + +impl ZeroPolyOnCoset { + pub fn new(n_log: usize, rate_bits: usize) -> Self { + let g_pow_n = F::coset_shift().exp_power_of_2(n_log); + let evals = F::two_adic_subgroup(rate_bits) + .into_iter() + .map(|x| g_pow_n * x - F::ONE) + .collect::>(); + let inverses = F::batch_multiplicative_inverse(&evals); + Self { + n: F::from_canonical_usize(1 << n_log), + rate: 1 << rate_bits, + evals, + inverses, + } + } + + /// Returns `Z_H(g * w^i)`. + pub fn eval(&self, i: usize) -> F { + self.evals[i % self.rate] + } + + /// Returns `1 / Z_H(g * w^i)`. + pub fn eval_inverse(&self, i: usize) -> F { + self.inverses[i % self.rate] + } + + /// Like `eval_inverse`, but for a range of indices starting with `i_start`. + pub fn eval_inverse_packed>(&self, i_start: usize) -> P { + let mut packed = P::ZEROS; + packed + .as_slice_mut() + .iter_mut() + .enumerate() + .for_each(|(j, packed_j)| *packed_j = self.eval_inverse(i_start + j)); + packed + } + + /// 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/maybe_rayon/.cargo/katex-header.html b/maybe_rayon/.cargo/katex-header.html new file mode 100644 index 000000000..20723b5d2 --- /dev/null +++ b/maybe_rayon/.cargo/katex-header.html @@ -0,0 +1 @@ +../../.cargo/katex-header.html \ No newline at end of file diff --git a/maybe_rayon/Cargo.toml b/maybe_rayon/Cargo.toml new file mode 100644 index 000000000..e43656321 --- /dev/null +++ b/maybe_rayon/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "plonky2_maybe_rayon" +description = "Feature-gated wrapper around rayon" +license = "MIT OR Apache-2.0" +version = "0.1.1" +edition = "2021" + +[features] +parallel = ["rayon"] + +[dependencies] +rayon = { version = "1.5.3", optional = true } + +# Display math equations properly in documentation +[package.metadata.docs.rs] +rustdoc-args = ["--html-in-header", ".cargo/katex-header.html"] diff --git a/maybe_rayon/LICENSE-APACHE b/maybe_rayon/LICENSE-APACHE new file mode 100644 index 000000000..1e5006dc1 --- /dev/null +++ b/maybe_rayon/LICENSE-APACHE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + diff --git a/maybe_rayon/LICENSE-MIT b/maybe_rayon/LICENSE-MIT new file mode 100644 index 000000000..86d690b22 --- /dev/null +++ b/maybe_rayon/LICENSE-MIT @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2022 The Plonky2 Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/maybe_rayon/README.md b/maybe_rayon/README.md new file mode 100644 index 000000000..bb4e2d8a9 --- /dev/null +++ b/maybe_rayon/README.md @@ -0,0 +1,13 @@ +## License + +Licensed under either of + +* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/maybe_rayon/src/lib.rs b/maybe_rayon/src/lib.rs new file mode 100644 index 000000000..c4bfb2e93 --- /dev/null +++ b/maybe_rayon/src/lib.rs @@ -0,0 +1,282 @@ +#[cfg(not(feature = "parallel"))] +use core::{ + iter::{FlatMap, IntoIterator, Iterator}, + slice::{Chunks, ChunksExact, ChunksExactMut, ChunksMut}, +}; + +#[cfg(feature = "parallel")] +pub use rayon::{ + self, + prelude::{ + IndexedParallelIterator, ParallelDrainFull, ParallelDrainRange, ParallelExtend, + ParallelIterator, + }, +}; +#[cfg(feature = "parallel")] +use rayon::{ + prelude::*, + slice::{ + Chunks as ParChunks, ChunksExact as ParChunksExact, ChunksExactMut as ParChunksExactMut, + ChunksMut as ParChunksMut, ParallelSlice, ParallelSliceMut, + }, +}; + +pub trait MaybeParIter<'data> { + #[cfg(feature = "parallel")] + type Item: Send + 'data; + + #[cfg(feature = "parallel")] + type Iter: ParallelIterator; + + #[cfg(not(feature = "parallel"))] + type Item; + + #[cfg(not(feature = "parallel"))] + type Iter: Iterator; + + fn par_iter(&'data self) -> Self::Iter; +} + +#[cfg(feature = "parallel")] +impl<'data, T> MaybeParIter<'data> for T +where + T: ?Sized + IntoParallelRefIterator<'data>, +{ + type Item = T::Item; + type Iter = T::Iter; + + fn par_iter(&'data self) -> Self::Iter { + self.par_iter() + } +} + +#[cfg(not(feature = "parallel"))] +impl<'data, T: 'data> MaybeParIter<'data> for Vec { + type Item = &'data T; + type Iter = std::slice::Iter<'data, T>; + + fn par_iter(&'data self) -> Self::Iter { + self.iter() + } +} + +#[cfg(not(feature = "parallel"))] +impl<'data, T: 'data> MaybeParIter<'data> for [T] { + type Item = &'data T; + type Iter = std::slice::Iter<'data, T>; + + fn par_iter(&'data self) -> Self::Iter { + self.iter() + } +} + +pub trait MaybeParIterMut<'data> { + #[cfg(feature = "parallel")] + type Item: Send + 'data; + + #[cfg(feature = "parallel")] + type Iter: ParallelIterator; + + #[cfg(not(feature = "parallel"))] + type Item; + + #[cfg(not(feature = "parallel"))] + type Iter: Iterator; + + fn par_iter_mut(&'data mut self) -> Self::Iter; +} + +#[cfg(feature = "parallel")] +impl<'data, T> MaybeParIterMut<'data> for T +where + T: ?Sized + IntoParallelRefMutIterator<'data>, +{ + type Item = T::Item; + type Iter = T::Iter; + + fn par_iter_mut(&'data mut self) -> Self::Iter { + self.par_iter_mut() + } +} + +#[cfg(not(feature = "parallel"))] +impl<'data, T: 'data> MaybeParIterMut<'data> for Vec { + type Item = &'data mut T; + type Iter = std::slice::IterMut<'data, T>; + + fn par_iter_mut(&'data mut self) -> Self::Iter { + self.iter_mut() + } +} + +#[cfg(not(feature = "parallel"))] +impl<'data, T: 'data> MaybeParIterMut<'data> for [T] { + type Item = &'data mut T; + type Iter = std::slice::IterMut<'data, T>; + + fn par_iter_mut(&'data mut self) -> Self::Iter { + self.iter_mut() + } +} + +pub trait MaybeIntoParIter { + #[cfg(feature = "parallel")] + type Item: Send; + + #[cfg(feature = "parallel")] + type Iter: ParallelIterator; + + #[cfg(not(feature = "parallel"))] + type Item; + + #[cfg(not(feature = "parallel"))] + type Iter: Iterator; + + fn into_par_iter(self) -> Self::Iter; +} + +#[cfg(feature = "parallel")] +impl MaybeIntoParIter for T +where + T: IntoParallelIterator, +{ + type Item = T::Item; + type Iter = T::Iter; + + fn into_par_iter(self) -> Self::Iter { + self.into_par_iter() + } +} + +#[cfg(not(feature = "parallel"))] +impl MaybeIntoParIter for T +where + T: IntoIterator, +{ + type Item = T::Item; + type Iter = T::IntoIter; + + fn into_par_iter(self) -> Self::Iter { + self.into_iter() + } +} + +#[cfg(feature = "parallel")] +pub trait MaybeParChunks { + fn par_chunks(&self, chunk_size: usize) -> ParChunks<'_, T>; + fn par_chunks_exact(&self, chunk_size: usize) -> ParChunksExact<'_, T>; +} + +#[cfg(not(feature = "parallel"))] +pub trait MaybeParChunks { + fn par_chunks(&self, chunk_size: usize) -> Chunks<'_, T>; + fn par_chunks_exact(&self, chunk_size: usize) -> ChunksExact<'_, T>; +} + +#[cfg(feature = "parallel")] +impl + ?Sized, U: Sync> MaybeParChunks for T { + fn par_chunks(&self, chunk_size: usize) -> ParChunks<'_, U> { + self.par_chunks(chunk_size) + } + fn par_chunks_exact(&self, chunk_size: usize) -> ParChunksExact<'_, U> { + self.par_chunks_exact(chunk_size) + } +} + +#[cfg(not(feature = "parallel"))] +impl MaybeParChunks for [T] { + fn par_chunks(&self, chunk_size: usize) -> Chunks<'_, T> { + self.chunks(chunk_size) + } + + fn par_chunks_exact(&self, chunk_size: usize) -> ChunksExact<'_, T> { + self.chunks_exact(chunk_size) + } +} + +#[cfg(feature = "parallel")] +pub trait MaybeParChunksMut { + fn par_chunks_mut(&mut self, chunk_size: usize) -> ParChunksMut<'_, T>; + fn par_chunks_exact_mut(&mut self, chunk_size: usize) -> ParChunksExactMut<'_, T>; +} + +#[cfg(not(feature = "parallel"))] +pub trait MaybeParChunksMut { + fn par_chunks_mut(&mut self, chunk_size: usize) -> ChunksMut<'_, T>; + fn par_chunks_exact_mut(&mut self, chunk_size: usize) -> ChunksExactMut<'_, T>; +} + +#[cfg(feature = "parallel")] +impl, U: Send> MaybeParChunksMut for T { + fn par_chunks_mut(&mut self, chunk_size: usize) -> ParChunksMut<'_, U> { + self.par_chunks_mut(chunk_size) + } + fn par_chunks_exact_mut(&mut self, chunk_size: usize) -> ParChunksExactMut<'_, U> { + self.par_chunks_exact_mut(chunk_size) + } +} + +#[cfg(not(feature = "parallel"))] +impl MaybeParChunksMut for [T] { + fn par_chunks_mut(&mut self, chunk_size: usize) -> ChunksMut<'_, T> { + self.chunks_mut(chunk_size) + } + fn par_chunks_exact_mut(&mut self, chunk_size: usize) -> ChunksExactMut<'_, T> { + self.chunks_exact_mut(chunk_size) + } +} + +#[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; + + fn find_any

(mut self, predicate: P) -> Option + where + P: Fn(&Self::Item) -> bool + Sync + Send, + { + 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")] +pub fn join(oper_a: A, oper_b: B) -> (RA, RB) +where + A: FnOnce() -> RA + Send, + B: FnOnce() -> RB + Send, + RA: Send, + RB: Send, +{ + rayon::join(oper_a, oper_b) +} + +#[cfg(not(feature = "parallel"))] +pub fn join(oper_a: A, oper_b: B) -> (RA, RB) +where + A: FnOnce() -> RA, + B: FnOnce() -> RB, +{ + (oper_a(), oper_b()) +} diff --git a/pgo-profile.sh b/pgo-profile.sh new file mode 100755 index 000000000..48be564a9 --- /dev/null +++ b/pgo-profile.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +rm -rf tmp/pgo + +if !(rustup help >/dev/null); then + echo "Rustup not found; ensure rustup is on PATH" 1>&2 + exit 1 +fi + +if !(rustup component add llvm-tools-preview); then + echo "Could not install llvm-tools-preview" 1>&2 + exit 1 +fi + +TOOLCHAIN_BASE=$(dirname $(dirname $(rustup which cargo))) +TOOLCHAIN_NAME=$(basename $TOOLCHAIN_BASE) +TARGET_TRIPLE=${TOOLCHAIN_NAME#*"-"} # e.g. nightly-x86_64-apple-darwin -> x86_64-apple-darwin +PROFDATA_PATH=$TOOLCHAIN_BASE/lib/rustlib/$TARGET_TRIPLE/bin/llvm-profdata + +if !(RUSTFLAGS="$RUSTFLAGS -Cprofile-generate=$(pwd)/tmp/pgo/data -Ctarget-cpu=native" cargo test --target-dir tmp/pgo/target --release test_recursive_recursive_verifier -- --ignored); then + echo "Build failed" 1>&2 + exit 1 +fi +if !($PROFDATA_PATH merge -o pgo-data.profdata tmp/pgo/data); then + echo "Could not create .profdata file" 1>&2 + exit 1 +fi + +rm -rf tmp/pgo + +echo '.profdata file successfully created. Add "-Cprofile-use=$(pwd)/pgo-data.profdata" to RUSTFLAGS to use it.' 1>&2 diff --git a/plonky2/.cargo/katex-header.html b/plonky2/.cargo/katex-header.html new file mode 100644 index 000000000..20723b5d2 --- /dev/null +++ b/plonky2/.cargo/katex-header.html @@ -0,0 +1 @@ +../../.cargo/katex-header.html \ No newline at end of file diff --git a/plonky2/Cargo.toml b/plonky2/Cargo.toml new file mode 100644 index 000000000..4cc44cccd --- /dev/null +++ b/plonky2/Cargo.toml @@ -0,0 +1,85 @@ +[package] +name = "plonky2" +description = "Recursive SNARKs based on PLONK and FRI" +version = "0.1.4" +license = "MIT OR Apache-2.0" +authors = ["Daniel Lubarov ", "William Borgeaud ", "Nicholas Ward "] +readme = "README.md" +repository = "https://github.com/0xPolygonZero/plonky2" +keywords = ["cryptography", "SNARK", "PLONK", "FRI"] +categories = ["cryptography"] +edition = "2021" + +[features] +default = ["gate_testing", "parallel", "rand_chacha", "std", "timing"] +gate_testing = [] +parallel = ["hashbrown/rayon", "plonky2_maybe_rayon/parallel"] +std = ["anyhow/std", "rand/std", "itertools/use_std"] +timing = ["std", "dep:web-time"] + +[dependencies] +ahash = { version = "0.8.3", default-features = false, features = ["compile-time-rng"] } # NOTE: Be sure to keep this version the same as the dependency in `hashbrown`. +anyhow = { version = "1.0.40", default-features = false } +hashbrown = { version = "0.14.0", default-features = false, features = ["ahash", "serde"] } # NOTE: When upgrading, see `ahash` dependency. +itertools = { version = "0.11.0", default-features = false } +keccak-hash = { version = "0.8.0", default-features = false } +log = { version = "0.4.14", default-features = false } +plonky2_maybe_rayon = { path = "../maybe_rayon", default-features = false } +num = { version = "0.4", default-features = false, features = ["rand"] } +plonky2_field = { path = "../field", default-features = false } +plonky2_util = { path = "../util", default-features = false } +rand = { version = "0.8.4", default-features = false } +rand_chacha = { version = "0.3.1", optional = true, default-features = false } +serde = { version = "1.0", default-features = false, features = ["derive", "rc"] } +serde_json = "1.0" +static_assertions = { version = "1.1.0", default-features = false } +unroll = { version = "0.1.5", default-features = false } +web-time = { version = "1.0.0", optional = true } + +[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] +getrandom = { version = "0.2", default-features = false, features = ["js"] } + +[dev-dependencies] +criterion = { version = "0.5.1", default-features = false } +env_logger = { version = "0.9.0", default-features = false } +num_cpus = { version = "1.14.0", default-features = false } +rand = { version = "0.8.4", default-features = false, features = ["getrandom"] } +rand_chacha = { version = "0.3.1", default-features = false } +serde_cbor = { version = "0.11.2" } +structopt = { version = "0.3.26", default-features = false } +tynm = { version = "0.1.6", default-features = false } + +[target.'cfg(not(target_env = "msvc"))'.dev-dependencies] +jemallocator = "0.5.0" + +[[bin]] +name = "generate_constants" +required-features = ["rand_chacha"] + +[[bench]] +name = "field_arithmetic" +harness = false + +[[bench]] +name = "ffts" +harness = false + +[[bench]] +name = "hashing" +harness = false + +[[bench]] +name = "merkle" +harness = false + +[[bench]] +name = "transpose" +harness = false + +[[bench]] +name = "reverse_index_bits" +harness = false + +# Display math equations properly in documentation +[package.metadata.docs.rs] +rustdoc-args = ["--html-in-header", ".cargo/katex-header.html"] diff --git a/plonky2/LICENSE-APACHE b/plonky2/LICENSE-APACHE new file mode 100644 index 000000000..1e5006dc1 --- /dev/null +++ b/plonky2/LICENSE-APACHE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + diff --git a/plonky2/LICENSE-MIT b/plonky2/LICENSE-MIT new file mode 100644 index 000000000..86d690b22 --- /dev/null +++ b/plonky2/LICENSE-MIT @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2022 The Plonky2 Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/plonky2/README.md b/plonky2/README.md new file mode 100644 index 000000000..7d3a3d65b --- /dev/null +++ b/plonky2/README.md @@ -0,0 +1,20 @@ +# Plonky2 + +Plonky2 is a SNARK implementation based on techniques from PLONK and FRI. It is the successor of [Plonky](https://github.com/0xPolygonZero/plonky), which was based on PLONK and Halo. + +Plonky2 is built for speed, and features a highly efficient recursive circuit. On a Macbook Pro, recursive proofs can be generated in about 170 ms. + + +## License + +Licensed under either of + +* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/plonky2/benches/allocator/mod.rs b/plonky2/benches/allocator/mod.rs new file mode 100644 index 000000000..441e5dc50 --- /dev/null +++ b/plonky2/benches/allocator/mod.rs @@ -0,0 +1,7 @@ +// Set up Jemalloc +#[cfg(not(target_env = "msvc"))] +use jemallocator::Jemalloc; + +#[cfg(not(target_env = "msvc"))] +#[global_allocator] +static GLOBAL: Jemalloc = Jemalloc; diff --git a/plonky2/benches/ffts.rs b/plonky2/benches/ffts.rs new file mode 100644 index 000000000..4149c02d4 --- /dev/null +++ b/plonky2/benches/ffts.rs @@ -0,0 +1,46 @@ +mod allocator; + +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use plonky2::field::goldilocks_field::GoldilocksField; +use plonky2::field::polynomial::PolynomialCoeffs; +use plonky2::field::types::Field; +use tynm::type_name; + +pub(crate) fn bench_ffts(c: &mut Criterion) { + let mut group = c.benchmark_group(&format!("fft<{}>", type_name::())); + + for size_log in [13, 14, 15, 16] { + let size = 1 << size_log; + group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, _| { + let coeffs = PolynomialCoeffs::new(F::rand_vec(size)); + b.iter(|| coeffs.clone().fft_with_options(None, None)); + }); + } +} + +pub(crate) fn bench_ldes(c: &mut Criterion) { + const RATE_BITS: usize = 3; + + let mut group = c.benchmark_group(&format!("lde<{}>", type_name::())); + + for size_log in [13, 14, 15, 16] { + let orig_size = 1 << (size_log - RATE_BITS); + let lde_size = 1 << size_log; + + group.bench_with_input(BenchmarkId::from_parameter(lde_size), &lde_size, |b, _| { + let coeffs = PolynomialCoeffs::new(F::rand_vec(orig_size)); + b.iter(|| { + let padded_coeffs = coeffs.lde(RATE_BITS); + padded_coeffs.fft_with_options(Some(RATE_BITS), None) + }); + }); + } +} + +fn criterion_benchmark(c: &mut Criterion) { + bench_ffts::(c); + bench_ldes::(c); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/plonky2/benches/field_arithmetic.rs b/plonky2/benches/field_arithmetic.rs new file mode 100644 index 000000000..9db45a500 --- /dev/null +++ b/plonky2/benches/field_arithmetic.rs @@ -0,0 +1,182 @@ +mod allocator; + +use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; +use plonky2::field::extension::quadratic::QuadraticExtension; +use plonky2::field::extension::quartic::QuarticExtension; +use plonky2::field::extension::quintic::QuinticExtension; +use plonky2::field::goldilocks_field::GoldilocksField; +use plonky2::field::types::Field; +use tynm::type_name; + +pub(crate) fn bench_field(c: &mut Criterion) { + c.bench_function(&format!("mul-throughput<{}>", type_name::()), |b| { + b.iter_batched( + || (F::rand(), F::rand(), F::rand(), F::rand()), + |(mut x, mut y, mut z, mut w)| { + for _ in 0..25 { + (x, y, z, w) = (x * y, y * z, z * w, w * x); + } + (x, y, z, w) + }, + BatchSize::SmallInput, + ) + }); + + c.bench_function(&format!("mul-latency<{}>", type_name::()), |b| { + b.iter_batched( + || F::rand(), + |mut x| { + for _ in 0..100 { + x = x * x; + } + x + }, + BatchSize::SmallInput, + ) + }); + + c.bench_function(&format!("sqr-throughput<{}>", type_name::()), |b| { + b.iter_batched( + || (F::rand(), F::rand(), F::rand(), F::rand()), + |(mut x, mut y, mut z, mut w)| { + for _ in 0..25 { + (x, y, z, w) = (x.square(), y.square(), z.square(), w.square()); + } + (x, y, z, w) + }, + BatchSize::SmallInput, + ) + }); + + c.bench_function(&format!("sqr-latency<{}>", type_name::()), |b| { + b.iter_batched( + || F::rand(), + |mut x| { + for _ in 0..100 { + x = x.square(); + } + x + }, + BatchSize::SmallInput, + ) + }); + + c.bench_function(&format!("add-throughput<{}>", type_name::()), |b| { + b.iter_batched( + || { + ( + F::rand(), + F::rand(), + F::rand(), + F::rand(), + F::rand(), + F::rand(), + F::rand(), + F::rand(), + F::rand(), + F::rand(), + ) + }, + |(mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h, mut i, mut j)| { + for _ in 0..10 { + (a, b, c, d, e, f, g, h, i, j) = ( + a + b, + b + c, + c + d, + d + e, + e + f, + f + g, + g + h, + h + i, + i + j, + j + a, + ); + } + (a, b, c, d, e, f, g, h, i, j) + }, + BatchSize::SmallInput, + ) + }); + + c.bench_function(&format!("add-latency<{}>", type_name::()), |b| { + b.iter_batched( + || F::rand(), + |mut x| { + for _ in 0..100 { + x = x + x; + } + x + }, + BatchSize::SmallInput, + ) + }); + + c.bench_function(&format!("try_inverse<{}>", type_name::()), |b| { + b.iter_batched(|| F::rand(), |x| x.try_inverse(), BatchSize::SmallInput) + }); + + c.bench_function( + &format!("batch_multiplicative_inverse-tiny<{}>", type_name::()), + |b| { + b.iter_batched( + || (0..2).map(|_| F::rand()).collect::>(), + |x| F::batch_multiplicative_inverse(&x), + BatchSize::SmallInput, + ) + }, + ); + + c.bench_function( + &format!("batch_multiplicative_inverse-small<{}>", type_name::()), + |b| { + b.iter_batched( + || (0..4).map(|_| F::rand()).collect::>(), + |x| F::batch_multiplicative_inverse(&x), + BatchSize::SmallInput, + ) + }, + ); + + c.bench_function( + &format!("batch_multiplicative_inverse-medium<{}>", type_name::()), + |b| { + b.iter_batched( + || (0..16).map(|_| F::rand()).collect::>(), + |x| F::batch_multiplicative_inverse(&x), + BatchSize::SmallInput, + ) + }, + ); + + c.bench_function( + &format!("batch_multiplicative_inverse-large<{}>", type_name::()), + |b| { + b.iter_batched( + || (0..256).map(|_| F::rand()).collect::>(), + |x| F::batch_multiplicative_inverse(&x), + BatchSize::LargeInput, + ) + }, + ); + + c.bench_function( + &format!("batch_multiplicative_inverse-huge<{}>", type_name::()), + |b| { + b.iter_batched( + || (0..65536).map(|_| F::rand()).collect::>(), + |x| F::batch_multiplicative_inverse(&x), + BatchSize::LargeInput, + ) + }, + ); +} + +fn criterion_benchmark(c: &mut Criterion) { + bench_field::(c); + bench_field::>(c); + bench_field::>(c); + bench_field::>(c); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/plonky2/benches/hashing.rs b/plonky2/benches/hashing.rs new file mode 100644 index 000000000..cf64b764a --- /dev/null +++ b/plonky2/benches/hashing.rs @@ -0,0 +1,41 @@ +mod allocator; + +use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; +use plonky2::field::goldilocks_field::GoldilocksField; +use plonky2::field::types::Sample; +use plonky2::hash::hash_types::{BytesHash, RichField}; +use plonky2::hash::keccak::KeccakHash; +use plonky2::hash::poseidon::{Poseidon, SPONGE_WIDTH}; +use plonky2::plonk::config::Hasher; +use tynm::type_name; + +pub(crate) fn bench_keccak(c: &mut Criterion) { + c.bench_function("keccak256", |b| { + b.iter_batched( + || (BytesHash::<32>::rand(), BytesHash::<32>::rand()), + |(left, right)| as Hasher>::two_to_one(left, right), + BatchSize::SmallInput, + ) + }); +} + +pub(crate) fn bench_poseidon(c: &mut Criterion) { + c.bench_function( + &format!("poseidon<{}, {SPONGE_WIDTH}>", type_name::()), + |b| { + b.iter_batched( + || F::rand_array::(), + |state| F::poseidon(state), + BatchSize::SmallInput, + ) + }, + ); +} + +fn criterion_benchmark(c: &mut Criterion) { + bench_poseidon::(c); + bench_keccak::(c); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/plonky2/benches/merkle.rs b/plonky2/benches/merkle.rs new file mode 100644 index 000000000..f9bae127f --- /dev/null +++ b/plonky2/benches/merkle.rs @@ -0,0 +1,37 @@ +mod allocator; + +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use plonky2::field::goldilocks_field::GoldilocksField; +use plonky2::hash::hash_types::RichField; +use plonky2::hash::keccak::KeccakHash; +use plonky2::hash::merkle_tree::MerkleTree; +use plonky2::hash::poseidon::PoseidonHash; +use plonky2::plonk::config::Hasher; +use tynm::type_name; + +const ELEMS_PER_LEAF: usize = 135; + +pub(crate) fn bench_merkle_tree>(c: &mut Criterion) { + let mut group = c.benchmark_group(&format!( + "merkle-tree<{}, {}>", + type_name::(), + type_name::() + )); + group.sample_size(10); + + for size_log in [13, 14, 15] { + let size = 1 << size_log; + group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, _| { + let leaves = vec![F::rand_vec(ELEMS_PER_LEAF); size]; + b.iter(|| MerkleTree::::new(leaves.clone(), 0)); + }); + } +} + +fn criterion_benchmark(c: &mut Criterion) { + bench_merkle_tree::(c); + bench_merkle_tree::>(c); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/plonky2/benches/reverse_index_bits.rs b/plonky2/benches/reverse_index_bits.rs new file mode 100644 index 000000000..5c838a18c --- /dev/null +++ b/plonky2/benches/reverse_index_bits.rs @@ -0,0 +1,32 @@ +mod allocator; + +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use plonky2::field::goldilocks_field::GoldilocksField; +use plonky2::field::types::Sample; +use plonky2_util::{reverse_index_bits, reverse_index_bits_in_place}; + +type F = GoldilocksField; + +fn benchmark_in_place(c: &mut Criterion) { + let mut group = c.benchmark_group("reverse-index-bits-in-place"); + for width in [1 << 8, 1 << 16, 1 << 24] { + group.bench_with_input(BenchmarkId::from_parameter(width), &width, |b, _| { + let mut values = F::rand_vec(width); + b.iter(|| reverse_index_bits_in_place(&mut values)); + }); + } +} + +fn benchmark_out_of_place(c: &mut Criterion) { + let mut group = c.benchmark_group("reverse-index-bits"); + for width in [1 << 8, 1 << 16, 1 << 24] { + group.bench_with_input(BenchmarkId::from_parameter(width), &width, |b, _| { + let values = F::rand_vec(width); + b.iter(|| reverse_index_bits(&values)); + }); + } +} + +criterion_group!(benches_in_place, benchmark_in_place); +criterion_group!(benches_out_of_place, benchmark_out_of_place); +criterion_main!(benches_in_place, benches_out_of_place); diff --git a/plonky2/benches/transpose.rs b/plonky2/benches/transpose.rs new file mode 100644 index 000000000..ceed126b1 --- /dev/null +++ b/plonky2/benches/transpose.rs @@ -0,0 +1,29 @@ +mod allocator; + +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use plonky2::field::goldilocks_field::GoldilocksField; +use plonky2::field::types::Sample; +use plonky2::util::transpose; + +fn criterion_benchmark(c: &mut Criterion) { + type F = GoldilocksField; + + // In practice, for the matrices we care about, each row is associated with a + // polynomial of degree 2^13, and has been low-degree extended to a length + // of 2^16. + const WIDTH: usize = 1 << 16; + + let mut group = c.benchmark_group("transpose"); + + // We have matrices with various numbers of polynomials. For example, the + // witness matrix involves 100+ polynomials. + for height in [5, 50, 100, 150] { + group.bench_with_input(BenchmarkId::from_parameter(height), &height, |b, _| { + let matrix = (0..height).map(|_| F::rand_vec(WIDTH)).collect::>(); + b.iter(|| transpose(&matrix)); + }); + } +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/plonky2/examples/bench_recursion.rs b/plonky2/examples/bench_recursion.rs new file mode 100644 index 000000000..c08d8c59d --- /dev/null +++ b/plonky2/examples/bench_recursion.rs @@ -0,0 +1,430 @@ +// HACK: Ideally this would live in `benches/`, but `cargo bench` doesn't allow +// custom CLI argument parsing (even with harness disabled). We could also have +// put it in `src/bin/`, but then we wouldn't have access to +// `[dev-dependencies]`. + +#[cfg(not(feature = "std"))] +extern crate alloc; + +#[cfg(not(feature = "std"))] +use alloc::sync::Arc; +use core::num::ParseIntError; +use core::ops::RangeInclusive; +use core::str::FromStr; +#[cfg(feature = "std")] +use std::sync::Arc; + +use anyhow::{anyhow, Context as _, Result}; +use itertools::Itertools; +use log::{info, Level, LevelFilter}; +use plonky2::gadgets::lookup::TIP5_TABLE; +use plonky2::gates::noop::NoopGate; +use plonky2::hash::hash_types::RichField; +use plonky2::iop::witness::{PartialWitness, WitnessWrite}; +use plonky2::plonk::circuit_builder::CircuitBuilder; +use plonky2::plonk::circuit_data::{CircuitConfig, CommonCircuitData, VerifierOnlyCircuitData}; +use plonky2::plonk::config::{AlgebraicHasher, GenericConfig, PoseidonGoldilocksConfig}; +use plonky2::plonk::proof::{CompressedProofWithPublicInputs, ProofWithPublicInputs}; +use plonky2::plonk::prover::prove; +use plonky2::util::serialization::DefaultGateSerializer; +use plonky2::util::timing::TimingTree; +use plonky2_field::extension::Extendable; +use plonky2_maybe_rayon::rayon; +use rand::rngs::OsRng; +use rand::{RngCore, SeedableRng}; +use rand_chacha::ChaCha8Rng; +use structopt::StructOpt; + +type ProofTuple = ( + ProofWithPublicInputs, + VerifierOnlyCircuitData, + CommonCircuitData, +); + +#[derive(Clone, StructOpt, Debug)] +#[structopt(name = "bench_recursion")] +struct Options { + /// Verbose mode (-v, -vv, -vvv, etc.) + #[structopt(short, long, parse(from_occurrences))] + verbose: usize, + + /// Apply an env_filter compatible log filter + #[structopt(long, env, default_value)] + log_filter: String, + + /// Random seed for deterministic runs. + /// If not specified a new seed is generated from OS entropy. + #[structopt(long, parse(try_from_str = parse_hex_u64))] + seed: Option, + + /// Number of compute threads to use. Defaults to number of cores. Can be a + /// single value or a rust style range. + #[structopt(long, parse(try_from_str = parse_range_usize))] + threads: Option>, + + /// Log2 gate count of the inner proof. Can be a single value or a rust + /// style range. + #[structopt(long, default_value="14", parse(try_from_str = parse_range_usize))] + size: RangeInclusive, + + /// Lookup type. If `lookup_type == 0` or `lookup_type > 2`, then a + /// benchmark with NoopGates only is run. If `lookup_type == 1`, a + /// benchmark with one lookup is run. If `lookup_type == 2`, a benchmark + /// with 515 lookups is run. + #[structopt(long, default_value="0", parse(try_from_str = parse_hex_u64))] + lookup_type: u64, +} + +/// Creates a dummy proof which should have `2 ** log2_size` rows. +fn dummy_proof, C: GenericConfig, const D: usize>( + config: &CircuitConfig, + log2_size: usize, +) -> Result> { + // 'size' is in degree, but we want number of noop gates. A non-zero amount of + // padding will be added and size will be rounded to the next power of two. To + // hit our target size, we go just under the previous power of two and hope + // padding is less than half the proof. + let num_dummy_gates = match log2_size { + 0 => return Err(anyhow!("size must be at least 1")), + 1 => 0, + 2 => 1, + n => (1 << (n - 1)) + 1, + }; + info!("Constructing inner proof with {} gates", num_dummy_gates); + let mut builder = CircuitBuilder::::new(config.clone()); + for _ in 0..num_dummy_gates { + builder.add_gate(NoopGate, vec![]); + } + builder.print_gate_counts(0); + + let data = builder.build::(); + let inputs = PartialWitness::new(); + + let mut timing = TimingTree::new("prove", Level::Debug); + let proof = prove::(&data.prover_only, &data.common, inputs, &mut timing)?; + timing.print(); + data.verify(proof.clone())?; + + Ok((proof, data.verifier_only, data.common)) +} + +fn dummy_lookup_proof, C: GenericConfig, const D: usize>( + config: &CircuitConfig, + log2_size: usize, +) -> Result> { + let mut builder = CircuitBuilder::::new(config.clone()); + let tip5_table = TIP5_TABLE.to_vec(); + let inps = 0..256; + let table = Arc::new(inps.zip_eq(tip5_table).collect()); + let tip5_idx = builder.add_lookup_table_from_pairs(table); + let initial_a = builder.add_virtual_target(); + builder.add_lookup_from_index(initial_a, tip5_idx); + builder.register_public_input(initial_a); + + // 'size' is in degree, but we want the number of gates in the circuit. + // A non-zero amount of padding will be added and size will be rounded to the + // next power of two. To hit our target size, we go just under the previous + // power of two and hope padding is less than half the proof. + let targeted_num_gates = match log2_size { + 0 => return Err(anyhow!("size must be at least 1")), + 1 => 0, + 2 => 1, + n => (1 << (n - 1)) + 1, + }; + assert!( + targeted_num_gates >= builder.num_gates(), + "size is too small to support lookups" + ); + + for _ in builder.num_gates()..targeted_num_gates { + builder.add_gate(NoopGate, vec![]); + } + builder.print_gate_counts(0); + + let data = builder.build::(); + let mut inputs = PartialWitness::::new(); + inputs.set_target(initial_a, F::ONE); + let mut timing = TimingTree::new("prove with one lookup", Level::Debug); + let proof = prove(&data.prover_only, &data.common, inputs, &mut timing)?; + timing.print(); + data.verify(proof.clone())?; + + Ok((proof, data.verifier_only, data.common)) +} + +/// Creates a dummy proof which has more than 256 lookups to one LUT +fn dummy_many_rows_proof< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +>( + config: &CircuitConfig, + log2_size: usize, +) -> Result> { + let mut builder = CircuitBuilder::::new(config.clone()); + let tip5_table = TIP5_TABLE.to_vec(); + let inps: Vec = (0..256).collect(); + let tip5_idx = builder.add_lookup_table_from_table(&inps, &tip5_table); + let initial_a = builder.add_virtual_target(); + + let output = builder.add_lookup_from_index(initial_a, tip5_idx); + for _ in 0..514 { + builder.add_lookup_from_index(output, 0); + } + + // 'size' is in degree, but we want the number of gates in the circuit. + // A non-zero amount of padding will be added and size will be rounded to the + // next power of two. To hit our target size, we go just under the previous + // power of two and hope padding is less than half the proof. + let targeted_num_gates = match log2_size { + 0 => return Err(anyhow!("size must be at least 1")), + 1 => 0, + 2 => 1, + n => (1 << (n - 1)) + 1, + }; + assert!( + targeted_num_gates >= builder.num_gates(), + "size is too small to support so many lookups" + ); + + for _ in 0..targeted_num_gates { + builder.add_gate(NoopGate, vec![]); + } + + builder.register_public_input(initial_a); + builder.register_public_input(output); + + let mut pw = PartialWitness::new(); + pw.set_target(initial_a, F::ONE); + let data = builder.build::(); + let mut timing = TimingTree::new("prove with many lookups", Level::Debug); + let proof = prove(&data.prover_only, &data.common, pw, &mut timing)?; + timing.print(); + + data.verify(proof.clone())?; + Ok((proof, data.verifier_only, data.common)) +} + +fn recursive_proof< + F: RichField + Extendable, + C: GenericConfig, + InnerC: GenericConfig, + const D: usize, +>( + inner: &ProofTuple, + config: &CircuitConfig, + min_degree_bits: Option, +) -> Result> +where + InnerC::Hasher: AlgebraicHasher, +{ + let (inner_proof, inner_vd, inner_cd) = inner; + let mut builder = CircuitBuilder::::new(config.clone()); + let pt = builder.add_virtual_proof_with_pis(inner_cd); + + let inner_data = builder.add_virtual_verifier_data(inner_cd.config.fri_config.cap_height); + + builder.verify_proof::(&pt, &inner_data, inner_cd); + builder.print_gate_counts(0); + + if let Some(min_degree_bits) = min_degree_bits { + // We don't want to pad all the way up to 2^min_degree_bits, as the builder will + // add a few special gates afterward. So just pad to 2^(min_degree_bits + // - 1) + 1. Then the builder will pad to the next power of two, + // 2^min_degree_bits. + let min_gates = (1 << (min_degree_bits - 1)) + 1; + for _ in builder.num_gates()..min_gates { + builder.add_gate(NoopGate, vec![]); + } + } + + let data = builder.build::(); + + let mut pw = PartialWitness::new(); + pw.set_proof_with_pis_target(&pt, inner_proof); + pw.set_verifier_data_target(&inner_data, inner_vd); + + let mut timing = TimingTree::new("prove", Level::Debug); + let proof = prove::(&data.prover_only, &data.common, pw, &mut timing)?; + timing.print(); + + data.verify(proof.clone())?; + + Ok((proof, data.verifier_only, data.common)) +} + +/// Test serialization and print some size info. +fn test_serialization, C: GenericConfig, const D: usize>( + proof: &ProofWithPublicInputs, + vd: &VerifierOnlyCircuitData, + common_data: &CommonCircuitData, +) -> Result<()> { + let proof_bytes = proof.to_bytes(); + info!("Proof length: {} bytes", proof_bytes.len()); + let proof_from_bytes = ProofWithPublicInputs::from_bytes(proof_bytes, common_data)?; + assert_eq!(proof, &proof_from_bytes); + + let now = std::time::Instant::now(); + let compressed_proof = proof.clone().compress(&vd.circuit_digest, common_data)?; + let decompressed_compressed_proof = compressed_proof + .clone() + .decompress(&vd.circuit_digest, common_data)?; + info!("{:.4}s to compress proof", now.elapsed().as_secs_f64()); + assert_eq!(proof, &decompressed_compressed_proof); + + let compressed_proof_bytes = compressed_proof.to_bytes(); + info!( + "Compressed proof length: {} bytes", + compressed_proof_bytes.len() + ); + let compressed_proof_from_bytes = + CompressedProofWithPublicInputs::from_bytes(compressed_proof_bytes, common_data)?; + assert_eq!(compressed_proof, compressed_proof_from_bytes); + + let gate_serializer = DefaultGateSerializer; + let common_data_bytes = common_data + .to_bytes(&gate_serializer) + .map_err(|_| anyhow::Error::msg("CommonCircuitData serialization failed."))?; + info!( + "Common circuit data length: {} bytes", + common_data_bytes.len() + ); + let common_data_from_bytes = + CommonCircuitData::::from_bytes(common_data_bytes, &gate_serializer) + .map_err(|_| anyhow::Error::msg("CommonCircuitData deserialization failed."))?; + assert_eq!(common_data, &common_data_from_bytes); + + Ok(()) +} + +pub fn benchmark_function( + config: &CircuitConfig, + log2_inner_size: usize, + lookup_type: u64, +) -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + + let dummy_proof_function = match lookup_type { + 0 => dummy_proof::, + 1 => dummy_lookup_proof::, + 2 => dummy_many_rows_proof::, + _ => dummy_proof::, + }; + + let name = match lookup_type { + 0 => "proof", + 1 => "one lookup proof", + 2 => "multiple lookups proof", + _ => "proof", + }; + // Start with a dummy proof of specified size + let inner = dummy_proof_function(config, log2_inner_size)?; + let (_, _, common_data) = &inner; + info!( + "Initial {} degree {} = 2^{}", + name, + common_data.degree(), + common_data.degree_bits() + ); + + // Recursively verify the proof + let middle = recursive_proof::(&inner, config, None)?; + let (_, _, common_data) = &middle; + info!( + "Single recursion {} degree {} = 2^{}", + name, + common_data.degree(), + common_data.degree_bits() + ); + + // Add a second layer of recursion to shrink the proof size further + let outer = recursive_proof::(&middle, config, None)?; + let (proof, vd, common_data) = &outer; + info!( + "Double recursion {} degree {} = 2^{}", + name, + common_data.degree(), + common_data.degree_bits() + ); + + test_serialization(proof, vd, common_data)?; + + Ok(()) +} + +fn main() -> Result<()> { + // Parse command line arguments, see `--help` for details. + let options = Options::from_args_safe()?; + // Initialize logging + let mut builder = env_logger::Builder::from_default_env(); + builder.parse_filters(&options.log_filter); + builder.format_timestamp(None); + match options.verbose { + 0 => &mut builder, + 1 => builder.filter_level(LevelFilter::Info), + 2 => builder.filter_level(LevelFilter::Debug), + _ => builder.filter_level(LevelFilter::Trace), + }; + builder.try_init()?; + + // Initialize randomness source + let rng_seed = options.seed.unwrap_or_else(|| OsRng.next_u64()); + info!("Using random seed {rng_seed:16x}"); + let _rng = ChaCha8Rng::seed_from_u64(rng_seed); + // TODO: Use `rng` to create deterministic runs + + let num_cpus = num_cpus::get(); + let threads = options.threads.unwrap_or(num_cpus..=num_cpus); + + let config = CircuitConfig::standard_recursion_config(); + + for log2_inner_size in options.size { + // Since the `size` is most likely to be an unbounded range we make that the + // outer iterator. + for threads in threads.clone() { + rayon::ThreadPoolBuilder::new() + .num_threads(threads) + .build() + .context("Failed to build thread pool.")? + .install(|| { + info!( + "Using {} compute threads on {} cores", + rayon::current_num_threads(), + num_cpus + ); + // Run the benchmark. `options.lookup_type` determines which benchmark to run. + benchmark_function(&config, log2_inner_size, options.lookup_type) + })?; + } + } + + Ok(()) +} + +fn parse_hex_u64(src: &str) -> Result { + let src = src.strip_prefix("0x").unwrap_or(src); + u64::from_str_radix(src, 16) +} + +fn parse_range_usize(src: &str) -> Result, ParseIntError> { + if let Some((left, right)) = src.split_once("..=") { + Ok(RangeInclusive::new( + usize::from_str(left)?, + usize::from_str(right)?, + )) + } else if let Some((left, right)) = src.split_once("..") { + Ok(RangeInclusive::new( + usize::from_str(left)?, + if right.is_empty() { + usize::MAX + } else { + usize::from_str(right)?.saturating_sub(1) + }, + )) + } else { + let value = usize::from_str(src)?; + Ok(RangeInclusive::new(value, value)) + } +} diff --git a/plonky2/examples/factorial.rs b/plonky2/examples/factorial.rs new file mode 100644 index 000000000..72d0c3b98 --- /dev/null +++ b/plonky2/examples/factorial.rs @@ -0,0 +1,44 @@ +use anyhow::Result; +use plonky2::field::types::Field; +use plonky2::iop::witness::{PartialWitness, WitnessWrite}; +use plonky2::plonk::circuit_builder::CircuitBuilder; +use plonky2::plonk::circuit_data::CircuitConfig; +use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + +/// An example of using Plonky2 to prove a statement of the form +/// "I know n * (n + 1) * ... * (n + 99)". +/// When n == 1, this is proving knowledge of 100!. +fn main() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + + // The arithmetic circuit. + let initial = builder.add_virtual_target(); + let mut cur_target = initial; + for i in 2..101 { + let i_target = builder.constant(F::from_canonical_u32(i)); + cur_target = builder.mul(cur_target, i_target); + } + + // Public inputs are the initial value (provided below) and the result (which is + // generated). + builder.register_public_input(initial); + builder.register_public_input(cur_target); + + let mut pw = PartialWitness::new(); + pw.set_target(initial, F::ONE); + + let data = builder.build::(); + let proof = data.prove(pw)?; + + println!( + "Factorial starting at {} is {}", + proof.public_inputs[0], proof.public_inputs[1] + ); + + data.verify(proof) +} diff --git a/plonky2/examples/fibonacci.rs b/plonky2/examples/fibonacci.rs new file mode 100644 index 000000000..00cd63271 --- /dev/null +++ b/plonky2/examples/fibonacci.rs @@ -0,0 +1,51 @@ +use anyhow::Result; +use plonky2::field::types::Field; +use plonky2::iop::witness::{PartialWitness, WitnessWrite}; +use plonky2::plonk::circuit_builder::CircuitBuilder; +use plonky2::plonk::circuit_data::CircuitConfig; +use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + +/// An example of using Plonky2 to prove a statement of the form +/// "I know the 100th element of the Fibonacci sequence, starting with constants +/// a and b." When a == 0 and b == 1, this is proving knowledge of the 100th +/// (standard) Fibonacci number. +fn main() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + + // The arithmetic circuit. + let initial_a = builder.add_virtual_target(); + let initial_b = builder.add_virtual_target(); + let mut prev_target = initial_a; + let mut cur_target = initial_b; + for _ in 0..99 { + let temp = builder.add(prev_target, cur_target); + prev_target = cur_target; + cur_target = temp; + } + + // Public inputs are the two initial values (provided below) and the result + // (which is generated). + builder.register_public_input(initial_a); + builder.register_public_input(initial_b); + builder.register_public_input(cur_target); + + // Provide initial values. + let mut pw = PartialWitness::new(); + pw.set_target(initial_a, F::ZERO); + pw.set_target(initial_b, F::ONE); + + let data = builder.build::(); + let proof = data.prove(pw)?; + + println!( + "100th Fibonacci number mod |F| (starting with {}, {}) is: {}", + proof.public_inputs[0], proof.public_inputs[1], proof.public_inputs[2] + ); + + data.verify(proof) +} diff --git a/plonky2/examples/fibonacci_serialization.rs b/plonky2/examples/fibonacci_serialization.rs new file mode 100644 index 000000000..7c75a2827 --- /dev/null +++ b/plonky2/examples/fibonacci_serialization.rs @@ -0,0 +1,69 @@ +use std::fs; + +use anyhow::Result; +use plonky2::field::types::Field; +use plonky2::iop::witness::{PartialWitness, WitnessWrite}; +use plonky2::plonk::circuit_builder::CircuitBuilder; +use plonky2::plonk::circuit_data::CircuitConfig; +use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + +/// An example of using Plonky2 to prove a statement of the form +/// "I know the 100th element of the Fibonacci sequence, starting with constants +/// a and b." When a == 0 and b == 1, this is proving knowledge of the 100th +/// (standard) Fibonacci number. This example also serializes the circuit data +/// and proof to JSON files. +fn main() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + + // The arithmetic circuit. + let initial_a = builder.add_virtual_target(); + let initial_b = builder.add_virtual_target(); + let mut prev_target = initial_a; + let mut cur_target = initial_b; + for _ in 0..99 { + let temp = builder.add(prev_target, cur_target); + prev_target = cur_target; + cur_target = temp; + } + + // Public inputs are the two initial values (provided below) and the result + // (which is generated). + builder.register_public_input(initial_a); + builder.register_public_input(initial_b); + builder.register_public_input(cur_target); + + // Provide initial values. + let mut pw = PartialWitness::new(); + pw.set_target(initial_a, F::ZERO); + pw.set_target(initial_b, F::ONE); + + let data = builder.build::(); + + let common_circuit_data_serialized = serde_json::to_string(&data.common).unwrap(); + fs::write("common_circuit_data.json", common_circuit_data_serialized) + .expect("Unable to write file"); + + let verifier_only_circuit_data_serialized = serde_json::to_string(&data.verifier_only).unwrap(); + fs::write( + "verifier_only_circuit_data.json", + verifier_only_circuit_data_serialized, + ) + .expect("Unable to write file"); + + let proof = data.prove(pw)?; + + let proof_serialized = serde_json::to_string(&proof).unwrap(); + fs::write("proof_with_public_inputs.json", proof_serialized).expect("Unable to write file"); + + println!( + "100th Fibonacci number mod |F| (starting with {}, {}) is: {}", + proof.public_inputs[0], proof.public_inputs[1], proof.public_inputs[2] + ); + + data.verify(proof) +} diff --git a/plonky2/examples/range_check.rs b/plonky2/examples/range_check.rs new file mode 100644 index 000000000..7078a30a2 --- /dev/null +++ b/plonky2/examples/range_check.rs @@ -0,0 +1,37 @@ +use anyhow::Result; +use plonky2::field::types::Field; +use plonky2::iop::witness::{PartialWitness, WitnessWrite}; +use plonky2::plonk::circuit_builder::CircuitBuilder; +use plonky2::plonk::circuit_data::CircuitConfig; +use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + +/// An example of using Plonky2 to prove that a given value lies in a given +/// range. +fn main() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + + // The secret value. + let value = builder.add_virtual_target(); + builder.register_public_input(value); + + let log_max = 6; + builder.range_check(value, log_max); + + let mut pw = PartialWitness::new(); + pw.set_target(value, F::from_canonical_usize(42)); + + let data = builder.build::(); + let proof = data.prove(pw)?; + + println!( + "Value {} is less than 2^{}", + proof.public_inputs[0], log_max, + ); + + data.verify(proof) +} diff --git a/plonky2/examples/square_root.rs b/plonky2/examples/square_root.rs new file mode 100644 index 000000000..c155ba1ec --- /dev/null +++ b/plonky2/examples/square_root.rs @@ -0,0 +1,154 @@ +use core::marker::PhantomData; + +use anyhow::Result; +use plonky2::field::types::{PrimeField, Sample}; +use plonky2::gates::arithmetic_base::ArithmeticBaseGenerator; +use plonky2::gates::poseidon::PoseidonGenerator; +use plonky2::gates::poseidon_mds::PoseidonMdsGenerator; +use plonky2::hash::hash_types::RichField; +use plonky2::iop::generator::{ + ConstantGenerator, GeneratedValues, RandomValueGenerator, SimpleGenerator, +}; +use plonky2::iop::target::Target; +use plonky2::iop::witness::{PartialWitness, PartitionWitness, Witness, WitnessWrite}; +use plonky2::plonk::circuit_builder::CircuitBuilder; +use plonky2::plonk::circuit_data::{CircuitConfig, CircuitData, CommonCircuitData}; +use plonky2::plonk::config::{AlgebraicHasher, GenericConfig, PoseidonGoldilocksConfig}; +use plonky2::recursion::dummy_circuit::DummyProofGenerator; +use plonky2::util::serialization::{ + Buffer, DefaultGateSerializer, IoResult, Read, WitnessGeneratorSerializer, Write, +}; +use plonky2::{get_generator_tag_impl, impl_generator_serializer, read_generator_impl}; +use plonky2_field::extension::Extendable; + +/// A generator used by the prover to calculate the square root (`x`) of a given +/// value (`x_squared`), outside of the circuit, in order to supply it as an +/// additional public input. +#[derive(Debug, Default)] +struct SquareRootGenerator, const D: usize> { + x: Target, + x_squared: Target, + _phantom: PhantomData, +} + +impl, const D: usize> SimpleGenerator + for SquareRootGenerator +{ + fn id(&self) -> String { + "SquareRootGenerator".to_string() + } + + fn dependencies(&self) -> Vec { + vec![self.x_squared] + } + + fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + let x_squared = witness.get_target(self.x_squared); + let x = x_squared.sqrt().unwrap(); + + println!("Square root: {x}"); + + out_buffer.set_target(self.x, x); + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_target(self.x)?; + dst.write_target(self.x_squared) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let x = src.read_target()?; + let x_squared = src.read_target()?; + Ok(Self { + x, + x_squared, + _phantom: PhantomData, + }) + } +} + +pub struct CustomGeneratorSerializer, const D: usize> { + pub _phantom: PhantomData, +} + +impl WitnessGeneratorSerializer for CustomGeneratorSerializer +where + F: RichField + Extendable, + C: GenericConfig + 'static, + C::Hasher: AlgebraicHasher, +{ + impl_generator_serializer! { + CustomGeneratorSerializer, + DummyProofGenerator, + ArithmeticBaseGenerator, + ConstantGenerator, + PoseidonGenerator, + PoseidonMdsGenerator, + RandomValueGenerator, + SquareRootGenerator + } +} + +/// An example of using Plonky2 to prove a statement of the form +/// "I know the square root of this field element." +fn main() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + + let config = CircuitConfig::standard_recursion_config(); + + let mut builder = CircuitBuilder::::new(config); + + let x = builder.add_virtual_target(); + let x_squared = builder.square(x); + + builder.register_public_input(x_squared); + + builder.add_simple_generator(SquareRootGenerator:: { + x, + x_squared, + _phantom: PhantomData, + }); + + // Randomly generate the value of x^2: any quadratic residue in the field works. + let x_squared_value = { + let mut val = F::rand(); + while !val.is_quadratic_residue() { + val = F::rand(); + } + val + }; + + let mut pw = PartialWitness::new(); + pw.set_target(x_squared, x_squared_value); + + let data = builder.build::(); + let proof = data.prove(pw.clone())?; + + let x_squared_actual = proof.public_inputs[0]; + println!("Field element (square): {x_squared_actual}"); + + // Test serialization + { + let gate_serializer = DefaultGateSerializer; + let generator_serializer = CustomGeneratorSerializer { + _phantom: PhantomData::, + }; + + let data_bytes = data + .to_bytes(&gate_serializer, &generator_serializer) + .map_err(|_| anyhow::Error::msg("CircuitData serialization failed."))?; + + let data_from_bytes = CircuitData::::from_bytes( + &data_bytes, + &gate_serializer, + &generator_serializer, + ) + .map_err(|_| anyhow::Error::msg("CircuitData deserialization failed."))?; + + assert_eq!(data, data_from_bytes); + } + + data.verify(proof) +} diff --git a/plonky2/plonky2.pdf b/plonky2/plonky2.pdf new file mode 100644 index 0000000000000000000000000000000000000000..8f0f9ece13a2e3997927736de6205d178a18a4dd GIT binary patch literal 236419 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 z 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

jcCV_f`s#NR+rk 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

}> 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 T3!}UOR1OYfym3RvRG0Bzq@f6N literal 0 HcmV?d00001 diff --git a/plonky2/src/bin/generate_constants.rs b/plonky2/src/bin/generate_constants.rs new file mode 100644 index 000000000..608df8155 --- /dev/null +++ b/plonky2/src/bin/generate_constants.rs @@ -0,0 +1,31 @@ +//! Generates random constants using ChaCha20, seeded with zero. + +#![allow(clippy::needless_range_loop)] + +use plonky2::field::goldilocks_field::GoldilocksField; +use plonky2::field::types::Field64; +use rand::{Rng, SeedableRng}; +use rand_chacha::ChaCha8Rng; + +const SAMPLE_RANGE_END: u64 = GoldilocksField::ORDER; + +const N: usize = 12 * 30; // For Poseidon-12 + +pub(crate) fn main() { + let mut rng = ChaCha8Rng::seed_from_u64(0); + let mut constants = [0u64; N]; + for i in 0..N { + constants[i] = rng.gen_range(0..SAMPLE_RANGE_END); + } + + // Print the constants in the format we prefer in our code. + for chunk in constants.chunks(4) { + for (i, c) in chunk.iter().enumerate() { + print!("{c:#018x},"); + if i != chunk.len() - 1 { + print!(" "); + } + } + println!(); + } +} diff --git a/plonky2/src/fri/challenges.rs b/plonky2/src/fri/challenges.rs new file mode 100644 index 000000000..be73a8c24 --- /dev/null +++ b/plonky2/src/fri/challenges.rs @@ -0,0 +1,113 @@ +use crate::field::extension::Extendable; +use crate::field::polynomial::PolynomialCoeffs; +use crate::fri::proof::{FriChallenges, FriChallengesTarget}; +use crate::fri::structure::{FriOpenings, FriOpeningsTarget}; +use crate::fri::FriConfig; +use crate::gadgets::polynomial::PolynomialCoeffsExtTarget; +use crate::hash::hash_types::{MerkleCapTarget, RichField}; +use crate::hash::merkle_tree::MerkleCap; +use crate::iop::challenger::{Challenger, RecursiveChallenger}; +use crate::iop::target::Target; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::config::{AlgebraicHasher, GenericConfig, Hasher}; + +impl> Challenger { + pub fn observe_openings(&mut self, openings: &FriOpenings) + where + F: RichField + Extendable, + { + for v in &openings.batches { + self.observe_extension_elements(&v.values); + } + } + + pub fn fri_challenges, const D: usize>( + &mut self, + commit_phase_merkle_caps: &[MerkleCap], + final_poly: &PolynomialCoeffs, + pow_witness: F, + degree_bits: usize, + config: &FriConfig, + ) -> FriChallenges + where + F: RichField + Extendable, + { + let num_fri_queries = config.num_query_rounds; + let lde_size = 1 << (degree_bits + config.rate_bits); + // Scaling factor to combine polynomials. + let fri_alpha = self.get_extension_challenge::(); + + // Recover the random betas used in the FRI reductions. + let fri_betas = commit_phase_merkle_caps + .iter() + .map(|cap| { + self.observe_cap::(cap); + self.get_extension_challenge::() + }) + .collect(); + + self.observe_extension_elements(&final_poly.coeffs); + + self.observe_element(pow_witness); + let fri_pow_response = self.get_challenge(); + + let fri_query_indices = (0..num_fri_queries) + .map(|_| self.get_challenge().to_canonical_u64() as usize % lde_size) + .collect(); + + FriChallenges { + fri_alpha, + fri_betas, + fri_pow_response, + fri_query_indices, + } + } +} + +impl, H: AlgebraicHasher, const D: usize> + RecursiveChallenger +{ + pub fn observe_openings(&mut self, openings: &FriOpeningsTarget) { + for v in &openings.batches { + self.observe_extension_elements(&v.values); + } + } + + pub fn fri_challenges( + &mut self, + builder: &mut CircuitBuilder, + commit_phase_merkle_caps: &[MerkleCapTarget], + final_poly: &PolynomialCoeffsExtTarget, + pow_witness: Target, + inner_fri_config: &FriConfig, + ) -> FriChallengesTarget { + let num_fri_queries = inner_fri_config.num_query_rounds; + // Scaling factor to combine polynomials. + let fri_alpha = self.get_extension_challenge(builder); + + // Recover the random betas used in the FRI reductions. + let fri_betas = commit_phase_merkle_caps + .iter() + .map(|cap| { + self.observe_cap(cap); + self.get_extension_challenge(builder) + }) + .collect(); + + self.observe_extension_elements(&final_poly.0); + + self.observe_element(pow_witness); + let fri_pow_response = self.get_challenge(builder); + + let fri_query_indices = (0..num_fri_queries) + .map(|_| self.get_challenge(builder)) + .collect(); + + FriChallengesTarget { + fri_alpha, + fri_betas, + fri_pow_response, + fri_query_indices, + } + } +} diff --git a/plonky2/src/fri/mod.rs b/plonky2/src/fri/mod.rs new file mode 100644 index 000000000..1f27af7aa --- /dev/null +++ b/plonky2/src/fri/mod.rs @@ -0,0 +1,116 @@ +//! Fast Reed-Solomon IOP (FRI) protocol. +//! +//! It provides both a native implementation and an in-circuit version +//! of the FRI verifier for recursive proof composition. + +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + +use serde::Serialize; + +use crate::fri::reduction_strategies::FriReductionStrategy; + +mod challenges; +pub mod oracle; +pub mod proof; +pub mod prover; +pub mod recursive_verifier; +pub mod reduction_strategies; +pub mod structure; +mod validate_shape; +pub mod verifier; +pub mod witness_util; + +/// A configuration for the FRI protocol. +#[derive(Debug, Clone, Eq, PartialEq, Serialize)] +pub struct FriConfig { + /// `rate = 2^{-rate_bits}`. + pub rate_bits: usize, + + /// Height of Merkle tree caps. + pub cap_height: usize, + + /// Number of bits used for grinding. + pub proof_of_work_bits: u32, + + /// The reduction strategy to be applied at each layer during the commit + /// phase. + pub reduction_strategy: FriReductionStrategy, + + /// Number of query rounds to perform. + pub num_query_rounds: usize, +} + +impl FriConfig { + pub fn rate(&self) -> f64 { + 1.0 / ((1 << self.rate_bits) as f64) + } + + pub fn fri_params(&self, degree_bits: usize, hiding: bool) -> FriParams { + let reduction_arity_bits = self.reduction_strategy.reduction_arity_bits( + degree_bits, + self.rate_bits, + self.cap_height, + self.num_query_rounds, + ); + FriParams { + config: self.clone(), + hiding, + degree_bits, + reduction_arity_bits, + } + } + + pub const fn num_cap_elements(&self) -> usize { + 1 << self.cap_height + } +} + +/// FRI parameters, including generated parameters which are specific to an +/// instance size, in contrast to `FriConfig` which is user-specified and +/// independent of instance size. +#[derive(Debug, Clone, Eq, PartialEq, Serialize)] +pub struct FriParams { + /// User-specified FRI configuration. + pub config: FriConfig, + + /// Whether to use a hiding variant of Merkle trees (where random salts are + /// added to leaves). + pub hiding: bool, + + /// The degree of the purported codeword, measured in bits. + pub degree_bits: usize, + + /// The arity of each FRI reduction step, expressed as the log2 of the + /// actual arity. For example, `[3, 2, 1]` would describe a FRI + /// reduction tree with 8-to-1 reduction, then a 4-to-1 reduction, then + /// a 2-to-1 reduction. After these reductions, the reduced polynomial + /// is sent directly. + pub reduction_arity_bits: Vec, +} + +impl FriParams { + pub fn total_arities(&self) -> usize { + self.reduction_arity_bits.iter().sum() + } + + pub(crate) fn max_arity_bits(&self) -> Option { + self.reduction_arity_bits.iter().copied().max() + } + + pub const fn lde_bits(&self) -> usize { + self.degree_bits + self.config.rate_bits + } + + pub const fn lde_size(&self) -> usize { + 1 << self.lde_bits() + } + + pub fn final_poly_bits(&self) -> usize { + self.degree_bits - self.total_arities() + } + + pub fn final_poly_len(&self) -> usize { + 1 << self.final_poly_bits() + } +} diff --git a/plonky2/src/fri/oracle.rs b/plonky2/src/fri/oracle.rs new file mode 100644 index 000000000..67666761f --- /dev/null +++ b/plonky2/src/fri/oracle.rs @@ -0,0 +1,239 @@ +#[cfg(not(feature = "std"))] +use alloc::{format, vec::Vec}; + +use itertools::Itertools; +use plonky2_field::types::Field; +use plonky2_maybe_rayon::*; + +use crate::field::extension::Extendable; +use crate::field::fft::FftRootTable; +use crate::field::packed::PackedField; +use crate::field::polynomial::{PolynomialCoeffs, PolynomialValues}; +use crate::fri::proof::FriProof; +use crate::fri::prover::fri_proof; +use crate::fri::structure::{FriBatchInfo, FriInstanceInfo}; +use crate::fri::FriParams; +use crate::hash::hash_types::RichField; +use crate::hash::merkle_tree::MerkleTree; +use crate::iop::challenger::Challenger; +use crate::plonk::config::GenericConfig; +use crate::timed; +use crate::util::reducing::ReducingFactor; +use crate::util::timing::TimingTree; +use crate::util::{log2_strict, reverse_bits, reverse_index_bits_in_place, transpose}; + +/// Four (~64 bit) field elements gives ~128 bit security. +pub const SALT_SIZE: usize = 4; + +/// Represents a FRI oracle, i.e. a batch of polynomials which have been +/// Merklized. +#[derive(Eq, PartialEq, Debug)] +pub struct PolynomialBatch, C: GenericConfig, const D: usize> +{ + pub polynomials: Vec>, + pub merkle_tree: MerkleTree, + pub degree_log: usize, + pub rate_bits: usize, + pub blinding: bool, +} + +impl, C: GenericConfig, const D: usize> Default + for PolynomialBatch +{ + fn default() -> Self { + PolynomialBatch { + polynomials: Vec::new(), + merkle_tree: MerkleTree::default(), + degree_log: 0, + rate_bits: 0, + blinding: false, + } + } +} + +impl, C: GenericConfig, const D: usize> + PolynomialBatch +{ + /// Creates a list polynomial commitment for the polynomials interpolating + /// the values in `values`. + pub fn from_values( + values: Vec>, + rate_bits: usize, + blinding: bool, + cap_height: usize, + timing: &mut TimingTree, + fft_root_table: Option<&FftRootTable>, + ) -> Self { + let coeffs = timed!( + timing, + "IFFT", + values.into_par_iter().map(|v| v.ifft()).collect::>() + ); + + Self::from_coeffs( + coeffs, + rate_bits, + blinding, + cap_height, + timing, + fft_root_table, + ) + } + + /// Creates a list polynomial commitment for the polynomials `polynomials`. + pub fn from_coeffs( + polynomials: Vec>, + rate_bits: usize, + blinding: bool, + cap_height: usize, + timing: &mut TimingTree, + fft_root_table: Option<&FftRootTable>, + ) -> Self { + let degree = polynomials[0].len(); + let lde_values = timed!( + timing, + "FFT + blinding", + Self::lde_values(&polynomials, rate_bits, blinding, fft_root_table) + ); + + let mut leaves = timed!(timing, "transpose LDEs", transpose(&lde_values)); + reverse_index_bits_in_place(&mut leaves); + let merkle_tree = timed!( + timing, + "build Merkle tree", + MerkleTree::new(leaves, cap_height) + ); + + Self { + polynomials, + merkle_tree, + degree_log: log2_strict(degree), + rate_bits, + blinding, + } + } + + fn lde_values( + polynomials: &[PolynomialCoeffs], + rate_bits: usize, + blinding: bool, + fft_root_table: Option<&FftRootTable>, + ) -> Vec> { + let degree = polynomials[0].len(); + + // If blinding, salt with two random elements to each leaf vector. + let salt_size = if blinding { SALT_SIZE } else { 0 }; + + polynomials + .par_iter() + .map(|p| { + assert_eq!(p.len(), degree, "Polynomial degrees inconsistent"); + p.lde(rate_bits) + .coset_fft_with_options(F::coset_shift(), Some(rate_bits), fft_root_table) + .values + }) + .chain( + (0..salt_size) + .into_par_iter() + .map(|_| F::rand_vec(degree << rate_bits)), + ) + .collect() + } + + /// Fetches LDE values at the `index * step`th point. + pub fn get_lde_values(&self, index: usize, step: usize) -> &[F] { + let index = index * step; + let index = reverse_bits(index, self.degree_log + self.rate_bits); + let slice = &self.merkle_tree.leaves[index]; + &slice[..slice.len() - if self.blinding { SALT_SIZE } else { 0 }] + } + + /// Like `get_lde_values`, but fetches LDE values from a batch of `P::WIDTH` + /// points, and returns packed values. + pub fn get_lde_values_packed

(&self, index_start: usize, step: usize) -> Vec

+ where + P: PackedField, + { + let row_wise = (0..P::WIDTH) + .map(|i| self.get_lde_values(index_start + i, step)) + .collect_vec(); + + // This is essentially a transpose, but we will not use the generic transpose + // method as we want inner lists to be of type P, not Vecs which would + // involve allocation. + let leaf_size = row_wise[0].len(); + (0..leaf_size) + .map(|j| { + let mut packed = P::ZEROS; + packed + .as_slice_mut() + .iter_mut() + .zip(&row_wise) + .for_each(|(packed_i, row_i)| *packed_i = row_i[j]); + packed + }) + .collect_vec() + } + + /// Produces a batch opening proof. + pub fn prove_openings( + instance: &FriInstanceInfo, + oracles: &[&Self], + challenger: &mut Challenger, + fri_params: &FriParams, + timing: &mut TimingTree, + ) -> FriProof { + assert!(D > 1, "Not implemented for D=1."); + let alpha = challenger.get_extension_challenge::(); + let mut alpha = ReducingFactor::new(alpha); + + // 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. 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| { + &oracles[fri_poly.oracle_index].polynomials[fri_poly.polynomial_index] + }); + let composition_poly = timed!( + timing, + &format!("reduce batch of {} polynomials", polynomials.len()), + alpha.reduce_polys_base(polys_coeff) + ); + let mut quotient = composition_poly.divide_by_linear(*point); + quotient.coeffs.push(F::Extension::ZERO); // pad back to power of two + alpha.shift_poly(&mut final_poly); + final_poly += quotient; + } + + let lde_final_poly = final_poly.lde(fri_params.config.rate_bits); + let lde_final_values = timed!( + timing, + &format!("perform final FFT {}", lde_final_poly.len()), + lde_final_poly.coset_fft(F::coset_shift().into()) + ); + + let fri_proof = fri_proof::( + &oracles + .par_iter() + .map(|c| &c.merkle_tree) + .collect::>(), + lde_final_poly, + lde_final_values, + challenger, + fri_params, + timing, + ); + + fri_proof + } +} diff --git a/plonky2/src/fri/proof.rs b/plonky2/src/fri/proof.rs new file mode 100644 index 000000000..d0578fdcc --- /dev/null +++ b/plonky2/src/fri/proof.rs @@ -0,0 +1,390 @@ +#[cfg(not(feature = "std"))] +use alloc::{vec, vec::Vec}; + +use hashbrown::HashMap; +use itertools::izip; +use serde::{Deserialize, Serialize}; + +use crate::field::extension::{flatten, unflatten, Extendable}; +use crate::field::polynomial::PolynomialCoeffs; +use crate::fri::FriParams; +use crate::gadgets::polynomial::PolynomialCoeffsExtTarget; +use crate::hash::hash_types::{MerkleCapTarget, RichField}; +use crate::hash::merkle_proofs::{MerkleProof, MerkleProofTarget}; +use crate::hash::merkle_tree::MerkleCap; +use crate::hash::path_compression::{compress_merkle_proofs, decompress_merkle_proofs}; +use crate::iop::ext_target::ExtensionTarget; +use crate::iop::target::Target; +use crate::plonk::config::Hasher; +use crate::plonk::plonk_common::salt_size; +use crate::plonk::proof::{FriInferredElements, ProofChallenges}; + +/// Evaluations and Merkle proof produced by the prover in a FRI query step. +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(bound = "")] +pub struct FriQueryStep, H: Hasher, const D: usize> { + pub evals: Vec, + pub merkle_proof: MerkleProof, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct FriQueryStepTarget { + pub evals: Vec>, + pub merkle_proof: MerkleProofTarget, +} + +/// Evaluations and Merkle proofs of the original set of polynomials, +/// before they are combined into a composition polynomial. +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(bound = "")] +pub struct FriInitialTreeProof> { + pub evals_proofs: Vec<(Vec, MerkleProof)>, +} + +impl> FriInitialTreeProof { + pub(crate) fn unsalted_eval(&self, oracle_index: usize, poly_index: usize, salted: bool) -> F { + self.unsalted_evals(oracle_index, salted)[poly_index] + } + + fn unsalted_evals(&self, oracle_index: usize, salted: bool) -> &[F] { + let evals = &self.evals_proofs[oracle_index].0; + &evals[..evals.len() - salt_size(salted)] + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct FriInitialTreeProofTarget { + pub evals_proofs: Vec<(Vec, MerkleProofTarget)>, +} + +impl FriInitialTreeProofTarget { + pub(crate) fn unsalted_eval( + &self, + oracle_index: usize, + poly_index: usize, + salted: bool, + ) -> Target { + self.unsalted_evals(oracle_index, salted)[poly_index] + } + + fn unsalted_evals(&self, oracle_index: usize, salted: bool) -> &[Target] { + let evals = &self.evals_proofs[oracle_index].0; + &evals[..evals.len() - salt_size(salted)] + } +} + +/// Proof for a FRI query round. +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(bound = "")] +pub struct FriQueryRound, H: Hasher, const D: usize> { + pub initial_trees_proof: FriInitialTreeProof, + pub steps: Vec>, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct FriQueryRoundTarget { + pub initial_trees_proof: FriInitialTreeProofTarget, + pub steps: Vec>, +} + +/// Compressed proof of the FRI query rounds. +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(bound = "")] +pub struct CompressedFriQueryRounds, H: Hasher, const D: usize> { + /// Query indices. + pub indices: Vec, + /// Map from initial indices `i` to the `FriInitialProof` for the `i`th + /// leaf. + pub initial_trees_proofs: HashMap>, + /// For each FRI query step, a map from indices `i` to the `FriQueryStep` + /// for the `i`th leaf. + pub steps: Vec>>, +} + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(bound = "")] +pub struct FriProof, H: Hasher, const D: usize> { + /// A Merkle cap for each reduced polynomial in the commit phase. + pub commit_phase_merkle_caps: Vec>, + /// Query rounds proofs + pub query_round_proofs: Vec>, + /// The final polynomial in coefficient form. + pub final_poly: PolynomialCoeffs, + /// Witness showing that the prover did PoW. + pub pow_witness: F, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct FriProofTarget { + pub commit_phase_merkle_caps: Vec, + pub query_round_proofs: Vec>, + pub final_poly: PolynomialCoeffsExtTarget, + pub pow_witness: Target, +} + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(bound = "")] +pub struct CompressedFriProof, H: Hasher, const D: usize> { + /// A Merkle cap for each reduced polynomial in the commit phase. + pub commit_phase_merkle_caps: Vec>, + /// Compressed query rounds proof. + pub query_round_proofs: CompressedFriQueryRounds, + /// The final polynomial in coefficient form. + pub final_poly: PolynomialCoeffs, + /// Witness showing that the prover did PoW. + pub pow_witness: F, +} + +impl, H: Hasher, const D: usize> FriProof { + /// Compress all the Merkle paths in the FRI proof and remove duplicate + /// indices. + pub fn compress(self, indices: &[usize], params: &FriParams) -> CompressedFriProof { + let FriProof { + commit_phase_merkle_caps, + query_round_proofs, + final_poly, + pow_witness, + .. + } = self; + let cap_height = params.config.cap_height; + let reduction_arity_bits = ¶ms.reduction_arity_bits; + let num_reductions = reduction_arity_bits.len(); + let num_initial_trees = query_round_proofs[0].initial_trees_proof.evals_proofs.len(); + + // "Transpose" the query round proofs, so that information for each Merkle tree + // is collected together. + let mut initial_trees_indices = vec![vec![]; num_initial_trees]; + let mut initial_trees_leaves = vec![vec![]; num_initial_trees]; + let mut initial_trees_proofs = vec![vec![]; num_initial_trees]; + let mut steps_indices = vec![vec![]; num_reductions]; + let mut steps_evals = vec![vec![]; num_reductions]; + let mut steps_proofs = vec![vec![]; num_reductions]; + + for (mut index, qrp) in indices.iter().cloned().zip(&query_round_proofs) { + let FriQueryRound { + initial_trees_proof, + steps, + } = qrp.clone(); + for (i, (leaves_data, proof)) in + initial_trees_proof.evals_proofs.into_iter().enumerate() + { + initial_trees_indices[i].push(index); + initial_trees_leaves[i].push(leaves_data); + initial_trees_proofs[i].push(proof); + } + for (i, query_step) in steps.into_iter().enumerate() { + let index_within_coset = index & ((1 << reduction_arity_bits[i]) - 1); + index >>= reduction_arity_bits[i]; + steps_indices[i].push(index); + let mut evals = query_step.evals; + // Remove the element that can be inferred. + evals.remove(index_within_coset); + steps_evals[i].push(evals); + steps_proofs[i].push(query_step.merkle_proof); + } + } + + // Compress all Merkle proofs. + let initial_trees_proofs = initial_trees_indices + .iter() + .zip(initial_trees_proofs) + .map(|(is, ps)| compress_merkle_proofs(cap_height, is, &ps)) + .collect::>(); + let steps_proofs = steps_indices + .iter() + .zip(steps_proofs) + .map(|(is, ps)| compress_merkle_proofs(cap_height, is, &ps)) + .collect::>(); + + let mut compressed_query_proofs = CompressedFriQueryRounds { + indices: indices.to_vec(), + initial_trees_proofs: HashMap::with_capacity(indices.len()), + steps: vec![HashMap::new(); num_reductions], + }; + + // Replace the query round proofs with the compressed versions. + for (i, mut index) in indices.iter().copied().enumerate() { + let initial_proof = FriInitialTreeProof { + evals_proofs: (0..num_initial_trees) + .map(|j| { + ( + initial_trees_leaves[j][i].clone(), + initial_trees_proofs[j][i].clone(), + ) + }) + .collect(), + }; + compressed_query_proofs + .initial_trees_proofs + .entry(index) + .or_insert(initial_proof); + for j in 0..num_reductions { + index >>= reduction_arity_bits[j]; + let query_step = FriQueryStep { + evals: steps_evals[j][i].clone(), + merkle_proof: steps_proofs[j][i].clone(), + }; + compressed_query_proofs.steps[j] + .entry(index) + .or_insert(query_step); + } + } + + CompressedFriProof { + commit_phase_merkle_caps, + query_round_proofs: compressed_query_proofs, + final_poly, + pow_witness, + } + } +} + +impl, H: Hasher, const D: usize> CompressedFriProof { + /// Decompress all the Merkle paths in the FRI proof and reinsert duplicate + /// indices. + pub(crate) fn decompress( + self, + challenges: &ProofChallenges, + fri_inferred_elements: FriInferredElements, + params: &FriParams, + ) -> FriProof { + let CompressedFriProof { + commit_phase_merkle_caps, + query_round_proofs, + final_poly, + pow_witness, + .. + } = self; + let FriChallenges { + fri_query_indices: indices, + .. + } = &challenges.fri_challenges; + let mut fri_inferred_elements = fri_inferred_elements.0.into_iter(); + let cap_height = params.config.cap_height; + let reduction_arity_bits = ¶ms.reduction_arity_bits; + let num_reductions = reduction_arity_bits.len(); + let num_initial_trees = query_round_proofs + .initial_trees_proofs + .values() + .next() + .unwrap() + .evals_proofs + .len(); + + // "Transpose" the query round proofs, so that information for each Merkle tree + // is collected together. + let mut initial_trees_indices = vec![vec![]; num_initial_trees]; + let mut initial_trees_leaves = vec![vec![]; num_initial_trees]; + let mut initial_trees_proofs = vec![vec![]; num_initial_trees]; + let mut steps_indices = vec![vec![]; num_reductions]; + let mut steps_evals = vec![vec![]; num_reductions]; + let mut steps_proofs = vec![vec![]; num_reductions]; + let height = params.degree_bits + params.config.rate_bits; + let heights = reduction_arity_bits + .iter() + .scan(height, |acc, &bits| { + *acc -= bits; + Some(*acc) + }) + .collect::>(); + + // Holds the `evals` vectors that have already been reconstructed at each + // reduction depth. + let mut evals_by_depth = + vec![HashMap::>::new(); params.reduction_arity_bits.len()]; + for &(mut index) in indices { + let initial_trees_proof = query_round_proofs.initial_trees_proofs[&index].clone(); + for (i, (leaves_data, proof)) in + initial_trees_proof.evals_proofs.into_iter().enumerate() + { + initial_trees_indices[i].push(index); + initial_trees_leaves[i].push(leaves_data); + initial_trees_proofs[i].push(proof); + } + for i in 0..num_reductions { + let index_within_coset = index & ((1 << reduction_arity_bits[i]) - 1); + index >>= reduction_arity_bits[i]; + let FriQueryStep { + mut evals, + merkle_proof, + } = query_round_proofs.steps[i][&index].clone(); + steps_indices[i].push(index); + if let Some(v) = evals_by_depth[i].get(&index) { + // If this index has already been seen, get `evals` from the `HashMap`. + evals = v.to_vec(); + } else { + // Otherwise insert the next inferred element. + evals.insert(index_within_coset, fri_inferred_elements.next().unwrap()); + evals_by_depth[i].insert(index, evals.clone()); + } + steps_evals[i].push(flatten(&evals)); + steps_proofs[i].push(merkle_proof); + } + } + + // Decompress all Merkle proofs. + let initial_trees_proofs = izip!( + &initial_trees_leaves, + &initial_trees_indices, + initial_trees_proofs + ) + .map(|(ls, is, ps)| decompress_merkle_proofs(ls, is, &ps, height, cap_height)) + .collect::>(); + let steps_proofs = izip!(&steps_evals, &steps_indices, steps_proofs, heights) + .map(|(ls, is, ps, h)| decompress_merkle_proofs(ls, is, &ps, h, cap_height)) + .collect::>(); + + let mut decompressed_query_proofs = Vec::with_capacity(num_reductions); + for i in 0..indices.len() { + let initial_trees_proof = FriInitialTreeProof { + evals_proofs: (0..num_initial_trees) + .map(|j| { + ( + initial_trees_leaves[j][i].clone(), + initial_trees_proofs[j][i].clone(), + ) + }) + .collect(), + }; + let steps = (0..num_reductions) + .map(|j| FriQueryStep { + evals: unflatten(&steps_evals[j][i]), + merkle_proof: steps_proofs[j][i].clone(), + }) + .collect(); + decompressed_query_proofs.push(FriQueryRound { + initial_trees_proof, + steps, + }) + } + + FriProof { + commit_phase_merkle_caps, + query_round_proofs: decompressed_query_proofs, + final_poly, + pow_witness, + } + } +} + +#[derive(Debug)] +pub struct FriChallenges, const D: usize> { + // Scaling factor to combine polynomials. + pub fri_alpha: F::Extension, + + // Betas used in the FRI commit phase reductions. + pub fri_betas: Vec, + + pub fri_pow_response: F, + + // Indices at which the oracle is queried in FRI. + pub fri_query_indices: Vec, +} + +#[derive(Debug)] +pub struct FriChallengesTarget { + pub fri_alpha: ExtensionTarget, + pub fri_betas: Vec>, + pub fri_pow_response: Target, + pub fri_query_indices: Vec, +} diff --git a/plonky2/src/fri/prover.rs b/plonky2/src/fri/prover.rs new file mode 100644 index 000000000..23f4801c5 --- /dev/null +++ b/plonky2/src/fri/prover.rs @@ -0,0 +1,224 @@ +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + +use plonky2_maybe_rayon::*; + +use crate::field::extension::{flatten, unflatten, Extendable}; +use crate::field::polynomial::{PolynomialCoeffs, PolynomialValues}; +use crate::fri::proof::{FriInitialTreeProof, FriProof, FriQueryRound, FriQueryStep}; +use crate::fri::{FriConfig, FriParams}; +use crate::hash::hash_types::RichField; +use crate::hash::hashing::PlonkyPermutation; +use crate::hash::merkle_tree::MerkleTree; +use crate::iop::challenger::Challenger; +use crate::plonk::config::GenericConfig; +use crate::plonk::plonk_common::reduce_with_powers; +use crate::timed; +use crate::util::reverse_index_bits_in_place; +use crate::util::timing::TimingTree; + +/// Builds a FRI proof. +pub fn fri_proof, C: GenericConfig, const D: usize>( + initial_merkle_trees: &[&MerkleTree], + // Coefficients of the polynomial on which the LDT is performed. Only the first `1/rate` + // coefficients are non-zero. + lde_polynomial_coeffs: PolynomialCoeffs, + // Evaluation of the polynomial on the large domain. + lde_polynomial_values: PolynomialValues, + challenger: &mut Challenger, + fri_params: &FriParams, + timing: &mut TimingTree, +) -> FriProof { + let n = lde_polynomial_values.len(); + assert_eq!(lde_polynomial_coeffs.len(), n); + + // Commit phase + let (trees, final_coeffs) = timed!( + timing, + "fold codewords in the commitment phase", + fri_committed_trees::( + lde_polynomial_coeffs, + lde_polynomial_values, + challenger, + fri_params, + ) + ); + + // PoW phase + let pow_witness = timed!( + timing, + "find proof-of-work witness", + fri_proof_of_work::(challenger, &fri_params.config) + ); + + // Query phase + let query_round_proofs = + fri_prover_query_rounds::(initial_merkle_trees, &trees, challenger, n, fri_params); + + FriProof { + commit_phase_merkle_caps: trees.iter().map(|t| t.cap.clone()).collect(), + query_round_proofs, + final_poly: final_coeffs, + pow_witness, + } +} + +type FriCommitedTrees = ( + Vec>::Hasher>>, + PolynomialCoeffs<>::Extension>, +); + +fn fri_committed_trees, C: GenericConfig, const D: usize>( + mut coeffs: PolynomialCoeffs, + mut values: PolynomialValues, + challenger: &mut Challenger, + fri_params: &FriParams, +) -> FriCommitedTrees { + let mut trees = Vec::with_capacity(fri_params.reduction_arity_bits.len()); + + let mut shift = F::MULTIPLICATIVE_GROUP_GENERATOR; + for arity_bits in &fri_params.reduction_arity_bits { + let arity = 1 << arity_bits; + + reverse_index_bits_in_place(&mut values.values); + let chunked_values = values + .values + .par_chunks(arity) + .map(|chunk: &[F::Extension]| flatten(chunk)) + .collect(); + let tree = MerkleTree::::new(chunked_values, fri_params.config.cap_height); + + challenger.observe_cap(&tree.cap); + trees.push(tree); + + let beta = challenger.get_extension_challenge::(); + // P(x) = sum_{i>(), + ); + shift = shift.exp_u64(arity as u64); + values = coeffs.coset_fft(shift.into()) + } + + // The coefficients being removed here should always be zero. + coeffs + .coeffs + .truncate(coeffs.len() >> fri_params.config.rate_bits); + + challenger.observe_extension_elements(&coeffs.coeffs); + (trees, coeffs) +} + +/// Performs the proof-of-work (a.k.a. grinding) step of the FRI protocol. +/// Returns the PoW witness. +fn fri_proof_of_work, C: GenericConfig, const D: usize>( + challenger: &mut Challenger, + config: &FriConfig, +) -> F { + let min_leading_zeros = config.proof_of_work_bits + (64 - F::order().bits()) as u32; + + // The easiest implementation would be repeatedly clone our Challenger. With + // each clone, we'd observe an incrementing PoW witness, then get the PoW + // response. If it contained sufficient leading zeros, we'd end the search, + // and store this clone as our new challenger. + // + // However, performance is critical here. We want to avoid cloning Challenger, + // particularly since it stores vectors, which means allocations. We'd like + // a more compact state to clone. + // + // We know that a duplex will be performed right after we send the PoW witness, + // so we can ignore any output_buffer, which will be invalidated. We also + // know input_buffer.len() < H::Permutation::WIDTH, an invariant of + // Challenger. + // + // We separate the duplex operation into two steps, one which can be performed + // now, and the other which depends on the PoW witness candidate. The first + // step is the overwrite our sponge state with any inputs (excluding the PoW + // witness candidate). The second step is to overwrite one more element of + // our sponge state with the candidate, then apply the permutation, + // obtaining our duplex's post-state which contains the PoW response. + let mut duplex_intermediate_state = challenger.sponge_state; + let witness_input_pos = challenger.input_buffer.len(); + duplex_intermediate_state.set_from_iter(challenger.input_buffer.clone(), 0); + + let pow_witness = (0..=F::NEG_ONE.to_canonical_u64()) + .into_par_iter() + .find_any(|&candidate| { + let mut duplex_state = duplex_intermediate_state; + duplex_state.set_elt(F::from_canonical_u64(candidate), witness_input_pos); + duplex_state.permute(); + let pow_response = duplex_state.squeeze().iter().last().unwrap(); + let leading_zeros = pow_response.to_canonical_u64().leading_zeros(); + leading_zeros >= min_leading_zeros + }) + .map(F::from_canonical_u64) + .expect("Proof of work failed. This is highly unlikely!"); + + // Recompute pow_response using our normal Challenger code, and make sure it + // matches. + challenger.observe_element(pow_witness); + let pow_response = challenger.get_challenge(); + let leading_zeros = pow_response.to_canonical_u64().leading_zeros(); + assert!(leading_zeros >= min_leading_zeros); + pow_witness +} + +fn fri_prover_query_rounds< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +>( + initial_merkle_trees: &[&MerkleTree], + trees: &[MerkleTree], + challenger: &mut Challenger, + n: usize, + fri_params: &FriParams, +) -> Vec> { + 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() +} + +fn fri_prover_query_round< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +>( + initial_merkle_trees: &[&MerkleTree], + trees: &[MerkleTree], + mut x_index: usize, + fri_params: &FriParams, +) -> FriQueryRound { + let mut query_steps = Vec::new(); + let initial_proof = initial_merkle_trees + .iter() + .map(|t| (t.get(x_index).to_vec(), t.prove(x_index))) + .collect::>(); + for (i, tree) in trees.iter().enumerate() { + let arity_bits = fri_params.reduction_arity_bits[i]; + let evals = unflatten(tree.get(x_index >> arity_bits)); + let merkle_proof = tree.prove(x_index >> arity_bits); + + query_steps.push(FriQueryStep { + evals, + merkle_proof, + }); + + x_index >>= arity_bits; + } + FriQueryRound { + initial_trees_proof: FriInitialTreeProof { + evals_proofs: initial_proof, + }, + steps: query_steps, + } +} diff --git a/plonky2/src/fri/recursive_verifier.rs b/plonky2/src/fri/recursive_verifier.rs new file mode 100644 index 000000000..1f8f5a28c --- /dev/null +++ b/plonky2/src/fri/recursive_verifier.rs @@ -0,0 +1,489 @@ +#[cfg(not(feature = "std"))] +use alloc::{format, vec::Vec}; + +use itertools::Itertools; + +use crate::field::extension::Extendable; +use crate::fri::proof::{ + FriChallengesTarget, FriInitialTreeProofTarget, FriProofTarget, FriQueryRoundTarget, + FriQueryStepTarget, +}; +use crate::fri::structure::{FriBatchInfoTarget, FriInstanceInfoTarget, FriOpeningsTarget}; +use crate::fri::{FriConfig, FriParams}; +use crate::gates::coset_interpolation::CosetInterpolationGate; +use crate::gates::gate::Gate; +use crate::gates::random_access::RandomAccessGate; +use crate::hash::hash_types::{MerkleCapTarget, RichField}; +use crate::iop::ext_target::{flatten_target, ExtensionTarget}; +use crate::iop::target::{BoolTarget, Target}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::config::{AlgebraicHasher, GenericConfig}; +use crate::util::reducing::ReducingFactorTarget; +use crate::util::{log2_strict, reverse_index_bits_in_place}; +use crate::with_context; + +impl, const D: usize> CircuitBuilder { + /// Computes P'(x^arity) from {P(x*g^i)}_(i=0..arity), where g is a + /// `arity`-th root of unity and P' is the FRI reduced polynomial. + fn compute_evaluation( + &mut self, + x: Target, + x_index_within_coset_bits: &[BoolTarget], + arity_bits: usize, + evals: &[ExtensionTarget], + beta: ExtensionTarget, + ) -> ExtensionTarget { + let arity = 1 << arity_bits; + debug_assert_eq!(evals.len(), arity); + + let g = F::primitive_root_of_unity(arity_bits); + let g_inv = g.exp_u64((arity as u64) - 1); + + // The evaluation vector needs to be reordered first. + let mut evals = evals.to_vec(); + reverse_index_bits_in_place(&mut evals); + // Want `g^(arity - rev_x_index_within_coset)` as in the out-of-circuit version. + // Compute it as `(g^-1)^rev_x_index_within_coset`. + let start = self.exp_from_bits_const_base(g_inv, x_index_within_coset_bits.iter().rev()); + let coset_start = self.mul(start, x); + + // The answer is gotten by interpolating {(x*g^i, P(x*g^i))} and evaluating at + // beta. + let interpolation_gate = >::with_max_degree( + arity_bits, + self.config.max_quotient_degree_factor, + ); + self.interpolate_coset(interpolation_gate, coset_start, &evals, beta) + } + + /// Make sure we have enough wires and routed wires to do the FRI checks + /// efficiently. This check isn't required -- without it we'd get errors + /// elsewhere in the stack -- but just gives more helpful errors. + fn check_recursion_config(&self, max_fri_arity_bits: usize) { + let random_access = RandomAccessGate::::new_from_config( + &self.config, + max_fri_arity_bits.max(self.config.fri_config.cap_height), + ); + let interpolation_gate = CosetInterpolationGate::::with_max_degree( + max_fri_arity_bits, + self.config.max_quotient_degree_factor, + ); + + let interpolation_wires = interpolation_gate.num_wires(); + let interpolation_routed_wires = interpolation_gate.num_routed_wires(); + + let min_wires = random_access.num_wires().max(interpolation_wires); + let min_routed_wires = random_access + .num_routed_wires() + .max(interpolation_routed_wires); + + assert!( + self.config.num_wires >= min_wires, + "To efficiently perform FRI checks with an arity of 2^{}, at least {} wires are needed. Consider reducing arity.", + max_fri_arity_bits, + min_wires + ); + + assert!( + self.config.num_routed_wires >= min_routed_wires, + "To efficiently perform FRI checks with an arity of 2^{}, at least {} routed wires are needed. Consider reducing arity.", + max_fri_arity_bits, + min_routed_wires + ); + } + + fn fri_verify_proof_of_work(&mut self, fri_pow_response: Target, config: &FriConfig) { + self.assert_leading_zeros( + fri_pow_response, + config.proof_of_work_bits + (64 - F::order().bits()) as u32, + ); + } + + pub fn verify_fri_proof>( + &mut self, + instance: &FriInstanceInfoTarget, + openings: &FriOpeningsTarget, + challenges: &FriChallengesTarget, + initial_merkle_caps: &[MerkleCapTarget], + proof: &FriProofTarget, + params: &FriParams, + ) where + C::Hasher: AlgebraicHasher, + { + if let Some(max_arity_bits) = params.max_arity_bits() { + self.check_recursion_config(max_arity_bits); + } + + debug_assert_eq!( + params.final_poly_len(), + proof.final_poly.len(), + "Final polynomial has wrong degree." + ); + + // Size of the LDE domain. + let n = params.lde_size(); + + with_context!( + self, + "check PoW", + self.fri_verify_proof_of_work(challenges.fri_pow_response, ¶ms.config) + ); + + // Check that parameters are coherent. + debug_assert_eq!( + params.config.num_query_rounds, + proof.query_round_proofs.len(), + "Number of query rounds does not match config." + ); + + let precomputed_reduced_evals = with_context!( + self, + "precompute reduced evaluations", + PrecomputedReducedOpeningsTarget::from_os_and_alpha( + openings, + challenges.fri_alpha, + self + ) + ); + + for (i, round_proof) in proof.query_round_proofs.iter().enumerate() { + // To minimize noise in our logs, we will only record a context for a single FRI + // query. The very first query will have some extra gates due to + // constants being registered, so the second query is a better + // representative. + let level = if i == 1 { + log::Level::Debug + } else { + log::Level::Trace + }; + + let num_queries = proof.query_round_proofs.len(); + with_context!( + self, + level, + &format!("verify one (of {num_queries}) query rounds"), + self.fri_verifier_query_round::( + instance, + challenges, + &precomputed_reduced_evals, + initial_merkle_caps, + proof, + challenges.fri_query_indices[i], + n, + round_proof, + params, + ) + ); + } + } + + fn fri_verify_initial_proof>( + &mut self, + x_index_bits: &[BoolTarget], + proof: &FriInitialTreeProofTarget, + initial_merkle_caps: &[MerkleCapTarget], + cap_index: Target, + ) { + for (i, ((evals, merkle_proof), cap)) in proof + .evals_proofs + .iter() + .zip(initial_merkle_caps) + .enumerate() + { + with_context!( + self, + &format!("verify {i}'th initial Merkle proof"), + self.verify_merkle_proof_to_cap_with_cap_index::( + evals.clone(), + x_index_bits, + cap_index, + cap, + merkle_proof + ) + ); + } + } + + fn fri_combine_initial( + &mut self, + instance: &FriInstanceInfoTarget, + proof: &FriInitialTreeProofTarget, + alpha: ExtensionTarget, + subgroup_x: Target, + precomputed_reduced_evals: &PrecomputedReducedOpeningsTarget, + params: &FriParams, + ) -> ExtensionTarget { + assert!(D > 1, "Not implemented for D=1."); + let degree_log = params.degree_bits; + debug_assert_eq!( + degree_log, + params.config.cap_height + proof.evals_proofs[0].1.siblings.len() + - params.config.rate_bits + ); + let subgroup_x = self.convert_to_ext(subgroup_x); + let mut alpha = ReducingFactorTarget::new(alpha); + let mut sum = self.zero_extension(); + + for (batch, reduced_openings) in instance + .batches + .iter() + .zip(&precomputed_reduced_evals.reduced_openings_at_point) + { + let FriBatchInfoTarget { point, polynomials } = batch; + let evals = polynomials + .iter() + .map(|p| { + let poly_blinding = instance.oracles[p.oracle_index].blinding; + let salted = params.hiding && poly_blinding; + proof.unsalted_eval(p.oracle_index, p.polynomial_index, salted) + }) + .collect_vec(); + let reduced_evals = alpha.reduce_base(&evals, self); + let numerator = self.sub_extension(reduced_evals, *reduced_openings); + let denominator = self.sub_extension(subgroup_x, *point); + sum = alpha.shift(sum, self); + sum = self.div_add_extension(numerator, denominator, sum); + } + + sum + } + + fn fri_verifier_query_round>( + &mut self, + instance: &FriInstanceInfoTarget, + challenges: &FriChallengesTarget, + precomputed_reduced_evals: &PrecomputedReducedOpeningsTarget, + initial_merkle_caps: &[MerkleCapTarget], + proof: &FriProofTarget, + x_index: Target, + n: usize, + round_proof: &FriQueryRoundTarget, + params: &FriParams, + ) where + C::Hasher: AlgebraicHasher, + { + let n_log = log2_strict(n); + + // Note that this `low_bits` decomposition permits non-canonical binary + // encodings. Here we verify that this has a negligible impact on + // soundness error. + Self::assert_noncanonical_indices_ok(¶ms.config); + let mut x_index_bits = self.low_bits(x_index, n_log, F::BITS); + + let cap_index = + self.le_sum(x_index_bits[x_index_bits.len() - params.config.cap_height..].iter()); + with_context!( + self, + "check FRI initial proof", + self.fri_verify_initial_proof::( + &x_index_bits, + &round_proof.initial_trees_proof, + initial_merkle_caps, + cap_index + ) + ); + + // `subgroup_x` is `subgroup[x_index]`, i.e., the actual field element in the + // domain. + let mut subgroup_x = with_context!(self, "compute x from its index", { + let g = self.constant(F::coset_shift()); + let phi = F::primitive_root_of_unity(n_log); + let phi = self.exp_from_bits_const_base(phi, x_index_bits.iter().rev()); + // subgroup_x = g * phi + self.mul(g, phi) + }); + + // old_eval is the last derived evaluation; it will be checked for consistency + // with its committed "parent" value in the next iteration. + let mut old_eval = with_context!( + self, + "combine initial oracles", + self.fri_combine_initial( + instance, + &round_proof.initial_trees_proof, + challenges.fri_alpha, + subgroup_x, + precomputed_reduced_evals, + params, + ) + ); + + for (i, &arity_bits) in params.reduction_arity_bits.iter().enumerate() { + let evals = &round_proof.steps[i].evals; + + // Split x_index into the index of the coset x is in, and the index of x within + // that coset. + let coset_index_bits = x_index_bits[arity_bits..].to_vec(); + let x_index_within_coset_bits = &x_index_bits[..arity_bits]; + let x_index_within_coset = self.le_sum(x_index_within_coset_bits.iter()); + + // Check consistency with our old evaluation from the previous round. + let new_eval = self.random_access_extension(x_index_within_coset, evals.clone()); + self.connect_extension(new_eval, old_eval); + + // Infer P(y) from {P(x)}_{x^arity=y}. + old_eval = with_context!( + self, + "infer evaluation using interpolation", + self.compute_evaluation( + subgroup_x, + x_index_within_coset_bits, + arity_bits, + evals, + challenges.fri_betas[i], + ) + ); + + with_context!( + self, + "verify FRI round Merkle proof.", + self.verify_merkle_proof_to_cap_with_cap_index::( + flatten_target(evals), + &coset_index_bits, + cap_index, + &proof.commit_phase_merkle_caps[i], + &round_proof.steps[i].merkle_proof, + ) + ); + + // Update the point x to x^arity. + subgroup_x = self.exp_power_of_2(subgroup_x, arity_bits); + + x_index_bits = coset_index_bits; + } + + // Final check of FRI. After all the reductions, we check that the final + // polynomial is equal to the one sent by the prover. + let eval = with_context!( + self, + &format!( + "evaluate final polynomial of length {}", + proof.final_poly.len() + ), + proof.final_poly.eval_scalar(self, subgroup_x) + ); + self.connect_extension(eval, old_eval); + } + + /// We decompose FRI query indices into bits without verifying that the + /// decomposition given by the prover is the canonical one. In + /// particular, if `x_index < 2^field_bits - p`, then the prover could + /// supply the binary encoding of either `x_index` or `x_index + p`, since + /// the are congruent mod `p`. However, this only occurs with + /// probability p_ambiguous = (2^field_bits - p) / p + /// which is small for the field that we use in practice. + /// + /// In particular, the soundness error of one FRI query is roughly the + /// codeword rate, which is much larger than this ambiguous-element + /// probability given any reasonable parameters. Thus ambiguous elements + /// contribute a negligible amount to soundness error. + /// + /// Here we compare the probabilities as a sanity check, to verify the claim + /// above. + fn assert_noncanonical_indices_ok(config: &FriConfig) { + let num_ambiguous_elems = u64::MAX - F::ORDER + 1; + let query_error = config.rate(); + let p_ambiguous = (num_ambiguous_elems as f64) / (F::ORDER as f64); + assert!(p_ambiguous < query_error * 1e-5, + "A non-negligible portion of field elements are in the range that permits non-canonical encodings. Need to do more analysis or enforce canonical encodings."); + } + + pub fn add_virtual_fri_proof( + &mut self, + num_leaves_per_oracle: &[usize], + params: &FriParams, + ) -> FriProofTarget { + let cap_height = params.config.cap_height; + let num_queries = params.config.num_query_rounds; + let commit_phase_merkle_caps = (0..params.reduction_arity_bits.len()) + .map(|_| self.add_virtual_cap(cap_height)) + .collect(); + let query_round_proofs = (0..num_queries) + .map(|_| self.add_virtual_fri_query(num_leaves_per_oracle, params)) + .collect(); + let final_poly = self.add_virtual_poly_coeff_ext(params.final_poly_len()); + let pow_witness = self.add_virtual_target(); + FriProofTarget { + commit_phase_merkle_caps, + query_round_proofs, + final_poly, + pow_witness, + } + } + + fn add_virtual_fri_query( + &mut self, + num_leaves_per_oracle: &[usize], + params: &FriParams, + ) -> FriQueryRoundTarget { + let cap_height = params.config.cap_height; + assert!(params.lde_bits() >= cap_height); + let mut merkle_proof_len = params.lde_bits() - cap_height; + + let initial_trees_proof = + self.add_virtual_fri_initial_trees_proof(num_leaves_per_oracle, merkle_proof_len); + + let mut steps = Vec::with_capacity(params.reduction_arity_bits.len()); + for &arity_bits in ¶ms.reduction_arity_bits { + assert!(merkle_proof_len >= arity_bits); + merkle_proof_len -= arity_bits; + steps.push(self.add_virtual_fri_query_step(arity_bits, merkle_proof_len)); + } + + FriQueryRoundTarget { + initial_trees_proof, + steps, + } + } + + fn add_virtual_fri_initial_trees_proof( + &mut self, + num_leaves_per_oracle: &[usize], + initial_merkle_proof_len: usize, + ) -> FriInitialTreeProofTarget { + let evals_proofs = num_leaves_per_oracle + .iter() + .map(|&num_oracle_leaves| { + let leaves = self.add_virtual_targets(num_oracle_leaves); + let merkle_proof = self.add_virtual_merkle_proof(initial_merkle_proof_len); + (leaves, merkle_proof) + }) + .collect(); + FriInitialTreeProofTarget { evals_proofs } + } + + fn add_virtual_fri_query_step( + &mut self, + arity_bits: usize, + merkle_proof_len: usize, + ) -> FriQueryStepTarget { + FriQueryStepTarget { + evals: self.add_virtual_extension_targets(1 << arity_bits), + merkle_proof: self.add_virtual_merkle_proof(merkle_proof_len), + } + } +} + +/// For each opening point, holds the reduced (by `alpha`) evaluations of each +/// polynomial that's opened at that point. +#[derive(Clone)] +struct PrecomputedReducedOpeningsTarget { + reduced_openings_at_point: Vec>, +} + +impl PrecomputedReducedOpeningsTarget { + fn from_os_and_alpha>( + openings: &FriOpeningsTarget, + alpha: ExtensionTarget, + builder: &mut CircuitBuilder, + ) -> Self { + let reduced_openings_at_point = openings + .batches + .iter() + .map(|batch| ReducingFactorTarget::new(alpha).reduce(&batch.values, builder)) + .collect(); + Self { + reduced_openings_at_point, + } + } +} diff --git a/plonky2/src/fri/reduction_strategies.rs b/plonky2/src/fri/reduction_strategies.rs new file mode 100644 index 000000000..1caec2dcd --- /dev/null +++ b/plonky2/src/fri/reduction_strategies.rs @@ -0,0 +1,168 @@ +#[cfg(not(feature = "std"))] +use alloc::{vec, vec::Vec}; + +use log::debug; +use serde::Serialize; +#[cfg(feature = "timing")] +use web_time::Instant; + +/// A method for deciding what arity to use at each reduction layer. +#[derive(Debug, Clone, Eq, PartialEq, Serialize)] +pub enum FriReductionStrategy { + /// Specifies the exact sequence of arities (expressed in bits) to use. + Fixed(Vec), + + /// `ConstantArityBits(arity_bits, final_poly_bits)` applies reductions of + /// arity `2^arity_bits` until the polynomial degree is less than or + /// equal to `2^final_poly_bits` or until any further + /// `arity_bits`-reduction makes the last FRI tree have height less than + /// `cap_height`. This tends to work well in the recursive setting, as + /// it avoids needing multiple configurations of gates used in FRI + /// verification, such as `InterpolationGate`. + ConstantArityBits(usize, usize), + + /// `MinSize(opt_max_arity_bits)` searches for an optimal sequence of + /// reduction arities, with an optional max `arity_bits`. If this proof + /// will have recursive proofs on top of it, a max `arity_bits` of 3 is + /// recommended. + MinSize(Option), +} + +impl FriReductionStrategy { + /// The arity of each FRI reduction step, expressed as the log2 of the + /// actual arity. + pub fn reduction_arity_bits( + &self, + mut degree_bits: usize, + rate_bits: usize, + cap_height: usize, + num_queries: usize, + ) -> Vec { + match self { + FriReductionStrategy::Fixed(reduction_arity_bits) => reduction_arity_bits.to_vec(), + &FriReductionStrategy::ConstantArityBits(arity_bits, final_poly_bits) => { + let mut result = Vec::new(); + while degree_bits > final_poly_bits + && degree_bits + rate_bits - arity_bits >= cap_height + { + result.push(arity_bits); + assert!(degree_bits >= arity_bits); + degree_bits -= arity_bits; + } + result.shrink_to_fit(); + result + } + FriReductionStrategy::MinSize(opt_max_arity_bits) => { + min_size_arity_bits(degree_bits, rate_bits, num_queries, *opt_max_arity_bits) + } + } + } +} + +fn min_size_arity_bits( + degree_bits: usize, + rate_bits: usize, + num_queries: usize, + opt_max_arity_bits: Option, +) -> Vec { + // 2^4 is the largest arity we see in optimal reduction sequences in practice. + // For 2^5 to occur in an optimal sequence, we would need a really massive + // polynomial. + let max_arity_bits = opt_max_arity_bits.unwrap_or(4); + + #[cfg(feature = "timing")] + let start = Instant::now(); + let (mut arity_bits, fri_proof_size) = + min_size_arity_bits_helper(degree_bits, rate_bits, num_queries, max_arity_bits, vec![]); + arity_bits.shrink_to_fit(); + + #[cfg(feature = "timing")] + debug!( + "min_size_arity_bits took {:.3}s", + start.elapsed().as_secs_f32() + ); + debug!( + "Smallest arity_bits {:?} results in estimated FRI proof size of {} elements", + arity_bits, fri_proof_size + ); + + arity_bits +} + +/// Return `(arity_bits, fri_proof_size)`. +fn min_size_arity_bits_helper( + degree_bits: usize, + rate_bits: usize, + num_queries: usize, + global_max_arity_bits: usize, + prefix: Vec, +) -> (Vec, usize) { + let sum_of_arities: usize = prefix.iter().sum(); + let current_layer_bits = degree_bits + rate_bits - sum_of_arities; + assert!(current_layer_bits >= rate_bits); + + let mut best_arity_bits = prefix.clone(); + let mut best_size = relative_proof_size(degree_bits, rate_bits, num_queries, &prefix); + + // The largest next_arity_bits to search. Note that any optimal arity sequence + // will be monotonically non-increasing, as a larger arity will shrink more + // Merkle proofs if it occurs earlier in the sequence. + let max_arity_bits = prefix + .last() + .copied() + .unwrap_or(global_max_arity_bits) + .min(current_layer_bits - rate_bits); + + for next_arity_bits in 1..=max_arity_bits { + let mut extended_prefix = prefix.clone(); + extended_prefix.push(next_arity_bits); + + let (arity_bits, size) = min_size_arity_bits_helper( + degree_bits, + rate_bits, + num_queries, + max_arity_bits, + extended_prefix, + ); + if size < best_size { + best_arity_bits = arity_bits; + best_size = size; + } + } + + (best_arity_bits, best_size) +} + +/// Compute the approximate size of a FRI proof with the given reduction +/// arities. Note that this ignores initial evaluations, which aren't affected +/// by arities, and some other minor contributions. The result is measured in +/// field elements. +fn relative_proof_size( + degree_bits: usize, + rate_bits: usize, + num_queries: usize, + arity_bits: &[usize], +) -> usize { + const D: usize = 4; + + let mut current_layer_bits = degree_bits + rate_bits; + + let mut total_elems = 0; + for arity_bits in arity_bits { + let arity = 1 << arity_bits; + + // Add neighboring evaluations, which are extension field elements. + total_elems += (arity - 1) * D * num_queries; + // Add siblings in the Merkle path. + total_elems += current_layer_bits * 4 * num_queries; + + current_layer_bits -= arity_bits; + } + + // Add the final polynomial's coefficients. + assert!(current_layer_bits >= rate_bits); + let final_poly_len = 1 << (current_layer_bits - rate_bits); + total_elems += D * final_poly_len; + + total_elems +} diff --git a/plonky2/src/fri/structure.rs b/plonky2/src/fri/structure.rs new file mode 100644 index 000000000..f659630fd --- /dev/null +++ b/plonky2/src/fri/structure.rs @@ -0,0 +1,90 @@ +//! Information about the structure of a FRI instance, in terms of the oracles +//! and polynomials involved, and the points they are opened at. + +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; +use core::ops::Range; + +use crate::field::extension::Extendable; +use crate::hash::hash_types::RichField; +use crate::iop::ext_target::ExtensionTarget; + +/// Describes an instance of a FRI-based batch opening. +pub struct FriInstanceInfo, const D: usize> { + /// The oracles involved, not counting oracles created during the commit + /// phase. + pub oracles: Vec, + /// Batches of openings, where each batch is associated with a particular + /// point. + pub batches: Vec>, +} + +/// Describes an instance of a FRI-based batch opening. +pub struct FriInstanceInfoTarget { + /// The oracles involved, not counting oracles created during the commit + /// phase. + pub oracles: Vec, + /// Batches of openings, where each batch is associated with a particular + /// point. + pub batches: Vec>, +} + +#[derive(Copy, Clone)] +pub struct FriOracleInfo { + pub num_polys: usize, + pub blinding: bool, +} + +/// A batch of openings at a particular point. +pub struct FriBatchInfo, const D: usize> { + pub point: F::Extension, + pub polynomials: Vec, +} + +/// A batch of openings at a particular point. +pub struct FriBatchInfoTarget { + pub point: ExtensionTarget, + pub polynomials: Vec, +} + +#[derive(Copy, Clone, Debug)] +pub struct FriPolynomialInfo { + /// Index into `FriInstanceInfo`'s `oracles` list. + pub oracle_index: usize, + /// Index of the polynomial within the oracle. + pub polynomial_index: usize, +} + +impl FriPolynomialInfo { + pub fn from_range( + oracle_index: usize, + polynomial_indices: Range, + ) -> Vec { + polynomial_indices + .map(|polynomial_index| FriPolynomialInfo { + oracle_index, + polynomial_index, + }) + .collect() + } +} + +/// Opened values of each polynomial. +pub struct FriOpenings, const D: usize> { + pub batches: Vec>, +} + +/// Opened values of each polynomial that's opened at a particular point. +pub struct FriOpeningBatch, const D: usize> { + pub values: Vec, +} + +/// Opened values of each polynomial. +pub struct FriOpeningsTarget { + pub batches: Vec>, +} + +/// Opened values of each polynomial that's opened at a particular point. +pub struct FriOpeningBatchTarget { + pub values: Vec>, +} diff --git a/plonky2/src/fri/validate_shape.rs b/plonky2/src/fri/validate_shape.rs new file mode 100644 index 000000000..526da8f77 --- /dev/null +++ b/plonky2/src/fri/validate_shape.rs @@ -0,0 +1,67 @@ +use anyhow::ensure; + +use crate::field::extension::Extendable; +use crate::fri::proof::{FriProof, FriQueryRound, FriQueryStep}; +use crate::fri::structure::FriInstanceInfo; +use crate::fri::FriParams; +use crate::hash::hash_types::RichField; +use crate::plonk::config::GenericConfig; +use crate::plonk::plonk_common::salt_size; + +pub(crate) fn validate_fri_proof_shape( + proof: &FriProof, + instance: &FriInstanceInfo, + params: &FriParams, +) -> anyhow::Result<()> +where + F: RichField + Extendable, + C: GenericConfig, +{ + let FriProof { + commit_phase_merkle_caps, + query_round_proofs, + final_poly, + pow_witness: _pow_witness, + } = proof; + + let cap_height = params.config.cap_height; + for cap in commit_phase_merkle_caps { + ensure!(cap.height() == cap_height); + } + + for query_round in query_round_proofs { + let FriQueryRound { + initial_trees_proof, + steps, + } = query_round; + + ensure!(initial_trees_proof.evals_proofs.len() == instance.oracles.len()); + for ((leaf, merkle_proof), oracle) in initial_trees_proof + .evals_proofs + .iter() + .zip(&instance.oracles) + { + ensure!(leaf.len() == oracle.num_polys + salt_size(oracle.blinding && params.hiding)); + ensure!(merkle_proof.len() + cap_height == params.lde_bits()); + } + + ensure!(steps.len() == params.reduction_arity_bits.len()); + let mut codeword_len_bits = params.lde_bits(); + for (step, arity_bits) in steps.iter().zip(¶ms.reduction_arity_bits) { + let FriQueryStep { + evals, + merkle_proof, + } = step; + + let arity = 1 << arity_bits; + codeword_len_bits -= arity_bits; + + ensure!(evals.len() == arity); + ensure!(merkle_proof.len() + cap_height == codeword_len_bits); + } + } + + ensure!(final_poly.len() == params.final_poly_len()); + + Ok(()) +} diff --git a/plonky2/src/fri/verifier.rs b/plonky2/src/fri/verifier.rs new file mode 100644 index 000000000..404f2e9ec --- /dev/null +++ b/plonky2/src/fri/verifier.rs @@ -0,0 +1,264 @@ +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + +use anyhow::{ensure, Result}; + +use crate::field::extension::{flatten, Extendable, FieldExtension}; +use crate::field::interpolation::{barycentric_weights, interpolate}; +use crate::field::types::Field; +use crate::fri::proof::{FriChallenges, FriInitialTreeProof, FriProof, FriQueryRound}; +use crate::fri::structure::{FriBatchInfo, FriInstanceInfo, FriOpenings}; +use crate::fri::validate_shape::validate_fri_proof_shape; +use crate::fri::{FriConfig, FriParams}; +use crate::hash::hash_types::RichField; +use crate::hash::merkle_proofs::verify_merkle_proof_to_cap; +use crate::hash::merkle_tree::MerkleCap; +use crate::plonk::config::{GenericConfig, Hasher}; +use crate::util::reducing::ReducingFactor; +use crate::util::{log2_strict, reverse_bits, reverse_index_bits_in_place}; + +/// Computes P'(x^arity) from {P(x*g^i)}_(i=0..arity), where g is a `arity`-th +/// root of unity and P' is the FRI reduced polynomial. +pub(crate) fn compute_evaluation, const D: usize>( + x: F, + x_index_within_coset: usize, + arity_bits: usize, + evals: &[F::Extension], + beta: F::Extension, +) -> F::Extension { + let arity = 1 << arity_bits; + debug_assert_eq!(evals.len(), arity); + + let g = F::primitive_root_of_unity(arity_bits); + + // The evaluation vector needs to be reordered first. + let mut evals = evals.to_vec(); + reverse_index_bits_in_place(&mut evals); + let rev_x_index_within_coset = reverse_bits(x_index_within_coset, arity_bits); + let coset_start = x * g.exp_u64((arity - rev_x_index_within_coset) as u64); + // The answer is gotten by interpolating {(x*g^i, P(x*g^i))} and evaluating at + // beta. + let points = g + .powers() + .map(|y| (coset_start * y).into()) + .zip(evals) + .collect::>(); + let barycentric_weights = barycentric_weights(&points); + interpolate(&points, beta, &barycentric_weights) +} + +pub(crate) fn fri_verify_proof_of_work, const D: usize>( + fri_pow_response: F, + config: &FriConfig, +) -> Result<()> { + ensure!( + fri_pow_response.to_canonical_u64().leading_zeros() + >= config.proof_of_work_bits + (64 - F::order().bits()) as u32, + "Invalid proof of work witness." + ); + + Ok(()) +} + +pub fn verify_fri_proof< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +>( + instance: &FriInstanceInfo, + openings: &FriOpenings, + challenges: &FriChallenges, + initial_merkle_caps: &[MerkleCap], + proof: &FriProof, + params: &FriParams, +) -> Result<()> { + validate_fri_proof_shape::(proof, instance, params)?; + + // Size of the LDE domain. + let n = params.lde_size(); + + // Check PoW. + fri_verify_proof_of_work(challenges.fri_pow_response, ¶ms.config)?; + + // Check that parameters are coherent. + ensure!( + params.config.num_query_rounds == proof.query_round_proofs.len(), + "Number of query rounds does not match config." + ); + + let precomputed_reduced_evals = + PrecomputedReducedOpenings::from_os_and_alpha(openings, challenges.fri_alpha); + for (&x_index, round_proof) in challenges + .fri_query_indices + .iter() + .zip(&proof.query_round_proofs) + { + fri_verifier_query_round::( + instance, + challenges, + &precomputed_reduced_evals, + initial_merkle_caps, + proof, + x_index, + n, + round_proof, + params, + )?; + } + + Ok(()) +} + +fn fri_verify_initial_proof>( + x_index: usize, + proof: &FriInitialTreeProof, + initial_merkle_caps: &[MerkleCap], +) -> Result<()> { + for ((evals, merkle_proof), cap) in proof.evals_proofs.iter().zip(initial_merkle_caps) { + verify_merkle_proof_to_cap::(evals.clone(), x_index, cap, merkle_proof)?; + } + + Ok(()) +} + +pub(crate) fn fri_combine_initial< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +>( + instance: &FriInstanceInfo, + proof: &FriInitialTreeProof, + alpha: F::Extension, + subgroup_x: F, + precomputed_reduced_evals: &PrecomputedReducedOpenings, + params: &FriParams, +) -> F::Extension { + assert!(D > 1, "Not implemented for D=1."); + let subgroup_x = F::Extension::from_basefield(subgroup_x); + let mut alpha = ReducingFactor::new(alpha); + let mut sum = F::Extension::ZERO; + + for (batch, reduced_openings) in instance + .batches + .iter() + .zip(&precomputed_reduced_evals.reduced_openings_at_point) + { + let FriBatchInfo { point, polynomials } = batch; + let evals = polynomials + .iter() + .map(|p| { + let poly_blinding = instance.oracles[p.oracle_index].blinding; + let salted = params.hiding && poly_blinding; + proof.unsalted_eval(p.oracle_index, p.polynomial_index, salted) + }) + .map(F::Extension::from_basefield); + let reduced_evals = alpha.reduce(evals); + let numerator = reduced_evals - *reduced_openings; + let denominator = subgroup_x - *point; + sum = alpha.shift(sum); + sum += numerator / denominator; + } + + sum +} + +fn fri_verifier_query_round< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +>( + instance: &FriInstanceInfo, + challenges: &FriChallenges, + precomputed_reduced_evals: &PrecomputedReducedOpenings, + initial_merkle_caps: &[MerkleCap], + proof: &FriProof, + mut x_index: usize, + n: usize, + round_proof: &FriQueryRound, + params: &FriParams, +) -> Result<()> { + fri_verify_initial_proof::( + x_index, + &round_proof.initial_trees_proof, + initial_merkle_caps, + )?; + // `subgroup_x` is `subgroup[x_index]`, i.e., the actual field element in the + // domain. + let log_n = log2_strict(n); + let mut subgroup_x = F::MULTIPLICATIVE_GROUP_GENERATOR + * F::primitive_root_of_unity(log_n).exp_u64(reverse_bits(x_index, log_n) as u64); + + // old_eval is the last derived evaluation; it will be checked for consistency + // with its committed "parent" value in the next iteration. + let mut old_eval = fri_combine_initial::( + instance, + &round_proof.initial_trees_proof, + challenges.fri_alpha, + subgroup_x, + precomputed_reduced_evals, + params, + ); + + for (i, &arity_bits) in params.reduction_arity_bits.iter().enumerate() { + let arity = 1 << arity_bits; + let evals = &round_proof.steps[i].evals; + + // Split x_index into the index of the coset x is in, and the index of x within + // that coset. + let coset_index = x_index >> arity_bits; + let x_index_within_coset = x_index & (arity - 1); + + // Check consistency with our old evaluation from the previous round. + ensure!(evals[x_index_within_coset] == old_eval); + + // Infer P(y) from {P(x)}_{x^arity=y}. + old_eval = compute_evaluation( + subgroup_x, + x_index_within_coset, + arity_bits, + evals, + challenges.fri_betas[i], + ); + + verify_merkle_proof_to_cap::( + flatten(evals), + coset_index, + &proof.commit_phase_merkle_caps[i], + &round_proof.steps[i].merkle_proof, + )?; + + // Update the point x to x^arity. + subgroup_x = subgroup_x.exp_power_of_2(arity_bits); + + x_index = coset_index; + } + + // Final check of FRI. After all the reductions, we check that the final + // polynomial is equal to the one sent by the prover. + ensure!( + proof.final_poly.eval(subgroup_x.into()) == old_eval, + "Final polynomial evaluation is invalid." + ); + + Ok(()) +} + +/// For each opening point, holds the reduced (by `alpha`) evaluations of each +/// polynomial that's opened at that point. +#[derive(Clone, Debug)] +pub(crate) struct PrecomputedReducedOpenings, const D: usize> { + pub reduced_openings_at_point: Vec, +} + +impl, const D: usize> PrecomputedReducedOpenings { + pub(crate) fn from_os_and_alpha(openings: &FriOpenings, alpha: F::Extension) -> Self { + let reduced_openings_at_point = openings + .batches + .iter() + .map(|batch| ReducingFactor::new(alpha).reduce(batch.values.iter())) + .collect(); + Self { + reduced_openings_at_point, + } + } +} diff --git a/plonky2/src/fri/witness_util.rs b/plonky2/src/fri/witness_util.rs new file mode 100644 index 000000000..b05834f5f --- /dev/null +++ b/plonky2/src/fri/witness_util.rs @@ -0,0 +1,72 @@ +use itertools::Itertools; + +use crate::field::extension::Extendable; +use crate::fri::proof::{FriProof, FriProofTarget}; +use crate::hash::hash_types::RichField; +use crate::iop::witness::WitnessWrite; +use crate::plonk::config::AlgebraicHasher; + +/// Set the targets in a `FriProofTarget` to their corresponding values in a +/// `FriProof`. +pub fn set_fri_proof_target( + witness: &mut W, + fri_proof_target: &FriProofTarget, + fri_proof: &FriProof, +) where + F: RichField + Extendable, + W: WitnessWrite + ?Sized, + H: AlgebraicHasher, +{ + witness.set_target(fri_proof_target.pow_witness, fri_proof.pow_witness); + + for (&t, &x) in fri_proof_target + .final_poly + .0 + .iter() + .zip_eq(&fri_proof.final_poly.coeffs) + { + witness.set_extension_target(t, x); + } + + for (t, x) in fri_proof_target + .commit_phase_merkle_caps + .iter() + .zip_eq(&fri_proof.commit_phase_merkle_caps) + { + witness.set_cap_target(t, x); + } + + for (qt, q) in fri_proof_target + .query_round_proofs + .iter() + .zip_eq(&fri_proof.query_round_proofs) + { + for (at, a) in qt + .initial_trees_proof + .evals_proofs + .iter() + .zip_eq(&q.initial_trees_proof.evals_proofs) + { + for (&t, &x) in at.0.iter().zip_eq(&a.0) { + witness.set_target(t, x); + } + for (&t, &x) in at.1.siblings.iter().zip_eq(&a.1.siblings) { + witness.set_hash_target(t, x); + } + } + + for (st, s) in qt.steps.iter().zip_eq(&q.steps) { + for (&t, &x) in st.evals.iter().zip_eq(&s.evals) { + witness.set_extension_target(t, x); + } + for (&t, &x) in st + .merkle_proof + .siblings + .iter() + .zip_eq(&s.merkle_proof.siblings) + { + witness.set_hash_target(t, x); + } + } + } +} diff --git a/plonky2/src/gadgets/arithmetic.rs b/plonky2/src/gadgets/arithmetic.rs new file mode 100644 index 000000000..549246fb2 --- /dev/null +++ b/plonky2/src/gadgets/arithmetic.rs @@ -0,0 +1,441 @@ +#[cfg(not(feature = "std"))] +use alloc::{ + string::{String, ToString}, + vec, + vec::Vec, +}; +use core::borrow::Borrow; + +use crate::field::extension::Extendable; +use crate::field::types::Field64; +use crate::gates::arithmetic_base::ArithmeticGate; +use crate::gates::exponentiation::ExponentiationGate; +use crate::hash::hash_types::RichField; +use crate::iop::generator::{GeneratedValues, SimpleGenerator}; +use crate::iop::target::{BoolTarget, Target}; +use crate::iop::witness::{PartitionWitness, Witness, WitnessWrite}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::CommonCircuitData; +use crate::util::serialization::{Buffer, IoResult, Read, Write}; + +impl, const D: usize> CircuitBuilder { + /// Computes `-x`. + pub fn neg(&mut self, x: Target) -> Target { + let neg_one = self.neg_one(); + self.mul(x, neg_one) + } + + /// Computes `x^2`. + pub fn square(&mut self, x: Target) -> Target { + self.mul(x, x) + } + + /// Computes `x^3`. + pub fn cube(&mut self, x: Target) -> Target { + self.mul_many([x, x, x]) + } + + /// Computes `const_0 * multiplicand_0 * multiplicand_1 + const_1 * addend`. + pub fn arithmetic( + &mut self, + const_0: F, + const_1: F, + multiplicand_0: Target, + multiplicand_1: Target, + addend: Target, + ) -> Target { + // If we're not configured to use the base arithmetic gate, just call + // arithmetic_extension. + if !self.config.use_base_arithmetic_gate { + let multiplicand_0_ext = self.convert_to_ext(multiplicand_0); + let multiplicand_1_ext = self.convert_to_ext(multiplicand_1); + let addend_ext = self.convert_to_ext(addend); + + return self + .arithmetic_extension( + const_0, + const_1, + multiplicand_0_ext, + multiplicand_1_ext, + addend_ext, + ) + .0[0]; + } + + // See if we can determine the result without adding an `ArithmeticGate`. + if let Some(result) = + self.arithmetic_special_cases(const_0, const_1, multiplicand_0, multiplicand_1, addend) + { + return result; + } + + // See if we've already computed the same operation. + let operation = BaseArithmeticOperation { + const_0, + const_1, + multiplicand_0, + multiplicand_1, + addend, + }; + if let Some(&result) = self.base_arithmetic_results.get(&operation) { + return result; + } + + // Otherwise, we must actually perform the operation using an + // ArithmeticExtensionGate slot. + let result = self.add_base_arithmetic_operation(operation); + self.base_arithmetic_results.insert(operation, result); + result + } + + fn add_base_arithmetic_operation(&mut self, operation: BaseArithmeticOperation) -> Target { + let gate = ArithmeticGate::new_from_config(&self.config); + let constants = vec![operation.const_0, operation.const_1]; + let (gate, i) = self.find_slot(gate, &constants, &constants); + let wires_multiplicand_0 = Target::wire(gate, ArithmeticGate::wire_ith_multiplicand_0(i)); + let wires_multiplicand_1 = Target::wire(gate, ArithmeticGate::wire_ith_multiplicand_1(i)); + let wires_addend = Target::wire(gate, ArithmeticGate::wire_ith_addend(i)); + + self.connect(operation.multiplicand_0, wires_multiplicand_0); + self.connect(operation.multiplicand_1, wires_multiplicand_1); + self.connect(operation.addend, wires_addend); + + Target::wire(gate, ArithmeticGate::wire_ith_output(i)) + } + + /// Checks for special cases where the value of + /// `const_0 * multiplicand_0 * multiplicand_1 + const_1 * addend` + /// can be determined without adding an `ArithmeticGate`. + fn arithmetic_special_cases( + &mut self, + const_0: F, + const_1: F, + multiplicand_0: Target, + multiplicand_1: Target, + addend: Target, + ) -> Option { + let zero = self.zero(); + + let mul_0_const = self.target_as_constant(multiplicand_0); + let mul_1_const = self.target_as_constant(multiplicand_1); + let addend_const = self.target_as_constant(addend); + + let first_term_zero = + const_0 == F::ZERO || multiplicand_0 == zero || multiplicand_1 == zero; + let second_term_zero = const_1 == F::ZERO || addend == zero; + + // If both terms are constant, return their (constant) sum. + let first_term_const = if first_term_zero { + Some(F::ZERO) + } else if let (Some(x), Some(y)) = (mul_0_const, mul_1_const) { + Some(x * y * const_0) + } else { + None + }; + let second_term_const = if second_term_zero { + Some(F::ZERO) + } else { + addend_const.map(|x| x * const_1) + }; + if let (Some(x), Some(y)) = (first_term_const, second_term_const) { + return Some(self.constant(x + y)); + } + + if first_term_zero && const_1.is_one() { + return Some(addend); + } + + if second_term_zero { + if let Some(x) = mul_0_const { + if (x * const_0).is_one() { + return Some(multiplicand_1); + } + } + if let Some(x) = mul_1_const { + if (x * const_0).is_one() { + return Some(multiplicand_0); + } + } + } + + None + } + + /// Computes `x * y + z`. + pub fn mul_add(&mut self, x: Target, y: Target, z: Target) -> Target { + self.arithmetic(F::ONE, F::ONE, x, y, z) + } + + /// Computes `x + C`. + pub fn add_const(&mut self, x: Target, c: F) -> Target { + let c = self.constant(c); + self.add(x, c) + } + + /// Computes `C * x`. + pub fn mul_const(&mut self, c: F, x: Target) -> Target { + let c = self.constant(c); + self.mul(c, x) + } + + /// Computes `C * x + y`. + pub fn mul_const_add(&mut self, c: F, x: Target, y: Target) -> Target { + let c = self.constant(c); + self.mul_add(c, x, y) + } + + /// Computes `x * y - z`. + pub fn mul_sub(&mut self, x: Target, y: Target, z: Target) -> Target { + self.arithmetic(F::ONE, F::NEG_ONE, x, y, z) + } + + /// Computes `x + y`. + pub fn add(&mut self, x: Target, y: Target) -> Target { + let one = self.one(); + // x + y = 1 * x * 1 + 1 * y + self.arithmetic(F::ONE, F::ONE, x, one, y) + } + + /// Adds `n` `Target`s. + pub fn add_many(&mut self, terms: impl IntoIterator) -> Target + where + T: Borrow, + { + terms + .into_iter() + .fold(self.zero(), |acc, t| self.add(acc, *t.borrow())) + } + + /// Computes `x - y`. + pub fn sub(&mut self, x: Target, y: Target) -> Target { + let one = self.one(); + // x - y = 1 * x * 1 + (-1) * y + self.arithmetic(F::ONE, F::NEG_ONE, x, one, y) + } + + /// Computes `x * y`. + pub fn mul(&mut self, x: Target, y: Target) -> Target { + // x * y = 1 * x * y + 0 * x + self.arithmetic(F::ONE, F::ZERO, x, y, x) + } + + /// Multiply `n` `Target`s. + pub fn mul_many(&mut self, terms: impl IntoIterator) -> Target + where + T: Borrow, + { + terms + .into_iter() + .fold(self.one(), |acc, t| self.mul(acc, *t.borrow())) + } + + /// Exponentiates `base` to the power of `2^power_log`. + pub fn exp_power_of_2(&mut self, base: Target, power_log: usize) -> Target { + if power_log > self.num_base_arithmetic_ops_per_gate() { + // Cheaper to just use `ExponentiateGate`. + return self.exp_u64(base, 1 << power_log); + } + + let mut product = base; + for _ in 0..power_log { + product = self.square(product); + } + product + } + + // TODO: Test + /// Exponentiates `base` to the power of `exponent`, given by its + /// little-endian bits. + pub fn exp_from_bits( + &mut self, + base: Target, + exponent_bits: impl IntoIterator>, + ) -> Target { + let _false = self._false(); + let gate = ExponentiationGate::new_from_config(&self.config); + let num_power_bits = gate.num_power_bits; + let mut exp_bits_vec: Vec = + exponent_bits.into_iter().map(|b| *b.borrow()).collect(); + while exp_bits_vec.len() < num_power_bits { + exp_bits_vec.push(_false); + } + let row = self.add_gate(gate.clone(), vec![]); + + self.connect(base, Target::wire(row, gate.wire_base())); + exp_bits_vec.iter().enumerate().for_each(|(i, bit)| { + self.connect(bit.target, Target::wire(row, gate.wire_power_bit(i))); + }); + + Target::wire(row, gate.wire_output()) + } + + // TODO: Test + /// Exponentiates `base` to the power of `exponent`, where `exponent < + /// 2^num_bits`. + pub fn exp(&mut self, base: Target, exponent: Target, num_bits: usize) -> Target { + let exponent_bits = self.split_le(exponent, num_bits); + + self.exp_from_bits(base, exponent_bits.iter()) + } + + /// Like `exp_from_bits` but with a constant base. + pub fn exp_from_bits_const_base( + &mut self, + base: F, + exponent_bits: impl IntoIterator>, + ) -> Target { + let base_t = self.constant(base); + let exponent_bits: Vec<_> = exponent_bits.into_iter().map(|b| *b.borrow()).collect(); + + if exponent_bits.len() > self.num_base_arithmetic_ops_per_gate() { + // Cheaper to just use `ExponentiateGate`. + return self.exp_from_bits(base_t, exponent_bits); + } + + let mut product = self.one(); + for (i, bit) in exponent_bits.iter().enumerate() { + let pow = 1 << i; + // If the bit is on, we multiply product by base^pow. + // We can arithmetize this as: + // product *= 1 + bit (base^pow - 1) + // product = (base^pow - 1) product bit + product + product = self.arithmetic( + base.exp_u64(pow as u64) - F::ONE, + F::ONE, + product, + bit.target, + product, + ) + } + product + } + + /// Exponentiates `base` to the power of a known `exponent`. + // TODO: Test + pub fn exp_u64(&mut self, base: Target, mut exponent: u64) -> Target { + let mut exp_bits = Vec::new(); + while exponent != 0 { + let bit = (exponent & 1) == 1; + let bit_target = self.constant_bool(bit); + exp_bits.push(bit_target); + exponent >>= 1; + } + + self.exp_from_bits(base, exp_bits) + } + + /// Computes `x / y`. Results in an unsatisfiable instance if `y = 0`. + pub fn div(&mut self, x: Target, y: Target) -> Target { + let x = self.convert_to_ext(x); + let y = self.convert_to_ext(y); + self.div_extension(x, y).0[0] + } + + /// Computes `1 / x`. Results in an unsatisfiable instance if `x = 0`. + pub fn inverse(&mut self, x: Target) -> Target { + let x_ext = self.convert_to_ext(x); + self.inverse_extension(x_ext).0[0] + } + + /// Computes the logical NOT of the provided [`BoolTarget`]. + pub fn not(&mut self, b: BoolTarget) -> BoolTarget { + let one = self.one(); + let res = self.sub(one, b.target); + BoolTarget::new_unsafe(res) + } + + /// Computes the logical AND of the provided [`BoolTarget`]s. + pub fn and(&mut self, b1: BoolTarget, b2: BoolTarget) -> BoolTarget { + BoolTarget::new_unsafe(self.mul(b1.target, b2.target)) + } + + /// Computes the logical OR through the arithmetic expression: `b1 + b2 - b1 + /// * b2`. + pub fn or(&mut self, b1: BoolTarget, b2: BoolTarget) -> BoolTarget { + let res_minus_b2 = self.arithmetic(-F::ONE, F::ONE, b1.target, b2.target, b1.target); + BoolTarget::new_unsafe(self.add(res_minus_b2, b2.target)) + } + + /// Outputs `x` if `b` is true, and else `y`, through the formula: `b*x + + /// (1-b)*y`. + pub fn _if(&mut self, b: BoolTarget, x: Target, y: Target) -> Target { + let not_b = self.not(b); + let maybe_x = self.mul(b.target, x); + self.mul_add(not_b.target, y, maybe_x) + } + + /// Checks whether `x` and `y` are equal and outputs the boolean result. + pub fn is_equal(&mut self, x: Target, y: Target) -> BoolTarget { + let zero = self.zero(); + + let equal = self.add_virtual_bool_target_unsafe(); + let not_equal = self.not(equal); + let inv = self.add_virtual_target(); + self.add_simple_generator(EqualityGenerator { x, y, equal, inv }); + + let diff = self.sub(x, y); + let not_equal_check = self.mul(equal.target, diff); + + let diff_normalized = self.mul(diff, inv); + let equal_check = self.sub(diff_normalized, not_equal.target); + + self.connect(not_equal_check, zero); + self.connect(equal_check, zero); + + equal + } +} + +#[derive(Debug, Default)] +pub struct EqualityGenerator { + x: Target, + y: Target, + equal: BoolTarget, + inv: Target, +} + +impl, const D: usize> SimpleGenerator for EqualityGenerator { + fn id(&self) -> String { + "EqualityGenerator".to_string() + } + + fn dependencies(&self) -> Vec { + vec![self.x, self.y] + } + + fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + let x = witness.get_target(self.x); + let y = witness.get_target(self.y); + + let inv = if x != y { (x - y).inverse() } else { F::ZERO }; + + out_buffer.set_bool_target(self.equal, x == y); + out_buffer.set_target(self.inv, inv); + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_target(self.x)?; + dst.write_target(self.y)?; + dst.write_target_bool(self.equal)?; + dst.write_target(self.inv) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let x = src.read_target()?; + let y = src.read_target()?; + let equal = src.read_target_bool()?; + let inv = src.read_target()?; + Ok(Self { x, y, equal, inv }) + } +} + +/// Represents a base arithmetic operation in the circuit. Used to memoize +/// results. +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub(crate) struct BaseArithmeticOperation { + const_0: F, + const_1: F, + multiplicand_0: Target, + multiplicand_1: Target, + addend: Target, +} diff --git a/plonky2/src/gadgets/arithmetic_extension.rs b/plonky2/src/gadgets/arithmetic_extension.rs new file mode 100644 index 000000000..9d2a277ef --- /dev/null +++ b/plonky2/src/gadgets/arithmetic_extension.rs @@ -0,0 +1,715 @@ +#[cfg(not(feature = "std"))] +use alloc::{ + string::{String, ToString}, + vec, + vec::Vec, +}; +use core::borrow::Borrow; + +use crate::field::extension::{Extendable, FieldExtension, OEF}; +use crate::field::types::{Field, Field64}; +use crate::gates::arithmetic_extension::ArithmeticExtensionGate; +use crate::gates::multiplication_extension::MulExtensionGate; +use crate::hash::hash_types::RichField; +use crate::iop::ext_target::{ExtensionAlgebraTarget, ExtensionTarget}; +use crate::iop::generator::{GeneratedValues, SimpleGenerator}; +use crate::iop::target::Target; +use crate::iop::witness::{PartitionWitness, Witness, WitnessWrite}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::CommonCircuitData; +use crate::util::bits_u64; +use crate::util::serialization::{Buffer, IoResult, Read, Write}; + +impl, const D: usize> CircuitBuilder { + pub fn arithmetic_extension( + &mut self, + const_0: F, + const_1: F, + multiplicand_0: ExtensionTarget, + multiplicand_1: ExtensionTarget, + addend: ExtensionTarget, + ) -> ExtensionTarget { + // See if we can determine the result without adding an `ArithmeticGate`. + if let Some(result) = self.arithmetic_extension_special_cases( + const_0, + const_1, + multiplicand_0, + multiplicand_1, + addend, + ) { + return result; + } + + // See if we've already computed the same operation. + let operation = ExtensionArithmeticOperation { + const_0, + const_1, + multiplicand_0, + multiplicand_1, + addend, + }; + if let Some(&result) = self.arithmetic_results.get(&operation) { + return result; + } + + let result = if self.target_as_constant_ext(addend) == Some(F::Extension::ZERO) { + // If the addend is zero, we use a multiplication gate. + self.compute_mul_extension_operation(operation) + } else { + // Otherwise, we use an arithmetic gate. + self.compute_arithmetic_extension_operation(operation) + }; + // Otherwise, we must actually perform the operation using an + // ArithmeticExtensionGate slot. + self.arithmetic_results.insert(operation, result); + result + } + + fn compute_arithmetic_extension_operation( + &mut self, + operation: ExtensionArithmeticOperation, + ) -> ExtensionTarget { + let gate = ArithmeticExtensionGate::new_from_config(&self.config); + let constants = vec![operation.const_0, operation.const_1]; + let (gate, i) = self.find_slot(gate, &constants, &constants); + let wires_multiplicand_0 = ExtensionTarget::from_range( + gate, + ArithmeticExtensionGate::::wires_ith_multiplicand_0(i), + ); + let wires_multiplicand_1 = ExtensionTarget::from_range( + gate, + ArithmeticExtensionGate::::wires_ith_multiplicand_1(i), + ); + let wires_addend = + ExtensionTarget::from_range(gate, ArithmeticExtensionGate::::wires_ith_addend(i)); + + self.connect_extension(operation.multiplicand_0, wires_multiplicand_0); + self.connect_extension(operation.multiplicand_1, wires_multiplicand_1); + self.connect_extension(operation.addend, wires_addend); + + ExtensionTarget::from_range(gate, ArithmeticExtensionGate::::wires_ith_output(i)) + } + + fn compute_mul_extension_operation( + &mut self, + operation: ExtensionArithmeticOperation, + ) -> ExtensionTarget { + let gate = MulExtensionGate::new_from_config(&self.config); + let constants = vec![operation.const_0]; + let (gate, i) = self.find_slot(gate, &constants, &constants); + let wires_multiplicand_0 = + ExtensionTarget::from_range(gate, MulExtensionGate::::wires_ith_multiplicand_0(i)); + let wires_multiplicand_1 = + ExtensionTarget::from_range(gate, MulExtensionGate::::wires_ith_multiplicand_1(i)); + + self.connect_extension(operation.multiplicand_0, wires_multiplicand_0); + self.connect_extension(operation.multiplicand_1, wires_multiplicand_1); + + ExtensionTarget::from_range(gate, MulExtensionGate::::wires_ith_output(i)) + } + + /// Checks for special cases where the value of + /// `const_0 * multiplicand_0 * multiplicand_1 + const_1 * addend` + /// can be determined without adding an `ArithmeticGate`. + fn arithmetic_extension_special_cases( + &mut self, + const_0: F, + const_1: F, + multiplicand_0: ExtensionTarget, + multiplicand_1: ExtensionTarget, + addend: ExtensionTarget, + ) -> Option> { + let zero = self.zero_extension(); + + let mul_0_const = self.target_as_constant_ext(multiplicand_0); + let mul_1_const = self.target_as_constant_ext(multiplicand_1); + let addend_const = self.target_as_constant_ext(addend); + + let first_term_zero = + const_0 == F::ZERO || multiplicand_0 == zero || multiplicand_1 == zero; + let second_term_zero = const_1 == F::ZERO || addend == zero; + + // If both terms are constant, return their (constant) sum. + let first_term_const = if first_term_zero { + Some(F::Extension::ZERO) + } else if let (Some(x), Some(y)) = (mul_0_const, mul_1_const) { + Some((x * y).scalar_mul(const_0)) + } else { + None + }; + let second_term_const = if second_term_zero { + Some(F::Extension::ZERO) + } else { + addend_const.map(|x| x.scalar_mul(const_1)) + }; + if let (Some(x), Some(y)) = (first_term_const, second_term_const) { + return Some(self.constant_extension(x + y)); + } + + if first_term_zero && const_1.is_one() { + return Some(addend); + } + + if second_term_zero { + if let Some(x) = mul_0_const { + if x.scalar_mul(const_0).is_one() { + return Some(multiplicand_1); + } + } + if let Some(x) = mul_1_const { + if x.scalar_mul(const_0).is_one() { + return Some(multiplicand_0); + } + } + } + + None + } + + /// Returns `a*b + c*d + e`. + pub fn wide_arithmetic_extension( + &mut self, + a: ExtensionTarget, + b: ExtensionTarget, + c: ExtensionTarget, + d: ExtensionTarget, + e: ExtensionTarget, + ) -> ExtensionTarget { + self.inner_product_extension(F::ONE, e, vec![(a, b), (c, d)]) + } + + /// Returns `sum_{(a,b) in vecs} constant * a * b`. + pub fn inner_product_extension( + &mut self, + constant: F, + starting_acc: ExtensionTarget, + pairs: Vec<(ExtensionTarget, ExtensionTarget)>, + ) -> ExtensionTarget { + let mut acc = starting_acc; + for (a, b) in pairs { + acc = self.arithmetic_extension(constant, F::ONE, a, b, acc); + } + acc + } + + pub fn add_extension( + &mut self, + a: ExtensionTarget, + b: ExtensionTarget, + ) -> ExtensionTarget { + let one = self.one_extension(); + self.arithmetic_extension(F::ONE, F::ONE, one, a, b) + } + + pub fn add_ext_algebra( + &mut self, + mut a: ExtensionAlgebraTarget, + b: ExtensionAlgebraTarget, + ) -> ExtensionAlgebraTarget { + for i in 0..D { + a.0[i] = self.add_extension(a.0[i], b.0[i]); + } + a + } + + /// Add `n` `ExtensionTarget`s. + pub fn add_many_extension( + &mut self, + terms: impl IntoIterator, + ) -> ExtensionTarget + where + T: Borrow>, + { + terms.into_iter().fold(self.zero_extension(), |acc, t| { + self.add_extension(acc, *t.borrow()) + }) + } + + pub fn sub_extension( + &mut self, + a: ExtensionTarget, + b: ExtensionTarget, + ) -> ExtensionTarget { + let one = self.one_extension(); + self.arithmetic_extension(F::ONE, F::NEG_ONE, one, a, b) + } + + pub fn sub_ext_algebra( + &mut self, + mut a: ExtensionAlgebraTarget, + b: ExtensionAlgebraTarget, + ) -> ExtensionAlgebraTarget { + for i in 0..D { + a.0[i] = self.sub_extension(a.0[i], b.0[i]); + } + a + } + + pub fn mul_extension_with_const( + &mut self, + const_0: F, + multiplicand_0: ExtensionTarget, + multiplicand_1: ExtensionTarget, + ) -> ExtensionTarget { + let zero = self.zero_extension(); + self.arithmetic_extension(const_0, F::ZERO, multiplicand_0, multiplicand_1, zero) + } + + pub fn mul_extension( + &mut self, + multiplicand_0: ExtensionTarget, + multiplicand_1: ExtensionTarget, + ) -> ExtensionTarget { + self.mul_extension_with_const(F::ONE, multiplicand_0, multiplicand_1) + } + + /// Computes `x^2`. + pub fn square_extension(&mut self, x: ExtensionTarget) -> ExtensionTarget { + self.mul_extension(x, x) + } + + /// Computes `x^3`. + pub fn cube_extension(&mut self, x: ExtensionTarget) -> ExtensionTarget { + self.mul_many_extension([x, x, x]) + } + + /// Returns `a * b + c`. + pub fn mul_add_ext_algebra( + &mut self, + a: ExtensionAlgebraTarget, + b: ExtensionAlgebraTarget, + c: ExtensionAlgebraTarget, + ) -> ExtensionAlgebraTarget { + let mut inner = vec![vec![]; D]; + let mut inner_w = vec![vec![]; D]; + for i in 0..D { + for j in 0..D - i { + inner[(i + j) % D].push((a.0[i], b.0[j])); + } + for j in D - i..D { + inner_w[(i + j) % D].push((a.0[i], b.0[j])); + } + } + let res = inner_w + .into_iter() + .zip(inner) + .zip(c.0) + .map(|((pairs_w, pairs), ci)| { + let acc = self.inner_product_extension(F::Extension::W, ci, pairs_w); + self.inner_product_extension(F::ONE, acc, pairs) + }) + .collect::>(); + + ExtensionAlgebraTarget(res.try_into().unwrap()) + } + + /// Returns `a * b`. + pub fn mul_ext_algebra( + &mut self, + a: ExtensionAlgebraTarget, + b: ExtensionAlgebraTarget, + ) -> ExtensionAlgebraTarget { + let zero = self.zero_ext_algebra(); + self.mul_add_ext_algebra(a, b, zero) + } + + /// Multiply `n` `ExtensionTarget`s. + pub fn mul_many_extension( + &mut self, + terms: impl IntoIterator, + ) -> ExtensionTarget + where + T: Borrow>, + { + terms.into_iter().fold(self.one_extension(), |acc, t| { + self.mul_extension(acc, *t.borrow()) + }) + } + + /// Like `mul_add`, but for `ExtensionTarget`s. + pub fn mul_add_extension( + &mut self, + a: ExtensionTarget, + b: ExtensionTarget, + c: ExtensionTarget, + ) -> ExtensionTarget { + self.arithmetic_extension(F::ONE, F::ONE, a, b, c) + } + + /// Like `add_const`, but for `ExtensionTarget`s. + pub fn add_const_extension(&mut self, x: ExtensionTarget, c: F) -> ExtensionTarget { + let c = self.constant_extension(c.into()); + self.add_extension(x, c) + } + + /// Like `mul_const`, but for `ExtensionTarget`s. + pub fn mul_const_extension(&mut self, c: F, x: ExtensionTarget) -> ExtensionTarget { + let c = self.constant_extension(c.into()); + self.mul_extension(c, x) + } + + /// Like `mul_const_add`, but for `ExtensionTarget`s. + pub fn mul_const_add_extension( + &mut self, + c: F, + x: ExtensionTarget, + y: ExtensionTarget, + ) -> ExtensionTarget { + let c = self.constant_extension(c.into()); + self.mul_add_extension(c, x, y) + } + + /// Like `mul_add`, but for `ExtensionTarget`s. + pub fn scalar_mul_add_extension( + &mut self, + a: Target, + b: ExtensionTarget, + c: ExtensionTarget, + ) -> ExtensionTarget { + let a_ext = self.convert_to_ext(a); + self.arithmetic_extension(F::ONE, F::ONE, a_ext, b, c) + } + + /// Like `mul_sub`, but for `ExtensionTarget`s. + pub fn mul_sub_extension( + &mut self, + a: ExtensionTarget, + b: ExtensionTarget, + c: ExtensionTarget, + ) -> ExtensionTarget { + self.arithmetic_extension(F::ONE, F::NEG_ONE, a, b, c) + } + + /// Like `mul_sub`, but for `ExtensionTarget`s. + pub fn scalar_mul_sub_extension( + &mut self, + a: Target, + b: ExtensionTarget, + c: ExtensionTarget, + ) -> ExtensionTarget { + let a_ext = self.convert_to_ext(a); + self.arithmetic_extension(F::ONE, F::NEG_ONE, a_ext, b, c) + } + + /// Returns `a * b`, where `b` is in the extension field and `a` is in the + /// base field. + pub fn scalar_mul_ext(&mut self, a: Target, b: ExtensionTarget) -> ExtensionTarget { + let a_ext = self.convert_to_ext(a); + self.mul_extension(a_ext, b) + } + + /// Returns `a * b + c`, where `b, c` are in the extension algebra and `a` + /// in the extension field. + pub fn scalar_mul_add_ext_algebra( + &mut self, + a: ExtensionTarget, + b: ExtensionAlgebraTarget, + mut c: ExtensionAlgebraTarget, + ) -> ExtensionAlgebraTarget { + for i in 0..D { + c.0[i] = self.mul_add_extension(a, b.0[i], c.0[i]); + } + c + } + + /// Returns `a * b`, where `b` is in the extension algebra and `a` in the + /// extension field. + pub fn scalar_mul_ext_algebra( + &mut self, + a: ExtensionTarget, + b: ExtensionAlgebraTarget, + ) -> ExtensionAlgebraTarget { + let zero = self.zero_ext_algebra(); + self.scalar_mul_add_ext_algebra(a, b, zero) + } + + /// Exponentiate `base` to the power of `2^power_log`. + // TODO: Test + pub fn exp_power_of_2_extension( + &mut self, + mut base: ExtensionTarget, + power_log: usize, + ) -> ExtensionTarget { + for _ in 0..power_log { + base = self.square_extension(base); + } + base + } + + /// Exponentiate `base` to the power of a known `exponent`. + // TODO: Test + pub fn exp_u64_extension( + &mut self, + base: ExtensionTarget, + exponent: u64, + ) -> ExtensionTarget { + match exponent { + 0 => return self.one_extension(), + 1 => return base, + 2 => return self.square_extension(base), + 3 => return self.cube_extension(base), + _ => (), + } + let mut current = base; + let mut product = self.one_extension(); + + for j in 0..bits_u64(exponent) { + if j != 0 { + current = self.square_extension(current); + } + if (exponent >> j & 1) != 0 { + product = self.mul_extension(product, current); + } + } + product + } + + /// Computes `x / y`. Results in an unsatisfiable instance if `y = 0`. + pub fn div_extension( + &mut self, + x: ExtensionTarget, + y: ExtensionTarget, + ) -> ExtensionTarget { + let zero = self.zero_extension(); + self.div_add_extension(x, y, zero) + } + + /// Computes ` x / y + z`. + pub fn div_add_extension( + &mut self, + x: ExtensionTarget, + y: ExtensionTarget, + z: ExtensionTarget, + ) -> ExtensionTarget { + let inv = self.add_virtual_extension_target(); + let one = self.one_extension(); + self.add_simple_generator(QuotientGeneratorExtension { + numerator: one, + denominator: y, + quotient: inv, + }); + + // Enforce that y times its purported inverse equals 1. + let y_inv = self.mul_extension(y, inv); + self.connect_extension(y_inv, one); + + self.mul_add_extension(x, inv, z) + } + + /// Computes `1 / x`. Results in an unsatisfiable instance if `x = 0`. + pub fn inverse_extension(&mut self, x: ExtensionTarget) -> ExtensionTarget { + let one = self.one_extension(); + self.div_extension(one, x) + } +} + +#[derive(Debug, Default)] +pub struct QuotientGeneratorExtension { + numerator: ExtensionTarget, + denominator: ExtensionTarget, + quotient: ExtensionTarget, +} + +impl, const D: usize> SimpleGenerator + for QuotientGeneratorExtension +{ + fn id(&self) -> String { + "QuotientGeneratorExtension".to_string() + } + + fn dependencies(&self) -> Vec { + let mut deps = self.numerator.to_target_array().to_vec(); + deps.extend(self.denominator.to_target_array()); + deps + } + + fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + let num = witness.get_extension_target(self.numerator); + let dem = witness.get_extension_target(self.denominator); + let quotient = num / dem; + out_buffer.set_extension_target(self.quotient, quotient) + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_target_ext(self.numerator)?; + dst.write_target_ext(self.denominator)?; + dst.write_target_ext(self.quotient) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let numerator = src.read_target_ext()?; + let denominator = src.read_target_ext()?; + let quotient = src.read_target_ext()?; + Ok(Self { + numerator, + denominator, + quotient, + }) + } +} + +/// An iterator over the powers of a certain base element `b`: `b^0, b^1, b^2, +/// ...`. +#[derive(Clone)] +pub struct PowersTarget { + base: ExtensionTarget, + current: ExtensionTarget, +} + +impl PowersTarget { + pub fn next>( + &mut self, + builder: &mut CircuitBuilder, + ) -> ExtensionTarget { + let result = self.current; + self.current = builder.mul_extension(self.base, self.current); + result + } + + pub fn repeated_frobenius>( + self, + k: usize, + builder: &mut CircuitBuilder, + ) -> Self { + let Self { base, current } = self; + Self { + base: base.repeated_frobenius(k, builder), + current: current.repeated_frobenius(k, builder), + } + } +} + +impl, const D: usize> CircuitBuilder { + pub fn powers(&mut self, base: ExtensionTarget) -> PowersTarget { + PowersTarget { + base, + current: self.one_extension(), + } + } +} + +/// Represents an extension arithmetic operation in the circuit. Used to memoize +/// results. +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub(crate) struct ExtensionArithmeticOperation, const D: usize> { + const_0: F, + const_1: F, + multiplicand_0: ExtensionTarget, + multiplicand_1: ExtensionTarget, + addend: ExtensionTarget, +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + + use crate::field::extension::algebra::ExtensionAlgebra; + use crate::field::types::Sample; + use crate::iop::ext_target::ExtensionAlgebraTarget; + use crate::iop::witness::{PartialWitness, WitnessWrite}; + use crate::plonk::circuit_builder::CircuitBuilder; + use crate::plonk::circuit_data::CircuitConfig; + use crate::plonk::config::{GenericConfig, KeccakGoldilocksConfig, PoseidonGoldilocksConfig}; + use crate::plonk::verifier::verify; + + #[test] + fn test_mul_many() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type FF = >::FE; + + let config = CircuitConfig::standard_recursion_config(); + + let mut pw = PartialWitness::::new(); + let mut builder = CircuitBuilder::::new(config); + + let vs = FF::rand_vec(3); + let ts = builder.add_virtual_extension_targets(3); + for (&v, &t) in vs.iter().zip(&ts) { + pw.set_extension_target(t, v); + } + let mul0 = builder.mul_many_extension(&ts); + let mul1 = { + let mut acc = builder.one_extension(); + for &t in &ts { + acc = builder.mul_extension(acc, t); + } + acc + }; + let mul2 = builder.constant_extension(vs.into_iter().product()); + + builder.connect_extension(mul0, mul1); + builder.connect_extension(mul1, mul2); + + let data = builder.build::(); + let proof = data.prove(pw)?; + + verify(proof, &data.verifier_only, &data.common) + } + + #[test] + fn test_div_extension() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type FF = >::FE; + + let config = CircuitConfig::standard_recursion_zk_config(); + + let pw = PartialWitness::new(); + let mut builder = CircuitBuilder::::new(config); + + let x = FF::rand(); + let y = FF::rand(); + let z = x / y; + let xt = builder.constant_extension(x); + let yt = builder.constant_extension(y); + let zt = builder.constant_extension(z); + let comp_zt = builder.div_extension(xt, yt); + builder.connect_extension(zt, comp_zt); + + let data = builder.build::(); + let proof = data.prove(pw)?; + + verify(proof, &data.verifier_only, &data.common) + } + + #[test] + fn test_mul_algebra() -> Result<()> { + const D: usize = 2; + type C = KeccakGoldilocksConfig; + type F = >::F; + type FF = >::FE; + + let config = CircuitConfig::standard_recursion_config(); + + let mut pw = PartialWitness::new(); + let mut builder = CircuitBuilder::::new(config); + + let xt = + ExtensionAlgebraTarget(builder.add_virtual_extension_targets(D).try_into().unwrap()); + let yt = + ExtensionAlgebraTarget(builder.add_virtual_extension_targets(D).try_into().unwrap()); + let zt = + ExtensionAlgebraTarget(builder.add_virtual_extension_targets(D).try_into().unwrap()); + let comp_zt = builder.mul_ext_algebra(xt, yt); + for i in 0..D { + builder.connect_extension(zt.0[i], comp_zt.0[i]); + } + + let x = ExtensionAlgebra::(FF::rand_array()); + let y = ExtensionAlgebra::(FF::rand_array()); + let z = x * y; + for i in 0..D { + pw.set_extension_target(xt.0[i], x.0[i]); + pw.set_extension_target(yt.0[i], y.0[i]); + pw.set_extension_target(zt.0[i], z.0[i]); + } + + let data = builder.build::(); + let proof = data.prove(pw)?; + + verify(proof, &data.verifier_only, &data.common) + } +} diff --git a/plonky2/src/gadgets/hash.rs b/plonky2/src/gadgets/hash.rs new file mode 100644 index 000000000..fcd46a55a --- /dev/null +++ b/plonky2/src/gadgets/hash.rs @@ -0,0 +1,26 @@ +use crate::field::extension::Extendable; +use crate::hash::hash_types::RichField; +use crate::iop::target::BoolTarget; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::config::AlgebraicHasher; + +impl, const D: usize> CircuitBuilder { + pub fn permute>( + &mut self, + inputs: H::AlgebraicPermutation, + ) -> H::AlgebraicPermutation { + // We don't want to swap any inputs, so set that wire to 0. + let _false = self._false(); + self.permute_swapped::(inputs, _false) + } + + /// Conditionally swap two chunks of the inputs (useful in verifying Merkle + /// proofs), then apply a cryptographic permutation. + pub(crate) fn permute_swapped>( + &mut self, + inputs: H::AlgebraicPermutation, + swap: BoolTarget, + ) -> H::AlgebraicPermutation { + H::permute_swapped(inputs, swap, self) + } +} diff --git a/plonky2/src/gadgets/interpolation.rs b/plonky2/src/gadgets/interpolation.rs new file mode 100644 index 000000000..b3071b0d0 --- /dev/null +++ b/plonky2/src/gadgets/interpolation.rs @@ -0,0 +1,115 @@ +#[cfg(not(feature = "std"))] +use alloc::vec; + +use plonky2_field::extension::Extendable; + +use crate::gates::coset_interpolation::CosetInterpolationGate; +use crate::hash::hash_types::RichField; +use crate::iop::ext_target::ExtensionTarget; +use crate::iop::target::Target; +use crate::plonk::circuit_builder::CircuitBuilder; + +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, + gate: CosetInterpolationGate, + coset_shift: Target, + values: &[ExtensionTarget], + evaluation_point: ExtensionTarget, + ) -> ExtensionTarget { + let row = self.num_gates(); + 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()), + ); + + let eval = ExtensionTarget::from_range(row, gate.wires_evaluation_value()); + self.add_gate(gate, vec![]); + + eval + } +} + +#[cfg(test)] +mod tests { + #[cfg(not(feature = "std"))] + use alloc::vec::Vec; + + use anyhow::Result; + + use crate::field::extension::FieldExtension; + use crate::field::interpolation::interpolant; + use crate::field::types::{Field, Sample}; + use crate::gates::coset_interpolation::CosetInterpolationGate; + 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 evals_coset_gates = (2..=4) + .map(|max_degree| { + builder.interpolate_coset( + CosetInterpolationGate::with_max_degree(subgroup_bits, max_degree), + coset_shift_target, + &value_targets, + zt, + ) + }) + .collect::>(); + let true_eval_target = builder.constant_extension(true_eval); + for &eval_coset_gate in evals_coset_gates.iter() { + builder.connect_extension(eval_coset_gate, 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/lookup.rs b/plonky2/src/gadgets/lookup.rs new file mode 100644 index 000000000..62a2e23d8 --- /dev/null +++ b/plonky2/src/gadgets/lookup.rs @@ -0,0 +1,169 @@ +#[cfg(not(feature = "std"))] +use alloc::{borrow::ToOwned, vec}; + +use crate::field::extension::Extendable; +use crate::gates::lookup::LookupGate; +use crate::gates::lookup_table::{LookupTable, LookupTableGate}; +use crate::gates::noop::NoopGate; +use crate::hash::hash_types::RichField; +use crate::iop::target::Target; +use crate::plonk::circuit_builder::CircuitBuilder; + +/// Lookup tables used in the tests and benchmarks. +/// +/// The following table was taken from the Tip5 paper. +pub const TIP5_TABLE: [u16; 256] = [ + 0, 7, 26, 63, 124, 215, 85, 254, 214, 228, 45, 185, 140, 173, 33, 240, 29, 177, 176, 32, 8, + 110, 87, 202, 204, 99, 150, 106, 230, 14, 235, 128, 213, 239, 212, 138, 23, 130, 208, 6, 44, + 71, 93, 116, 146, 189, 251, 81, 199, 97, 38, 28, 73, 179, 95, 84, 152, 48, 35, 119, 49, 88, + 242, 3, 148, 169, 72, 120, 62, 161, 166, 83, 175, 191, 137, 19, 100, 129, 112, 55, 221, 102, + 218, 61, 151, 237, 68, 164, 17, 147, 46, 234, 203, 216, 22, 141, 65, 57, 123, 12, 244, 54, 219, + 231, 96, 77, 180, 154, 5, 253, 133, 165, 98, 195, 205, 134, 245, 30, 9, 188, 59, 142, 186, 197, + 181, 144, 92, 31, 224, 163, 111, 74, 58, 69, 113, 196, 67, 246, 225, 10, 121, 50, 60, 157, 90, + 122, 2, 250, 101, 75, 178, 159, 24, 36, 201, 11, 243, 132, 198, 190, 114, 233, 39, 52, 21, 209, + 108, 238, 91, 187, 18, 104, 194, 37, 153, 34, 200, 143, 126, 155, 236, 118, 64, 80, 172, 89, + 94, 193, 135, 183, 86, 107, 252, 13, 167, 206, 136, 220, 207, 103, 171, 160, 76, 182, 227, 217, + 158, 56, 174, 4, 66, 109, 139, 162, 184, 211, 249, 47, 125, 232, 117, 43, 16, 42, 127, 20, 241, + 25, 149, 105, 156, 51, 53, 168, 145, 247, 223, 79, 78, 226, 15, 222, 82, 115, 70, 210, 27, 41, + 1, 170, 40, 131, 192, 229, 248, 255, +]; + +/// This is a table with 256 arbitrary values. +pub const OTHER_TABLE: [u16; 256] = [ + 2, 6, 25, 3, 9, 7, 0, 3, 25, 35, 10, 19, 36, 45, 216, 247, 35, 39, 57, 126, 2, 6, 25, 3, 9, 7, + 0, 3, 25, 35, 10, 19, 36, 45, 216, 247, 35, 39, 57, 126, 2, 6, 25, 3, 9, 7, 0, 3, 25, 35, 10, + 19, 36, 45, 216, 247, 35, 39, 57, 126, 2, 6, 25, 3, 9, 7, 0, 3, 25, 35, 10, 19, 36, 45, 216, + 247, 35, 39, 57, 126, 2, 6, 25, 3, 9, 7, 0, 3, 25, 35, 10, 19, 36, 45, 216, 247, 35, 39, 57, + 126, 2, 6, 25, 3, 9, 7, 0, 3, 25, 35, 10, 19, 36, 45, 216, 247, 35, 39, 57, 126, 2, 6, 25, 3, + 9, 7, 0, 3, 25, 35, 10, 19, 36, 45, 216, 247, 35, 39, 57, 126, 2, 6, 25, 3, 9, 7, 0, 3, 25, 35, + 10, 19, 36, 45, 216, 247, 35, 39, 57, 126, 2, 6, 25, 3, 9, 7, 0, 3, 25, 35, 10, 19, 36, 45, + 216, 247, 35, 39, 57, 126, 2, 6, 25, 3, 9, 7, 0, 3, 25, 35, 10, 19, 36, 45, 216, 247, 35, 39, + 57, 126, 2, 6, 25, 3, 9, 7, 0, 3, 25, 35, 10, 19, 36, 45, 216, 247, 35, 39, 57, 126, 2, 6, 25, + 3, 9, 7, 0, 3, 25, 35, 10, 19, 36, 45, 216, 247, 35, 39, 57, 126, 2, 6, 25, 3, 9, 7, 0, 3, 25, + 35, 10, 19, 36, 45, 216, 247, +]; + +/// This is a smaller lookup table with arbitrary values. +pub const SMALLER_TABLE: [u16; 8] = [2, 24, 56, 100, 128, 16, 20, 49]; + +impl, const D: usize> CircuitBuilder { + /// Adds a lookup table to the list of stored lookup tables `self.luts` + /// based on a table of (input, output) pairs. It returns the index of the + /// LUT within `self.luts`. + pub fn add_lookup_table_from_pairs(&mut self, table: LookupTable) -> usize { + self.update_luts_from_pairs(table) + } + + /// Adds a lookup table to the list of stored lookup tables `self.luts` + /// based on a table, represented as a slice `&[u16]` of inputs and a slice + /// `&[u16]` of outputs. It returns the index of the LUT within `self.luts`. + pub fn add_lookup_table_from_table(&mut self, inps: &[u16], outs: &[u16]) -> usize { + self.update_luts_from_table(inps, outs) + } + + /// Adds a lookup table to the list of stored lookup tables `self.luts` + /// based on a function. It returns the index of the LUT within `self.luts`. + pub fn add_lookup_table_from_fn(&mut self, f: fn(u16) -> u16, inputs: &[u16]) -> usize { + self.update_luts_from_fn(f, inputs) + } + + /// Adds a lookup (input, output) pair to the stored lookups. Takes a + /// `Target` input and returns a `Target` output. + pub fn add_lookup_from_index(&mut self, looking_in: Target, lut_index: usize) -> Target { + assert!( + lut_index < self.get_luts_length(), + "lut number {} not in luts (length = {})", + lut_index, + self.get_luts_length() + ); + let looking_out = self.add_virtual_target(); + self.update_lookups(looking_in, looking_out, lut_index); + looking_out + } + + /// We call this function at the end of circuit building right before the PI + /// gate to add all `LookupTableGate` and `LookupGate`. It also updates + /// `self.lookup_rows` accordingly. + pub fn add_all_lookups(&mut self) { + for lut_index in 0..self.num_luts() { + assert!( + !self.get_lut_lookups(lut_index).is_empty(), + "LUT number {:?} is unused", + lut_index + ); + if !self.get_lut_lookups(lut_index).is_empty() { + // Create LU gates. Connect them to the stored lookups. + let last_lu_gate = self.num_gates(); + + let lut = self.get_lut(lut_index); + + let lookups = self.get_lut_lookups(lut_index).to_owned(); + + let gate = LookupGate::new_from_table(&self.config, lut.clone()); + let num_slots = LookupGate::num_slots(&self.config); + + // Given the number of lookups and the number of slots for each gate, it is + // possible to compute the number of gates that will employ all + // their slots; such gates can can be instantiated with + // `add_gate` rather than being instantiated slot by slot + + // lookup_iter will iterate over the lookups that can be placed in fully + // utilized gates, splitting them in chunks that can be placed + // in the same `LookupGate` + let lookup_iter = lookups.chunks_exact(num_slots); + // `last_chunk` will contain the remainder of lookups, which cannot fill all the + // slots of a `LookupGate`; this last chunk will be processed by incrementally + // filling slots, to avoid that the `LookupGenerator` is run on unused slots + let last_chunk = lookup_iter.remainder(); + // handle chunks that can fill all the slots of a `LookupGate` + lookup_iter.for_each(|chunk| { + let row = self.add_gate(gate.clone(), vec![]); + for (i, (looking_in, looking_out)) in chunk.iter().enumerate() { + let gate_in = Target::wire(row, LookupGate::wire_ith_looking_inp(i)); + let gate_out = Target::wire(row, LookupGate::wire_ith_looking_out(i)); + self.connect(gate_in, *looking_in); + self.connect(gate_out, *looking_out); + } + }); + // deal with the last chunk + for (looking_in, looking_out) in last_chunk.iter() { + let (gate, i) = + self.find_slot(gate.clone(), &[F::from_canonical_usize(lut_index)], &[]); + let gate_in = Target::wire(gate, LookupGate::wire_ith_looking_inp(i)); + let gate_out = Target::wire(gate, LookupGate::wire_ith_looking_out(i)); + self.connect(gate_in, *looking_in); + self.connect(gate_out, *looking_out); + } + + // Create LUT gates. Nothing is connected to them. + let last_lut_gate = self.num_gates(); + let num_lut_entries = LookupTableGate::num_slots(&self.config); + let num_lut_rows = (self.get_luts_idx_length(lut_index) - 1) / num_lut_entries + 1; + let gate = + LookupTableGate::new_from_table(&self.config, lut.clone(), last_lut_gate); + // Also instances of `LookupTableGate` can be placed with the `add_gate` + // function rather than being instantiated slot by slot; note + // that in this case there is no need to separately handle the + // last chunk of LUT entries that cannot fill all the slots of a + // `LookupTableGate`, as the generator already handles empty slots + for _ in 0..num_lut_rows { + self.add_gate(gate.clone(), vec![]); + } + + let first_lut_gate = self.num_gates() - 1; + + // Will ensure the next row's wires will be all zeros. With this, there is no + // distinction between the transition constraints on the first row + // and on the other rows. Additionally, initial constraints become a simple zero + // check. + self.add_gate(NoopGate, vec![]); + + // These elements are increasing: the gate rows are deliberately upside down. + // This is necessary for constraint evaluation so that you do not need values of + // the next row's wires, which aren't provided in the evaluation + // variables. + self.add_lookup_rows(last_lu_gate, last_lut_gate, first_lut_gate); + } + } + } +} diff --git a/plonky2/src/gadgets/mod.rs b/plonky2/src/gadgets/mod.rs new file mode 100644 index 000000000..cc14a8355 --- /dev/null +++ b/plonky2/src/gadgets/mod.rs @@ -0,0 +1,15 @@ +//! Helper gadgets providing additional methods to +//! [CircuitBuilder](crate::plonk::circuit_builder::CircuitBuilder), +//! to ease circuit creation. + +pub mod arithmetic; +pub mod arithmetic_extension; +pub mod hash; +pub mod interpolation; +pub mod lookup; +pub mod polynomial; +pub mod random_access; +pub mod range_check; +pub mod select; +pub mod split_base; +pub mod split_join; diff --git a/plonky2/src/gadgets/polynomial.rs b/plonky2/src/gadgets/polynomial.rs new file mode 100644 index 000000000..42959fbbc --- /dev/null +++ b/plonky2/src/gadgets/polynomial.rs @@ -0,0 +1,93 @@ +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + +use crate::field::extension::Extendable; +use crate::hash::hash_types::RichField; +use crate::iop::ext_target::{ExtensionAlgebraTarget, ExtensionTarget}; +use crate::iop::target::Target; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::util::reducing::ReducingFactorTarget; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct PolynomialCoeffsExtTarget(pub Vec>); + +impl PolynomialCoeffsExtTarget { + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn eval_scalar>( + &self, + builder: &mut CircuitBuilder, + point: Target, + ) -> ExtensionTarget { + let point = builder.convert_to_ext(point); + let mut point = ReducingFactorTarget::new(point); + point.reduce(&self.0, builder) + } + + pub fn eval>( + &self, + builder: &mut CircuitBuilder, + point: ExtensionTarget, + ) -> ExtensionTarget { + let mut point = ReducingFactorTarget::new(point); + point.reduce(&self.0, builder) + } +} + +pub struct PolynomialCoeffsExtAlgebraTarget(pub Vec>); + +impl PolynomialCoeffsExtAlgebraTarget { + pub fn eval_scalar( + &self, + builder: &mut CircuitBuilder, + point: ExtensionTarget, + ) -> ExtensionAlgebraTarget + where + F: RichField + Extendable, + { + let mut acc = builder.zero_ext_algebra(); + for &c in self.0.iter().rev() { + acc = builder.scalar_mul_add_ext_algebra(point, acc, c); + } + acc + } + + pub fn eval( + &self, + builder: &mut CircuitBuilder, + point: ExtensionAlgebraTarget, + ) -> ExtensionAlgebraTarget + where + F: RichField + Extendable, + { + let mut acc = builder.zero_ext_algebra(); + for &c in self.0.iter().rev() { + acc = builder.mul_add_ext_algebra(point, acc, c); + } + acc + } + + /// Evaluate the polynomial at a point given its powers. The first power is + /// the point itself, not 1. + pub fn eval_with_powers( + &self, + builder: &mut CircuitBuilder, + powers: &[ExtensionAlgebraTarget], + ) -> ExtensionAlgebraTarget + where + F: RichField + Extendable, + { + debug_assert_eq!(self.0.len(), powers.len() + 1); + let acc = self.0[0]; + self.0[1..] + .iter() + .zip(powers) + .fold(acc, |acc, (&x, &c)| builder.mul_add_ext_algebra(c, x, acc)) + } +} diff --git a/plonky2/src/gadgets/random_access.rs b/plonky2/src/gadgets/random_access.rs new file mode 100644 index 000000000..87a12cb08 --- /dev/null +++ b/plonky2/src/gadgets/random_access.rs @@ -0,0 +1,150 @@ +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + +use crate::field::extension::Extendable; +use crate::gates::random_access::RandomAccessGate; +use crate::hash::hash_types::{HashOutTarget, MerkleCapTarget, RichField}; +use crate::iop::ext_target::ExtensionTarget; +use crate::iop::target::Target; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::VerifierCircuitTarget; +use crate::util::log2_strict; + +impl, const D: usize> CircuitBuilder { + /// Checks that a `Target` matches a vector at a particular index. + pub fn random_access(&mut self, access_index: Target, v: Vec) -> Target { + let vec_size = v.len(); + let bits = log2_strict(vec_size); + debug_assert!(vec_size > 0); + if vec_size == 1 { + return v[0]; + } + let claimed_element = self.add_virtual_target(); + + let dummy_gate = RandomAccessGate::::new_from_config(&self.config, bits); + let (row, copy) = self.find_slot(dummy_gate, &[], &[]); + + v.iter().enumerate().for_each(|(i, &val)| { + self.connect(val, Target::wire(row, dummy_gate.wire_list_item(i, copy))); + }); + self.connect( + access_index, + Target::wire(row, dummy_gate.wire_access_index(copy)), + ); + self.connect( + claimed_element, + Target::wire(row, dummy_gate.wire_claimed_element(copy)), + ); + + claimed_element + } + + /// Like `random_access`, but with `ExtensionTarget`s rather than simple + /// `Target`s. + pub fn random_access_extension( + &mut self, + access_index: Target, + v: Vec>, + ) -> ExtensionTarget { + let selected: Vec<_> = (0..D) + .map(|i| self.random_access(access_index, v.iter().map(|et| et.0[i]).collect())) + .collect(); + + ExtensionTarget(selected.try_into().unwrap()) + } + + /// Like `random_access`, but with `HashOutTarget`s rather than simple + /// `Target`s. + pub fn random_access_hash( + &mut self, + access_index: Target, + v: Vec, + ) -> HashOutTarget { + let selected = core::array::from_fn(|i| { + self.random_access( + access_index, + v.iter().map(|hash| hash.elements[i]).collect(), + ) + }); + selected.into() + } + + /// Like `random_access`, but with `MerkleCapTarget`s rather than simple + /// `Target`s. + pub fn random_access_merkle_cap( + &mut self, + access_index: Target, + v: Vec, + ) -> MerkleCapTarget { + let cap_size = v[0].0.len(); + assert!(v.iter().all(|cap| cap.0.len() == cap_size)); + + let selected = (0..cap_size) + .map(|i| self.random_access_hash(access_index, v.iter().map(|cap| cap.0[i]).collect())) + .collect(); + MerkleCapTarget(selected) + } + + /// Like `random_access`, but with `VerifierCircuitTarget`s rather than + /// simple `Target`s. + pub fn random_access_verifier_data( + &mut self, + access_index: Target, + v: Vec, + ) -> VerifierCircuitTarget { + let constants_sigmas_caps = v.iter().map(|vk| vk.constants_sigmas_cap.clone()).collect(); + let circuit_digests = v.iter().map(|vk| vk.circuit_digest).collect(); + let constants_sigmas_cap = + self.random_access_merkle_cap(access_index, constants_sigmas_caps); + let circuit_digest = self.random_access_hash(access_index, circuit_digests); + VerifierCircuitTarget { + constants_sigmas_cap, + circuit_digest, + } + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + + use super::*; + use crate::field::types::{Field, Sample}; + use crate::iop::witness::PartialWitness; + use crate::plonk::circuit_data::CircuitConfig; + use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + use crate::plonk::verifier::verify; + + fn test_random_access_given_len(len_log: usize) -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type FF = >::FE; + let len = 1 << len_log; + let config = CircuitConfig::standard_recursion_config(); + let pw = PartialWitness::new(); + let mut builder = CircuitBuilder::::new(config); + let vec = FF::rand_vec(len); + let v: Vec<_> = vec.iter().map(|x| builder.constant_extension(*x)).collect(); + + for i in 0..len { + let it = builder.constant(F::from_canonical_usize(i)); + let elem = builder.constant_extension(vec[i]); + let res = builder.random_access_extension(it, v.clone()); + builder.connect_extension(elem, res); + } + + let data = builder.build::(); + let proof = data.prove(pw)?; + + verify(proof, &data.verifier_only, &data.common) + } + + #[test] + fn test_random_access() -> Result<()> { + for len_log in 1..3 { + test_random_access_given_len(len_log)?; + } + Ok(()) + } +} diff --git a/plonky2/src/gadgets/range_check.rs b/plonky2/src/gadgets/range_check.rs new file mode 100644 index 000000000..9a66a6a6c --- /dev/null +++ b/plonky2/src/gadgets/range_check.rs @@ -0,0 +1,105 @@ +#[cfg(not(feature = "std"))] +use alloc::{ + string::{String, ToString}, + vec, + vec::Vec, +}; + +use crate::field::extension::Extendable; +use crate::hash::hash_types::RichField; +use crate::iop::generator::{GeneratedValues, SimpleGenerator}; +use crate::iop::target::{BoolTarget, Target}; +use crate::iop::witness::{PartitionWitness, Witness, WitnessWrite}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::CommonCircuitData; +use crate::util::serialization::{Buffer, IoResult, Read, Write}; + +impl, const D: usize> CircuitBuilder { + /// Checks that `x < 2^n_log` using a `BaseSumGate`. + pub fn range_check(&mut self, x: Target, n_log: usize) { + self.split_le(x, n_log); + } + + /// Returns the first `num_low_bits` little-endian bits of `x`. + pub fn low_bits(&mut self, x: Target, num_low_bits: usize, num_bits: usize) -> Vec { + let mut res = self.split_le(x, num_bits); + res.truncate(num_low_bits); + res + } + + /// Returns `(a,b)` such that `x = a + 2^n_log * b` with `a < 2^n_log`. + /// `x` is assumed to be range-checked for having `num_bits` bits. + pub fn split_low_high(&mut self, x: Target, n_log: usize, num_bits: usize) -> (Target, Target) { + let low = self.add_virtual_target(); + let high = self.add_virtual_target(); + + self.add_simple_generator(LowHighGenerator { + integer: x, + n_log, + low, + high, + }); + + self.range_check(low, n_log); + self.range_check(high, num_bits - n_log); + + let pow2 = self.constant(F::from_canonical_u64(1 << n_log)); + let comp_x = self.mul_add(high, pow2, low); + self.connect(x, comp_x); + + (low, high) + } + + pub fn assert_bool(&mut self, b: BoolTarget) { + let z = self.mul_sub(b.target, b.target, b.target); + let zero = self.zero(); + self.connect(z, zero); + } +} + +#[derive(Debug, Default)] +pub struct LowHighGenerator { + integer: Target, + n_log: usize, + low: Target, + high: Target, +} + +impl, const D: usize> SimpleGenerator for LowHighGenerator { + fn id(&self) -> String { + "LowHighGenerator".to_string() + } + + fn dependencies(&self) -> Vec { + vec![self.integer] + } + + fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + let integer_value = witness.get_target(self.integer).to_canonical_u64(); + let low = integer_value & ((1 << self.n_log) - 1); + let high = integer_value >> self.n_log; + + out_buffer.set_target(self.low, F::from_canonical_u64(low)); + out_buffer.set_target(self.high, F::from_canonical_u64(high)); + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_target(self.integer)?; + dst.write_usize(self.n_log)?; + dst.write_target(self.low)?; + dst.write_target(self.high) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let integer = src.read_target()?; + let n_log = src.read_usize()?; + let low = src.read_target()?; + let high = src.read_target()?; + Ok(Self { + integer, + n_log, + low, + high, + }) + } +} diff --git a/plonky2/src/gadgets/select.rs b/plonky2/src/gadgets/select.rs new file mode 100644 index 000000000..60bde8cb7 --- /dev/null +++ b/plonky2/src/gadgets/select.rs @@ -0,0 +1,82 @@ +use crate::field::extension::Extendable; +use crate::hash::hash_types::RichField; +use crate::iop::ext_target::ExtensionTarget; +use crate::iop::target::{BoolTarget, Target}; +use crate::plonk::circuit_builder::CircuitBuilder; + +impl, const D: usize> CircuitBuilder { + /// Selects `x` or `y` based on `b`, i.e., this returns `if b { x } else { y + /// }`. + pub fn select_ext( + &mut self, + b: BoolTarget, + x: ExtensionTarget, + y: ExtensionTarget, + ) -> ExtensionTarget { + let b_ext = self.convert_to_ext(b.target); + self.select_ext_generalized(b_ext, x, y) + } + + /// Like `select_ext`, but accepts a condition input which does not + /// necessarily have to be binary. In this case, it computes the + /// arithmetic generalization of `if b { x } else { y }`, i.e. `bx - + /// (by-y)`. + pub fn select_ext_generalized( + &mut self, + b: ExtensionTarget, + x: ExtensionTarget, + y: ExtensionTarget, + ) -> ExtensionTarget { + let tmp = self.mul_sub_extension(b, y, y); + self.mul_sub_extension(b, x, tmp) + } + + /// See `select_ext`. + pub fn select(&mut self, b: BoolTarget, x: Target, y: Target) -> Target { + let tmp = self.mul_sub(b.target, y, y); + self.mul_sub(b.target, x, tmp) + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + + use crate::field::types::Sample; + use crate::iop::witness::{PartialWitness, WitnessWrite}; + 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_select() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type FF = >::FE; + let config = CircuitConfig::standard_recursion_config(); + let mut pw = PartialWitness::::new(); + let mut builder = CircuitBuilder::::new(config); + + let (x, y) = (FF::rand(), FF::rand()); + let xt = builder.add_virtual_extension_target(); + let yt = builder.add_virtual_extension_target(); + let truet = builder._true(); + let falset = builder._false(); + + pw.set_extension_target(xt, x); + pw.set_extension_target(yt, y); + + let should_be_x = builder.select_ext(truet, xt, yt); + let should_be_y = builder.select_ext(falset, xt, yt); + + builder.connect_extension(should_be_x, xt); + builder.connect_extension(should_be_y, yt); + + let data = builder.build::(); + let proof = data.prove(pw)?; + + verify(proof, &data.verifier_only, &data.common) + } +} diff --git a/plonky2/src/gadgets/split_base.rs b/plonky2/src/gadgets/split_base.rs new file mode 100644 index 000000000..89fd20724 --- /dev/null +++ b/plonky2/src/gadgets/split_base.rs @@ -0,0 +1,202 @@ +#[cfg(not(feature = "std"))] +use alloc::{format, string::String, vec, vec::Vec}; +use core::borrow::Borrow; + +use itertools::Itertools; + +use crate::field::extension::Extendable; +use crate::gates::base_sum::BaseSumGate; +use crate::hash::hash_types::RichField; +use crate::iop::generator::{GeneratedValues, SimpleGenerator}; +use crate::iop::target::{BoolTarget, Target}; +use crate::iop::witness::{PartitionWitness, Witness, WitnessWrite}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::CommonCircuitData; +use crate::util::log_floor; +use crate::util::serialization::{Buffer, IoResult, Read, Write}; + +impl, const D: usize> CircuitBuilder { + /// Split the given element into a list of targets, where each one + /// represents a base-B limb of the element, with little-endian + /// ordering. + pub fn split_le_base(&mut self, x: Target, num_limbs: usize) -> Vec { + let gate_type = BaseSumGate::::new(num_limbs); + let gate = self.add_gate(gate_type, vec![]); + let sum = Target::wire(gate, BaseSumGate::::WIRE_SUM); + self.connect(x, sum); + + Target::wires_from_range(gate, gate_type.limbs()) + } + + /// Asserts that `x`'s big-endian bit representation has at least + /// `leading_zeros` leading zeros. + pub(crate) fn assert_leading_zeros(&mut self, x: Target, leading_zeros: u32) { + self.range_check(x, (64 - leading_zeros) as usize); + } + + /// Takes an iterator of bits `(b_i)` and returns `sum b_i * 2^i`, i.e., + /// the number with little-endian bit representation given by `bits`. + pub fn le_sum(&mut self, bits: impl Iterator>) -> Target { + let bits = bits.map(|b| *b.borrow()).collect_vec(); + let num_bits = bits.len(); + assert!( + num_bits <= log_floor(F::ORDER, 2), + "{} bits may overflow the field", + num_bits + ); + if num_bits == 0 { + return self.zero(); + } + + // Check if it's cheaper to just do this with arithmetic operations. + let arithmetic_ops = num_bits - 1; + if arithmetic_ops <= self.num_base_arithmetic_ops_per_gate() { + let two = self.two(); + let mut rev_bits = bits.iter().rev(); + let mut sum = rev_bits.next().unwrap().target; + for &bit in rev_bits { + sum = self.mul_add(two, sum, bit.target); + } + return sum; + } + + debug_assert!( + BaseSumGate::<2>::START_LIMBS + num_bits <= self.config.num_routed_wires, + "Not enough routed wires." + ); + let gate_type = BaseSumGate::<2>::new_from_config::(&self.config); + let row = self.add_gate(gate_type, vec![]); + for (limb, wire) in bits + .iter() + .zip(BaseSumGate::<2>::START_LIMBS..BaseSumGate::<2>::START_LIMBS + num_bits) + { + self.connect(limb.target, Target::wire(row, wire)); + } + for l in gate_type.limbs().skip(num_bits) { + self.assert_zero(Target::wire(row, l)); + } + + self.add_simple_generator(BaseSumGenerator::<2> { row, limbs: bits }); + + Target::wire(row, BaseSumGate::<2>::WIRE_SUM) + } +} + +#[derive(Debug, Default)] +pub struct BaseSumGenerator { + row: usize, + limbs: Vec, +} + +impl, const B: usize, const D: usize> SimpleGenerator + for BaseSumGenerator +{ + fn id(&self) -> String { + format!("BaseSumGenerator + Base: {B}") + } + + fn dependencies(&self) -> Vec { + self.limbs.iter().map(|b| b.target).collect() + } + + fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + let sum = self + .limbs + .iter() + .map(|&t| witness.get_bool_target(t)) + .rev() + .fold(F::ZERO, |acc, limb| { + acc * F::from_canonical_usize(B) + F::from_bool(limb) + }); + + out_buffer.set_target(Target::wire(self.row, BaseSumGate::::WIRE_SUM), sum); + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.row)?; + dst.write_target_bool_vec(&self.limbs) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let row = src.read_usize()?; + let limbs = src.read_target_bool_vec()?; + Ok(Self { row, limbs }) + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + use plonky2_field::types::Field; + use rand::rngs::OsRng; + use rand::Rng; + + use super::*; + use crate::iop::witness::PartialWitness; + use crate::plonk::circuit_data::CircuitConfig; + use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + use crate::plonk::verifier::verify; + + #[test] + fn test_split_base() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + let config = CircuitConfig::standard_recursion_config(); + let pw = PartialWitness::new(); + let mut builder = CircuitBuilder::::new(config); + let x = F::from_canonical_usize(0b110100000); // 416 = 1532 in base 6. + let xt = builder.constant(x); + let limbs = builder.split_le_base::<6>(xt, 24); + let one = builder.one(); + let two = builder.two(); + let three = builder.constant(F::from_canonical_u64(3)); + let five = builder.constant(F::from_canonical_u64(5)); + builder.connect(limbs[0], two); + builder.connect(limbs[1], three); + builder.connect(limbs[2], five); + builder.connect(limbs[3], one); + + builder.assert_leading_zeros(xt, 64 - 9); + let data = builder.build::(); + + let proof = data.prove(pw)?; + + verify(proof, &data.verifier_only, &data.common) + } + + #[test] + fn test_base_sum() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + let config = CircuitConfig::standard_recursion_config(); + let pw = PartialWitness::new(); + let mut builder = CircuitBuilder::::new(config); + + let n = OsRng.gen_range(0..(1 << 30)); + let x = builder.constant(F::from_canonical_usize(n)); + + let zero = builder._false(); + let one = builder._true(); + + let y = builder.le_sum( + (0..30) + .scan(n, |acc, _| { + let tmp = *acc % 2; + *acc /= 2; + Some(if tmp == 1 { one } else { zero }) + }) + .collect::>() + .iter(), + ); + + builder.connect(x, y); + + let data = builder.build::(); + + let proof = data.prove(pw)?; + + verify(proof, &data.verifier_only, &data.common) + } +} diff --git a/plonky2/src/gadgets/split_join.rs b/plonky2/src/gadgets/split_join.rs new file mode 100644 index 000000000..2a6403d02 --- /dev/null +++ b/plonky2/src/gadgets/split_join.rs @@ -0,0 +1,167 @@ +#[cfg(not(feature = "std"))] +use alloc::{ + string::{String, ToString}, + vec, + vec::Vec, +}; + +use crate::field::extension::Extendable; +use crate::gates::base_sum::BaseSumGate; +use crate::hash::hash_types::RichField; +use crate::iop::generator::{GeneratedValues, SimpleGenerator}; +use crate::iop::target::{BoolTarget, Target}; +use crate::iop::witness::{PartitionWitness, Witness, WitnessWrite}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::CommonCircuitData; +use crate::util::ceil_div_usize; +use crate::util::serialization::{Buffer, IoResult, Read, Write}; + +impl, const D: usize> CircuitBuilder { + /// Split the given integer into a list of wires, where each one represents + /// a bit of the integer, with little-endian ordering. + /// Verifies that the decomposition is correct by using `k` `BaseSum<2>` + /// gates with `k` such that `k * num_routed_wires >= num_bits`. + pub fn split_le(&mut self, integer: Target, num_bits: usize) -> Vec { + if num_bits == 0 { + return Vec::new(); + } + let gate_type = BaseSumGate::<2>::new_from_config::(&self.config); + let k = ceil_div_usize(num_bits, gate_type.num_limbs); + let gates = (0..k) + .map(|_| self.add_gate(gate_type, vec![])) + .collect::>(); + + let mut bits = Vec::with_capacity(num_bits); + for &gate in &gates { + for limb_column in gate_type.limbs() { + // `new_unsafe` is safe here because BaseSumGate::<2> forces it to be in `{0, + // 1}`. + bits.push(BoolTarget::new_unsafe(Target::wire(gate, limb_column))); + } + } + for b in bits.drain(num_bits..) { + self.assert_zero(b.target); + } + + let zero = self.zero(); + let base = F::TWO.exp_u64(gate_type.num_limbs as u64); + let mut acc = zero; + for &gate in gates.iter().rev() { + let sum = Target::wire(gate, BaseSumGate::<2>::WIRE_SUM); + acc = self.mul_const_add(base, acc, sum); + } + self.connect(acc, integer); + + self.add_simple_generator(WireSplitGenerator { + integer, + gates, + num_limbs: gate_type.num_limbs, + }); + + bits + } +} + +#[derive(Debug, Default)] +pub struct SplitGenerator { + integer: Target, + bits: Vec, +} + +impl, const D: usize> SimpleGenerator for SplitGenerator { + fn id(&self) -> String { + "SplitGenerator".to_string() + } + + fn dependencies(&self) -> Vec { + vec![self.integer] + } + + fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + let mut integer_value = witness.get_target(self.integer).to_canonical_u64(); + + for &b in &self.bits { + let b_value = integer_value & 1; + out_buffer.set_target(b, F::from_canonical_u64(b_value)); + integer_value >>= 1; + } + + debug_assert_eq!( + integer_value, 0, + "Integer too large to fit in given number of bits" + ); + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_target(self.integer)?; + dst.write_target_vec(&self.bits) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let integer = src.read_target()?; + let bits = src.read_target_vec()?; + Ok(Self { integer, bits }) + } +} + +#[derive(Debug, Default)] +pub struct WireSplitGenerator { + integer: Target, + gates: Vec, + num_limbs: usize, +} + +impl, const D: usize> SimpleGenerator for WireSplitGenerator { + fn id(&self) -> String { + "WireSplitGenerator".to_string() + } + + fn dependencies(&self) -> Vec { + vec![self.integer] + } + + fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + let mut integer_value = witness.get_target(self.integer).to_canonical_u64(); + + for &gate in &self.gates { + let sum = Target::wire(gate, BaseSumGate::<2>::WIRE_SUM); + + // If num_limbs >= 64, we don't need to truncate since `integer_value` is + // already limited to 64 bits, and trying to do so would cause + // overflow. Hence the conditional. + let mut truncated_value = integer_value; + if self.num_limbs < 64 { + truncated_value = integer_value & ((1 << self.num_limbs) - 1); + integer_value >>= self.num_limbs; + } else { + integer_value = 0; + }; + + out_buffer.set_target(sum, F::from_canonical_u64(truncated_value)); + } + + debug_assert_eq!( + integer_value, + 0, + "Integer too large to fit in {} many `BaseSumGate`s", + self.gates.len() + ); + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_target(self.integer)?; + dst.write_usize_vec(&self.gates)?; + dst.write_usize(self.num_limbs) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let integer = src.read_target()?; + let gates = src.read_usize_vec()?; + let num_limbs = src.read_usize()?; + Ok(Self { + integer, + gates, + num_limbs, + }) + } +} diff --git a/plonky2/src/gates/arithmetic_base.rs b/plonky2/src/gates/arithmetic_base.rs new file mode 100644 index 000000000..7a72619cc --- /dev/null +++ b/plonky2/src/gates/arithmetic_base.rs @@ -0,0 +1,274 @@ +#[cfg(not(feature = "std"))] +use alloc::{ + format, + string::{String, ToString}, + vec::Vec, +}; + +use crate::field::extension::Extendable; +use crate::field::packed::PackedField; +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, WitnessGeneratorRef}; +use crate::iop::target::Target; +use crate::iop::witness::{PartitionWitness, Witness, WitnessWrite}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::{CircuitConfig, CommonCircuitData}; +use crate::plonk::vars::{ + EvaluationTargets, EvaluationVars, EvaluationVarsBase, EvaluationVarsBaseBatch, + EvaluationVarsBasePacked, +}; +use crate::util::serialization::{Buffer, IoResult, Read, Write}; + +/// A gate which can perform a weighted multiply-add, i.e. `result = c0.x.y + +/// c1.z`. If the config has enough routed wires, it can support several such +/// operations in one gate. +#[derive(Debug, Clone)] +pub struct ArithmeticGate { + /// Number of arithmetic operations performed by an arithmetic gate. + pub num_ops: usize, +} + +impl ArithmeticGate { + pub const fn new_from_config(config: &CircuitConfig) -> Self { + Self { + num_ops: Self::num_ops(config), + } + } + + /// Determine the maximum number of operations that can fit in one gate for + /// the given config. + pub(crate) const fn num_ops(config: &CircuitConfig) -> usize { + let wires_per_op = 4; + config.num_routed_wires / wires_per_op + } + + pub const fn wire_ith_multiplicand_0(i: usize) -> usize { + 4 * i + } + pub const fn wire_ith_multiplicand_1(i: usize) -> usize { + 4 * i + 1 + } + pub const fn wire_ith_addend(i: usize) -> usize { + 4 * i + 2 + } + pub const fn wire_ith_output(i: usize) -> usize { + 4 * i + 3 + } +} + +impl, const D: usize> Gate for ArithmeticGate { + fn id(&self) -> String { + format!("{self:?}") + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.num_ops) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let num_ops = src.read_usize()?; + Ok(Self { num_ops }) + } + + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { + let const_0 = vars.local_constants[0]; + let const_1 = vars.local_constants[1]; + + let mut constraints = Vec::with_capacity(self.num_ops); + for i in 0..self.num_ops { + let multiplicand_0 = vars.local_wires[Self::wire_ith_multiplicand_0(i)]; + let multiplicand_1 = vars.local_wires[Self::wire_ith_multiplicand_1(i)]; + let addend = vars.local_wires[Self::wire_ith_addend(i)]; + let output = vars.local_wires[Self::wire_ith_output(i)]; + let computed_output = multiplicand_0 * multiplicand_1 * const_0 + addend * const_1; + + constraints.push(output - computed_output); + } + + 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 const_0 = vars.local_constants[0]; + let const_1 = vars.local_constants[1]; + + let mut constraints = Vec::with_capacity(self.num_ops); + for i in 0..self.num_ops { + let multiplicand_0 = vars.local_wires[Self::wire_ith_multiplicand_0(i)]; + let multiplicand_1 = vars.local_wires[Self::wire_ith_multiplicand_1(i)]; + let addend = vars.local_wires[Self::wire_ith_addend(i)]; + let output = vars.local_wires[Self::wire_ith_output(i)]; + let computed_output = { + let scaled_mul = + builder.mul_many_extension([const_0, multiplicand_0, multiplicand_1]); + builder.mul_add_extension(const_1, addend, scaled_mul) + }; + + let diff = builder.sub_extension(output, computed_output); + constraints.push(diff); + } + + constraints + } + + fn generators(&self, row: usize, local_constants: &[F]) -> Vec> { + (0..self.num_ops) + .map(|i| { + WitnessGeneratorRef::new( + ArithmeticBaseGenerator { + row, + const_0: local_constants[0], + const_1: local_constants[1], + i, + } + .adapter(), + ) + }) + .collect() + } + + fn num_wires(&self) -> usize { + self.num_ops * 4 + } + + fn num_constants(&self) -> usize { + 2 + } + + fn degree(&self) -> usize { + 3 + } + + fn num_constraints(&self) -> usize { + self.num_ops + } +} + +impl, const D: usize> PackedEvaluableBase for ArithmeticGate { + fn eval_unfiltered_base_packed>( + &self, + vars: EvaluationVarsBasePacked

, + mut yield_constr: StridedConstraintConsumer

, + ) { + let const_0 = vars.local_constants[0]; + let const_1 = vars.local_constants[1]; + + for i in 0..self.num_ops { + let multiplicand_0 = vars.local_wires[Self::wire_ith_multiplicand_0(i)]; + let multiplicand_1 = vars.local_wires[Self::wire_ith_multiplicand_1(i)]; + let addend = vars.local_wires[Self::wire_ith_addend(i)]; + let output = vars.local_wires[Self::wire_ith_output(i)]; + let computed_output = multiplicand_0 * multiplicand_1 * const_0 + addend * const_1; + + yield_constr.one(output - computed_output); + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct ArithmeticBaseGenerator, const D: usize> { + row: usize, + const_0: F, + const_1: F, + i: usize, +} + +impl, const D: usize> SimpleGenerator + for ArithmeticBaseGenerator +{ + fn id(&self) -> String { + "ArithmeticBaseGenerator".to_string() + } + + fn dependencies(&self) -> Vec { + [ + ArithmeticGate::wire_ith_multiplicand_0(self.i), + ArithmeticGate::wire_ith_multiplicand_1(self.i), + ArithmeticGate::wire_ith_addend(self.i), + ] + .iter() + .map(|&i| Target::wire(self.row, i)) + .collect() + } + + fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + let get_wire = |wire: usize| -> F { witness.get_target(Target::wire(self.row, wire)) }; + + let multiplicand_0 = get_wire(ArithmeticGate::wire_ith_multiplicand_0(self.i)); + let multiplicand_1 = get_wire(ArithmeticGate::wire_ith_multiplicand_1(self.i)); + let addend = get_wire(ArithmeticGate::wire_ith_addend(self.i)); + + let output_target = Target::wire(self.row, ArithmeticGate::wire_ith_output(self.i)); + + let computed_output = + multiplicand_0 * multiplicand_1 * self.const_0 + addend * self.const_1; + + out_buffer.set_target(output_target, computed_output) + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.row)?; + dst.write_field(self.const_0)?; + dst.write_field(self.const_1)?; + dst.write_usize(self.i) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let row = src.read_usize()?; + let const_0 = src.read_field()?; + let const_1 = src.read_field()?; + let i = src.read_usize()?; + Ok(Self { + row, + const_0, + const_1, + i, + }) + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + + use crate::field::goldilocks_field::GoldilocksField; + use crate::gates::arithmetic_base::ArithmeticGate; + use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; + use crate::plonk::circuit_data::CircuitConfig; + use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + + #[test] + fn low_degree() { + let gate = ArithmeticGate::new_from_config(&CircuitConfig::standard_recursion_config()); + test_low_degree::(gate); + } + + #[test] + fn eval_fns() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + let gate = ArithmeticGate::new_from_config(&CircuitConfig::standard_recursion_config()); + test_eval_fns::(gate) + } +} diff --git a/plonky2/src/gates/arithmetic_extension.rs b/plonky2/src/gates/arithmetic_extension.rs new file mode 100644 index 000000000..a95ca4e44 --- /dev/null +++ b/plonky2/src/gates/arithmetic_extension.rs @@ -0,0 +1,269 @@ +#[cfg(not(feature = "std"))] +use alloc::{ + format, + string::{String, ToString}, + vec::Vec, +}; +use core::ops::Range; + +use crate::field::extension::{Extendable, FieldExtension}; +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, WitnessGeneratorRef}; +use crate::iop::target::Target; +use crate::iop::witness::{PartitionWitness, Witness, WitnessWrite}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::{CircuitConfig, CommonCircuitData}; +use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; +use crate::util::serialization::{Buffer, IoResult, Read, Write}; + +/// A gate which can perform a weighted multiply-add, i.e. `result = c0.x.y + +/// c1.z`. If the config has enough routed wires, it can support several such +/// operations in one gate. +#[derive(Debug, Clone)] +pub struct ArithmeticExtensionGate { + /// Number of arithmetic operations performed by an arithmetic gate. + pub num_ops: usize, +} + +impl ArithmeticExtensionGate { + pub const fn new_from_config(config: &CircuitConfig) -> Self { + Self { + num_ops: Self::num_ops(config), + } + } + + /// Determine the maximum number of operations that can fit in one gate for + /// the given config. + pub(crate) const fn num_ops(config: &CircuitConfig) -> usize { + let wires_per_op = 4 * D; + config.num_routed_wires / wires_per_op + } + + pub const fn wires_ith_multiplicand_0(i: usize) -> Range { + 4 * D * i..4 * D * i + D + } + pub const fn wires_ith_multiplicand_1(i: usize) -> Range { + 4 * D * i + D..4 * D * i + 2 * D + } + pub const fn wires_ith_addend(i: usize) -> Range { + 4 * D * i + 2 * D..4 * D * i + 3 * D + } + pub const fn wires_ith_output(i: usize) -> Range { + 4 * D * i + 3 * D..4 * D * i + 4 * D + } +} + +impl, const D: usize> Gate for ArithmeticExtensionGate { + fn id(&self) -> String { + format!("{self:?}") + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.num_ops) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let num_ops = src.read_usize()?; + Ok(Self { num_ops }) + } + + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { + let const_0 = vars.local_constants[0]; + let const_1 = vars.local_constants[1]; + + let mut constraints = Vec::with_capacity(self.num_ops * D); + for i in 0..self.num_ops { + let multiplicand_0 = vars.get_local_ext_algebra(Self::wires_ith_multiplicand_0(i)); + let multiplicand_1 = vars.get_local_ext_algebra(Self::wires_ith_multiplicand_1(i)); + let addend = vars.get_local_ext_algebra(Self::wires_ith_addend(i)); + let output = vars.get_local_ext_algebra(Self::wires_ith_output(i)); + let computed_output = + (multiplicand_0 * multiplicand_1).scalar_mul(const_0) + addend.scalar_mul(const_1); + + constraints.extend((output - computed_output).to_basefield_array()); + } + + constraints + } + + fn eval_unfiltered_base_one( + &self, + vars: EvaluationVarsBase, + mut yield_constr: StridedConstraintConsumer, + ) { + let const_0 = vars.local_constants[0]; + let const_1 = vars.local_constants[1]; + + for i in 0..self.num_ops { + let multiplicand_0 = vars.get_local_ext(Self::wires_ith_multiplicand_0(i)); + let multiplicand_1 = vars.get_local_ext(Self::wires_ith_multiplicand_1(i)); + let addend = vars.get_local_ext(Self::wires_ith_addend(i)); + let output = vars.get_local_ext(Self::wires_ith_output(i)); + let computed_output = + (multiplicand_0 * multiplicand_1).scalar_mul(const_0) + addend.scalar_mul(const_1); + + yield_constr.many((output - computed_output).to_basefield_array()); + } + } + + fn eval_unfiltered_circuit( + &self, + builder: &mut CircuitBuilder, + vars: EvaluationTargets, + ) -> Vec> { + let const_0 = vars.local_constants[0]; + let const_1 = vars.local_constants[1]; + + let mut constraints = Vec::with_capacity(self.num_ops * D); + for i in 0..self.num_ops { + let multiplicand_0 = vars.get_local_ext_algebra(Self::wires_ith_multiplicand_0(i)); + let multiplicand_1 = vars.get_local_ext_algebra(Self::wires_ith_multiplicand_1(i)); + let addend = vars.get_local_ext_algebra(Self::wires_ith_addend(i)); + let output = vars.get_local_ext_algebra(Self::wires_ith_output(i)); + let computed_output = { + let mul = builder.mul_ext_algebra(multiplicand_0, multiplicand_1); + let scaled_mul = builder.scalar_mul_ext_algebra(const_0, mul); + builder.scalar_mul_add_ext_algebra(const_1, addend, scaled_mul) + }; + + let diff = builder.sub_ext_algebra(output, computed_output); + constraints.extend(diff.to_ext_target_array()); + } + + constraints + } + + fn generators(&self, row: usize, local_constants: &[F]) -> Vec> { + (0..self.num_ops) + .map(|i| { + WitnessGeneratorRef::new( + ArithmeticExtensionGenerator { + row, + const_0: local_constants[0], + const_1: local_constants[1], + i, + } + .adapter(), + ) + }) + .collect() + } + + fn num_wires(&self) -> usize { + self.num_ops * 4 * D + } + + fn num_constants(&self) -> usize { + 2 + } + + fn degree(&self) -> usize { + 3 + } + + fn num_constraints(&self) -> usize { + self.num_ops * D + } +} + +#[derive(Clone, Debug, Default)] +pub struct ArithmeticExtensionGenerator, const D: usize> { + row: usize, + const_0: F, + const_1: F, + i: usize, +} + +impl, const D: usize> SimpleGenerator + for ArithmeticExtensionGenerator +{ + fn id(&self) -> String { + "ArithmeticExtensionGenerator".to_string() + } + + fn dependencies(&self) -> Vec { + ArithmeticExtensionGate::::wires_ith_multiplicand_0(self.i) + .chain(ArithmeticExtensionGate::::wires_ith_multiplicand_1( + self.i, + )) + .chain(ArithmeticExtensionGate::::wires_ith_addend(self.i)) + .map(|i| Target::wire(self.row, i)) + .collect() + } + + fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + let extract_extension = |range: Range| -> F::Extension { + let t = ExtensionTarget::from_range(self.row, range); + witness.get_extension_target(t) + }; + + let multiplicand_0 = extract_extension( + ArithmeticExtensionGate::::wires_ith_multiplicand_0(self.i), + ); + let multiplicand_1 = extract_extension( + ArithmeticExtensionGate::::wires_ith_multiplicand_1(self.i), + ); + let addend = extract_extension(ArithmeticExtensionGate::::wires_ith_addend(self.i)); + + let output_target = ExtensionTarget::from_range( + self.row, + ArithmeticExtensionGate::::wires_ith_output(self.i), + ); + + let computed_output = (multiplicand_0 * multiplicand_1).scalar_mul(self.const_0) + + addend.scalar_mul(self.const_1); + + out_buffer.set_extension_target(output_target, computed_output) + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.row)?; + dst.write_field(self.const_0)?; + dst.write_field(self.const_1)?; + dst.write_usize(self.i) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let row = src.read_usize()?; + let const_0 = src.read_field()?; + let const_1 = src.read_field()?; + let i = src.read_usize()?; + Ok(Self { + row, + const_0, + const_1, + i, + }) + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + + use crate::field::goldilocks_field::GoldilocksField; + use crate::gates::arithmetic_extension::ArithmeticExtensionGate; + use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; + use crate::plonk::circuit_data::CircuitConfig; + use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + + #[test] + fn low_degree() { + let gate = + ArithmeticExtensionGate::new_from_config(&CircuitConfig::standard_recursion_config()); + test_low_degree::(gate); + } + + #[test] + fn eval_fns() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + let gate = + ArithmeticExtensionGate::new_from_config(&CircuitConfig::standard_recursion_config()); + test_eval_fns::(gate) + } +} diff --git a/plonky2/src/gates/base_sum.rs b/plonky2/src/gates/base_sum.rs new file mode 100644 index 000000000..0f38415d4 --- /dev/null +++ b/plonky2/src/gates/base_sum.rs @@ -0,0 +1,246 @@ +#[cfg(not(feature = "std"))] +use alloc::{format, string::String, vec, vec::Vec}; +use core::ops::Range; + +use crate::field::extension::Extendable; +use crate::field::packed::PackedField; +use crate::field::types::{Field, Field64}; +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, WitnessGeneratorRef}; +use crate::iop::target::Target; +use crate::iop::witness::{PartitionWitness, Witness, WitnessWrite}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::{CircuitConfig, CommonCircuitData}; +use crate::plonk::plonk_common::{reduce_with_powers, reduce_with_powers_ext_circuit}; +use crate::plonk::vars::{ + EvaluationTargets, EvaluationVars, EvaluationVarsBase, EvaluationVarsBaseBatch, + EvaluationVarsBasePacked, +}; +use crate::util::log_floor; +use crate::util::serialization::{Buffer, IoResult, Read, Write}; + +/// A gate which can decompose a number into base B little-endian limbs. +#[derive(Copy, Clone, Debug)] +pub struct BaseSumGate { + pub num_limbs: usize, +} + +impl BaseSumGate { + pub const fn new(num_limbs: usize) -> Self { + Self { num_limbs } + } + + pub fn new_from_config(config: &CircuitConfig) -> Self { + let num_limbs = + log_floor(F::ORDER - 1, B as u64).min(config.num_routed_wires - Self::START_LIMBS); + Self::new(num_limbs) + } + + pub const WIRE_SUM: usize = 0; + pub const START_LIMBS: usize = 1; + + /// Returns the index of the `i`th limb wire. + pub const fn limbs(&self) -> Range { + Self::START_LIMBS..Self::START_LIMBS + self.num_limbs + } +} + +impl, const D: usize, const B: usize> Gate for BaseSumGate { + fn id(&self) -> String { + format!("{self:?} + Base: {B}") + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.num_limbs) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let num_limbs = src.read_usize()?; + Ok(Self { num_limbs }) + } + + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { + let sum = vars.local_wires[Self::WIRE_SUM]; + let limbs = vars.local_wires[self.limbs()].to_vec(); + let computed_sum = reduce_with_powers(&limbs, F::Extension::from_canonical_usize(B)); + let mut constraints = vec![computed_sum - sum]; + for limb in limbs { + constraints.push( + (0..B) + .map(|i| limb - F::Extension::from_canonical_usize(i)) + .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 base = builder.constant(F::from_canonical_usize(B)); + let sum = vars.local_wires[Self::WIRE_SUM]; + let limbs = vars.local_wires[self.limbs()].to_vec(); + let computed_sum = reduce_with_powers_ext_circuit(builder, &limbs, base); + let mut constraints = vec![builder.sub_extension(computed_sum, sum)]; + for limb in limbs { + constraints.push({ + let mut acc = builder.one_extension(); + (0..B).for_each(|i| { + // We update our accumulator as: + // acc' = acc (x - i) + // = acc x + (-i) acc + // Since -i is constant, we can do this in one arithmetic_extension call. + let neg_i = -F::from_canonical_usize(i); + acc = builder.arithmetic_extension(F::ONE, neg_i, acc, limb, acc) + }); + acc + }); + } + constraints + } + + fn generators(&self, row: usize, _local_constants: &[F]) -> Vec> { + let gen = BaseSplitGenerator:: { + row, + num_limbs: self.num_limbs, + }; + vec![WitnessGeneratorRef::new(gen.adapter())] + } + + // 1 for the sum then `num_limbs` for the limbs. + fn num_wires(&self) -> usize { + 1 + self.num_limbs + } + + fn num_constants(&self) -> usize { + 0 + } + + // Bounded by the range-check (x-0)*(x-1)*...*(x-B+1). + fn degree(&self) -> usize { + B + } + + // 1 for checking the sum then `num_limbs` for range-checking the limbs. + fn num_constraints(&self) -> usize { + 1 + self.num_limbs + } +} + +impl, const D: usize, const B: usize> PackedEvaluableBase + for BaseSumGate +{ + fn eval_unfiltered_base_packed>( + &self, + vars: EvaluationVarsBasePacked

, + mut yield_constr: StridedConstraintConsumer

, + ) { + let sum = vars.local_wires[Self::WIRE_SUM]; + let limbs = vars.local_wires.view(self.limbs()); + let computed_sum = reduce_with_powers(limbs, F::from_canonical_usize(B)); + + yield_constr.one(computed_sum - sum); + + let constraints_iter = limbs.iter().map(|&limb| { + (0..B) + .map(|i| limb - F::from_canonical_usize(i)) + .product::

() + }); + yield_constr.many(constraints_iter); + } +} + +#[derive(Debug, Default)] +pub struct BaseSplitGenerator { + row: usize, + num_limbs: usize, +} + +impl, const B: usize, const D: usize> SimpleGenerator + for BaseSplitGenerator +{ + fn id(&self) -> String { + format!("BaseSplitGenerator + Base: {B}") + } + + fn dependencies(&self) -> Vec { + vec![Target::wire(self.row, BaseSumGate::::WIRE_SUM)] + } + + fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + let sum_value = witness + .get_target(Target::wire(self.row, BaseSumGate::::WIRE_SUM)) + .to_canonical_u64() as usize; + debug_assert_eq!( + (0..self.num_limbs).fold(sum_value, |acc, _| acc / B), + 0, + "Integer too large to fit in given number of limbs" + ); + + let limbs = (BaseSumGate::::START_LIMBS..BaseSumGate::::START_LIMBS + self.num_limbs) + .map(|i| Target::wire(self.row, i)); + let limbs_value = (0..self.num_limbs) + .scan(sum_value, |acc, _| { + let tmp = *acc % B; + *acc /= B; + Some(F::from_canonical_usize(tmp)) + }) + .collect::>(); + + for (b, b_value) in limbs.zip(limbs_value) { + out_buffer.set_target(b, b_value); + } + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.row)?; + dst.write_usize(self.num_limbs) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let row = src.read_usize()?; + let num_limbs = src.read_usize()?; + Ok(Self { row, num_limbs }) + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + + use crate::field::goldilocks_field::GoldilocksField; + use crate::gates::base_sum::BaseSumGate; + use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; + use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + + #[test] + fn low_degree() { + test_low_degree::(BaseSumGate::<6>::new(11)) + } + + #[test] + fn eval_fns() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + test_eval_fns::(BaseSumGate::<6>::new(11)) + } +} diff --git a/plonky2/src/gates/constant.rs b/plonky2/src/gates/constant.rs new file mode 100644 index 000000000..cc62de7fe --- /dev/null +++ b/plonky2/src/gates/constant.rs @@ -0,0 +1,158 @@ +#[cfg(not(feature = "std"))] +use alloc::{format, string::String, vec, vec::Vec}; + +use serde::{Deserialize, Serialize}; + +use crate::field::extension::Extendable; +use crate::field::packed::PackedField; +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::WitnessGeneratorRef; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::CommonCircuitData; +use crate::plonk::vars::{ + EvaluationTargets, EvaluationVars, EvaluationVarsBase, EvaluationVarsBaseBatch, + EvaluationVarsBasePacked, +}; +use crate::util::serialization::{Buffer, IoResult, Read, Write}; + +/// A gate which takes a single constant parameter and outputs that value. +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub struct ConstantGate { + pub(crate) num_consts: usize, +} + +impl ConstantGate { + pub const fn new(num_consts: usize) -> Self { + Self { num_consts } + } + + pub fn const_input(&self, i: usize) -> usize { + debug_assert!(i < self.num_consts); + i + } + + pub fn wire_output(&self, i: usize) -> usize { + debug_assert!(i < self.num_consts); + i + } +} + +impl, const D: usize> Gate for ConstantGate { + fn id(&self) -> String { + format!("{self:?}") + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.num_consts) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let num_consts = src.read_usize()?; + Ok(Self { num_consts }) + } + + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { + (0..self.num_consts) + .map(|i| { + vars.local_constants[self.const_input(i)] - vars.local_wires[self.wire_output(i)] + }) + .collect() + } + + 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> { + (0..self.num_consts) + .map(|i| { + builder.sub_extension( + vars.local_constants[self.const_input(i)], + vars.local_wires[self.wire_output(i)], + ) + }) + .collect() + } + + fn generators(&self, _row: usize, _local_constants: &[F]) -> Vec> { + vec![] + } + + fn num_wires(&self) -> usize { + self.num_consts + } + + fn num_constants(&self) -> usize { + self.num_consts + } + + fn degree(&self) -> usize { + 1 + } + + fn num_constraints(&self) -> usize { + self.num_consts + } + + fn extra_constant_wires(&self) -> Vec<(usize, usize)> { + (0..self.num_consts) + .map(|i| (self.const_input(i), self.wire_output(i))) + .collect() + } +} + +impl, const D: usize> PackedEvaluableBase for ConstantGate { + fn eval_unfiltered_base_packed>( + &self, + vars: EvaluationVarsBasePacked

, + mut yield_constr: StridedConstraintConsumer

, + ) { + yield_constr.many((0..self.num_consts).map(|i| { + vars.local_constants[self.const_input(i)] - vars.local_wires[self.wire_output(i)] + })); + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + + use crate::field::goldilocks_field::GoldilocksField; + use crate::gates::constant::ConstantGate; + use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; + use crate::plonk::circuit_data::CircuitConfig; + use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + + #[test] + fn low_degree() { + let num_consts = CircuitConfig::standard_recursion_config().num_constants; + let gate = ConstantGate { num_consts }; + test_low_degree::(gate) + } + + #[test] + fn eval_fns() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + let num_consts = CircuitConfig::standard_recursion_config().num_constants; + let gate = ConstantGate { num_consts }; + test_eval_fns::(gate) + } +} diff --git a/plonky2/src/gates/coset_interpolation.rs b/plonky2/src/gates/coset_interpolation.rs new file mode 100644 index 000000000..a9e22f47f --- /dev/null +++ b/plonky2/src/gates/coset_interpolation.rs @@ -0,0 +1,881 @@ +#[cfg(not(feature = "std"))] +use alloc::{ + format, + string::{String, ToString}, + vec, + vec::Vec, +}; +use core::marker::PhantomData; +use core::ops::Range; + +use crate::field::extension::algebra::ExtensionAlgebra; +use crate::field::extension::{Extendable, FieldExtension, OEF}; +use crate::field::interpolation::barycentric_weights; +use crate::field::types::Field; +use crate::gates::gate::Gate; +use crate::gates::util::StridedConstraintConsumer; +use crate::hash::hash_types::RichField; +use crate::iop::ext_target::{ExtensionAlgebraTarget, ExtensionTarget}; +use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGeneratorRef}; +use crate::iop::target::Target; +use crate::iop::wire::Wire; +use crate::iop::witness::{PartitionWitness, Witness, WitnessWrite}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::CommonCircuitData; +use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; +use crate::util::serialization::{Buffer, IoResult, Read, Write}; + +/// One of the instantiations of `InterpolationGate`: allows constraints of +/// variable degree, up to `1<, const D: usize> { + pub subgroup_bits: usize, + pub degree: usize, + pub barycentric_weights: Vec, + _phantom: PhantomData, +} + +impl, const D: usize> CosetInterpolationGate { + pub fn new(subgroup_bits: usize) -> Self { + Self::with_max_degree(subgroup_bits, 1 << subgroup_bits) + } + + pub(crate) fn with_max_degree(subgroup_bits: usize, max_degree: usize) -> Self { + assert!(max_degree > 1, "need at least quadratic constraints"); + + let n_points = 1 << subgroup_bits; + + // Number of intermediate values required to compute interpolation with degree + // bound + let n_intermediates = (n_points - 2) / (max_degree - 1); + + // Find minimum degree such that (n_points - 2) / (degree - 1) < n_intermediates + // + 1 Minimizing the degree this way allows the gate to be in a larger + // selector group + let degree = (n_points - 2) / (n_intermediates + 1) + 2; + + let barycentric_weights = barycentric_weights( + &F::two_adic_subgroup(subgroup_bits) + .into_iter() + .map(|x| (x, F::ZERO)) + .collect::>(), + ); + + Self { + subgroup_bits, + degree, + barycentric_weights, + _phantom: PhantomData, + } + } + + const fn num_points(&self) -> usize { + 1 << self.subgroup_bits + } + + /// Wire index of the coset shift. + pub(crate) const fn wire_shift(&self) -> usize { + 0 + } + + const fn start_values(&self) -> usize { + 1 + } + + /// Wire indices of the `i`th interpolant value. + pub(crate) fn wires_value(&self, i: usize) -> Range { + debug_assert!(i < self.num_points()); + let start = self.start_values() + i * D; + start..start + D + } + + const fn start_evaluation_point(&self) -> usize { + self.start_values() + self.num_points() * D + } + + /// Wire indices of the point to evaluate the interpolant at. + pub(crate) const fn wires_evaluation_point(&self) -> Range { + let start = self.start_evaluation_point(); + start..start + D + } + + const fn start_evaluation_value(&self) -> usize { + self.start_evaluation_point() + D + } + + /// Wire indices of the interpolated value. + pub(crate) const fn wires_evaluation_value(&self) -> Range { + let start = self.start_evaluation_value(); + start..start + D + } + + const fn start_intermediates(&self) -> usize { + self.start_evaluation_value() + D + } + + pub const fn num_routed_wires(&self) -> usize { + self.start_intermediates() + } + + fn num_intermediates(&self) -> usize { + (self.num_points() - 2) / (self.degree() - 1) + } + + /// The wires corresponding to the i'th intermediate evaluation. + fn wires_intermediate_eval(&self, i: usize) -> Range { + debug_assert!(i < self.num_intermediates()); + let start = self.start_intermediates() + D * i; + start..start + D + } + + /// The wires corresponding to the i'th intermediate product. + fn wires_intermediate_prod(&self, i: usize) -> Range { + debug_assert!(i < self.num_intermediates()); + let start = self.start_intermediates() + D * (self.num_intermediates() + i); + start..start + D + } + + /// End of wire indices, exclusive. + fn end(&self) -> usize { + self.start_intermediates() + D * (2 * self.num_intermediates() + 1) + } + + /// Wire indices of the shifted point to evaluate the interpolant at. + fn wires_shifted_evaluation_point(&self) -> Range { + let start = self.start_intermediates() + D * 2 * self.num_intermediates(); + start..start + D + } +} + +impl, const D: usize> Gate for CosetInterpolationGate { + fn id(&self) -> String { + format!("{self:?}") + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.subgroup_bits)?; + dst.write_usize(self.degree)?; + dst.write_usize(self.barycentric_weights.len())?; + dst.write_field_vec(&self.barycentric_weights) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let subgroup_bits = src.read_usize()?; + let degree = src.read_usize()?; + let length = src.read_usize()?; + let barycentric_weights: Vec = src.read_field_vec(length)?; + Ok(Self { + subgroup_bits, + degree, + barycentric_weights, + _phantom: PhantomData, + }) + } + + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { + let mut constraints = Vec::with_capacity(self.num_constraints()); + + let shift = vars.local_wires[self.wire_shift()]; + let evaluation_point = vars.get_local_ext_algebra(self.wires_evaluation_point()); + let shifted_evaluation_point = + vars.get_local_ext_algebra(self.wires_shifted_evaluation_point()); + constraints.extend( + (evaluation_point - shifted_evaluation_point.scalar_mul(shift)).to_basefield_array(), + ); + + let domain = F::two_adic_subgroup(self.subgroup_bits); + let values = (0..self.num_points()) + .map(|i| vars.get_local_ext_algebra(self.wires_value(i))) + .collect::>(); + let weights = &self.barycentric_weights; + + let (mut computed_eval, mut computed_prod) = partial_interpolate_ext_algebra( + &domain[..self.degree()], + &values[..self.degree()], + &weights[..self.degree()], + shifted_evaluation_point, + ExtensionAlgebra::ZERO, + ExtensionAlgebra::one(), + ); + + for i in 0..self.num_intermediates() { + let intermediate_eval = vars.get_local_ext_algebra(self.wires_intermediate_eval(i)); + let intermediate_prod = vars.get_local_ext_algebra(self.wires_intermediate_prod(i)); + constraints.extend((intermediate_eval - computed_eval).to_basefield_array()); + constraints.extend((intermediate_prod - computed_prod).to_basefield_array()); + + let start_index = 1 + (self.degree() - 1) * (i + 1); + let end_index = (start_index + self.degree() - 1).min(self.num_points()); + (computed_eval, computed_prod) = partial_interpolate_ext_algebra( + &domain[start_index..end_index], + &values[start_index..end_index], + &weights[start_index..end_index], + shifted_evaluation_point, + intermediate_eval, + intermediate_prod, + ); + } + + let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); + constraints.extend((evaluation_value - computed_eval).to_basefield_array()); + + constraints + } + + fn eval_unfiltered_base_one( + &self, + vars: EvaluationVarsBase, + mut yield_constr: StridedConstraintConsumer, + ) { + let shift = vars.local_wires[self.wire_shift()]; + let evaluation_point = vars.get_local_ext(self.wires_evaluation_point()); + let shifted_evaluation_point = vars.get_local_ext(self.wires_shifted_evaluation_point()); + yield_constr.many( + (evaluation_point - shifted_evaluation_point.scalar_mul(shift)).to_basefield_array(), + ); + + let domain = F::two_adic_subgroup(self.subgroup_bits); + let values = (0..self.num_points()) + .map(|i| vars.get_local_ext(self.wires_value(i))) + .collect::>(); + let weights = &self.barycentric_weights; + + let (mut computed_eval, mut computed_prod) = partial_interpolate( + &domain[..self.degree()], + &values[..self.degree()], + &weights[..self.degree()], + shifted_evaluation_point, + F::Extension::ZERO, + F::Extension::ONE, + ); + + for i in 0..self.num_intermediates() { + let intermediate_eval = vars.get_local_ext(self.wires_intermediate_eval(i)); + let intermediate_prod = vars.get_local_ext(self.wires_intermediate_prod(i)); + yield_constr.many((intermediate_eval - computed_eval).to_basefield_array()); + yield_constr.many((intermediate_prod - computed_prod).to_basefield_array()); + + let start_index = 1 + (self.degree() - 1) * (i + 1); + let end_index = (start_index + self.degree() - 1).min(self.num_points()); + (computed_eval, computed_prod) = partial_interpolate( + &domain[start_index..end_index], + &values[start_index..end_index], + &weights[start_index..end_index], + shifted_evaluation_point, + intermediate_eval, + intermediate_prod, + ); + } + + let evaluation_value = vars.get_local_ext(self.wires_evaluation_value()); + yield_constr.many((evaluation_value - computed_eval).to_basefield_array()); + } + + fn eval_unfiltered_circuit( + &self, + builder: &mut CircuitBuilder, + vars: EvaluationTargets, + ) -> Vec> { + let mut constraints = Vec::with_capacity(self.num_constraints()); + + let shift = vars.local_wires[self.wire_shift()]; + let evaluation_point = vars.get_local_ext_algebra(self.wires_evaluation_point()); + let shifted_evaluation_point = + vars.get_local_ext_algebra(self.wires_shifted_evaluation_point()); + + let neg_one = builder.neg_one(); + let neg_shift = builder.scalar_mul_ext(neg_one, shift); + constraints.extend( + builder + .scalar_mul_add_ext_algebra(neg_shift, shifted_evaluation_point, evaluation_point) + .to_ext_target_array(), + ); + + let domain = F::two_adic_subgroup(self.subgroup_bits); + let values = (0..self.num_points()) + .map(|i| vars.get_local_ext_algebra(self.wires_value(i))) + .collect::>(); + let weights = &self.barycentric_weights; + + let initial_eval = builder.zero_ext_algebra(); + let initial_prod = builder.constant_ext_algebra(F::Extension::ONE.into()); + let (mut computed_eval, mut computed_prod) = partial_interpolate_ext_algebra_target( + builder, + &domain[..self.degree()], + &values[..self.degree()], + &weights[..self.degree()], + shifted_evaluation_point, + initial_eval, + initial_prod, + ); + + for i in 0..self.num_intermediates() { + let intermediate_eval = vars.get_local_ext_algebra(self.wires_intermediate_eval(i)); + let intermediate_prod = vars.get_local_ext_algebra(self.wires_intermediate_prod(i)); + constraints.extend( + builder + .sub_ext_algebra(intermediate_eval, computed_eval) + .to_ext_target_array(), + ); + constraints.extend( + builder + .sub_ext_algebra(intermediate_prod, computed_prod) + .to_ext_target_array(), + ); + + let start_index = 1 + (self.degree() - 1) * (i + 1); + let end_index = (start_index + self.degree() - 1).min(self.num_points()); + (computed_eval, computed_prod) = partial_interpolate_ext_algebra_target( + builder, + &domain[start_index..end_index], + &values[start_index..end_index], + &weights[start_index..end_index], + shifted_evaluation_point, + intermediate_eval, + intermediate_prod, + ); + } + + let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); + constraints.extend( + builder + .sub_ext_algebra(evaluation_value, computed_eval) + .to_ext_target_array(), + ); + + constraints + } + + fn generators(&self, row: usize, _local_constants: &[F]) -> Vec> { + let gen = InterpolationGenerator::::new(row, self.clone()); + vec![WitnessGeneratorRef::new(gen.adapter())] + } + + fn num_wires(&self) -> usize { + self.end() + } + + fn num_constants(&self) -> usize { + 0 + } + + fn degree(&self) -> usize { + self.degree + } + + fn num_constraints(&self) -> usize { + // D constraints to check for consistency of the shifted evaluation point, plus + // D constraints for the evaluation value. + D + D + 2 * D * self.num_intermediates() + } +} + +#[derive(Debug, Default)] +pub struct InterpolationGenerator, const D: usize> { + row: usize, + gate: CosetInterpolationGate, + interpolation_domain: Vec, + _phantom: PhantomData, +} + +impl, const D: usize> InterpolationGenerator { + fn new(row: usize, gate: CosetInterpolationGate) -> Self { + let interpolation_domain = F::two_adic_subgroup(gate.subgroup_bits); + InterpolationGenerator { + row, + gate, + interpolation_domain, + _phantom: PhantomData, + } + } +} + +impl, const D: usize> SimpleGenerator + for InterpolationGenerator +{ + fn id(&self) -> String { + "InterpolationGenerator".to_string() + } + + 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) + }; + + let evaluation_point = get_local_ext(self.gate.wires_evaluation_point()); + let shift = get_local_wire(self.gate.wire_shift()); + let shifted_evaluation_point = evaluation_point.scalar_mul(shift.inverse()); + let degree = self.gate.degree(); + + out_buffer.set_ext_wires( + self.gate.wires_shifted_evaluation_point().map(local_wire), + shifted_evaluation_point, + ); + + let domain = &self.interpolation_domain; + let values = (0..self.gate.num_points()) + .map(|i| get_local_ext(self.gate.wires_value(i))) + .collect::>(); + let weights = &self.gate.barycentric_weights; + + let (mut computed_eval, mut computed_prod) = partial_interpolate( + &domain[..degree], + &values[..degree], + &weights[..degree], + shifted_evaluation_point, + F::Extension::ZERO, + F::Extension::ONE, + ); + + for i in 0..self.gate.num_intermediates() { + let intermediate_eval_wires = self.gate.wires_intermediate_eval(i).map(local_wire); + let intermediate_prod_wires = self.gate.wires_intermediate_prod(i).map(local_wire); + out_buffer.set_ext_wires(intermediate_eval_wires, computed_eval); + out_buffer.set_ext_wires(intermediate_prod_wires, computed_prod); + + let start_index = 1 + (degree - 1) * (i + 1); + let end_index = (start_index + degree - 1).min(self.gate.num_points()); + (computed_eval, computed_prod) = partial_interpolate( + &domain[start_index..end_index], + &values[start_index..end_index], + &weights[start_index..end_index], + shifted_evaluation_point, + computed_eval, + computed_prod, + ); + } + + let evaluation_value_wires = self.gate.wires_evaluation_value().map(local_wire); + out_buffer.set_ext_wires(evaluation_value_wires, computed_eval); + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.row)?; + self.gate.serialize(dst, _common_data) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let row = src.read_usize()?; + let gate = CosetInterpolationGate::deserialize(src, _common_data)?; + Ok(Self::new(row, gate)) + } +} + +/// Interpolate the polynomial defined by its values on an arbitrary domain at +/// the given point `x`. +/// +/// The domain lies in a base field while the values and evaluation point may be +/// from an extension field. The Barycentric weights are precomputed and taken +/// as arguments. +pub fn interpolate_over_base_domain, const D: usize>( + domain: &[F], + values: &[F::Extension], + barycentric_weights: &[F], + x: F::Extension, +) -> F::Extension { + let (result, _) = partial_interpolate( + domain, + values, + barycentric_weights, + x, + F::Extension::ZERO, + F::Extension::ONE, + ); + result +} + +/// Perform a partial interpolation of the polynomial defined by its values on +/// an arbitrary domain. +/// +/// The Barycentric algorithm to interpolate a polynomial at a given point `x` +/// is a linear pass over the sequence of domain points, values, and Barycentric +/// weights which maintains two accumulated values, a partial evaluation and a +/// partial product. This partially updates the accumulated values, so that +/// starting with an initial evaluation of 0 and a partial evaluation +/// of 1 and running over the whole domain is a full interpolation. +fn partial_interpolate, const D: usize>( + domain: &[F], + values: &[F::Extension], + barycentric_weights: &[F], + x: F::Extension, + initial_eval: F::Extension, + initial_partial_prod: F::Extension, +) -> (F::Extension, F::Extension) { + let n = domain.len(); + assert_ne!(n, 0); + assert_eq!(n, values.len()); + assert_eq!(n, barycentric_weights.len()); + + let weighted_values = values + .iter() + .zip(barycentric_weights.iter()) + .map(|(&value, &weight)| value.scalar_mul(weight)); + + weighted_values.zip(domain.iter()).fold( + (initial_eval, initial_partial_prod), + |(eval, terms_partial_prod), (val, &x_i)| { + let term = x - x_i.into(); + let next_eval = eval * term + val * terms_partial_prod; + let next_terms_partial_prod = terms_partial_prod * term; + (next_eval, next_terms_partial_prod) + }, + ) +} + +fn partial_interpolate_ext_algebra, const D: usize>( + domain: &[F::BaseField], + values: &[ExtensionAlgebra], + barycentric_weights: &[F::BaseField], + x: ExtensionAlgebra, + initial_eval: ExtensionAlgebra, + initial_partial_prod: ExtensionAlgebra, +) -> (ExtensionAlgebra, ExtensionAlgebra) { + let n = domain.len(); + assert_ne!(n, 0); + assert_eq!(n, values.len()); + assert_eq!(n, barycentric_weights.len()); + + let weighted_values = values + .iter() + .zip(barycentric_weights.iter()) + .map(|(&value, &weight)| value.scalar_mul(F::from_basefield(weight))); + + weighted_values.zip(domain.iter()).fold( + (initial_eval, initial_partial_prod), + |(eval, terms_partial_prod), (val, &x_i)| { + let term = x - F::from_basefield(x_i).into(); + let next_eval = eval * term + val * terms_partial_prod; + let next_terms_partial_prod = terms_partial_prod * term; + (next_eval, next_terms_partial_prod) + }, + ) +} + +fn partial_interpolate_ext_algebra_target, const D: usize>( + builder: &mut CircuitBuilder, + domain: &[F], + values: &[ExtensionAlgebraTarget], + barycentric_weights: &[F], + point: ExtensionAlgebraTarget, + initial_eval: ExtensionAlgebraTarget, + initial_partial_prod: ExtensionAlgebraTarget, +) -> (ExtensionAlgebraTarget, ExtensionAlgebraTarget) { + let n = values.len(); + debug_assert!(n != 0); + debug_assert!(domain.len() == n); + debug_assert!(barycentric_weights.len() == n); + + values + .iter() + .cloned() + .zip(domain.iter().cloned()) + .zip(barycentric_weights.iter().cloned()) + .fold( + (initial_eval, initial_partial_prod), + |(eval, partial_prod), ((val, x), weight)| { + let x_target = builder.constant_ext_algebra(F::Extension::from(x).into()); + let weight_target = builder.constant_extension(F::Extension::from(weight)); + let term = builder.sub_ext_algebra(point, x_target); + let weighted_val = builder.scalar_mul_ext_algebra(weight_target, val); + let new_eval = builder.mul_ext_algebra(eval, term); + let new_eval = builder.mul_add_ext_algebra(weighted_val, partial_prod, new_eval); + let new_partial_prod = builder.mul_ext_algebra(partial_prod, term); + (new_eval, new_partial_prod) + }, + ) +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + use plonky2_field::polynomial::PolynomialValues; + use plonky2_util::log2_strict; + + use super::*; + use crate::field::goldilocks_field::GoldilocksField; + use crate::field::types::{Field, Sample}; + use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; + use crate::hash::hash_types::HashOut; + use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + + #[test] + fn test_degree_and_wires_minimized() { + let gate = >::with_max_degree(3, 2); + assert_eq!(gate.num_intermediates(), 6); + assert_eq!(gate.degree(), 2); + + let gate = >::with_max_degree(3, 3); + assert_eq!(gate.num_intermediates(), 3); + assert_eq!(gate.degree(), 3); + + let gate = >::with_max_degree(3, 4); + assert_eq!(gate.num_intermediates(), 2); + assert_eq!(gate.degree(), 4); + + let gate = >::with_max_degree(3, 5); + assert_eq!(gate.num_intermediates(), 1); + assert_eq!(gate.degree(), 5); + + let gate = >::with_max_degree(3, 6); + assert_eq!(gate.num_intermediates(), 1); + assert_eq!(gate.degree(), 5); + + let gate = >::with_max_degree(3, 7); + assert_eq!(gate.num_intermediates(), 1); + assert_eq!(gate.degree(), 5); + + let gate = >::with_max_degree(4, 3); + assert_eq!(gate.num_intermediates(), 7); + assert_eq!(gate.degree(), 3); + + let gate = >::with_max_degree(4, 6); + assert_eq!(gate.num_intermediates(), 2); + assert_eq!(gate.degree(), 6); + + let gate = >::with_max_degree(4, 8); + assert_eq!(gate.num_intermediates(), 2); + assert_eq!(gate.degree(), 6); + + let gate = >::with_max_degree(4, 9); + assert_eq!(gate.num_intermediates(), 1); + assert_eq!(gate.degree(), 9); + } + + #[test] + fn wire_indices_degree2() { + let gate = CosetInterpolationGate:: { + subgroup_bits: 2, + degree: 2, + barycentric_weights: barycentric_weights( + &GoldilocksField::two_adic_subgroup(2) + .into_iter() + .map(|x| (x, GoldilocksField::ZERO)) + .collect::>(), + ), + _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_value(2), 9..13); + assert_eq!(gate.wires_value(3), 13..17); + assert_eq!(gate.wires_evaluation_point(), 17..21); + assert_eq!(gate.wires_evaluation_value(), 21..25); + assert_eq!(gate.wires_intermediate_eval(0), 25..29); + assert_eq!(gate.wires_intermediate_eval(1), 29..33); + assert_eq!(gate.wires_intermediate_prod(0), 33..37); + assert_eq!(gate.wires_intermediate_prod(1), 37..41); + assert_eq!(gate.wires_shifted_evaluation_point(), 41..45); + assert_eq!(gate.num_wires(), 45); + } + + #[test] + fn wire_indices_degree_3() { + let gate = CosetInterpolationGate:: { + subgroup_bits: 2, + degree: 3, + barycentric_weights: barycentric_weights( + &GoldilocksField::two_adic_subgroup(2) + .into_iter() + .map(|x| (x, GoldilocksField::ZERO)) + .collect::>(), + ), + _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_value(2), 9..13); + assert_eq!(gate.wires_value(3), 13..17); + assert_eq!(gate.wires_evaluation_point(), 17..21); + assert_eq!(gate.wires_evaluation_value(), 21..25); + assert_eq!(gate.wires_intermediate_eval(0), 25..29); + assert_eq!(gate.wires_intermediate_prod(0), 29..33); + assert_eq!(gate.wires_shifted_evaluation_point(), 33..37); + assert_eq!(gate.num_wires(), 37); + } + + #[test] + fn wire_indices_degree_n() { + let gate = CosetInterpolationGate:: { + subgroup_bits: 2, + degree: 4, + barycentric_weights: barycentric_weights( + &GoldilocksField::two_adic_subgroup(2) + .into_iter() + .map(|x| (x, GoldilocksField::ZERO)) + .collect::>(), + ), + _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_value(2), 9..13); + assert_eq!(gate.wires_value(3), 13..17); + assert_eq!(gate.wires_evaluation_point(), 17..21); + assert_eq!(gate.wires_evaluation_value(), 21..25); + assert_eq!(gate.wires_shifted_evaluation_point(), 25..29); + assert_eq!(gate.num_wires(), 29); + } + + #[test] + fn low_degree() { + test_low_degree::(CosetInterpolationGate::new(2)); + } + + #[test] + fn eval_fns() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + for degree in 2..=4 { + test_eval_fns::(CosetInterpolationGate::with_max_degree(2, degree))?; + } + Ok(()) + } + + #[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(shift: F, values: PolynomialValues, eval_point: FF) -> Vec { + let domain = F::two_adic_subgroup(log2_strict(values.len())); + let shifted_eval_point = + >::scalar_mul(&eval_point, shift.inverse()); + let weights = + barycentric_weights(&domain.iter().map(|&x| (x, F::ZERO)).collect::>()); + let (intermediate_eval, intermediate_prod) = partial_interpolate::<_, D>( + &domain[..3], + &values.values[..3], + &weights[..3], + shifted_eval_point, + FF::ZERO, + FF::ONE, + ); + let eval = interpolate_over_base_domain::<_, D>( + &domain, + &values.values, + &weights, + shifted_eval_point, + ); + let mut v = vec![shift]; + for val in values.values.iter() { + v.extend(val.0); + } + v.extend(eval_point.0); + v.extend(eval.0); + v.extend(intermediate_eval.0); + v.extend(intermediate_prod.0); + v.extend(shifted_eval_point.0); + v.iter().map(|&x| x.into()).collect() + } + + // Get a working row for InterpolationGate. + let shift = F::rand(); + let values = PolynomialValues::new(core::iter::repeat_with(FF::rand).take(4).collect()); + let eval_point = FF::rand(); + let gate = CosetInterpolationGate::::with_max_degree(2, 3); + let vars = EvaluationVars { + local_constants: &[], + local_wires: &get_wires(shift, values, eval_point), + public_inputs_hash: &HashOut::rand(), + }; + + assert!( + gate.eval_unfiltered(vars).iter().all(|x| x.is_zero()), + "Gate constraints are not satisfied." + ); + } + + #[test] + fn test_num_wires_constraints() { + let gate = >::with_max_degree(4, 8); + assert_eq!(gate.num_wires(), 47); + assert_eq!(gate.num_constraints(), 12); + + let gate = >::with_max_degree(3, 8); + assert_eq!(gate.num_wires(), 23); + assert_eq!(gate.num_constraints(), 4); + + let gate = >::with_max_degree(4, 16); + assert_eq!(gate.num_wires(), 39); + assert_eq!(gate.num_constraints(), 4); + } +} diff --git a/plonky2/src/gates/exponentiation.rs b/plonky2/src/gates/exponentiation.rs new file mode 100644 index 000000000..5905f9264 --- /dev/null +++ b/plonky2/src/gates/exponentiation.rs @@ -0,0 +1,429 @@ +#[cfg(not(feature = "std"))] +use alloc::{ + format, + string::{String, ToString}, + vec, + vec::Vec, +}; +use core::marker::PhantomData; + +use crate::field::extension::Extendable; +use crate::field::ops::Square; +use crate::field::packed::PackedField; +use crate::field::types::Field; +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, WitnessGeneratorRef}; +use crate::iop::target::Target; +use crate::iop::wire::Wire; +use crate::iop::witness::{PartitionWitness, Witness, WitnessWrite}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::{CircuitConfig, CommonCircuitData}; +use crate::plonk::vars::{ + EvaluationTargets, EvaluationVars, EvaluationVarsBase, EvaluationVarsBaseBatch, + EvaluationVarsBasePacked, +}; +use crate::util::serialization::{Buffer, IoResult, Read, Write}; + +/// A gate for raising a value to a power. +#[derive(Clone, Debug, Default)] +pub struct ExponentiationGate, const D: usize> { + pub num_power_bits: usize, + pub _phantom: PhantomData, +} + +impl, const D: usize> ExponentiationGate { + pub const fn new(num_power_bits: usize) -> Self { + Self { + num_power_bits, + _phantom: PhantomData, + } + } + + pub fn new_from_config(config: &CircuitConfig) -> Self { + let num_power_bits = Self::max_power_bits(config.num_wires, config.num_routed_wires); + Self::new(num_power_bits) + } + + fn max_power_bits(num_wires: usize, num_routed_wires: usize) -> usize { + // 2 wires are reserved for the base and output. + let max_for_routed_wires = num_routed_wires - 2; + let max_for_wires = (num_wires - 2) / 2; + max_for_routed_wires.min(max_for_wires) + } + + pub const fn wire_base(&self) -> usize { + 0 + } + + /// The `i`th bit of the exponent, in little-endian order. + pub fn wire_power_bit(&self, i: usize) -> usize { + debug_assert!(i < self.num_power_bits); + 1 + i + } + + pub const fn wire_output(&self) -> usize { + 1 + self.num_power_bits + } + + pub fn wire_intermediate_value(&self, i: usize) -> usize { + debug_assert!(i < self.num_power_bits); + 2 + self.num_power_bits + i + } +} + +impl, const D: usize> Gate for ExponentiationGate { + fn id(&self) -> String { + format!("{self:?}") + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.num_power_bits) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let num_power_bits = src.read_usize()?; + Ok(Self::new(num_power_bits)) + } + + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { + let base = vars.local_wires[self.wire_base()]; + + let power_bits: Vec<_> = (0..self.num_power_bits) + .map(|i| vars.local_wires[self.wire_power_bit(i)]) + .collect(); + let intermediate_values: Vec<_> = (0..self.num_power_bits) + .map(|i| vars.local_wires[self.wire_intermediate_value(i)]) + .collect(); + + let output = vars.local_wires[self.wire_output()]; + + let mut constraints = Vec::with_capacity(self.num_constraints()); + + for i in 0..self.num_power_bits { + let prev_intermediate_value = if i == 0 { + F::Extension::ONE + } else { + intermediate_values[i - 1].square() + }; + + // power_bits is in LE order, but we accumulate in BE order. + let cur_bit = power_bits[self.num_power_bits - i - 1]; + + let not_cur_bit = F::Extension::ONE - cur_bit; + let computed_intermediate_value = + prev_intermediate_value * (cur_bit * base + not_cur_bit); + constraints.push(computed_intermediate_value - intermediate_values[i]); + } + + constraints.push(output - intermediate_values[self.num_power_bits - 1]); + + 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 base = vars.local_wires[self.wire_base()]; + + let power_bits: Vec<_> = (0..self.num_power_bits) + .map(|i| vars.local_wires[self.wire_power_bit(i)]) + .collect(); + let intermediate_values: Vec<_> = (0..self.num_power_bits) + .map(|i| vars.local_wires[self.wire_intermediate_value(i)]) + .collect(); + + let output = vars.local_wires[self.wire_output()]; + + let mut constraints = Vec::with_capacity(self.num_constraints()); + + let one = builder.one_extension(); + for i in 0..self.num_power_bits { + let prev_intermediate_value = if i == 0 { + one + } else { + builder.square_extension(intermediate_values[i - 1]) + }; + + // power_bits is in LE order, but we accumulate in BE order. + let cur_bit = power_bits[self.num_power_bits - i - 1]; + let mul_by = builder.select_ext_generalized(cur_bit, base, one); + let intermediate_value_diff = + builder.mul_sub_extension(prev_intermediate_value, mul_by, intermediate_values[i]); + constraints.push(intermediate_value_diff); + } + + let output_diff = + builder.sub_extension(output, intermediate_values[self.num_power_bits - 1]); + constraints.push(output_diff); + + constraints + } + + fn generators(&self, row: usize, _local_constants: &[F]) -> Vec> { + let gen = ExponentiationGenerator:: { + row, + gate: self.clone(), + }; + vec![WitnessGeneratorRef::new(gen.adapter())] + } + + fn num_wires(&self) -> usize { + self.wire_intermediate_value(self.num_power_bits - 1) + 1 + } + + fn num_constants(&self) -> usize { + 0 + } + + fn degree(&self) -> usize { + 4 + } + + fn num_constraints(&self) -> usize { + self.num_power_bits + 1 + } +} + +impl, const D: usize> PackedEvaluableBase + for ExponentiationGate +{ + fn eval_unfiltered_base_packed>( + &self, + vars: EvaluationVarsBasePacked

, + mut yield_constr: StridedConstraintConsumer

, + ) { + let base = vars.local_wires[self.wire_base()]; + + let power_bits: Vec<_> = (0..self.num_power_bits) + .map(|i| vars.local_wires[self.wire_power_bit(i)]) + .collect(); + let intermediate_values: Vec<_> = (0..self.num_power_bits) + .map(|i| vars.local_wires[self.wire_intermediate_value(i)]) + .collect(); + + let output = vars.local_wires[self.wire_output()]; + + for i in 0..self.num_power_bits { + let prev_intermediate_value = if i == 0 { + P::ONES + } else { + intermediate_values[i - 1].square() + }; + + // power_bits is in LE order, but we accumulate in BE order. + let cur_bit = power_bits[self.num_power_bits - i - 1]; + + let not_cur_bit = P::ONES - cur_bit; + let computed_intermediate_value = + prev_intermediate_value * (cur_bit * base + not_cur_bit); + yield_constr.one(computed_intermediate_value - intermediate_values[i]); + } + + yield_constr.one(output - intermediate_values[self.num_power_bits - 1]); + } +} + +#[derive(Debug, Default)] +pub struct ExponentiationGenerator, const D: usize> { + row: usize, + gate: ExponentiationGate, +} + +impl, const D: usize> SimpleGenerator + for ExponentiationGenerator +{ + fn id(&self) -> String { + "ExponentiationGenerator".to_string() + } + + fn dependencies(&self) -> Vec { + let local_target = |column| Target::wire(self.row, column); + + let mut deps = Vec::with_capacity(self.gate.num_power_bits + 1); + deps.push(local_target(self.gate.wire_base())); + for i in 0..self.gate.num_power_bits { + deps.push(local_target(self.gate.wire_power_bit(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 num_power_bits = self.gate.num_power_bits; + let base = get_local_wire(self.gate.wire_base()); + + let power_bits = (0..num_power_bits) + .map(|i| get_local_wire(self.gate.wire_power_bit(i))) + .collect::>(); + let mut intermediate_values = Vec::with_capacity(num_power_bits); + + let mut current_intermediate_value = F::ONE; + for i in 0..num_power_bits { + if power_bits[num_power_bits - i - 1] == F::ONE { + current_intermediate_value *= base; + } + intermediate_values.push(current_intermediate_value); + current_intermediate_value *= current_intermediate_value; + } + + for i in 0..num_power_bits { + let intermediate_value_wire = local_wire(self.gate.wire_intermediate_value(i)); + out_buffer.set_wire(intermediate_value_wire, intermediate_values[i]); + } + + let output_wire = local_wire(self.gate.wire_output()); + out_buffer.set_wire(output_wire, intermediate_values[num_power_bits - 1]); + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.row)?; + self.gate.serialize(dst, _common_data) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let row = src.read_usize()?; + let gate = ExponentiationGate::deserialize(src, _common_data)?; + Ok(Self { row, gate }) + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + use rand::rngs::OsRng; + use rand::Rng; + + use super::*; + use crate::field::goldilocks_field::GoldilocksField; + use crate::field::types::Sample; + 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::util::log2_ceil; + + const MAX_POWER_BITS: usize = 17; + + #[test] + fn wire_indices() { + let gate = ExponentiationGate:: { + num_power_bits: 5, + _phantom: PhantomData, + }; + + assert_eq!(gate.wire_base(), 0); + assert_eq!(gate.wire_power_bit(0), 1); + assert_eq!(gate.wire_power_bit(4), 5); + assert_eq!(gate.wire_output(), 6); + assert_eq!(gate.wire_intermediate_value(0), 7); + assert_eq!(gate.wire_intermediate_value(4), 11); + } + + #[test] + fn low_degree() { + let config = CircuitConfig { + num_wires: 120, + num_routed_wires: 30, + ..CircuitConfig::standard_recursion_config() + }; + + test_low_degree::(ExponentiationGate::new_from_config(&config)); + } + + #[test] + fn eval_fns() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + test_eval_fns::(ExponentiationGate::new_from_config( + &CircuitConfig::standard_recursion_config(), + )) + } + + #[test] + fn test_gate_constraint() { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type FF = >::FE; + + /// Returns the local wires for an exponentiation gate given the base, + /// power, and power bit values. + fn get_wires(base: F, power: u64) -> Vec { + let mut power_bits = Vec::new(); + let mut cur_power = power; + while cur_power > 0 { + power_bits.push(cur_power % 2); + cur_power /= 2; + } + + let num_power_bits = power_bits.len(); + + let power_bits_f: Vec<_> = power_bits + .iter() + .map(|b| F::from_canonical_u64(*b)) + .collect(); + + let mut v = vec![base]; + v.extend(power_bits_f); + + let mut intermediate_values = Vec::new(); + let mut current_intermediate_value = F::ONE; + for i in 0..num_power_bits { + if power_bits[num_power_bits - i - 1] == 1 { + current_intermediate_value *= base; + } + intermediate_values.push(current_intermediate_value); + current_intermediate_value *= current_intermediate_value; + } + let output_value = intermediate_values[num_power_bits - 1]; + v.push(output_value); + v.extend(intermediate_values); + + v.iter().map(|&x| x.into()).collect::>() + } + + let mut rng = OsRng; + + let base = F::TWO; + let power = rng.gen::() % (1 << MAX_POWER_BITS); + let num_power_bits = log2_ceil(power + 1); + let gate = ExponentiationGate:: { + num_power_bits, + _phantom: PhantomData, + }; + + let vars = EvaluationVars { + local_constants: &[], + local_wires: &get_wires(base, power as u64), + 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/gate.rs b/plonky2/src/gates/gate.rs new file mode 100644 index 000000000..d16d3ac8b --- /dev/null +++ b/plonky2/src/gates/gate.rs @@ -0,0 +1,372 @@ +#[cfg(not(feature = "std"))] +use alloc::{string::String, sync::Arc, vec, vec::Vec}; +use core::any::Any; +use core::fmt::{Debug, Error, Formatter}; +use core::hash::{Hash, Hasher}; +use core::ops::Range; +#[cfg(feature = "std")] +use std::sync::Arc; + +use hashbrown::HashMap; +use serde::{Serialize, Serializer}; + +use crate::field::batch_util::batch_multiply_inplace; +use crate::field::extension::{Extendable, FieldExtension}; +use crate::field::types::Field; +use crate::gates::selectors::UNUSED_SELECTOR; +use crate::gates::util::StridedConstraintConsumer; +use crate::hash::hash_types::RichField; +use crate::iop::ext_target::ExtensionTarget; +use crate::iop::generator::WitnessGeneratorRef; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::CommonCircuitData; +use crate::plonk::vars::{ + EvaluationTargets, EvaluationVars, EvaluationVarsBase, EvaluationVarsBaseBatch, +}; +use crate::util::serialization::{Buffer, IoResult}; + +/// A custom gate. +/// +/// Vanilla Plonk arithmetization only supports basic fan-in 2 / fan-out 1 +/// arithmetic gates, each of the form +/// +/// $$ a.b \cdot q_M + a \cdot q_L + b \cdot q_R + c \cdot q_O + q_C = 0 $$ +/// +/// where: +/// - $q_M$, $q_L$, $q_R$ and $q_O$ are boolean selectors, +/// - $a$, $b$ and $c$ are values used as inputs and output respectively, +/// - $q_C$ is a constant (possibly 0). +/// +/// This allows expressing simple operations like multiplication, addition, etc. +/// For instance, to define a multiplication, one can set $q_M=1$, $q_L=q_R=0$, +/// $q_O = -1$ and $q_C = 0$. +/// +/// Hence, the gate equation simplifies to $a.b - c = 0$, or equivalently to +/// $a.b = c$. +/// +/// However, such a gate is fairly limited for more complex computations. Hence, +/// when a computation may require too many of these "vanilla" gates, or when a +/// computation arises often within the same circuit, one may want to construct +/// a tailored custom gate. These custom gates can use more selectors and are +/// not necessarily limited to 2 inputs + 1 output = 3 wires. +/// For instance, plonky2 supports natively a custom Poseidon hash gate that +/// uses 135 wires. +/// +/// Note however that extending the number of wires necessary for a custom gate +/// comes at a price, and may impact the overall performances when generating +/// proofs for a circuit containing them. +pub trait Gate, const D: usize>: 'static + Send + Sync { + /// Defines a unique identifier for this custom gate. + /// + /// This is used as differentiating tag in gate serializers. + fn id(&self) -> String; + + /// Serializes this custom gate to the targeted byte buffer, with the + /// provided [`CommonCircuitData`]. + fn serialize(&self, dst: &mut Vec, common_data: &CommonCircuitData) -> IoResult<()>; + + /// Deserializes the bytes in the provided buffer into this custom gate, + /// given some [`CommonCircuitData`]. + fn deserialize(src: &mut Buffer, common_data: &CommonCircuitData) -> IoResult + where + Self: Sized; + + /// Defines and evaluates the constraints that enforce the statement + /// represented by this gate. Constraints must be defined in the + /// extension of this custom gate base field. + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec; + + /// Like `eval_unfiltered`, but specialized for points in the base field. + /// + /// + /// `eval_unfiltered_base_batch` calls this method by default. If + /// `eval_unfiltered_base_batch` is overridden, then + /// `eval_unfiltered_base_one` is not necessary. + /// + /// By default, this just calls `eval_unfiltered`, which treats the point as + /// an extension field element. This isn't very efficient. + fn eval_unfiltered_base_one( + &self, + vars_base: EvaluationVarsBase, + mut yield_constr: StridedConstraintConsumer, + ) { + // Note that this method uses `yield_constr` instead of returning its + // constraints. `yield_constr` abstracts out the underlying memory + // layout. + let local_constants = &vars_base + .local_constants + .iter() + .map(|c| F::Extension::from_basefield(*c)) + .collect::>(); + let local_wires = &vars_base + .local_wires + .iter() + .map(|w| F::Extension::from_basefield(*w)) + .collect::>(); + let public_inputs_hash = &vars_base.public_inputs_hash; + let vars = EvaluationVars { + local_constants, + local_wires, + public_inputs_hash, + }; + let values = self.eval_unfiltered(vars); + + // Each value should be in the base field, i.e. only the degree-zero part should + // be nonzero. + values.into_iter().for_each(|value| { + debug_assert!(F::Extension::is_in_basefield(&value)); + yield_constr.one(value.to_basefield_array()[0]) + }) + } + + fn eval_unfiltered_base_batch(&self, vars_base: EvaluationVarsBaseBatch) -> Vec { + let mut res = vec![F::ZERO; vars_base.len() * self.num_constraints()]; + for (i, vars_base_one) in vars_base.iter().enumerate() { + self.eval_unfiltered_base_one( + vars_base_one, + StridedConstraintConsumer::new(&mut res, vars_base.len(), i), + ); + } + res + } + + /// Defines the recursive constraints that enforce the statement represented + /// by this custom gate. This is necessary to recursively verify proofs + /// generated from a circuit containing such gates. + /// + /// **Note**: The order of the recursive constraints output by this method + /// should match exactly the order of the constraints obtained by the + /// non-recursive [`Gate::eval_unfiltered`] method, otherwise the prover + /// won't be able to generate proofs. + fn eval_unfiltered_circuit( + &self, + builder: &mut CircuitBuilder, + vars: EvaluationTargets, + ) -> Vec>; + + fn eval_filtered( + &self, + mut vars: EvaluationVars, + row: usize, + selector_index: usize, + group_range: Range, + num_selectors: usize, + num_lookup_selectors: usize, + ) -> Vec { + let filter = compute_filter( + row, + group_range, + vars.local_constants[selector_index], + num_selectors > 1, + ); + vars.remove_prefix(num_selectors); + vars.remove_prefix(num_lookup_selectors); + self.eval_unfiltered(vars) + .into_iter() + .map(|c| filter * c) + .collect() + } + + /// The result is an array of length `vars_batch.len() * + /// self.num_constraints()`. Constraint `j` for point `i` is at index `j + /// * batch_size + i`. + fn eval_filtered_base_batch( + &self, + mut vars_batch: EvaluationVarsBaseBatch, + row: usize, + selector_index: usize, + group_range: Range, + num_selectors: usize, + num_lookup_selectors: usize, + ) -> Vec { + let filters: Vec<_> = vars_batch + .iter() + .map(|vars| { + compute_filter( + row, + group_range.clone(), + vars.local_constants[selector_index], + num_selectors > 1, + ) + }) + .collect(); + vars_batch.remove_prefix(num_selectors + num_lookup_selectors); + let mut res_batch = self.eval_unfiltered_base_batch(vars_batch); + for res_chunk in res_batch.chunks_exact_mut(filters.len()) { + batch_multiply_inplace(res_chunk, &filters); + } + res_batch + } + + /// Adds this gate's filtered constraints into the + /// `combined_gate_constraints` buffer. + fn eval_filtered_circuit( + &self, + builder: &mut CircuitBuilder, + mut vars: EvaluationTargets, + row: usize, + selector_index: usize, + group_range: Range, + num_selectors: usize, + num_lookup_selectors: usize, + combined_gate_constraints: &mut [ExtensionTarget], + ) { + let filter = compute_filter_circuit( + builder, + row, + group_range, + vars.local_constants[selector_index], + num_selectors > 1, + ); + vars.remove_prefix(num_selectors); + vars.remove_prefix(num_lookup_selectors); + let my_constraints = self.eval_unfiltered_circuit(builder, vars); + for (acc, c) in combined_gate_constraints.iter_mut().zip(my_constraints) { + *acc = builder.mul_add_extension(filter, c, *acc); + } + } + + /// The generators used to populate the witness. + /// + /// **Note**: This should return exactly 1 generator per operation in the + /// gate. + fn generators(&self, row: usize, local_constants: &[F]) -> Vec>; + + /// The number of wires used by this gate. + /// + /// While vanilla Plonk can only evaluate one addition/multiplication at a + /// time, a wider configuration may be able to accommodate several + /// identical gates at once. This is particularly helpful for tiny + /// custom gates that are being used extensively in circuits. + /// + /// For instance, the + /// [crate::gates::multiplication_extension::MulExtensionGate] takes `3*D` + /// wires per multiplication (where `D`` is the degree of the extension), + /// hence for a usual configuration of 80 routed wires with D=2, one can + /// evaluate 13 multiplications within a single gate. + fn num_wires(&self) -> usize; + + /// The number of constants used by this gate. + fn num_constants(&self) -> usize; + + /// The maximum degree among this gate's constraint polynomials. + fn degree(&self) -> usize; + + /// The number of constraints defined by this sole custom gate. + fn num_constraints(&self) -> usize; + + /// Number of operations performed by the gate. + fn num_ops(&self) -> usize { + self.generators(0, &vec![F::ZERO; self.num_constants()]) + .len() + } + + /// Enables gates to store some "routed constants", if they have both unused + /// constants and unused routed wires. + /// + /// Each entry in the returned `Vec` has the form `(constant_index, + /// wire_index)`. `wire_index` must correspond to a *routed* wire. + fn extra_constant_wires(&self) -> Vec<(usize, usize)> { + vec![] + } +} + +/// A wrapper trait over a `Gate`, to allow for gate serialization. +pub trait AnyGate, const D: usize>: Gate { + fn as_any(&self) -> &dyn Any; +} + +impl, F: RichField + Extendable, const D: usize> AnyGate for T { + fn as_any(&self) -> &dyn Any { + self + } +} + +/// A wrapper around an `Arc` which implements `PartialEq`, `Eq` and +/// `Hash` based on gate IDs. +#[derive(Clone)] +pub struct GateRef, const D: usize>(pub Arc>); + +impl, const D: usize> GateRef { + pub fn new>(gate: G) -> GateRef { + GateRef(Arc::new(gate)) + } +} + +impl, const D: usize> PartialEq for GateRef { + fn eq(&self, other: &Self) -> bool { + self.0.id() == other.0.id() + } +} + +impl, const D: usize> Hash for GateRef { + fn hash(&self, state: &mut H) { + self.0.id().hash(state) + } +} + +impl, const D: usize> Eq for GateRef {} + +impl, const D: usize> Debug for GateRef { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + write!(f, "{}", self.0.id()) + } +} + +impl, const D: usize> Serialize for GateRef { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str(&self.0.id()) + } +} + +/// Map between gate parameters and available slots. +/// An available slot is of the form `(row, op)`, meaning the current available +/// slot is at gate index `row` in the `op`-th operation. +#[derive(Clone, Debug, Default)] +pub struct CurrentSlot, const D: usize> { + pub current_slot: HashMap, (usize, usize)>, +} + +/// A gate along with any constants used to configure it. +#[derive(Clone)] +pub struct GateInstance, const D: usize> { + pub gate_ref: GateRef, + pub constants: Vec, +} + +/// Map each gate to a boolean prefix used to construct the gate's selector +/// polynomial. +#[derive(Debug, Clone)] +pub struct PrefixedGate, const D: usize> { + pub gate: GateRef, + pub prefix: Vec, +} + +/// A gate's filter designed so that it is non-zero if `s = row`. +fn compute_filter(row: usize, group_range: Range, s: K, many_selector: bool) -> K { + debug_assert!(group_range.contains(&row)); + group_range + .filter(|&i| i != row) + .chain(many_selector.then_some(UNUSED_SELECTOR)) + .map(|i| K::from_canonical_usize(i) - s) + .product() +} + +fn compute_filter_circuit, const D: usize>( + builder: &mut CircuitBuilder, + row: usize, + group_range: Range, + s: ExtensionTarget, + many_selectors: bool, +) -> ExtensionTarget { + debug_assert!(group_range.contains(&row)); + let v = group_range + .filter(|&i| i != row) + .chain(many_selectors.then_some(UNUSED_SELECTOR)) + .map(|i| { + let c = builder.constant_extension(F::Extension::from_canonical_usize(i)); + builder.sub_extension(c, s) + }) + .collect::>(); + builder.mul_many_extension(v) +} diff --git a/plonky2/src/gates/gate_testing.rs b/plonky2/src/gates/gate_testing.rs new file mode 100644 index 000000000..b84cd66e4 --- /dev/null +++ b/plonky2/src/gates/gate_testing.rs @@ -0,0 +1,164 @@ +#[cfg(not(feature = "std"))] +use alloc::{vec, vec::Vec}; + +use anyhow::{ensure, Result}; + +use crate::field::extension::{Extendable, FieldExtension}; +use crate::field::polynomial::{PolynomialCoeffs, PolynomialValues}; +use crate::field::types::{Field, Sample}; +use crate::gates::gate::Gate; +use crate::hash::hash_types::{HashOut, RichField}; +use crate::iop::witness::{PartialWitness, WitnessWrite}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::CircuitConfig; +use crate::plonk::config::GenericConfig; +use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBaseBatch}; +use crate::plonk::verifier::verify; +use crate::util::{log2_ceil, transpose}; + +const WITNESS_SIZE: usize = 1 << 5; +const WITNESS_DEGREE: usize = WITNESS_SIZE - 1; + +/// Tests that the constraints imposed by the given gate are low-degree by +/// applying them to random low-degree witness polynomials. +pub fn test_low_degree, G: Gate, const D: usize>(gate: G) { + let rate_bits = log2_ceil(gate.degree() + 1); + + let wire_ldes = random_low_degree_matrix::(gate.num_wires(), rate_bits); + let constant_ldes = random_low_degree_matrix::(gate.num_constants(), rate_bits); + assert_eq!(wire_ldes.len(), constant_ldes.len()); + let public_inputs_hash = &HashOut::rand(); + + let constraint_evals = wire_ldes + .iter() + .zip(constant_ldes.iter()) + .map(|(local_wires, local_constants)| EvaluationVars { + local_constants, + local_wires, + public_inputs_hash, + }) + .map(|vars| gate.eval_unfiltered(vars)) + .collect::>(); + + let constraint_eval_degrees = transpose(&constraint_evals) + .into_iter() + .map(PolynomialValues::new) + .map(|p| p.degree()) + .collect::>(); + + assert_eq!( + constraint_eval_degrees.len(), + gate.num_constraints(), + "eval should return num_constraints() constraints" + ); + + let expected_eval_degree = WITNESS_DEGREE * gate.degree(); + + assert!( + constraint_eval_degrees + .iter() + .all(|°| deg <= expected_eval_degree), + "Expected degrees at most {} * {} = {}, actual {:?}", + WITNESS_SIZE, + gate.degree(), + expected_eval_degree, + constraint_eval_degrees + ); +} + +fn random_low_degree_matrix(num_polys: usize, rate_bits: usize) -> Vec> { + let polys = (0..num_polys) + .map(|_| random_low_degree_values(rate_bits)) + .collect::>(); + + if polys.is_empty() { + // We want a Vec of many empty Vecs, whereas transpose would just give an empty + // Vec. + vec![Vec::new(); WITNESS_SIZE << rate_bits] + } else { + transpose(&polys) + } +} + +fn random_low_degree_values(rate_bits: usize) -> Vec { + PolynomialCoeffs::new(F::rand_vec(WITNESS_SIZE)) + .lde(rate_bits) + .fft() + .values +} + +pub fn test_eval_fns< + F: RichField + Extendable, + C: GenericConfig, + G: Gate, + const D: usize, +>( + gate: G, +) -> Result<()> { + // Test that `eval_unfiltered` and `eval_unfiltered_base` are coherent. + let wires_base = F::rand_vec(gate.num_wires()); + let constants_base = F::rand_vec(gate.num_constants()); + let wires = wires_base + .iter() + .map(|&x| F::Extension::from_basefield(x)) + .collect::>(); + let constants = constants_base + .iter() + .map(|&x| F::Extension::from_basefield(x)) + .collect::>(); + let public_inputs_hash = HashOut::rand(); + + // Batch of 1. + let vars_base_batch = + EvaluationVarsBaseBatch::new(1, &constants_base, &wires_base, &public_inputs_hash); + let vars = EvaluationVars { + local_constants: &constants, + local_wires: &wires, + public_inputs_hash: &public_inputs_hash, + }; + + let evals_base = gate.eval_unfiltered_base_batch(vars_base_batch); + let evals = gate.eval_unfiltered(vars); + // This works because we have a batch of 1. + ensure!( + evals + == evals_base + .into_iter() + .map(F::Extension::from_basefield) + .collect::>() + ); + + // Test that `eval_unfiltered` and `eval_unfiltered_recursively` are coherent. + let wires = F::Extension::rand_vec(gate.num_wires()); + let constants = F::Extension::rand_vec(gate.num_constants()); + + let config = CircuitConfig::standard_recursion_config(); + let mut pw = PartialWitness::new(); + let mut builder = CircuitBuilder::::new(config); + + let wires_t = builder.add_virtual_extension_targets(wires.len()); + let constants_t = builder.add_virtual_extension_targets(constants.len()); + pw.set_extension_targets(&wires_t, &wires); + pw.set_extension_targets(&constants_t, &constants); + let public_inputs_hash_t = builder.add_virtual_hash(); + pw.set_hash_target(public_inputs_hash_t, public_inputs_hash); + + let vars = EvaluationVars { + local_constants: &constants, + local_wires: &wires, + public_inputs_hash: &public_inputs_hash, + }; + let evals = gate.eval_unfiltered(vars); + + let vars_t = EvaluationTargets { + local_constants: &constants_t, + local_wires: &wires_t, + public_inputs_hash: &public_inputs_hash_t, + }; + let evals_t = gate.eval_unfiltered_circuit(&mut builder, vars_t); + pw.set_extension_targets(&evals_t, &evals); + + let data = builder.build::(); + let proof = data.prove(pw)?; + verify::(proof, &data.verifier_only, &data.common) +} diff --git a/plonky2/src/gates/lookup.rs b/plonky2/src/gates/lookup.rs new file mode 100644 index 000000000..f029b1386 --- /dev/null +++ b/plonky2/src/gates/lookup.rs @@ -0,0 +1,241 @@ +#[cfg(not(feature = "std"))] +use alloc::{ + format, + string::{String, ToString}, + vec, + vec::Vec, +}; +use core::usize; + +use itertools::Itertools; +use keccak_hash::keccak; + +use super::lookup_table::LookupTable; +use crate::field::extension::Extendable; +use crate::field::packed::PackedField; +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, WitnessGeneratorRef}; +use crate::iop::target::Target; +use crate::iop::witness::{PartitionWitness, Witness, WitnessWrite}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::{CircuitConfig, CommonCircuitData}; +use crate::plonk::vars::{ + EvaluationTargets, EvaluationVars, EvaluationVarsBase, EvaluationVarsBaseBatch, + EvaluationVarsBasePacked, +}; +use crate::util::serialization::{Buffer, IoResult, Read, Write}; + +pub type Lookup = Vec<(Target, Target)>; + +/// A gate which stores (input, output) lookup pairs made elsewhere in the +/// trace. It doesn't check any constraints itself. +#[derive(Debug, Clone)] +pub struct LookupGate { + /// Number of lookups per gate. + pub num_slots: usize, + /// LUT associated to the gate. + lut: LookupTable, + /// The Keccak hash of the lookup table. + lut_hash: [u8; 32], +} + +impl LookupGate { + pub fn new_from_table(config: &CircuitConfig, lut: LookupTable) -> Self { + let table_bytes = lut + .iter() + .flat_map(|(input, output)| [input.to_le_bytes(), output.to_le_bytes()].concat()) + .collect_vec(); + + Self { + num_slots: Self::num_slots(config), + lut, + lut_hash: keccak(table_bytes).0, + } + } + pub(crate) const fn num_slots(config: &CircuitConfig) -> usize { + let wires_per_lookup = 2; + config.num_routed_wires / wires_per_lookup + } + + pub const fn wire_ith_looking_inp(i: usize) -> usize { + 2 * i + } + + pub const fn wire_ith_looking_out(i: usize) -> usize { + 2 * i + 1 + } +} + +impl, const D: usize> Gate for LookupGate { + fn id(&self) -> String { + // Custom implementation to not have the entire lookup table + format!( + "LookupGate {{num_slots: {}, lut_hash: {:?}}}", + self.num_slots, self.lut_hash + ) + } + + fn serialize(&self, dst: &mut Vec, common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.num_slots)?; + for (i, lut) in common_data.luts.iter().enumerate() { + if lut == &self.lut { + dst.write_usize(i)?; + return dst.write_all(&self.lut_hash); + } + } + + panic!("The associated lookup table couldn't be found.") + } + + fn deserialize(src: &mut Buffer, common_data: &CommonCircuitData) -> IoResult { + let num_slots = src.read_usize()?; + let lut_index = src.read_usize()?; + let mut lut_hash = [0u8; 32]; + src.read_exact(&mut lut_hash)?; + + Ok(Self { + num_slots, + lut: common_data.luts[lut_index].clone(), + lut_hash, + }) + } + + fn eval_unfiltered(&self, _vars: EvaluationVars) -> Vec { + // No main trace constraints for lookups. + vec![] + } + + 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> { + // No main trace constraints for lookups. + vec![] + } + + fn generators(&self, row: usize, _local_constants: &[F]) -> Vec> { + (0..self.num_slots) + .map(|i| { + WitnessGeneratorRef::new( + LookupGenerator { + row, + lut: self.lut.clone(), + slot_nb: i, + } + .adapter(), + ) + }) + .collect() + } + + fn num_wires(&self) -> usize { + self.num_slots * 2 + } + + fn num_constants(&self) -> usize { + 0 + } + + fn degree(&self) -> usize { + 0 + } + + fn num_constraints(&self) -> usize { + 0 + } +} + +impl, const D: usize> PackedEvaluableBase for LookupGate { + fn eval_unfiltered_base_packed>( + &self, + _vars: EvaluationVarsBasePacked

, + mut _yield_constr: StridedConstraintConsumer

, + ) { + } +} + +#[derive(Clone, Debug, Default)] +pub struct LookupGenerator { + row: usize, + lut: LookupTable, + slot_nb: usize, +} + +impl, const D: usize> SimpleGenerator for LookupGenerator { + fn id(&self) -> String { + "LookupGenerator".to_string() + } + + fn dependencies(&self) -> Vec { + vec![Target::wire( + self.row, + LookupGate::wire_ith_looking_inp(self.slot_nb), + )] + } + + fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + let get_wire = |wire: usize| -> F { witness.get_target(Target::wire(self.row, wire)) }; + + let input_val = get_wire(LookupGate::wire_ith_looking_inp(self.slot_nb)); + let (input, output) = self.lut[input_val.to_canonical_u64() as usize]; + if input_val == F::from_canonical_u16(input) { + let output_val = F::from_canonical_u16(output); + + let out_wire = Target::wire(self.row, LookupGate::wire_ith_looking_out(self.slot_nb)); + out_buffer.set_target(out_wire, output_val); + } else { + for (input, output) in self.lut.iter() { + if input_val == F::from_canonical_u16(*input) { + let output_val = F::from_canonical_u16(*output); + + let out_wire = + Target::wire(self.row, LookupGate::wire_ith_looking_out(self.slot_nb)); + out_buffer.set_target(out_wire, output_val); + return; + } + } + panic!("Incorrect input value provided"); + }; + } + + fn serialize(&self, dst: &mut Vec, common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.row)?; + dst.write_usize(self.slot_nb)?; + for (i, lut) in common_data.luts.iter().enumerate() { + if lut == &self.lut { + return dst.write_usize(i); + } + } + + panic!("The associated lookup table couldn't be found.") + } + + fn deserialize(src: &mut Buffer, common_data: &CommonCircuitData) -> IoResult { + let row = src.read_usize()?; + let slot_nb = src.read_usize()?; + let lut_index = src.read_usize()?; + + Ok(Self { + row, + lut: common_data.luts[lut_index].clone(), + slot_nb, + }) + } +} diff --git a/plonky2/src/gates/lookup_table.rs b/plonky2/src/gates/lookup_table.rs new file mode 100644 index 000000000..6406931e7 --- /dev/null +++ b/plonky2/src/gates/lookup_table.rs @@ -0,0 +1,260 @@ +#[cfg(not(feature = "std"))] +use alloc::{ + format, + string::{String, ToString}, + sync::Arc, + vec, + vec::Vec, +}; +use core::usize; +#[cfg(feature = "std")] +use std::sync::Arc; + +use itertools::Itertools; +use keccak_hash::keccak; +use plonky2_util::ceil_div_usize; + +use crate::field::extension::Extendable; +use crate::field::packed::PackedField; +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, WitnessGeneratorRef}; +use crate::iop::target::Target; +use crate::iop::witness::{PartitionWitness, WitnessWrite}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::{CircuitConfig, CommonCircuitData}; +use crate::plonk::vars::{ + EvaluationTargets, EvaluationVars, EvaluationVarsBase, EvaluationVarsBaseBatch, + EvaluationVarsBasePacked, +}; +use crate::util::serialization::{Buffer, IoResult, Read, Write}; + +pub type LookupTable = Arc>; + +/// A gate which stores the set of (input, output) value pairs of a lookup +/// table, and their multiplicities. +#[derive(Debug, Clone)] +pub struct LookupTableGate { + /// Number of lookup entries per gate. + pub num_slots: usize, + /// Lookup table associated to the gate. + pub lut: LookupTable, + /// The Keccak hash of the lookup table. + lut_hash: [u8; 32], + /// First row of the lookup table. + last_lut_row: usize, +} + +impl LookupTableGate { + pub fn new_from_table(config: &CircuitConfig, lut: LookupTable, last_lut_row: usize) -> Self { + let table_bytes = lut + .iter() + .flat_map(|(input, output)| [input.to_le_bytes(), output.to_le_bytes()].concat()) + .collect_vec(); + + Self { + num_slots: Self::num_slots(config), + lut, + lut_hash: keccak(table_bytes).0, + last_lut_row, + } + } + + pub(crate) const fn num_slots(config: &CircuitConfig) -> usize { + let wires_per_entry = 3; + config.num_routed_wires / wires_per_entry + } + + /// Wire for the looked input. + pub const fn wire_ith_looked_inp(i: usize) -> usize { + 3 * i + } + + // Wire for the looked output. + pub const fn wire_ith_looked_out(i: usize) -> usize { + 3 * i + 1 + } + + /// Wire for the multiplicity. Set after the trace has been generated. + pub const fn wire_ith_multiplicity(i: usize) -> usize { + 3 * i + 2 + } +} + +impl, const D: usize> Gate for LookupTableGate { + fn id(&self) -> String { + // Custom implementation to not have the entire lookup table + format!( + "LookupTableGate {{num_slots: {}, lut_hash: {:?}, last_lut_row: {}}}", + self.num_slots, self.lut_hash, self.last_lut_row + ) + } + + fn serialize(&self, dst: &mut Vec, common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.num_slots)?; + dst.write_usize(self.last_lut_row)?; + for (i, lut) in common_data.luts.iter().enumerate() { + if lut == &self.lut { + dst.write_usize(i)?; + return dst.write_all(&self.lut_hash); + } + } + + panic!("The associated lookup table couldn't be found.") + } + + fn deserialize(src: &mut Buffer, common_data: &CommonCircuitData) -> IoResult { + let num_slots = src.read_usize()?; + let last_lut_row = src.read_usize()?; + let lut_index = src.read_usize()?; + let mut lut_hash = [0u8; 32]; + src.read_exact(&mut lut_hash)?; + + Ok(Self { + num_slots, + lut: common_data.luts[lut_index].clone(), + lut_hash, + last_lut_row, + }) + } + + fn eval_unfiltered(&self, _vars: EvaluationVars) -> Vec { + // No main trace constraints for the lookup table. + vec![] + } + + 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> { + // No main trace constraints for the lookup table. + vec![] + } + + fn generators(&self, row: usize, _local_constants: &[F]) -> Vec> { + (0..self.num_slots) + .map(|i| { + WitnessGeneratorRef::new( + LookupTableGenerator { + row, + lut: self.lut.clone(), + slot_nb: i, + num_slots: self.num_slots, + last_lut_row: self.last_lut_row, + } + .adapter(), + ) + }) + .collect() + } + + fn num_wires(&self) -> usize { + self.num_slots * 3 + } + + fn num_constants(&self) -> usize { + 0 + } + + fn degree(&self) -> usize { + 0 + } + + fn num_constraints(&self) -> usize { + 0 + } +} + +impl, const D: usize> PackedEvaluableBase for LookupTableGate { + fn eval_unfiltered_base_packed>( + &self, + _vars: EvaluationVarsBasePacked

, + mut _yield_constr: StridedConstraintConsumer

, + ) { + } +} + +#[derive(Clone, Debug, Default)] +pub struct LookupTableGenerator { + row: usize, + lut: LookupTable, + slot_nb: usize, + num_slots: usize, + last_lut_row: usize, +} + +impl, const D: usize> SimpleGenerator for LookupTableGenerator { + fn id(&self) -> String { + "LookupTableGenerator".to_string() + } + + fn dependencies(&self) -> Vec { + vec![] + } + + fn run_once(&self, _witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + let first_row = self.last_lut_row + ceil_div_usize(self.lut.len(), self.num_slots) - 1; + let slot = (first_row - self.row) * self.num_slots + self.slot_nb; + + let slot_input_target = + Target::wire(self.row, LookupTableGate::wire_ith_looked_inp(self.slot_nb)); + let slot_output_target = + Target::wire(self.row, LookupTableGate::wire_ith_looked_out(self.slot_nb)); + + if slot < self.lut.len() { + let (input, output) = self.lut[slot]; + out_buffer.set_target(slot_input_target, F::from_canonical_usize(input as usize)); + out_buffer.set_target(slot_output_target, F::from_canonical_usize(output as usize)); + } else { + // Pad with zeros. + out_buffer.set_target(slot_input_target, F::ZERO); + out_buffer.set_target(slot_output_target, F::ZERO); + } + } + + fn serialize(&self, dst: &mut Vec, common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.row)?; + dst.write_usize(self.slot_nb)?; + dst.write_usize(self.num_slots)?; + dst.write_usize(self.last_lut_row)?; + for (i, lut) in common_data.luts.iter().enumerate() { + if lut == &self.lut { + return dst.write_usize(i); + } + } + + panic!("The associated lookup table couldn't be found.") + } + + fn deserialize(src: &mut Buffer, common_data: &CommonCircuitData) -> IoResult { + let row = src.read_usize()?; + let slot_nb = src.read_usize()?; + let num_slots = src.read_usize()?; + let last_lut_row = src.read_usize()?; + let lut_index = src.read_usize()?; + + Ok(Self { + row, + lut: common_data.luts[lut_index].clone(), + slot_nb, + num_slots, + last_lut_row, + }) + } +} diff --git a/plonky2/src/gates/mod.rs b/plonky2/src/gates/mod.rs new file mode 100644 index 000000000..502547233 --- /dev/null +++ b/plonky2/src/gates/mod.rs @@ -0,0 +1,54 @@ +//! plonky2 custom gates. +//! +//! Vanilla Plonk arithmetization only supports basic fan-in 2 / fan-out 1 +//! arithmetic gates, each of the form +//! +//! $$ a.b.q_M + a.q_L + b.q_R + c.q_O + q_C = 0 $$ +//! +//! where: +//! - $q_M$, $q_L$, $q_R$ and $q_O$ are boolean selectors, +//! - $a$, $b$ and $c$ are values used as inputs and output respectively, +//! - $q_C$ is a constant (possibly 0). +//! +//! This allows expressing simple operations like multiplication, addition, etc. +//! For instance, to define a multiplication, one can set $q_M=1$, $q_L=q_R=0$, +//! $q_O = -1$ and $q_C = 0$. +//! +//! Hence, the gate equation simplifies to $a.b - c = 0$, or equivalently to +//! $a.b = c$. +//! +//! However, such a gate is fairly limited for more complex computations. Hence, +//! when a computation may require too many of these "vanilla" gates, or when a +//! computation arises often within the same circuit, one may want to construct +//! a tailored custom gate. These custom gates can use more selectors and are +//! not necessarily limited to 2 inputs + 1 output = 3 wires. +//! For instance, plonky2 supports natively a custom Poseidon hash gate that +//! uses 135 wires. + +// Gates have `new` methods that return `GateRef`s. + +pub mod arithmetic_base; +pub mod arithmetic_extension; +pub mod base_sum; +pub mod constant; +pub mod coset_interpolation; +pub mod exponentiation; +pub mod gate; +pub mod lookup; +pub mod lookup_table; +pub mod multiplication_extension; +pub mod noop; +pub mod packed_util; +pub mod poseidon; +pub mod poseidon_mds; +pub mod public_input; +pub mod random_access; +pub mod reducing; +pub mod reducing_extension; +pub(crate) mod selectors; +pub mod util; + +// Can't use #[cfg(test)] here because it needs to be visible to other crates. +// See https://github.com/rust-lang/cargo/issues/8379 +#[cfg(any(feature = "gate_testing", test))] +pub mod gate_testing; diff --git a/plonky2/src/gates/multiplication_extension.rs b/plonky2/src/gates/multiplication_extension.rs new file mode 100644 index 000000000..4c80bd233 --- /dev/null +++ b/plonky2/src/gates/multiplication_extension.rs @@ -0,0 +1,236 @@ +#[cfg(not(feature = "std"))] +use alloc::{ + format, + string::{String, ToString}, + vec::Vec, +}; +use core::ops::Range; + +use crate::field::extension::{Extendable, FieldExtension}; +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, WitnessGeneratorRef}; +use crate::iop::target::Target; +use crate::iop::witness::{PartitionWitness, Witness, WitnessWrite}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::{CircuitConfig, CommonCircuitData}; +use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; +use crate::util::serialization::{Buffer, IoResult, Read, Write}; + +/// A gate which can perform a weighted multiplication, i.e. `result = c0.x.y` +/// on [`ExtensionTarget`]. If the config has enough routed wires, it can +/// support several such operations in one gate. +#[derive(Debug, Clone)] +pub struct MulExtensionGate { + /// Number of multiplications performed by the gate. + pub num_ops: usize, +} + +impl MulExtensionGate { + pub const fn new_from_config(config: &CircuitConfig) -> Self { + Self { + num_ops: Self::num_ops(config), + } + } + + /// Determine the maximum number of operations that can fit in one gate for + /// the given config. + pub(crate) const fn num_ops(config: &CircuitConfig) -> usize { + let wires_per_op = 3 * D; + config.num_routed_wires / wires_per_op + } + + pub const fn wires_ith_multiplicand_0(i: usize) -> Range { + 3 * D * i..3 * D * i + D + } + pub const fn wires_ith_multiplicand_1(i: usize) -> Range { + 3 * D * i + D..3 * D * i + 2 * D + } + pub const fn wires_ith_output(i: usize) -> Range { + 3 * D * i + 2 * D..3 * D * i + 3 * D + } +} + +impl, const D: usize> Gate for MulExtensionGate { + fn id(&self) -> String { + format!("{self:?}") + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.num_ops) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let num_ops = src.read_usize()?; + Ok(Self { num_ops }) + } + + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { + let const_0 = vars.local_constants[0]; + + let mut constraints = Vec::with_capacity(self.num_ops * D); + for i in 0..self.num_ops { + let multiplicand_0 = vars.get_local_ext_algebra(Self::wires_ith_multiplicand_0(i)); + let multiplicand_1 = vars.get_local_ext_algebra(Self::wires_ith_multiplicand_1(i)); + let output = vars.get_local_ext_algebra(Self::wires_ith_output(i)); + let computed_output = (multiplicand_0 * multiplicand_1).scalar_mul(const_0); + + constraints.extend((output - computed_output).to_basefield_array()); + } + + constraints + } + + fn eval_unfiltered_base_one( + &self, + vars: EvaluationVarsBase, + mut yield_constr: StridedConstraintConsumer, + ) { + let const_0 = vars.local_constants[0]; + + for i in 0..self.num_ops { + let multiplicand_0 = vars.get_local_ext(Self::wires_ith_multiplicand_0(i)); + let multiplicand_1 = vars.get_local_ext(Self::wires_ith_multiplicand_1(i)); + let output = vars.get_local_ext(Self::wires_ith_output(i)); + let computed_output = (multiplicand_0 * multiplicand_1).scalar_mul(const_0); + + yield_constr.many((output - computed_output).to_basefield_array()); + } + } + + fn eval_unfiltered_circuit( + &self, + builder: &mut CircuitBuilder, + vars: EvaluationTargets, + ) -> Vec> { + let const_0 = vars.local_constants[0]; + + let mut constraints = Vec::with_capacity(self.num_ops * D); + for i in 0..self.num_ops { + let multiplicand_0 = vars.get_local_ext_algebra(Self::wires_ith_multiplicand_0(i)); + let multiplicand_1 = vars.get_local_ext_algebra(Self::wires_ith_multiplicand_1(i)); + let output = vars.get_local_ext_algebra(Self::wires_ith_output(i)); + let computed_output = { + let mul = builder.mul_ext_algebra(multiplicand_0, multiplicand_1); + builder.scalar_mul_ext_algebra(const_0, mul) + }; + + let diff = builder.sub_ext_algebra(output, computed_output); + constraints.extend(diff.to_ext_target_array()); + } + + constraints + } + + fn generators(&self, row: usize, local_constants: &[F]) -> Vec> { + (0..self.num_ops) + .map(|i| { + WitnessGeneratorRef::new( + MulExtensionGenerator { + row, + const_0: local_constants[0], + i, + } + .adapter(), + ) + }) + .collect() + } + + fn num_wires(&self) -> usize { + self.num_ops * 3 * D + } + + fn num_constants(&self) -> usize { + 1 + } + + fn degree(&self) -> usize { + 3 + } + + fn num_constraints(&self) -> usize { + self.num_ops * D + } +} + +#[derive(Clone, Debug, Default)] +pub struct MulExtensionGenerator, const D: usize> { + row: usize, + const_0: F, + i: usize, +} + +impl, const D: usize> SimpleGenerator + for MulExtensionGenerator +{ + fn id(&self) -> String { + "MulExtensionGenerator".to_string() + } + + fn dependencies(&self) -> Vec { + MulExtensionGate::::wires_ith_multiplicand_0(self.i) + .chain(MulExtensionGate::::wires_ith_multiplicand_1(self.i)) + .map(|i| Target::wire(self.row, i)) + .collect() + } + + fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + let extract_extension = |range: Range| -> F::Extension { + let t = ExtensionTarget::from_range(self.row, range); + witness.get_extension_target(t) + }; + + let multiplicand_0 = + extract_extension(MulExtensionGate::::wires_ith_multiplicand_0(self.i)); + let multiplicand_1 = + extract_extension(MulExtensionGate::::wires_ith_multiplicand_1(self.i)); + + let output_target = + ExtensionTarget::from_range(self.row, MulExtensionGate::::wires_ith_output(self.i)); + + let computed_output = (multiplicand_0 * multiplicand_1).scalar_mul(self.const_0); + + out_buffer.set_extension_target(output_target, computed_output) + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.row)?; + dst.write_field(self.const_0)?; + dst.write_usize(self.i) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let row = src.read_usize()?; + let const_0 = src.read_field()?; + let i = src.read_usize()?; + Ok(Self { row, const_0, i }) + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + + use super::*; + use crate::field::goldilocks_field::GoldilocksField; + use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; + use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + + #[test] + fn low_degree() { + let gate = MulExtensionGate::new_from_config(&CircuitConfig::standard_recursion_config()); + test_low_degree::(gate); + } + + #[test] + fn eval_fns() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + let gate = MulExtensionGate::new_from_config(&CircuitConfig::standard_recursion_config()); + test_eval_fns::(gate) + } +} diff --git a/plonky2/src/gates/noop.rs b/plonky2/src/gates/noop.rs new file mode 100644 index 000000000..54cb6422e --- /dev/null +++ b/plonky2/src/gates/noop.rs @@ -0,0 +1,90 @@ +#[cfg(not(feature = "std"))] +use alloc::{string::String, vec::Vec}; + +use crate::field::extension::Extendable; +use crate::gates::gate::Gate; +use crate::hash::hash_types::RichField; +use crate::iop::ext_target::ExtensionTarget; +use crate::iop::generator::WitnessGeneratorRef; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::CommonCircuitData; +use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBaseBatch}; +use crate::util::serialization::{Buffer, IoResult}; + +/// A gate which does nothing. +pub struct NoopGate; + +impl, const D: usize> Gate for NoopGate { + fn id(&self) -> String { + "NoopGate".into() + } + + fn serialize( + &self, + _dst: &mut Vec, + _common_data: &CommonCircuitData, + ) -> IoResult<()> { + Ok(()) + } + + fn deserialize(_src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + Ok(Self) + } + + fn eval_unfiltered(&self, _vars: EvaluationVars) -> Vec { + Vec::new() + } + + fn eval_unfiltered_base_batch(&self, _vars: EvaluationVarsBaseBatch) -> Vec { + Vec::new() + } + + fn eval_unfiltered_circuit( + &self, + _builder: &mut CircuitBuilder, + _vars: EvaluationTargets, + ) -> Vec> { + Vec::new() + } + + fn generators(&self, _row: usize, _local_constants: &[F]) -> Vec> { + Vec::new() + } + + fn num_wires(&self) -> usize { + 0 + } + + fn num_constants(&self) -> usize { + 0 + } + + fn degree(&self) -> usize { + 0 + } + + fn num_constraints(&self) -> usize { + 0 + } +} + +#[cfg(test)] +mod tests { + use crate::field::goldilocks_field::GoldilocksField; + use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; + use crate::gates::noop::NoopGate; + use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + + #[test] + fn low_degree() { + test_low_degree::(NoopGate) + } + + #[test] + fn eval_fns() -> anyhow::Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + test_eval_fns::(NoopGate) + } +} diff --git a/plonky2/src/gates/packed_util.rs b/plonky2/src/gates/packed_util.rs new file mode 100644 index 000000000..8d9b96f37 --- /dev/null +++ b/plonky2/src/gates/packed_util.rs @@ -0,0 +1,43 @@ +#[cfg(not(feature = "std"))] +use alloc::{vec, vec::Vec}; + +use crate::field::extension::Extendable; +use crate::field::packable::Packable; +use crate::field::packed::PackedField; +use crate::gates::gate::Gate; +use crate::gates::util::StridedConstraintConsumer; +use crate::hash::hash_types::RichField; +use crate::plonk::vars::{EvaluationVarsBaseBatch, EvaluationVarsBasePacked}; + +pub trait PackedEvaluableBase, const D: usize>: Gate { + fn eval_unfiltered_base_packed>( + &self, + vars_base: EvaluationVarsBasePacked

, + yield_constr: StridedConstraintConsumer

, + ); + + /// Evaluates entire batch of points. Returns a matrix of constraints. + /// Constraint `j` for point `i` is at `index j * batch_size + i`. + fn eval_unfiltered_base_batch_packed(&self, vars_batch: EvaluationVarsBaseBatch) -> Vec { + let mut res = vec![F::ZERO; vars_batch.len() * self.num_constraints()]; + let (vars_packed_iter, vars_leftovers_iter) = vars_batch.pack::<::Packing>(); + let leftovers_start = vars_batch.len() - vars_leftovers_iter.len(); + for (i, vars_packed) in vars_packed_iter.enumerate() { + self.eval_unfiltered_base_packed( + vars_packed, + StridedConstraintConsumer::new( + &mut res[..], + vars_batch.len(), + ::Packing::WIDTH * i, + ), + ); + } + for (i, vars_leftovers) in vars_leftovers_iter.enumerate() { + self.eval_unfiltered_base_packed( + vars_leftovers, + StridedConstraintConsumer::new(&mut res[..], vars_batch.len(), leftovers_start + i), + ); + } + res + } +} diff --git a/plonky2/src/gates/poseidon.rs b/plonky2/src/gates/poseidon.rs new file mode 100644 index 000000000..f3ed0a831 --- /dev/null +++ b/plonky2/src/gates/poseidon.rs @@ -0,0 +1,641 @@ +#[cfg(not(feature = "std"))] +use alloc::{ + format, + string::{String, ToString}, + vec, + vec::Vec, +}; +use core::marker::PhantomData; + +use crate::field::extension::Extendable; +use crate::field::types::Field; +use crate::gates::gate::Gate; +use crate::gates::poseidon_mds::PoseidonMdsGate; +use crate::gates::util::StridedConstraintConsumer; +use crate::hash::hash_types::RichField; +use crate::hash::poseidon; +use crate::hash::poseidon::{Poseidon, SPONGE_WIDTH}; +use crate::iop::ext_target::ExtensionTarget; +use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGeneratorRef}; +use crate::iop::target::Target; +use crate::iop::wire::Wire; +use crate::iop::witness::{PartitionWitness, Witness, WitnessWrite}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::CommonCircuitData; +use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; +use crate::util::serialization::{Buffer, IoResult, Read, Write}; + +/// Evaluates a full Poseidon permutation with 12 state elements. +/// +/// This also has some extra features to make it suitable for efficiently +/// verifying Merkle proofs. It has a flag which can be used to swap the first +/// four inputs with the next four, for ordering sibling digests. +#[derive(Debug, Default)] +pub struct PoseidonGate, const D: usize>(PhantomData); + +impl, const D: usize> PoseidonGate { + pub const fn new() -> Self { + Self(PhantomData) + } + + /// The wire index for the `i`th input to the permutation. + pub const fn wire_input(i: usize) -> usize { + i + } + + /// The wire index for the `i`th output to the permutation. + pub const fn wire_output(i: usize) -> usize { + SPONGE_WIDTH + i + } + + /// If this is set to 1, the first four inputs will be swapped with the next + /// four inputs. This is useful for ordering hashes in Merkle proofs. + /// Otherwise, this should be set to 0. + pub const WIRE_SWAP: usize = 2 * SPONGE_WIDTH; + + const START_DELTA: usize = 2 * SPONGE_WIDTH + 1; + + /// A wire which stores `swap * (input[i + 4] - input[i])`; used to compute + /// the swapped inputs. + fn wire_delta(i: usize) -> usize { + assert!(i < 4); + Self::START_DELTA + i + } + + const START_FULL_0: usize = Self::START_DELTA + 4; + + /// A wire which stores the input of the `i`-th S-box of the `round`-th + /// round of the first set of full rounds. + fn wire_full_sbox_0(round: usize, i: usize) -> usize { + debug_assert!( + round != 0, + "First round S-box inputs are not stored as wires" + ); + debug_assert!(round < poseidon::HALF_N_FULL_ROUNDS); + debug_assert!(i < SPONGE_WIDTH); + Self::START_FULL_0 + SPONGE_WIDTH * (round - 1) + i + } + + const START_PARTIAL: usize = + Self::START_FULL_0 + SPONGE_WIDTH * (poseidon::HALF_N_FULL_ROUNDS - 1); + + /// A wire which stores the input of the S-box of the `round`-th round of + /// the partial rounds. + fn wire_partial_sbox(round: usize) -> usize { + debug_assert!(round < poseidon::N_PARTIAL_ROUNDS); + Self::START_PARTIAL + round + } + + const START_FULL_1: usize = Self::START_PARTIAL + poseidon::N_PARTIAL_ROUNDS; + + /// A wire which stores the input of the `i`-th S-box of the `round`-th + /// round of the second set of full rounds. + fn wire_full_sbox_1(round: usize, i: usize) -> usize { + debug_assert!(round < poseidon::HALF_N_FULL_ROUNDS); + debug_assert!(i < SPONGE_WIDTH); + Self::START_FULL_1 + SPONGE_WIDTH * round + i + } + + /// End of wire indices, exclusive. + const fn end() -> usize { + Self::START_FULL_1 + SPONGE_WIDTH * poseidon::HALF_N_FULL_ROUNDS + } +} + +impl, const D: usize> Gate for PoseidonGate { + fn id(&self) -> String { + format!("{self:?}") + } + + fn serialize( + &self, + _dst: &mut Vec, + _common_data: &CommonCircuitData, + ) -> IoResult<()> { + Ok(()) + } + + fn deserialize(_src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + Ok(PoseidonGate::new()) + } + + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { + let mut constraints = Vec::with_capacity(self.num_constraints()); + + // Assert that `swap` is binary. + let swap = vars.local_wires[Self::WIRE_SWAP]; + constraints.push(swap * (swap - F::Extension::ONE)); + + // Assert that each delta wire is set properly: `delta_i = swap * (rhs - lhs)`. + for i in 0..4 { + let input_lhs = vars.local_wires[Self::wire_input(i)]; + let input_rhs = vars.local_wires[Self::wire_input(i + 4)]; + let delta_i = vars.local_wires[Self::wire_delta(i)]; + constraints.push(swap * (input_rhs - input_lhs) - delta_i); + } + + // Compute the possibly-swapped input layer. + let mut state = [F::Extension::ZERO; SPONGE_WIDTH]; + for i in 0..4 { + let delta_i = vars.local_wires[Self::wire_delta(i)]; + let input_lhs = Self::wire_input(i); + let input_rhs = Self::wire_input(i + 4); + state[i] = vars.local_wires[input_lhs] + delta_i; + state[i + 4] = vars.local_wires[input_rhs] - delta_i; + } + for i in 8..SPONGE_WIDTH { + state[i] = vars.local_wires[Self::wire_input(i)]; + } + + let mut round_ctr = 0; + + // First set of full rounds. + for r in 0..poseidon::HALF_N_FULL_ROUNDS { + ::constant_layer_field(&mut state, round_ctr); + if r != 0 { + for i in 0..SPONGE_WIDTH { + let sbox_in = vars.local_wires[Self::wire_full_sbox_0(r, i)]; + constraints.push(state[i] - sbox_in); + state[i] = sbox_in; + } + } + ::sbox_layer_field(&mut state); + state = ::mds_layer_field(&state); + round_ctr += 1; + } + + // Partial rounds. + ::partial_first_constant_layer(&mut state); + state = ::mds_partial_layer_init(&state); + for r in 0..(poseidon::N_PARTIAL_ROUNDS - 1) { + let sbox_in = vars.local_wires[Self::wire_partial_sbox(r)]; + constraints.push(state[0] - sbox_in); + state[0] = ::sbox_monomial(sbox_in); + state[0] += + F::Extension::from_canonical_u64(::FAST_PARTIAL_ROUND_CONSTANTS[r]); + state = ::mds_partial_layer_fast_field(&state, r); + } + let sbox_in = vars.local_wires[Self::wire_partial_sbox(poseidon::N_PARTIAL_ROUNDS - 1)]; + constraints.push(state[0] - sbox_in); + state[0] = ::sbox_monomial(sbox_in); + state = + ::mds_partial_layer_fast_field(&state, poseidon::N_PARTIAL_ROUNDS - 1); + round_ctr += poseidon::N_PARTIAL_ROUNDS; + + // Second set of full rounds. + for r in 0..poseidon::HALF_N_FULL_ROUNDS { + ::constant_layer_field(&mut state, round_ctr); + for i in 0..SPONGE_WIDTH { + let sbox_in = vars.local_wires[Self::wire_full_sbox_1(r, i)]; + constraints.push(state[i] - sbox_in); + state[i] = sbox_in; + } + ::sbox_layer_field(&mut state); + state = ::mds_layer_field(&state); + round_ctr += 1; + } + + for i in 0..SPONGE_WIDTH { + constraints.push(state[i] - vars.local_wires[Self::wire_output(i)]); + } + + constraints + } + + fn eval_unfiltered_base_one( + &self, + vars: EvaluationVarsBase, + mut yield_constr: StridedConstraintConsumer, + ) { + // Assert that `swap` is binary. + let swap = vars.local_wires[Self::WIRE_SWAP]; + yield_constr.one(swap * swap.sub_one()); + + // Assert that each delta wire is set properly: `delta_i = swap * (rhs - lhs)`. + for i in 0..4 { + let input_lhs = vars.local_wires[Self::wire_input(i)]; + let input_rhs = vars.local_wires[Self::wire_input(i + 4)]; + let delta_i = vars.local_wires[Self::wire_delta(i)]; + yield_constr.one(swap * (input_rhs - input_lhs) - delta_i); + } + + // Compute the possibly-swapped input layer. + let mut state = [F::ZERO; SPONGE_WIDTH]; + for i in 0..4 { + let delta_i = vars.local_wires[Self::wire_delta(i)]; + let input_lhs = Self::wire_input(i); + let input_rhs = Self::wire_input(i + 4); + state[i] = vars.local_wires[input_lhs] + delta_i; + state[i + 4] = vars.local_wires[input_rhs] - delta_i; + } + for i in 8..SPONGE_WIDTH { + state[i] = vars.local_wires[Self::wire_input(i)]; + } + + let mut round_ctr = 0; + + // First set of full rounds. + for r in 0..poseidon::HALF_N_FULL_ROUNDS { + ::constant_layer(&mut state, round_ctr); + if r != 0 { + for i in 0..SPONGE_WIDTH { + let sbox_in = vars.local_wires[Self::wire_full_sbox_0(r, i)]; + yield_constr.one(state[i] - sbox_in); + state[i] = sbox_in; + } + } + ::sbox_layer(&mut state); + state = ::mds_layer(&state); + round_ctr += 1; + } + + // Partial rounds. + ::partial_first_constant_layer(&mut state); + state = ::mds_partial_layer_init(&state); + for r in 0..(poseidon::N_PARTIAL_ROUNDS - 1) { + let sbox_in = vars.local_wires[Self::wire_partial_sbox(r)]; + yield_constr.one(state[0] - sbox_in); + state[0] = ::sbox_monomial(sbox_in); + state[0] += F::from_canonical_u64(::FAST_PARTIAL_ROUND_CONSTANTS[r]); + state = ::mds_partial_layer_fast(&state, r); + } + let sbox_in = vars.local_wires[Self::wire_partial_sbox(poseidon::N_PARTIAL_ROUNDS - 1)]; + yield_constr.one(state[0] - sbox_in); + state[0] = ::sbox_monomial(sbox_in); + state = ::mds_partial_layer_fast(&state, poseidon::N_PARTIAL_ROUNDS - 1); + round_ctr += poseidon::N_PARTIAL_ROUNDS; + + // Second set of full rounds. + for r in 0..poseidon::HALF_N_FULL_ROUNDS { + ::constant_layer(&mut state, round_ctr); + for i in 0..SPONGE_WIDTH { + let sbox_in = vars.local_wires[Self::wire_full_sbox_1(r, i)]; + yield_constr.one(state[i] - sbox_in); + state[i] = sbox_in; + } + ::sbox_layer(&mut state); + state = ::mds_layer(&state); + round_ctr += 1; + } + + for i in 0..SPONGE_WIDTH { + yield_constr.one(state[i] - vars.local_wires[Self::wire_output(i)]); + } + } + + fn eval_unfiltered_circuit( + &self, + builder: &mut CircuitBuilder, + vars: EvaluationTargets, + ) -> Vec> { + // The naive method is more efficient if we have enough routed wires for + // PoseidonMdsGate. + let use_mds_gate = + builder.config.num_routed_wires >= PoseidonMdsGate::::new().num_wires(); + + let mut constraints = Vec::with_capacity(self.num_constraints()); + + // Assert that `swap` is binary. + let swap = vars.local_wires[Self::WIRE_SWAP]; + constraints.push(builder.mul_sub_extension(swap, swap, swap)); + + // Assert that each delta wire is set properly: `delta_i = swap * (rhs - lhs)`. + for i in 0..4 { + let input_lhs = vars.local_wires[Self::wire_input(i)]; + let input_rhs = vars.local_wires[Self::wire_input(i + 4)]; + let delta_i = vars.local_wires[Self::wire_delta(i)]; + let diff = builder.sub_extension(input_rhs, input_lhs); + constraints.push(builder.mul_sub_extension(swap, diff, delta_i)); + } + + // Compute the possibly-swapped input layer. + let mut state = [builder.zero_extension(); SPONGE_WIDTH]; + for i in 0..4 { + let delta_i = vars.local_wires[Self::wire_delta(i)]; + let input_lhs = vars.local_wires[Self::wire_input(i)]; + let input_rhs = vars.local_wires[Self::wire_input(i + 4)]; + state[i] = builder.add_extension(input_lhs, delta_i); + state[i + 4] = builder.sub_extension(input_rhs, delta_i); + } + for i in 8..SPONGE_WIDTH { + state[i] = vars.local_wires[Self::wire_input(i)]; + } + + let mut round_ctr = 0; + + // First set of full rounds. + for r in 0..poseidon::HALF_N_FULL_ROUNDS { + ::constant_layer_circuit(builder, &mut state, round_ctr); + if r != 0 { + for i in 0..SPONGE_WIDTH { + let sbox_in = vars.local_wires[Self::wire_full_sbox_0(r, i)]; + constraints.push(builder.sub_extension(state[i], sbox_in)); + state[i] = sbox_in; + } + } + ::sbox_layer_circuit(builder, &mut state); + state = ::mds_layer_circuit(builder, &state); + round_ctr += 1; + } + + // Partial rounds. + if use_mds_gate { + for r in 0..poseidon::N_PARTIAL_ROUNDS { + ::constant_layer_circuit(builder, &mut state, round_ctr); + let sbox_in = vars.local_wires[Self::wire_partial_sbox(r)]; + constraints.push(builder.sub_extension(state[0], sbox_in)); + state[0] = ::sbox_monomial_circuit(builder, sbox_in); + state = ::mds_layer_circuit(builder, &state); + round_ctr += 1; + } + } else { + ::partial_first_constant_layer_circuit(builder, &mut state); + state = ::mds_partial_layer_init_circuit(builder, &state); + for r in 0..(poseidon::N_PARTIAL_ROUNDS - 1) { + let sbox_in = vars.local_wires[Self::wire_partial_sbox(r)]; + constraints.push(builder.sub_extension(state[0], sbox_in)); + state[0] = ::sbox_monomial_circuit(builder, sbox_in); + let c = ::FAST_PARTIAL_ROUND_CONSTANTS[r]; + let c = F::Extension::from_canonical_u64(c); + let c = builder.constant_extension(c); + state[0] = builder.add_extension(state[0], c); + state = ::mds_partial_layer_fast_circuit(builder, &state, r); + } + let sbox_in = vars.local_wires[Self::wire_partial_sbox(poseidon::N_PARTIAL_ROUNDS - 1)]; + constraints.push(builder.sub_extension(state[0], sbox_in)); + state[0] = ::sbox_monomial_circuit(builder, sbox_in); + state = ::mds_partial_layer_fast_circuit( + builder, + &state, + poseidon::N_PARTIAL_ROUNDS - 1, + ); + round_ctr += poseidon::N_PARTIAL_ROUNDS; + } + + // Second set of full rounds. + for r in 0..poseidon::HALF_N_FULL_ROUNDS { + ::constant_layer_circuit(builder, &mut state, round_ctr); + for i in 0..SPONGE_WIDTH { + let sbox_in = vars.local_wires[Self::wire_full_sbox_1(r, i)]; + constraints.push(builder.sub_extension(state[i], sbox_in)); + state[i] = sbox_in; + } + ::sbox_layer_circuit(builder, &mut state); + state = ::mds_layer_circuit(builder, &state); + round_ctr += 1; + } + + for i in 0..SPONGE_WIDTH { + constraints + .push(builder.sub_extension(state[i], vars.local_wires[Self::wire_output(i)])); + } + + constraints + } + + fn generators(&self, row: usize, _local_constants: &[F]) -> Vec> { + let gen = PoseidonGenerator:: { + row, + _phantom: PhantomData, + }; + vec![WitnessGeneratorRef::new(gen.adapter())] + } + + fn num_wires(&self) -> usize { + Self::end() + } + + fn num_constants(&self) -> usize { + 0 + } + + fn degree(&self) -> usize { + 7 + } + + fn num_constraints(&self) -> usize { + SPONGE_WIDTH * (poseidon::N_FULL_ROUNDS_TOTAL - 1) + + poseidon::N_PARTIAL_ROUNDS + + SPONGE_WIDTH + + 1 + + 4 + } +} + +#[derive(Debug, Default)] +pub struct PoseidonGenerator + Poseidon, const D: usize> { + row: usize, + _phantom: PhantomData, +} + +impl + Poseidon, const D: usize> SimpleGenerator + for PoseidonGenerator +{ + fn id(&self) -> String { + "PoseidonGenerator".to_string() + } + + fn dependencies(&self) -> Vec { + (0..SPONGE_WIDTH) + .map(|i| PoseidonGate::::wire_input(i)) + .chain(Some(PoseidonGate::::WIRE_SWAP)) + .map(|column| Target::wire(self.row, column)) + .collect() + } + + fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + let local_wire = |column| Wire { + row: self.row, + column, + }; + + let mut state = (0..SPONGE_WIDTH) + .map(|i| witness.get_wire(local_wire(PoseidonGate::::wire_input(i)))) + .collect::>(); + + let swap_value = witness.get_wire(local_wire(PoseidonGate::::WIRE_SWAP)); + debug_assert!(swap_value == F::ZERO || swap_value == F::ONE); + + for i in 0..4 { + let delta_i = swap_value * (state[i + 4] - state[i]); + out_buffer.set_wire(local_wire(PoseidonGate::::wire_delta(i)), delta_i); + } + + if swap_value == F::ONE { + for i in 0..4 { + state.swap(i, 4 + i); + } + } + + let mut state: [F; SPONGE_WIDTH] = state.try_into().unwrap(); + let mut round_ctr = 0; + + for r in 0..poseidon::HALF_N_FULL_ROUNDS { + ::constant_layer_field(&mut state, round_ctr); + if r != 0 { + for i in 0..SPONGE_WIDTH { + out_buffer.set_wire( + local_wire(PoseidonGate::::wire_full_sbox_0(r, i)), + state[i], + ); + } + } + ::sbox_layer_field(&mut state); + state = ::mds_layer_field(&state); + round_ctr += 1; + } + + ::partial_first_constant_layer(&mut state); + state = ::mds_partial_layer_init(&state); + for r in 0..(poseidon::N_PARTIAL_ROUNDS - 1) { + out_buffer.set_wire( + local_wire(PoseidonGate::::wire_partial_sbox(r)), + state[0], + ); + state[0] = ::sbox_monomial(state[0]); + state[0] += F::from_canonical_u64(::FAST_PARTIAL_ROUND_CONSTANTS[r]); + state = ::mds_partial_layer_fast_field(&state, r); + } + out_buffer.set_wire( + local_wire(PoseidonGate::::wire_partial_sbox( + poseidon::N_PARTIAL_ROUNDS - 1, + )), + state[0], + ); + state[0] = ::sbox_monomial(state[0]); + state = + ::mds_partial_layer_fast_field(&state, poseidon::N_PARTIAL_ROUNDS - 1); + round_ctr += poseidon::N_PARTIAL_ROUNDS; + + for r in 0..poseidon::HALF_N_FULL_ROUNDS { + ::constant_layer_field(&mut state, round_ctr); + for i in 0..SPONGE_WIDTH { + out_buffer.set_wire( + local_wire(PoseidonGate::::wire_full_sbox_1(r, i)), + state[i], + ); + } + ::sbox_layer_field(&mut state); + state = ::mds_layer_field(&state); + round_ctr += 1; + } + + for i in 0..SPONGE_WIDTH { + out_buffer.set_wire(local_wire(PoseidonGate::::wire_output(i)), state[i]); + } + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.row) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let row = src.read_usize()?; + Ok(Self { + row, + _phantom: PhantomData, + }) + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + use plonky2_field::goldilocks_field::GoldilocksField; + + use super::*; + use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; + use crate::iop::generator::generate_partial_witness; + use crate::iop::witness::PartialWitness; + use crate::plonk::circuit_data::CircuitConfig; + use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + + #[test] + fn wire_indices() { + type F = GoldilocksField; + type Gate = PoseidonGate; + + assert_eq!(Gate::wire_input(0), 0); + assert_eq!(Gate::wire_input(11), 11); + assert_eq!(Gate::wire_output(0), 12); + assert_eq!(Gate::wire_output(11), 23); + assert_eq!(Gate::WIRE_SWAP, 24); + assert_eq!(Gate::wire_delta(0), 25); + assert_eq!(Gate::wire_delta(3), 28); + assert_eq!(Gate::wire_full_sbox_0(1, 0), 29); + assert_eq!(Gate::wire_full_sbox_0(3, 0), 53); + assert_eq!(Gate::wire_full_sbox_0(3, 11), 64); + assert_eq!(Gate::wire_partial_sbox(0), 65); + assert_eq!(Gate::wire_partial_sbox(21), 86); + assert_eq!(Gate::wire_full_sbox_1(0, 0), 87); + assert_eq!(Gate::wire_full_sbox_1(3, 0), 123); + assert_eq!(Gate::wire_full_sbox_1(3, 11), 134); + } + + #[test] + fn generated_output() { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + + let config = CircuitConfig { + num_wires: 143, + ..CircuitConfig::standard_recursion_config() + }; + let mut builder = CircuitBuilder::new(config); + type Gate = PoseidonGate; + let gate = Gate::new(); + let row = builder.add_gate(gate, vec![]); + let circuit = builder.build_prover::(); + + let permutation_inputs = (0..SPONGE_WIDTH) + .map(F::from_canonical_usize) + .collect::>(); + + let mut inputs = PartialWitness::new(); + inputs.set_wire( + Wire { + row, + column: Gate::WIRE_SWAP, + }, + F::ZERO, + ); + for i in 0..SPONGE_WIDTH { + inputs.set_wire( + Wire { + row, + column: Gate::wire_input(i), + }, + permutation_inputs[i], + ); + } + + let witness = generate_partial_witness(inputs, &circuit.prover_only, &circuit.common); + + let expected_outputs: [F; SPONGE_WIDTH] = + F::poseidon(permutation_inputs.try_into().unwrap()); + for i in 0..SPONGE_WIDTH { + let out = witness.get_wire(Wire { + row: 0, + column: Gate::wire_output(i), + }); + assert_eq!(out, expected_outputs[i]); + } + } + + #[test] + fn low_degree() { + type F = GoldilocksField; + let gate = PoseidonGate::::new(); + test_low_degree(gate) + } + + #[test] + fn eval_fns() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + let gate = PoseidonGate::::new(); + test_eval_fns::(gate) + } +} diff --git a/plonky2/src/gates/poseidon_mds.rs b/plonky2/src/gates/poseidon_mds.rs new file mode 100644 index 000000000..ffaa937f6 --- /dev/null +++ b/plonky2/src/gates/poseidon_mds.rs @@ -0,0 +1,296 @@ +#[cfg(not(feature = "std"))] +use alloc::{ + format, + string::{String, ToString}, + vec, + vec::Vec, +}; +use core::marker::PhantomData; +use core::ops::Range; + +use crate::field::extension::algebra::ExtensionAlgebra; +use crate::field::extension::{Extendable, FieldExtension}; +use crate::field::types::Field; +use crate::gates::gate::Gate; +use crate::gates::util::StridedConstraintConsumer; +use crate::hash::hash_types::RichField; +use crate::hash::poseidon::{Poseidon, SPONGE_WIDTH}; +use crate::iop::ext_target::{ExtensionAlgebraTarget, ExtensionTarget}; +use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGeneratorRef}; +use crate::iop::target::Target; +use crate::iop::witness::{PartitionWitness, Witness, WitnessWrite}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::CommonCircuitData; +use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; +use crate::util::serialization::{Buffer, IoResult, Read, Write}; + +/// Poseidon MDS Gate +#[derive(Debug, Default)] +pub struct PoseidonMdsGate + Poseidon, const D: usize>(PhantomData); + +impl + Poseidon, const D: usize> PoseidonMdsGate { + pub const fn new() -> Self { + Self(PhantomData) + } + + pub fn wires_input(i: usize) -> Range { + assert!(i < SPONGE_WIDTH); + i * D..(i + 1) * D + } + + pub fn wires_output(i: usize) -> Range { + assert!(i < SPONGE_WIDTH); + (SPONGE_WIDTH + i) * D..(SPONGE_WIDTH + i + 1) * D + } + + // Following are methods analogous to ones in `Poseidon`, but for extension + // algebras. + + /// Same as `mds_row_shf` for an extension algebra of `F`. + fn mds_row_shf_algebra( + r: usize, + v: &[ExtensionAlgebra; SPONGE_WIDTH], + ) -> ExtensionAlgebra { + debug_assert!(r < SPONGE_WIDTH); + let mut res = ExtensionAlgebra::ZERO; + + for i in 0..SPONGE_WIDTH { + let coeff = F::Extension::from_canonical_u64(::MDS_MATRIX_CIRC[i]); + res += v[(i + r) % SPONGE_WIDTH].scalar_mul(coeff); + } + { + let coeff = F::Extension::from_canonical_u64(::MDS_MATRIX_DIAG[r]); + res += v[r].scalar_mul(coeff); + } + + res + } + + /// Same as `mds_row_shf_recursive` for an extension algebra of `F`. + fn mds_row_shf_algebra_circuit( + builder: &mut CircuitBuilder, + r: usize, + v: &[ExtensionAlgebraTarget; SPONGE_WIDTH], + ) -> ExtensionAlgebraTarget { + debug_assert!(r < SPONGE_WIDTH); + let mut res = builder.zero_ext_algebra(); + + for i in 0..SPONGE_WIDTH { + let coeff = builder.constant_extension(F::Extension::from_canonical_u64( + ::MDS_MATRIX_CIRC[i], + )); + res = builder.scalar_mul_add_ext_algebra(coeff, v[(i + r) % SPONGE_WIDTH], res); + } + { + let coeff = builder.constant_extension(F::Extension::from_canonical_u64( + ::MDS_MATRIX_DIAG[r], + )); + res = builder.scalar_mul_add_ext_algebra(coeff, v[r], res); + } + + res + } + + /// Same as `mds_layer` for an extension algebra of `F`. + fn mds_layer_algebra( + state: &[ExtensionAlgebra; SPONGE_WIDTH], + ) -> [ExtensionAlgebra; SPONGE_WIDTH] { + let mut result = [ExtensionAlgebra::ZERO; SPONGE_WIDTH]; + + for r in 0..SPONGE_WIDTH { + result[r] = Self::mds_row_shf_algebra(r, state); + } + + result + } + + /// Same as `mds_layer_recursive` for an extension algebra of `F`. + fn mds_layer_algebra_circuit( + builder: &mut CircuitBuilder, + state: &[ExtensionAlgebraTarget; SPONGE_WIDTH], + ) -> [ExtensionAlgebraTarget; SPONGE_WIDTH] { + let mut result = [builder.zero_ext_algebra(); SPONGE_WIDTH]; + + for r in 0..SPONGE_WIDTH { + result[r] = Self::mds_row_shf_algebra_circuit(builder, r, state); + } + + result + } +} + +impl + Poseidon, const D: usize> Gate for PoseidonMdsGate { + fn id(&self) -> String { + format!("{self:?}") + } + + fn serialize( + &self, + _dst: &mut Vec, + _common_data: &CommonCircuitData, + ) -> IoResult<()> { + Ok(()) + } + + fn deserialize(_src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + Ok(PoseidonMdsGate::new()) + } + + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { + let inputs: [_; SPONGE_WIDTH] = (0..SPONGE_WIDTH) + .map(|i| vars.get_local_ext_algebra(Self::wires_input(i))) + .collect::>() + .try_into() + .unwrap(); + + let computed_outputs = Self::mds_layer_algebra(&inputs); + + (0..SPONGE_WIDTH) + .map(|i| vars.get_local_ext_algebra(Self::wires_output(i))) + .zip(computed_outputs) + .flat_map(|(out, computed_out)| (out - computed_out).to_basefield_array()) + .collect() + } + + fn eval_unfiltered_base_one( + &self, + vars: EvaluationVarsBase, + mut yield_constr: StridedConstraintConsumer, + ) { + let inputs: [_; SPONGE_WIDTH] = (0..SPONGE_WIDTH) + .map(|i| vars.get_local_ext(Self::wires_input(i))) + .collect::>() + .try_into() + .unwrap(); + + let computed_outputs = F::mds_layer_field(&inputs); + + yield_constr.many( + (0..SPONGE_WIDTH) + .map(|i| vars.get_local_ext(Self::wires_output(i))) + .zip(computed_outputs) + .flat_map(|(out, computed_out)| (out - computed_out).to_basefield_array()), + ) + } + + fn eval_unfiltered_circuit( + &self, + builder: &mut CircuitBuilder, + vars: EvaluationTargets, + ) -> Vec> { + let inputs: [_; SPONGE_WIDTH] = (0..SPONGE_WIDTH) + .map(|i| vars.get_local_ext_algebra(Self::wires_input(i))) + .collect::>() + .try_into() + .unwrap(); + + let computed_outputs = Self::mds_layer_algebra_circuit(builder, &inputs); + + (0..SPONGE_WIDTH) + .map(|i| vars.get_local_ext_algebra(Self::wires_output(i))) + .zip(computed_outputs) + .flat_map(|(out, computed_out)| { + builder + .sub_ext_algebra(out, computed_out) + .to_ext_target_array() + }) + .collect() + } + + fn generators(&self, row: usize, _local_constants: &[F]) -> Vec> { + let gen = PoseidonMdsGenerator:: { row }; + vec![WitnessGeneratorRef::new(gen.adapter())] + } + + fn num_wires(&self) -> usize { + 2 * D * SPONGE_WIDTH + } + + fn num_constants(&self) -> usize { + 0 + } + + fn degree(&self) -> usize { + 1 + } + + fn num_constraints(&self) -> usize { + SPONGE_WIDTH * D + } +} + +#[derive(Clone, Debug, Default)] +pub struct PoseidonMdsGenerator { + row: usize, +} + +impl + Poseidon, const D: usize> SimpleGenerator + for PoseidonMdsGenerator +{ + fn id(&self) -> String { + "PoseidonMdsGenerator".to_string() + } + + fn dependencies(&self) -> Vec { + (0..SPONGE_WIDTH) + .flat_map(|i| { + Target::wires_from_range(self.row, PoseidonMdsGate::::wires_input(i)) + }) + .collect() + } + + fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + let get_local_get_target = |wire_range| ExtensionTarget::from_range(self.row, wire_range); + let get_local_ext = + |wire_range| witness.get_extension_target(get_local_get_target(wire_range)); + + let inputs: [_; SPONGE_WIDTH] = (0..SPONGE_WIDTH) + .map(|i| get_local_ext(PoseidonMdsGate::::wires_input(i))) + .collect::>() + .try_into() + .unwrap(); + + let outputs = F::mds_layer_field(&inputs); + + for (i, &out) in outputs.iter().enumerate() { + out_buffer.set_extension_target( + get_local_get_target(PoseidonMdsGate::::wires_output(i)), + out, + ); + } + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.row) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let row = src.read_usize()?; + Ok(Self { row }) + } +} + +#[cfg(test)] +mod tests { + use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; + use crate::gates::poseidon_mds::PoseidonMdsGate; + use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + + #[test] + fn low_degree() { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + let gate = PoseidonMdsGate::::new(); + test_low_degree(gate) + } + + #[test] + fn eval_fns() -> anyhow::Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + let gate = PoseidonMdsGate::::new(); + test_eval_fns::(gate) + } +} diff --git a/plonky2/src/gates/public_input.rs b/plonky2/src/gates/public_input.rs new file mode 100644 index 000000000..3a754c7c9 --- /dev/null +++ b/plonky2/src/gates/public_input.rs @@ -0,0 +1,134 @@ +#[cfg(not(feature = "std"))] +use alloc::{string::String, vec::Vec}; +use core::ops::Range; + +use crate::field::extension::Extendable; +use crate::field::packed::PackedField; +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::WitnessGeneratorRef; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::CommonCircuitData; +use crate::plonk::vars::{ + EvaluationTargets, EvaluationVars, EvaluationVarsBase, EvaluationVarsBaseBatch, + EvaluationVarsBasePacked, +}; +use crate::util::serialization::{Buffer, IoResult}; + +/// A gate whose first four wires will be equal to a hash of public inputs. +pub struct PublicInputGate; + +impl PublicInputGate { + pub const fn wires_public_inputs_hash() -> Range { + 0..4 + } +} + +impl, const D: usize> Gate for PublicInputGate { + fn id(&self) -> String { + "PublicInputGate".into() + } + + fn serialize( + &self, + _dst: &mut Vec, + _common_data: &CommonCircuitData, + ) -> IoResult<()> { + Ok(()) + } + + fn deserialize(_src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + Ok(Self) + } + + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { + Self::wires_public_inputs_hash() + .zip(vars.public_inputs_hash.elements) + .map(|(wire, hash_part)| vars.local_wires[wire] - hash_part.into()) + .collect() + } + + 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> { + Self::wires_public_inputs_hash() + .zip(vars.public_inputs_hash.elements) + .map(|(wire, hash_part)| { + let hash_part_ext = builder.convert_to_ext(hash_part); + builder.sub_extension(vars.local_wires[wire], hash_part_ext) + }) + .collect() + } + + fn generators(&self, _row: usize, _local_constants: &[F]) -> Vec> { + Vec::new() + } + + fn num_wires(&self) -> usize { + 4 + } + + fn num_constants(&self) -> usize { + 0 + } + + fn degree(&self) -> usize { + 1 + } + + fn num_constraints(&self) -> usize { + 4 + } +} + +impl, const D: usize> PackedEvaluableBase for PublicInputGate { + fn eval_unfiltered_base_packed>( + &self, + vars: EvaluationVarsBasePacked

, + mut yield_constr: StridedConstraintConsumer

, + ) { + yield_constr.many( + Self::wires_public_inputs_hash() + .zip(vars.public_inputs_hash.elements) + .map(|(wire, hash_part)| vars.local_wires[wire] - hash_part), + ) + } +} + +#[cfg(test)] +mod tests { + use crate::field::goldilocks_field::GoldilocksField; + use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; + use crate::gates::public_input::PublicInputGate; + use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + + #[test] + fn low_degree() { + test_low_degree::(PublicInputGate) + } + + #[test] + fn eval_fns() -> anyhow::Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + test_eval_fns::(PublicInputGate) + } +} diff --git a/plonky2/src/gates/random_access.rs b/plonky2/src/gates/random_access.rs new file mode 100644 index 000000000..c31377d16 --- /dev/null +++ b/plonky2/src/gates/random_access.rs @@ -0,0 +1,537 @@ +#[cfg(not(feature = "std"))] +use alloc::{ + format, + string::{String, ToString}, + vec, + vec::Vec, +}; +use core::marker::PhantomData; + +use itertools::Itertools; + +use crate::field::extension::Extendable; +use crate::field::packed::PackedField; +use crate::field::types::Field; +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, WitnessGeneratorRef}; +use crate::iop::target::Target; +use crate::iop::wire::Wire; +use crate::iop::witness::{PartitionWitness, Witness, WitnessWrite}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::{CircuitConfig, CommonCircuitData}; +use crate::plonk::vars::{ + EvaluationTargets, EvaluationVars, EvaluationVarsBase, EvaluationVarsBaseBatch, + EvaluationVarsBasePacked, +}; +use crate::util::serialization::{Buffer, IoResult, Read, Write}; + +/// A gate for checking that a particular element of a list matches a given +/// value. +#[derive(Copy, Clone, Debug, Default)] +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, +} + +impl, const D: usize> RandomAccessGate { + const fn new(num_copies: usize, bits: usize, num_extra_constants: usize) -> Self { + Self { + bits, + num_copies, + num_extra_constants, + _phantom: PhantomData, + } + } + + pub fn new_from_config(config: &CircuitConfig, bits: usize) -> Self { + // We can access a list of 2^bits elements. + let vec_size = 1 << bits; + + // We need `(2 + vec_size) * num_copies` routed wires. + let max_copies = (config.num_routed_wires / (2 + vec_size)).min( + // 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, + max_extra_constants.min(config.num_constants), + ) + } + + /// Length of the list being accessed. + const 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); + (2 + self.vec_size()) * copy + 2 + i + } + + const fn start_extra_constants(&self) -> usize { + (2 + self.vec_size()) * self.num_copies + } + + fn wire_extra_constant(&self, i: usize) -> usize { + debug_assert!(i < self.num_extra_constants); + self.start_extra_constants() + i + } + + /// All above wires are routed. + pub const fn num_routed_wires(&self) -> usize { + self.start_extra_constants() + self.num_extra_constants + } + + /// An intermediate wire where the prover gives the (purported) binary + /// decomposition of the index. + pub fn wire_bit(&self, i: usize, copy: usize) -> usize { + debug_assert!(i < self.bits); + debug_assert!(copy < self.num_copies); + self.num_routed_wires() + copy * self.bits + i + } +} + +impl, const D: usize> Gate for RandomAccessGate { + fn id(&self) -> String { + format!("{self:?}") + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.bits)?; + dst.write_usize(self.num_copies)?; + dst.write_usize(self.num_extra_constants)?; + Ok(()) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let bits = src.read_usize()?; + let num_copies = src.read_usize()?; + let num_extra_constants = src.read_usize()?; + Ok(Self::new(num_copies, bits, num_extra_constants)) + } + + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { + let mut constraints = Vec::with_capacity(self.num_constraints()); + + for copy in 0..self.num_copies { + let access_index = vars.local_wires[self.wire_access_index(copy)]; + let mut list_items = (0..self.vec_size()) + .map(|i| vars.local_wires[self.wire_list_item(i, copy)]) + .collect::>(); + let claimed_element = vars.local_wires[self.wire_claimed_element(copy)]; + let bits = (0..self.bits) + .map(|i| vars.local_wires[self.wire_bit(i, copy)]) + .collect::>(); + + // Assert that each bit wire value is indeed boolean. + for &b in &bits { + constraints.push(b * (b - F::Extension::ONE)); + } + + // Assert that the binary decomposition was correct. + let reconstructed_index = bits + .iter() + .rev() + .fold(F::Extension::ZERO, |acc, &b| acc.double() + b); + constraints.push(reconstructed_index - access_index); + + // Repeatedly fold the list, selecting the left or right item from each pair + // based on the corresponding bit. + for b in bits { + list_items = list_items + .iter() + .tuples() + .map(|(&x, &y)| x + b * (y - x)) + .collect() + } + + debug_assert_eq!(list_items.len(), 1); + constraints.push(list_items[0] - claimed_element); + } + + constraints.extend( + (0..self.num_extra_constants) + .map(|i| vars.local_constants[i] - vars.local_wires[self.wire_extra_constant(i)]), + ); + + 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 zero = builder.zero_extension(); + let two = builder.two_extension(); + let mut constraints = Vec::with_capacity(self.num_constraints()); + + for copy in 0..self.num_copies { + let access_index = vars.local_wires[self.wire_access_index(copy)]; + let mut list_items = (0..self.vec_size()) + .map(|i| vars.local_wires[self.wire_list_item(i, copy)]) + .collect::>(); + let claimed_element = vars.local_wires[self.wire_claimed_element(copy)]; + let bits = (0..self.bits) + .map(|i| vars.local_wires[self.wire_bit(i, copy)]) + .collect::>(); + + // Assert that each bit wire value is indeed boolean. + for &b in &bits { + constraints.push(builder.mul_sub_extension(b, b, b)); + } + + // Assert that the binary decomposition was correct. + let reconstructed_index = bits + .iter() + .rev() + .fold(zero, |acc, &b| builder.mul_add_extension(acc, two, b)); + constraints.push(builder.sub_extension(reconstructed_index, access_index)); + + // Repeatedly fold the list, selecting the left or right item from each pair + // based on the corresponding bit. + for b in bits { + list_items = list_items + .iter() + .tuples() + .map(|(&x, &y)| builder.select_ext_generalized(b, y, x)) + .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], + vars.local_wires[self.wire_extra_constant(i)], + ) + })); + + constraints + } + + fn generators(&self, row: usize, _local_constants: &[F]) -> Vec> { + (0..self.num_copies) + .map(|copy| { + WitnessGeneratorRef::new( + RandomAccessGenerator { + row, + gate: *self, + copy, + } + .adapter(), + ) + }) + .collect() + } + + fn num_wires(&self) -> usize { + self.wire_bit(self.bits - 1, self.num_copies - 1) + 1 + } + + fn num_constants(&self) -> usize { + self.num_extra_constants + } + + fn degree(&self) -> usize { + self.bits + 1 + } + + fn num_constraints(&self) -> usize { + let constraints_per_copy = self.bits + 2; + self.num_copies * constraints_per_copy + self.num_extra_constants + } + + fn extra_constant_wires(&self) -> Vec<(usize, usize)> { + (0..self.num_extra_constants) + .map(|i| (i, self.wire_extra_constant(i))) + .collect() + } +} + +impl, const D: usize> PackedEvaluableBase + for RandomAccessGate +{ + fn eval_unfiltered_base_packed>( + &self, + vars: EvaluationVarsBasePacked

, + mut yield_constr: StridedConstraintConsumer

, + ) { + for copy in 0..self.num_copies { + let access_index = vars.local_wires[self.wire_access_index(copy)]; + let mut list_items = (0..self.vec_size()) + .map(|i| vars.local_wires[self.wire_list_item(i, copy)]) + .collect::>(); + let claimed_element = vars.local_wires[self.wire_claimed_element(copy)]; + let bits = (0..self.bits) + .map(|i| vars.local_wires[self.wire_bit(i, copy)]) + .collect::>(); + + // Assert that each bit wire value is indeed boolean. + for &b in &bits { + yield_constr.one(b * (b - F::ONE)); + } + + // Assert that the binary decomposition was correct. + let reconstructed_index = bits.iter().rev().fold(P::ZEROS, |acc, &b| acc + acc + b); + yield_constr.one(reconstructed_index - access_index); + + // Repeatedly fold the list, selecting the left or right item from each pair + // based on the corresponding bit. + for b in bits { + list_items = list_items + .iter() + .tuples() + .map(|(&x, &y)| x + b * (y - x)) + .collect() + } + + debug_assert_eq!(list_items.len(), 1); + yield_constr.one(list_items[0] - claimed_element); + } + yield_constr.many( + (0..self.num_extra_constants) + .map(|i| vars.local_constants[i] - vars.local_wires[self.wire_extra_constant(i)]), + ); + } +} + +#[derive(Debug, Default)] +pub struct RandomAccessGenerator, const D: usize> { + row: usize, + gate: RandomAccessGate, + copy: usize, +} + +impl, const D: usize> SimpleGenerator + for RandomAccessGenerator +{ + fn id(&self) -> String { + "RandomAccessGenerator".to_string() + } + + fn dependencies(&self) -> Vec { + let local_target = |column| Target::wire(self.row, column); + + let mut deps = vec![local_target(self.gate.wire_access_index(self.copy))]; + for i in 0..self.gate.vec_size() { + deps.push(local_target(self.gate.wire_list_item(i, self.copy))); + } + 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 mut set_local_wire = |column, value| out_buffer.set_wire(local_wire(column), value); + + let copy = self.copy; + let vec_size = self.gate.vec_size(); + + let access_index_f = get_local_wire(self.gate.wire_access_index(copy)); + let access_index = access_index_f.to_canonical_u64() as usize; + debug_assert!( + access_index < vec_size, + "Access index {} is larger than the vector size {}", + access_index, + vec_size + ); + + set_local_wire( + self.gate.wire_claimed_element(copy), + get_local_wire(self.gate.wire_list_item(access_index, copy)), + ); + + for i in 0..self.gate.bits { + let bit = F::from_bool(((access_index >> i) & 1) != 0); + set_local_wire(self.gate.wire_bit(i, copy), bit); + } + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.row)?; + dst.write_usize(self.copy)?; + self.gate.serialize(dst, _common_data) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let row = src.read_usize()?; + let copy = src.read_usize()?; + let gate = RandomAccessGate::::deserialize(src, _common_data)?; + Ok(Self { row, gate, copy }) + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + use rand::rngs::OsRng; + use rand::Rng; + + use super::*; + use crate::field::goldilocks_field::GoldilocksField; + use crate::field::types::Sample; + use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; + use crate::hash::hash_types::HashOut; + use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + + #[test] + fn low_degree() { + test_low_degree::(RandomAccessGate::new(4, 4, 1)); + } + + #[test] + fn eval_fns() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + test_eval_fns::(RandomAccessGate::new(4, 4, 1)) + } + + #[test] + fn test_gate_constraint() { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type FF = >::FE; + + /// Returns the local wires for a random access gate given the vectors, + /// elements to compare, and indices. + fn get_wires( + bits: usize, + lists: Vec>, + access_indices: Vec, + claimed_elements: Vec, + constants: &[F], + ) -> Vec { + let num_copies = lists.len(); + let vec_size = lists[0].len(); + + let mut v = Vec::new(); + let mut bit_vals = Vec::new(); + for copy in 0..num_copies { + let access_index = access_indices[copy]; + v.push(F::from_canonical_usize(access_index)); + v.push(claimed_elements[copy]); + for j in 0..vec_size { + v.push(lists[copy][j]); + } + + for i in 0..bits { + bit_vals.push(F::from_bool(((access_index >> i) & 1) != 0)); + } + } + v.extend(constants); + v.extend(bit_vals); + + v.iter().map(|&x| x.into()).collect() + } + + let bits = 3; + let vec_size = 1 << bits; + let num_copies = 4; + let lists = (0..num_copies) + .map(|_| F::rand_vec(vec_size)) + .collect::>(); + let access_indices = (0..num_copies) + .map(|_| OsRng.gen_range(0..vec_size)) + .collect::>(); + let gate = RandomAccessGate:: { + bits, + num_copies, + num_extra_constants: 1, + _phantom: PhantomData, + }; + let constants = F::rand_vec(gate.num_constants()); + + let good_claimed_elements = lists + .iter() + .zip(&access_indices) + .map(|(l, &i)| l[i]) + .collect(); + let good_vars = EvaluationVars { + local_constants: &constants.iter().map(|&x| x.into()).collect::>(), + local_wires: &get_wires( + bits, + lists.clone(), + access_indices.clone(), + good_claimed_elements, + &constants, + ), + public_inputs_hash: &HashOut::rand(), + }; + let bad_claimed_elements = F::rand_vec(4); + let bad_vars = EvaluationVars { + local_constants: &constants.iter().map(|&x| x.into()).collect::>(), + local_wires: &get_wires( + bits, + lists, + access_indices, + bad_claimed_elements, + &constants, + ), + public_inputs_hash: &HashOut::rand(), + }; + + assert!( + gate.eval_unfiltered(good_vars).iter().all(|x| x.is_zero()), + "Gate constraints are not satisfied." + ); + assert!( + !gate.eval_unfiltered(bad_vars).iter().all(|x| x.is_zero()), + "Gate constraints are satisfied but should not be." + ); + } +} diff --git a/plonky2/src/gates/reducing.rs b/plonky2/src/gates/reducing.rs new file mode 100644 index 000000000..a2dd34975 --- /dev/null +++ b/plonky2/src/gates/reducing.rs @@ -0,0 +1,267 @@ +#[cfg(not(feature = "std"))] +use alloc::{ + format, + string::{String, ToString}, + vec, + vec::Vec, +}; +use core::ops::Range; + +use crate::field::extension::{Extendable, FieldExtension}; +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, WitnessGeneratorRef}; +use crate::iop::target::Target; +use crate::iop::witness::{PartitionWitness, Witness, WitnessWrite}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::CommonCircuitData; +use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; +use crate::util::serialization::{Buffer, IoResult, Read, Write}; + +/// Computes `sum alpha^i c_i` for a vector `c_i` of `num_coeffs` elements of +/// the base field. +#[derive(Debug, Default, Clone)] +pub struct ReducingGate { + pub num_coeffs: usize, +} + +impl ReducingGate { + pub const fn new(num_coeffs: usize) -> Self { + Self { num_coeffs } + } + + pub fn max_coeffs_len(num_wires: usize, num_routed_wires: usize) -> usize { + (num_routed_wires - 3 * D).min((num_wires - 2 * D) / (D + 1)) + } + + pub const fn wires_output() -> Range { + 0..D + } + pub const fn wires_alpha() -> Range { + D..2 * D + } + pub const fn wires_old_acc() -> Range { + 2 * D..3 * D + } + const START_COEFFS: usize = 3 * D; + pub const fn wires_coeffs(&self) -> Range { + Self::START_COEFFS..Self::START_COEFFS + self.num_coeffs + } + const fn start_accs(&self) -> usize { + Self::START_COEFFS + self.num_coeffs + } + const fn wires_accs(&self, i: usize) -> Range { + if i == self.num_coeffs - 1 { + // The last accumulator is the output. + return Self::wires_output(); + } + self.start_accs() + D * i..self.start_accs() + D * (i + 1) + } +} + +impl, const D: usize> Gate for ReducingGate { + fn id(&self) -> String { + format!("{self:?}") + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.num_coeffs)?; + Ok(()) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult + where + Self: Sized, + { + let num_coeffs = src.read_usize()?; + Ok(Self::new(num_coeffs)) + } + + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { + let alpha = vars.get_local_ext_algebra(Self::wires_alpha()); + let old_acc = vars.get_local_ext_algebra(Self::wires_old_acc()); + let coeffs = self + .wires_coeffs() + .map(|i| vars.local_wires[i]) + .collect::>(); + let accs = (0..self.num_coeffs) + .map(|i| vars.get_local_ext_algebra(self.wires_accs(i))) + .collect::>(); + + let mut constraints = Vec::with_capacity(>::num_constraints(self)); + let mut acc = old_acc; + for i in 0..self.num_coeffs { + constraints.push(acc * alpha + coeffs[i].into() - accs[i]); + acc = accs[i]; + } + + constraints + .into_iter() + .flat_map(|alg| alg.to_basefield_array()) + .collect() + } + + fn eval_unfiltered_base_one( + &self, + vars: EvaluationVarsBase, + mut yield_constr: StridedConstraintConsumer, + ) { + let alpha = vars.get_local_ext(Self::wires_alpha()); + let old_acc = vars.get_local_ext(Self::wires_old_acc()); + let coeffs = self + .wires_coeffs() + .map(|i| vars.local_wires[i]) + .collect::>(); + let accs = (0..self.num_coeffs) + .map(|i| vars.get_local_ext(self.wires_accs(i))) + .collect::>(); + + let mut acc = old_acc; + for i in 0..self.num_coeffs { + yield_constr.many((acc * alpha + coeffs[i].into() - accs[i]).to_basefield_array()); + acc = accs[i]; + } + } + + fn eval_unfiltered_circuit( + &self, + builder: &mut CircuitBuilder, + vars: EvaluationTargets, + ) -> Vec> { + let alpha = vars.get_local_ext_algebra(Self::wires_alpha()); + let old_acc = vars.get_local_ext_algebra(Self::wires_old_acc()); + let coeffs = self + .wires_coeffs() + .map(|i| vars.local_wires[i]) + .collect::>(); + let accs = (0..self.num_coeffs) + .map(|i| vars.get_local_ext_algebra(self.wires_accs(i))) + .collect::>(); + + let mut constraints = Vec::with_capacity(>::num_constraints(self)); + let mut acc = old_acc; + for i in 0..self.num_coeffs { + let coeff = builder.convert_to_ext_algebra(coeffs[i]); + let mut tmp = builder.mul_add_ext_algebra(acc, alpha, coeff); + tmp = builder.sub_ext_algebra(tmp, accs[i]); + constraints.push(tmp); + acc = accs[i]; + } + + constraints + .into_iter() + .flat_map(|alg| alg.to_ext_target_array()) + .collect() + } + + fn generators(&self, row: usize, _local_constants: &[F]) -> Vec> { + vec![WitnessGeneratorRef::new( + ReducingGenerator { + row, + gate: self.clone(), + } + .adapter(), + )] + } + + fn num_wires(&self) -> usize { + 2 * D + self.num_coeffs * (D + 1) + } + + fn num_constants(&self) -> usize { + 0 + } + + fn degree(&self) -> usize { + 2 + } + + fn num_constraints(&self) -> usize { + D * self.num_coeffs + } +} + +#[derive(Debug, Default)] +pub struct ReducingGenerator { + row: usize, + gate: ReducingGate, +} + +impl, const D: usize> SimpleGenerator for ReducingGenerator { + fn id(&self) -> String { + "ReducingGenerator".to_string() + } + + fn dependencies(&self) -> Vec { + ReducingGate::::wires_alpha() + .chain(ReducingGate::::wires_old_acc()) + .chain(self.gate.wires_coeffs()) + .map(|i| Target::wire(self.row, i)) + .collect() + } + + fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + let extract_extension = |range: Range| -> F::Extension { + let t = ExtensionTarget::from_range(self.row, range); + witness.get_extension_target(t) + }; + + let alpha = extract_extension(ReducingGate::::wires_alpha()); + let old_acc = extract_extension(ReducingGate::::wires_old_acc()); + let coeffs = witness.get_targets( + &self + .gate + .wires_coeffs() + .map(|i| Target::wire(self.row, i)) + .collect::>(), + ); + let accs = (0..self.gate.num_coeffs) + .map(|i| ExtensionTarget::from_range(self.row, self.gate.wires_accs(i))) + .collect::>(); + let output = ExtensionTarget::from_range(self.row, ReducingGate::::wires_output()); + + let mut acc = old_acc; + for i in 0..self.gate.num_coeffs { + let computed_acc = acc * alpha + coeffs[i].into(); + out_buffer.set_extension_target(accs[i], computed_acc); + acc = computed_acc; + } + out_buffer.set_extension_target(output, acc); + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.row)?; + as Gate>::serialize(&self.gate, dst, _common_data) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let row = src.read_usize()?; + let gate = as Gate>::deserialize(src, _common_data)?; + Ok(Self { row, gate }) + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + + use crate::field::goldilocks_field::GoldilocksField; + use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; + use crate::gates::reducing::ReducingGate; + use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + + #[test] + fn low_degree() { + test_low_degree::(ReducingGate::new(22)); + } + + #[test] + fn eval_fns() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + test_eval_fns::(ReducingGate::new(22)) + } +} diff --git a/plonky2/src/gates/reducing_extension.rs b/plonky2/src/gates/reducing_extension.rs new file mode 100644 index 000000000..02ba58804 --- /dev/null +++ b/plonky2/src/gates/reducing_extension.rs @@ -0,0 +1,262 @@ +#[cfg(not(feature = "std"))] +use alloc::{ + format, + string::{String, ToString}, + vec, + vec::Vec, +}; +use core::ops::Range; + +use crate::field::extension::{Extendable, FieldExtension}; +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, WitnessGeneratorRef}; +use crate::iop::target::Target; +use crate::iop::witness::{PartitionWitness, Witness, WitnessWrite}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::CommonCircuitData; +use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; +use crate::util::serialization::{Buffer, IoResult, Read, Write}; + +/// Computes `sum alpha^i c_i` for a vector `c_i` of `num_coeffs` elements of +/// the extension field. +#[derive(Debug, Clone, Default)] +pub struct ReducingExtensionGate { + pub num_coeffs: usize, +} + +impl ReducingExtensionGate { + pub const fn new(num_coeffs: usize) -> Self { + Self { num_coeffs } + } + + pub fn max_coeffs_len(num_wires: usize, num_routed_wires: usize) -> usize { + // `3*D` routed wires are used for the output, alpha and old accumulator. + // Need `num_coeffs*D` routed wires for coeffs, and `(num_coeffs-1)*D` wires for + // accumulators. + ((num_routed_wires - 3 * D) / D).min((num_wires - 2 * D) / (D * 2)) + } + + pub const fn wires_output() -> Range { + 0..D + } + pub const fn wires_alpha() -> Range { + D..2 * D + } + pub const fn wires_old_acc() -> Range { + 2 * D..3 * D + } + const START_COEFFS: usize = 3 * D; + pub const fn wires_coeff(i: usize) -> Range { + Self::START_COEFFS + i * D..Self::START_COEFFS + (i + 1) * D + } + const fn start_accs(&self) -> usize { + Self::START_COEFFS + self.num_coeffs * D + } + fn wires_accs(&self, i: usize) -> Range { + debug_assert!(i < self.num_coeffs); + if i == self.num_coeffs - 1 { + // The last accumulator is the output. + return Self::wires_output(); + } + self.start_accs() + D * i..self.start_accs() + D * (i + 1) + } +} + +impl, const D: usize> Gate for ReducingExtensionGate { + fn id(&self) -> String { + format!("{self:?}") + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.num_coeffs)?; + Ok(()) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult + where + Self: Sized, + { + let num_coeffs = src.read_usize()?; + Ok(Self::new(num_coeffs)) + } + + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { + let alpha = vars.get_local_ext_algebra(Self::wires_alpha()); + let old_acc = vars.get_local_ext_algebra(Self::wires_old_acc()); + let coeffs = (0..self.num_coeffs) + .map(|i| vars.get_local_ext_algebra(Self::wires_coeff(i))) + .collect::>(); + let accs = (0..self.num_coeffs) + .map(|i| vars.get_local_ext_algebra(self.wires_accs(i))) + .collect::>(); + + let mut constraints = Vec::with_capacity(>::num_constraints(self)); + let mut acc = old_acc; + for i in 0..self.num_coeffs { + constraints.push(acc * alpha + coeffs[i] - accs[i]); + acc = accs[i]; + } + + constraints + .into_iter() + .flat_map(|alg| alg.to_basefield_array()) + .collect() + } + + fn eval_unfiltered_base_one( + &self, + vars: EvaluationVarsBase, + mut yield_constr: StridedConstraintConsumer, + ) { + let alpha = vars.get_local_ext(Self::wires_alpha()); + let old_acc = vars.get_local_ext(Self::wires_old_acc()); + let coeffs = (0..self.num_coeffs) + .map(|i| vars.get_local_ext(Self::wires_coeff(i))) + .collect::>(); + let accs = (0..self.num_coeffs) + .map(|i| vars.get_local_ext(self.wires_accs(i))) + .collect::>(); + + let mut acc = old_acc; + for i in 0..self.num_coeffs { + yield_constr.many((acc * alpha + coeffs[i] - accs[i]).to_basefield_array()); + acc = accs[i]; + } + } + + fn eval_unfiltered_circuit( + &self, + builder: &mut CircuitBuilder, + vars: EvaluationTargets, + ) -> Vec> { + let alpha = vars.get_local_ext_algebra(Self::wires_alpha()); + let old_acc = vars.get_local_ext_algebra(Self::wires_old_acc()); + let coeffs = (0..self.num_coeffs) + .map(|i| vars.get_local_ext_algebra(Self::wires_coeff(i))) + .collect::>(); + let accs = (0..self.num_coeffs) + .map(|i| vars.get_local_ext_algebra(self.wires_accs(i))) + .collect::>(); + + let mut constraints = Vec::with_capacity(>::num_constraints(self)); + let mut acc = old_acc; + for i in 0..self.num_coeffs { + let coeff = coeffs[i]; + let mut tmp = builder.mul_add_ext_algebra(acc, alpha, coeff); + tmp = builder.sub_ext_algebra(tmp, accs[i]); + constraints.push(tmp); + acc = accs[i]; + } + + constraints + .into_iter() + .flat_map(|alg| alg.to_ext_target_array()) + .collect() + } + + fn generators(&self, row: usize, _local_constants: &[F]) -> Vec> { + vec![WitnessGeneratorRef::new( + ReducingGenerator { + row, + gate: self.clone(), + } + .adapter(), + )] + } + + fn num_wires(&self) -> usize { + 2 * D + 2 * D * self.num_coeffs + } + + fn num_constants(&self) -> usize { + 0 + } + + fn degree(&self) -> usize { + 2 + } + + fn num_constraints(&self) -> usize { + D * self.num_coeffs + } +} + +#[derive(Debug, Default)] +pub struct ReducingGenerator { + row: usize, + gate: ReducingExtensionGate, +} + +impl, const D: usize> SimpleGenerator for ReducingGenerator { + fn id(&self) -> String { + "ReducingExtensionGenerator".to_string() + } + + fn dependencies(&self) -> Vec { + ReducingExtensionGate::::wires_alpha() + .chain(ReducingExtensionGate::::wires_old_acc()) + .chain((0..self.gate.num_coeffs).flat_map(ReducingExtensionGate::::wires_coeff)) + .map(|i| Target::wire(self.row, i)) + .collect() + } + + fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + let local_extension = |range: Range| -> F::Extension { + let t = ExtensionTarget::from_range(self.row, range); + witness.get_extension_target(t) + }; + + let alpha = local_extension(ReducingExtensionGate::::wires_alpha()); + let old_acc = local_extension(ReducingExtensionGate::::wires_old_acc()); + let coeffs = (0..self.gate.num_coeffs) + .map(|i| local_extension(ReducingExtensionGate::::wires_coeff(i))) + .collect::>(); + let accs = (0..self.gate.num_coeffs) + .map(|i| ExtensionTarget::from_range(self.row, self.gate.wires_accs(i))) + .collect::>(); + + let mut acc = old_acc; + for i in 0..self.gate.num_coeffs { + let computed_acc = acc * alpha + coeffs[i]; + out_buffer.set_extension_target(accs[i], computed_acc); + acc = computed_acc; + } + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.row)?; + as Gate>::serialize(&self.gate, dst, _common_data) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let row = src.read_usize()?; + let gate = as Gate>::deserialize(src, _common_data)?; + Ok(Self { row, gate }) + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + + use crate::field::goldilocks_field::GoldilocksField; + use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; + use crate::gates::reducing_extension::ReducingExtensionGate; + use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + + #[test] + fn low_degree() { + test_low_degree::(ReducingExtensionGate::new(22)); + } + + #[test] + fn eval_fns() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + test_eval_fns::(ReducingExtensionGate::new(22)) + } +} diff --git a/plonky2/src/gates/selectors.rs b/plonky2/src/gates/selectors.rs new file mode 100644 index 000000000..dd4a9109e --- /dev/null +++ b/plonky2/src/gates/selectors.rs @@ -0,0 +1,194 @@ +#[cfg(not(feature = "std"))] +use alloc::{vec, vec::Vec}; +use core::ops::Range; + +use serde::Serialize; + +use crate::field::extension::Extendable; +use crate::field::polynomial::PolynomialValues; +use crate::gates::gate::{GateInstance, GateRef}; +use crate::hash::hash_types::RichField; +use crate::plonk::circuit_builder::LookupWire; + +/// Placeholder value to indicate that a gate doesn't use a selector polynomial. +pub(crate) const UNUSED_SELECTOR: usize = u32::MAX as usize; + +#[derive(Debug, Clone, Eq, PartialEq, Serialize)] +pub struct SelectorsInfo { + pub(crate) selector_indices: Vec, + pub(crate) groups: Vec>, +} + +impl SelectorsInfo { + pub fn num_selectors(&self) -> usize { + self.groups.len() + } +} + +/// Enum listing the different selectors for lookup constraints: +/// - `TransSre` is for Sum and RE transition constraints. +/// - `TransLdc` is for LDC transition constraints. +/// - `InitSre` is for the initial constraint of Sum and Re. +/// - `LastLdc` is for the final LDC (and Sum) constraint. +/// - `StartEnd` indicates where lookup end selectors begin. +pub enum LookupSelectors { + TransSre = 0, + TransLdc, + InitSre, + LastLdc, + StartEnd, +} + +/// Returns selector polynomials for each LUT. We have two constraint domains +/// (remember that gates are stored upside down): +/// - [last_lut_row, first_lut_row] (Sum and RE transition constraints), +/// - [last_lu_row, last_lut_row - 1] (LDC column transition constraints). +/// We also add two more: +/// - {first_lut_row + 1} where we check the initial values of sum and RE (which +/// are 0), +/// - {last_lu_row} where we check that the last value of LDC is 0. +/// Conceptually they're part of the selector ends lookups, but since we can +/// have one polynomial for *all* LUTs it's here. +pub(crate) fn selectors_lookup, const D: usize>( + _gates: &[GateRef], + instances: &[GateInstance], + lookup_rows: &[LookupWire], +) -> Vec> { + let n = instances.len(); + let mut lookup_selectors = Vec::with_capacity(LookupSelectors::StartEnd as usize); + for _ in 0..LookupSelectors::StartEnd as usize { + lookup_selectors.push(PolynomialValues::::new(vec![F::ZERO; n])); + } + + for &LookupWire { + last_lu_gate: last_lu_row, + last_lut_gate: last_lut_row, + first_lut_gate: first_lut_row, + } in lookup_rows + { + for row in last_lut_row..first_lut_row + 1 { + lookup_selectors[LookupSelectors::TransSre as usize].values[row] = F::ONE; + } + for row in last_lu_row..last_lut_row { + lookup_selectors[LookupSelectors::TransLdc as usize].values[row] = F::ONE; + } + lookup_selectors[LookupSelectors::InitSre as usize].values[first_lut_row + 1] = F::ONE; + lookup_selectors[LookupSelectors::LastLdc as usize].values[last_lu_row] = F::ONE; + } + lookup_selectors +} + +/// Returns selectors for checking the validity of the LUTs. +/// Each selector equals one on its respective LUT's `last_lut_row`, and 0 +/// elsewhere. +pub(crate) fn selector_ends_lookups, const D: usize>( + lookup_rows: &[LookupWire], + instances: &[GateInstance], +) -> Vec> { + let n = instances.len(); + let mut lookups_ends = Vec::with_capacity(lookup_rows.len()); + for &LookupWire { + last_lu_gate: _, + last_lut_gate: last_lut_row, + first_lut_gate: _, + } in lookup_rows + { + let mut lookup_ends = PolynomialValues::::new(vec![F::ZERO; n]); + lookup_ends.values[last_lut_row] = F::ONE; + lookups_ends.push(lookup_ends); + } + lookups_ends +} + +/// Returns the selector polynomials and related information. +/// +/// Selector polynomials are computed as follows: +/// Partition the gates into (the smallest amount of) groups `{ G_i }`, such +/// that for each group `G` `|G| + max_{g in G} g.degree() <= max_degree`. These +/// groups are constructed greedily from the list of gates sorted by degree. +/// We build a selector polynomial `S_i` for each group `G_i`, with +/// S_i\[j\] = +/// if j-th row gate=g_k in G_i +/// k +/// else +/// UNUSED_SELECTOR +pub(crate) fn selector_polynomials, const D: usize>( + gates: &[GateRef], + instances: &[GateInstance], + max_degree: usize, +) -> (Vec>, SelectorsInfo) { + let n = instances.len(); + let num_gates = gates.len(); + let max_gate_degree = gates.last().expect("No gates?").0.degree(); + + let index = |id| gates.iter().position(|g| g.0.id() == id).unwrap(); + + // Special case if we can use only one selector polynomial. + if max_gate_degree + num_gates - 1 <= max_degree { + // We *want* `groups` to be a vector containing one Range (all gates are in one + // selector group), but Clippy doesn't trust us. + #[allow(clippy::single_range_in_vec_init)] + return ( + vec![PolynomialValues::new( + instances + .iter() + .map(|g| F::from_canonical_usize(index(g.gate_ref.0.id()))) + .collect(), + )], + SelectorsInfo { + selector_indices: vec![0; num_gates], + groups: vec![0..num_gates], + }, + ); + } + + if max_gate_degree >= max_degree { + panic!( + "{} has too high degree. Consider increasing `quotient_degree_factor`.", + gates.last().unwrap().0.id() + ); + } + + // Greedily construct the groups. + let mut groups = Vec::new(); + let mut start = 0; + while start < num_gates { + let mut size = 0; + while (start + size < gates.len()) && (size + gates[start + size].0.degree() < max_degree) { + size += 1; + } + groups.push(start..start + size); + start += size; + } + + let group = |i| groups.iter().position(|range| range.contains(&i)).unwrap(); + + // `selector_indices[i] = j` iff the `i`-th gate uses the `j`-th selector + // polynomial. + let selector_indices = (0..num_gates).map(group).collect(); + + // Placeholder value to indicate that a gate doesn't use a selector polynomial. + let unused = F::from_canonical_usize(UNUSED_SELECTOR); + + let mut polynomials = vec![PolynomialValues::zero(n); groups.len()]; + for (j, g) in instances.iter().enumerate() { + let GateInstance { gate_ref, .. } = g; + let i = index(gate_ref.0.id()); + let gr = group(i); + for g in 0..groups.len() { + polynomials[g].values[j] = if g == gr { + F::from_canonical_usize(i) + } else { + unused + }; + } + } + + ( + polynomials, + SelectorsInfo { + selector_indices, + groups, + }, + ) +} diff --git a/plonky2/src/gates/util.rs b/plonky2/src/gates/util.rs new file mode 100644 index 000000000..75ee4d986 --- /dev/null +++ b/plonky2/src/gates/util.rs @@ -0,0 +1,66 @@ +use core::marker::PhantomData; + +use crate::field::packed::PackedField; + +/// Writes constraints yielded by a gate to a buffer, with a given stride. +/// Permits us to abstract the underlying memory layout. In particular, we can +/// make a matrix of constraints where every column is an evaluation point and +/// every row is a constraint index, with the matrix stored in row-contiguous +/// form. +pub struct StridedConstraintConsumer<'a, P: PackedField> { + // This is a particularly neat way of doing this, more so than a slice. We increase start by + // stride at every step and terminate when it equals end. + start: *mut P::Scalar, + end: *mut P::Scalar, + stride: usize, + _phantom: PhantomData<&'a mut [P::Scalar]>, +} + +impl<'a, P: PackedField> StridedConstraintConsumer<'a, P> { + pub fn new(buffer: &'a mut [P::Scalar], stride: usize, offset: usize) -> Self { + assert!(stride >= P::WIDTH); + assert!(offset < stride); + assert_eq!(buffer.len() % stride, 0); + let ptr_range = buffer.as_mut_ptr_range(); + // `wrapping_add` is needed to avoid undefined behavior. Plain `add` causes UB + // if 'the ... resulting pointer [is neither] in bounds or one byte past + // the end of the same allocated object'; the UB results even if the + // pointer is not dereferenced. `end` will be more than one byte past + // the buffer unless `offset` is 0. The same applies to `start` if the buffer + // has length 0 and the offset is not 0. + // We _could_ do pointer arithmetic without `wrapping_add`, but the logic would + // be unnecessarily complicated. + let start = ptr_range.start.wrapping_add(offset); + let end = ptr_range.end.wrapping_add(offset); + Self { + start, + end, + stride, + _phantom: PhantomData, + } + } + + /// Emit one constraint. + pub fn one(&mut self, constraint: P) { + if self.start != self.end { + // # Safety + // The checks in `new` guarantee that this points to valid space. + unsafe { + *self.start.cast() = constraint; + } + // See the comment in `new`. `wrapping_add` is needed to avoid UB if we've just + // exhausted our buffer (and hence we're setting `self.start` to point past the + // end). + self.start = self.start.wrapping_add(self.stride); + } else { + panic!("gate produced too many constraints"); + } + } + + /// Convenience method that calls `.one()` multiple times. + pub fn many>(&mut self, constraints: I) { + constraints + .into_iter() + .for_each(|constraint| self.one(constraint)); + } +} diff --git a/plonky2/src/hash/arch/aarch64/mod.rs b/plonky2/src/hash/arch/aarch64/mod.rs new file mode 100644 index 000000000..b8ae14afb --- /dev/null +++ b/plonky2/src/hash/arch/aarch64/mod.rs @@ -0,0 +1,2 @@ +#[cfg(target_feature = "neon")] +pub(crate) mod poseidon_goldilocks_neon; diff --git a/plonky2/src/hash/arch/aarch64/poseidon_goldilocks_neon.rs b/plonky2/src/hash/arch/aarch64/poseidon_goldilocks_neon.rs new file mode 100644 index 000000000..eaa579532 --- /dev/null +++ b/plonky2/src/hash/arch/aarch64/poseidon_goldilocks_neon.rs @@ -0,0 +1,969 @@ +#![allow(clippy::assertions_on_constants)] + +use core::arch::aarch64::*; +use core::arch::asm; +use core::mem::transmute; + +use static_assertions::const_assert; +use unroll::unroll_for_loops; + +use crate::field::goldilocks_field::GoldilocksField; +use crate::hash::poseidon::Poseidon; +use crate::util::branch_hint; + +// ========================================== CONSTANTS +// =========================================== + +const WIDTH: usize = 12; + +const EPSILON: u64 = 0xffffffff; + +// The round constants to be applied by the second set of full rounds. These are +// just the usual round constants, shifted by one round, with zeros shifted in. +/* +const fn make_final_round_constants() -> [u64; WIDTH * HALF_N_FULL_ROUNDS] { + let mut res = [0; WIDTH * HALF_N_FULL_ROUNDS]; + let mut i: usize = 0; + while i < WIDTH * (HALF_N_FULL_ROUNDS - 1) { + res[i] = ALL_ROUND_CONSTANTS[i + WIDTH * (HALF_N_FULL_ROUNDS + N_PARTIAL_ROUNDS + 1)]; + i += 1; + } + res +} +const FINAL_ROUND_CONSTANTS: [u64; WIDTH * HALF_N_FULL_ROUNDS] = make_final_round_constants(); +*/ + +// ===================================== COMPILE-TIME CHECKS +// ====================================== + +/// The MDS matrix multiplication ASM is specific to the MDS matrix below. We +/// want this file to fail to compile if it has been changed. +#[allow(dead_code)] +const fn check_mds_matrix() -> bool { + // Can't == two arrays in a const_assert! (: + let mut i = 0; + let wanted_matrix_circ = [17, 15, 41, 16, 2, 28, 13, 13, 39, 18, 34, 20]; + let wanted_matrix_diag = [8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + while i < WIDTH { + if ::MDS_MATRIX_CIRC[i] != wanted_matrix_circ[i] + || ::MDS_MATRIX_DIAG[i] != wanted_matrix_diag[i] + { + return false; + } + i += 1; + } + true +} +const_assert!(check_mds_matrix()); + +/// Ensure that the first WIDTH round constants are in canonical* form. This is +/// required because the first constant layer does not handle double overflow. +/// *: round_const == GoldilocksField::ORDER is safe. +/* +#[allow(dead_code)] +const fn check_round_const_bounds_init() -> bool { + let mut i = 0; + while i < WIDTH { + if ALL_ROUND_CONSTANTS[i] > GoldilocksField::ORDER { + return false; + } + i += 1; + } + true +} +const_assert!(check_round_const_bounds_init()); +*/ +// ====================================== SCALAR ARITHMETIC ======================================= + +/// Addition modulo ORDER accounting for wraparound. Correct only when a + b < +/// 2**64 + ORDER. +#[inline(always)] +unsafe fn add_with_wraparound(a: u64, b: u64) -> u64 { + let res: u64; + let adj: u64; + asm!( + "adds {res}, {a}, {b}", + // Set adj to 0xffffffff if addition overflowed and 0 otherwise. + // 'cs' for 'carry set'. + "csetm {adj:w}, cs", + a = in(reg) a, + b = in(reg) b, + res = lateout(reg) res, + adj = lateout(reg) adj, + options(pure, nomem, nostack), + ); + res + adj // adj is EPSILON if wraparound occurred and 0 otherwise +} + +/// Subtraction of a and (b >> 32) modulo ORDER accounting for wraparound. +#[inline(always)] +unsafe fn sub_with_wraparound_lsr32(a: u64, b: u64) -> u64 { + let mut b_hi = b >> 32; + // Make sure that LLVM emits two separate instructions for the shift and the + // subtraction. This reduces pressure on the execution units with access to + // the flags, as they are no longer responsible for the shift. The hack is + // to insert a fake computation between the two instructions with an `asm` + // block to make LLVM think that they can't be merged. + asm!( + "/* {0} */", // Make Rust think we're using the register. + inlateout(reg) b_hi, + options(nomem, nostack, preserves_flags, pure), + ); + // This could be done with a.overflowing_add(b_hi), but `checked_sub` signals to + // the compiler that overflow is unlikely (note: this is a standard library + // implementation detail, not part of the spec). + match a.checked_sub(b_hi) { + Some(res) => res, + None => { + // Super rare. Better off branching. + branch_hint(); + let res_wrapped = a.wrapping_sub(b_hi); + res_wrapped - EPSILON + } + } +} + +/// Multiplication of the low word (i.e., x as u32) by EPSILON. +#[inline(always)] +unsafe fn mul_epsilon(x: u64) -> u64 { + let res; + asm!( + // Use UMULL to save one instruction. The compiler emits two: extract the low word and then + // multiply. + "umull {res}, {x:w}, {epsilon:w}", + x = in(reg) x, + epsilon = in(reg) EPSILON, + res = lateout(reg) res, + options(pure, nomem, nostack, preserves_flags), + ); + res +} + +#[inline(always)] +unsafe fn multiply(x: u64, y: u64) -> u64 { + let xy = (x as u128) * (y as u128); + let xy_lo = xy as u64; + let xy_hi = (xy >> 64) as u64; + + let res0 = sub_with_wraparound_lsr32(xy_lo, xy_hi); + + let xy_hi_lo_mul_epsilon = mul_epsilon(xy_hi); + + // add_with_wraparound is safe, as xy_hi_lo_mul_epsilon <= 0xfffffffe00000001 <= + // ORDER. + add_with_wraparound(res0, xy_hi_lo_mul_epsilon) +} + +// ==================================== STANDALONE CONST LAYER +// ===================================== + +/// Standalone const layer. Run only once, at the start of round 1. Remaining +/// const layers are fused with the preceding MDS matrix multiplication. +/* +#[inline(always)] +#[unroll_for_loops] +unsafe fn const_layer_full( + mut state: [u64; WIDTH], + round_constants: &[u64; WIDTH], +) -> [u64; WIDTH] { + assert!(WIDTH == 12); + for i in 0..12 { + let rc = round_constants[i]; + // add_with_wraparound is safe, because rc is in canonical form. + state[i] = add_with_wraparound(state[i], rc); + } + state +} +*/ +// ========================================== FULL ROUNDS ========================================== + +/// Full S-box. +#[inline(always)] +#[unroll_for_loops] +unsafe fn sbox_layer_full(state: [u64; WIDTH]) -> [u64; WIDTH] { + // This is done in scalar. S-boxes in vector are only slightly slower + // throughput-wise but have an insane latency (~100 cycles) on the M1. + + let mut state2 = [0u64; WIDTH]; + assert!(WIDTH == 12); + for i in 0..12 { + state2[i] = multiply(state[i], state[i]); + } + + let mut state3 = [0u64; WIDTH]; + let mut state4 = [0u64; WIDTH]; + assert!(WIDTH == 12); + for i in 0..12 { + state3[i] = multiply(state[i], state2[i]); + state4[i] = multiply(state2[i], state2[i]); + } + + let mut state7 = [0u64; WIDTH]; + assert!(WIDTH == 12); + for i in 0..12 { + state7[i] = multiply(state3[i], state4[i]); + } + + state7 +} + +#[inline(always)] +unsafe fn mds_reduce( + // `cumul_a` and `cumul_b` represent two separate field elements. We take advantage of + // vectorization by reducing them simultaneously. + [cumul_a, cumul_b]: [uint32x4_t; 2], +) -> uint64x2_t { + // Form: + // `lo = [cumul_a[0] + cumul_a[2] * 2**32, cumul_b[0] + cumul_b[2] * 2**32]` + // `hi = [cumul_a[1] + cumul_a[3] * 2**32, cumul_b[1] + cumul_b[3] * 2**32]` + // Observe that the result `== lo + hi * 2**16 (mod Goldilocks)`. + let mut lo = vreinterpretq_u64_u32(vuzp1q_u32(cumul_a, cumul_b)); + let mut hi = vreinterpretq_u64_u32(vuzp2q_u32(cumul_a, cumul_b)); + // Add the high 48 bits of `lo` to `hi`. This cannot overflow. + hi = vsraq_n_u64::<16>(hi, lo); + // Now, result `== lo.bits[0..16] + hi * 2**16 (mod Goldilocks)`. + // Set the high 48 bits of `lo` to the low 48 bits of `hi`. + lo = vsliq_n_u64::<16>(lo, hi); + // At this point, result `== lo + hi.bits[48..64] * 2**64 (mod Goldilocks)`. + // It remains to fold `hi.bits[48..64]` into `lo`. + let top = { + // Extract the top 16 bits of `hi` as a `u32`. + // Interpret `hi` as a vector of bytes, so we can use a table lookup + // instruction. + let hi_u8 = vreinterpretq_u8_u64(hi); + // Indices defining the permutation. `0xff` is out of bounds, producing `0`. + let top_idx = + transmute::<[u8; 8], uint8x8_t>([0x06, 0x07, 0xff, 0xff, 0x0e, 0x0f, 0xff, 0xff]); + let top_u8 = vqtbl1_u8(hi_u8, top_idx); + vreinterpret_u32_u8(top_u8) + }; + // result `== lo + top * 2**64 (mod Goldilocks)`. + let adj_lo = vmlal_n_u32(lo, top, EPSILON as u32); + let wraparound_mask = vcgtq_u64(lo, adj_lo); + vsraq_n_u64::<32>(adj_lo, wraparound_mask) // Add epsilon on overflow. +} + +#[inline(always)] +unsafe fn mds_layer_full(state: [u64; WIDTH]) -> [u64; WIDTH] { + // This function performs an MDS multiplication in complex FFT space. + // However, instead of performing a width-12 FFT, we perform three width-4 FFTs, + // which is cheaper. The 12x12 matrix-vector multiplication (a convolution) + // becomes two 3x3 real matrix-vector multiplications and one 3x3 complex + // matrix-vector multiplication. + + // We split each 64-bit into four chunks of 16 bits. To prevent overflow, each + // chunk is 32 bits long. Each NEON vector below represents one field + // element and consists of four 32-bit chunks: `elem == vector[0] + + // vector[1] * 2**16 + vector[2] * 2**32 + vector[3] * 2**48`. + + // Constants that we multiply by. + let mut consts: uint32x4_t = transmute::<[u32; 4], _>([2, 4, 8, 16]); + + // Prevent LLVM from turning fused multiply (by power of 2)-add (1 instruction) + // into shift and add (two instructions). This fake `asm` block means that + // LLVM no longer knows the contents of `consts`. + asm!("/* {0:v} */", // Make Rust think the register is being used. + inout(vreg) consts, + options(pure, nomem, nostack, preserves_flags), + ); + + // Four length-3 complex FFTs. + let mut state_fft = [vdupq_n_u32(0); 12]; + for i in 0..3 { + // Interpret each field element as a 4-vector of `u16`s. + let x0 = vcreate_u16(state[i]); + let x1 = vcreate_u16(state[i + 3]); + let x2 = vcreate_u16(state[i + 6]); + let x3 = vcreate_u16(state[i + 9]); + + // `vaddl_u16` and `vsubl_u16` yield 4-vectors of `u32`s. + let y0 = vaddl_u16(x0, x2); + let y1 = vaddl_u16(x1, x3); + let y2 = vsubl_u16(x0, x2); + let y3 = vsubl_u16(x1, x3); + + let z0 = vaddq_u32(y0, y1); + let z1 = vsubq_u32(y0, y1); + let z2 = y2; + let z3 = y3; + + // The FFT is `[z0, z2 + z3 i, z1, z2 - z3 i]`. + + state_fft[i] = z0; + state_fft[i + 3] = z1; + state_fft[i + 6] = z2; + state_fft[i + 9] = z3; + } + + // 3x3 real matrix-vector mul for component 0 of the FFTs. + // Multiply the vector `[x0, x1, x2]` by the matrix + // `[[ 64, 64, 128],` + // ` [128, 64, 64],` + // ` [ 64, 128, 64]]` + // The results are divided by 4 (this ends up cancelling out some later + // computations). + { + let x0 = state_fft[0]; + let x1 = state_fft[1]; + let x2 = state_fft[2]; + + let t = vshlq_n_u32::<4>(x0); + let u = vaddq_u32(x1, x2); + + let y0 = vshlq_n_u32::<4>(u); + let y1 = vmlaq_laneq_u32::<3>(t, x2, consts); + let y2 = vmlaq_laneq_u32::<3>(t, x1, consts); + + state_fft[0] = vaddq_u32(y0, y1); + state_fft[1] = vaddq_u32(y1, y2); + state_fft[2] = vaddq_u32(y0, y2); + } + + // 3x3 real matrix-vector mul for component 2 of the FFTs. + // Multiply the vector `[x0, x1, x2]` by the matrix + // `[[ -4, -8, 32],` + // ` [-32, -4, -8],` + // ` [ 8, -32, -4]]` + // The results are divided by 4 (this ends up cancelling out some later + // computations). + { + let x0 = state_fft[3]; + let x1 = state_fft[4]; + let x2 = state_fft[5]; + state_fft[3] = vmlsq_laneq_u32::<2>(vmlaq_laneq_u32::<0>(x0, x1, consts), x2, consts); + state_fft[4] = vmlaq_laneq_u32::<0>(vmlaq_laneq_u32::<2>(x1, x0, consts), x2, consts); + state_fft[5] = vmlsq_laneq_u32::<0>(x2, vmlsq_laneq_u32::<1>(x0, x1, consts), consts); + } + + // 3x3 complex matrix-vector mul for components 1 and 3 of the FFTs. + // Multiply the vector `[x0r + x0i i, x1r + x1i i, x2r + x2i i]` by the matrix + // `[[ 4 + 2i, 2 + 32i, 2 - 8i],` + // ` [-8 - 2i, 4 + 2i, 2 + 32i],` + // ` [32 - 2i, -8 - 2i, 4 + 2i]]` + // The results are divided by 2 (this ends up cancelling out some later + // computations). + { + let x0r = state_fft[6]; + let x1r = state_fft[7]; + let x2r = state_fft[8]; + + let x0i = state_fft[9]; + let x1i = state_fft[10]; + let x2i = state_fft[11]; + + // real part of result <- real part of input + let r0rr = vaddq_u32(vmlaq_laneq_u32::<0>(x1r, x0r, consts), x2r); + let r1rr = vmlaq_laneq_u32::<0>(x2r, vmlsq_laneq_u32::<0>(x1r, x0r, consts), consts); + let r2rr = vmlsq_laneq_u32::<0>(x2r, vmlsq_laneq_u32::<1>(x1r, x0r, consts), consts); + + // real part of result <- imaginary part of input + let r0ri = vmlsq_laneq_u32::<1>(vmlaq_laneq_u32::<3>(x0i, x1i, consts), x2i, consts); + let r1ri = vmlsq_laneq_u32::<3>(vsubq_u32(x0i, x1i), x2i, consts); + let r2ri = vsubq_u32(vaddq_u32(x0i, x1i), x2i); + + // real part of result (total) + let r0r = vsubq_u32(r0rr, r0ri); + let r1r = vaddq_u32(r1rr, r1ri); + let r2r = vmlaq_laneq_u32::<0>(r2ri, r2rr, consts); + + // imaginary part of result <- real part of input + let r0ir = vmlsq_laneq_u32::<1>(vmlaq_laneq_u32::<3>(x0r, x1r, consts), x2r, consts); + let r1ir = vmlaq_laneq_u32::<3>(vsubq_u32(x1r, x0r), x2r, consts); + let r2ir = vsubq_u32(x2r, vaddq_u32(x0r, x1r)); + + // imaginary part of result <- imaginary part of input + let r0ii = vaddq_u32(vmlaq_laneq_u32::<0>(x1i, x0i, consts), x2i); + let r1ii = vmlaq_laneq_u32::<0>(x2i, vmlsq_laneq_u32::<0>(x1i, x0i, consts), consts); + let r2ii = vmlsq_laneq_u32::<0>(x2i, vmlsq_laneq_u32::<1>(x1i, x0i, consts), consts); + + // imaginary part of result (total) + let r0i = vaddq_u32(r0ir, r0ii); + let r1i = vaddq_u32(r1ir, r1ii); + let r2i = vmlaq_laneq_u32::<0>(r2ir, r2ii, consts); + + state_fft[6] = r0r; + state_fft[7] = r1r; + state_fft[8] = r2r; + + state_fft[9] = r0i; + state_fft[10] = r1i; + state_fft[11] = r2i; + } + + // Three length-4 inverse FFTs. + // Normally, such IFFT would divide by 4, but we've already taken care of that. + for i in 0..3 { + let z0 = state_fft[i]; + let z1 = state_fft[i + 3]; + let z2 = state_fft[i + 6]; + let z3 = state_fft[i + 9]; + + let y0 = vsubq_u32(z0, z1); + let y1 = vaddq_u32(z0, z1); + let y2 = z2; + let y3 = z3; + + let x0 = vaddq_u32(y0, y2); + let x1 = vaddq_u32(y1, y3); + let x2 = vsubq_u32(y0, y2); + let x3 = vsubq_u32(y1, y3); + + state_fft[i] = x0; + state_fft[i + 3] = x1; + state_fft[i + 6] = x2; + state_fft[i + 9] = x3; + } + + // Perform `res[0] += state[0] * 8` for the diagonal component of the MDS + // matrix. + state_fft[0] = vmlal_laneq_u16::<4>( + state_fft[0], + vcreate_u16(state[0]), // Each 16-bit chunk gets zero-extended. + vreinterpretq_u16_u32(consts), // Hack: these constants fit in `u16s`, so we can bit-cast. + ); + + let mut res_arr = [0; 12]; + for i in 0..6 { + let res = mds_reduce([state_fft[2 * i], state_fft[2 * i + 1]]); + res_arr[2 * i] = vgetq_lane_u64::<0>(res); + res_arr[2 * i + 1] = vgetq_lane_u64::<1>(res); + } + + res_arr +} + +// ======================================== PARTIAL ROUNDS +// ========================================= + +/* +#[rustfmt::skip] +macro_rules! mds_reduce_asm { + ($c0:literal, $c1:literal, $out:literal, $consts:literal) => { + concat!( + // Swizzle + "zip1.2d ", $out, ",", $c0, ",", $c1, "\n", // lo + "zip2.2d ", $c0, ",", $c0, ",", $c1, "\n", // hi + + // Reduction from u96 + "usra.2d ", $c0, ",", $out, ", #32\n", "sli.2d ", $out, ",", $c0, ", #32\n", + // Extract high 32-bits. + "uzp2.4s ", $c0, ",", $c0, ",", $c0, "\n", + // Multiply by EPSILON and accumulate. + "mov.16b ", $c1, ",", $out, "\n", + "umlal.2d ", $out, ",", $c0, ", ", $consts, "[0]\n", + "cmhi.2d ", $c1, ",", $c1, ",", $out, "\n", + "usra.2d ", $out, ",", $c1, ", #32", + ) + }; +} + +#[inline(always)] +unsafe fn partial_round( + (state_scalar, state_vector): ([u64; WIDTH], [uint64x2_t; 5]), + round_constants: &[u64; WIDTH], +) -> ([u64; WIDTH], [uint64x2_t; 5]) { + // see readme-asm.md + + // mds_consts0 == [0xffffffff, 1 << 1, 1 << 3, 1 << 5] + // mds_consts1 == [1 << 8, 1 << 10, 1 << 12, 1 << 16] + let mds_consts0: uint32x4_t = vld1q_u32((&MDS_CONSTS[0..4]).as_ptr().cast::()); + let mds_consts1: uint32x4_t = vld1q_u32((&MDS_CONSTS[4..8]).as_ptr().cast::()); + + let res0: u64; + let res1: u64; + let res23: uint64x2_t; + let res45: uint64x2_t; + let res67: uint64x2_t; + let res89: uint64x2_t; + let res1011: uint64x2_t; + + let res2_scalar: u64; + let res3_scalar: u64; + let res4_scalar: u64; + let res5_scalar: u64; + let res6_scalar: u64; + let res7_scalar: u64; + let res8_scalar: u64; + let res9_scalar: u64; + let res10_scalar: u64; + let res11_scalar: u64; + + asm!( + "ldp d0, d1, [{rc_ptr}, #16]", + "fmov d21, {s1}", + "ldp {lo0}, {lo1}, [{rc_ptr}]", + "umulh {t0}, {s0}, {s0}", + "mul {t1}, {s0}, {s0}", + "subs {t1}, {t1}, {t0}, lsr #32", + "csetm {t2:w}, cc", + "lsl {t3}, {t0}, #32", + "sub {t1}, {t1}, {t2}", + "mov {t0:w}, {t0:w}", + "sub {t0}, {t3}, {t0}", + "adds {t0}, {t1}, {t0}", + "csetm {t1:w}, cs", + "add {t0}, {t0}, {t1}", + "umulh {t1}, {s0}, {t0}", + "umulh {t2}, {t0}, {t0}", + "mul {s0}, {s0}, {t0}", + "mul {t0}, {t0}, {t0}", + "subs {s0}, {s0}, {t1}, lsr #32", + "csetm {t3:w}, cc", + "subs {t0}, {t0}, {t2}, lsr #32", + "csetm {t4:w}, cc", + "lsl {t5}, {t1}, #32", + "lsl {t6}, {t2}, #32", + "sub {s0}, {s0}, {t3}", + "sub {t0}, {t0}, {t4}", + "mov {t1:w}, {t1:w}", + "mov {t2:w}, {t2:w}", + "sub {t1}, {t5}, {t1}", + "ushll.2d v10, v21, #10", + "sub {t2}, {t6}, {t2}", + "ushll.2d v11, v21, #16", + "adds {t1}, {s0}, {t1}", + "uaddw.2d v0, v0, v22", + "csetm {s0:w}, cs", + "umlal.2d v1, v22, v31[1]", + "adds {t2}, {t0}, {t2}", + "uaddw2.2d v10, v10, v22", + "csetm {t0:w}, cs", + "uaddw2.2d v11, v11, v22", + "add {t1}, {t1}, {s0}", + "ldp d2, d3, [{rc_ptr}, #32]", + "add {t2}, {t2}, {t0}", + "ushll.2d v12, v21, #3", + "umulh {s0}, {t1}, {t2}", + "ushll.2d v13, v21, #12", + "mul {t0}, {t1}, {t2}", + "umlal.2d v0, v23, v30[1]", + "add {lo1}, {lo1}, {s1:w}, uxtw", + "uaddw2.2d v10, v10, v23", + "add {lo0}, {lo0}, {s1:w}, uxtw", + "uaddw.2d v11, v11, v23", + "lsr {hi0}, {s1}, #32", + "umlal2.2d v1, v23, v30[1]", + "lsr {t3}, {s2}, #32", + "umlal.2d v2, v22, v31[3]", + "lsr {t4}, {s3}, #32", + "umlal2.2d v12, v22, v31[1]", + "add {hi1}, {hi0}, {t3}", + "umlal.2d v3, v22, v30[2]", + "add {hi0}, {hi0}, {t3}, lsl #1", + "umlal2.2d v13, v22, v31[3]", + "add {lo1}, {lo1}, {s2:w}, uxtw", + "ldp d4, d5, [{rc_ptr}, #48]", + "add {lo0}, {lo0}, {s2:w}, uxtw #1", + "ushll.2d v14, v21, #8", + "lsr {t3}, {s4}, #32", + "ushll.2d v15, v21, #1", + "lsr {t5}, {s5}, #32", + "umlal.2d v0, v24, v30[2]", + "subs {t0}, {t0}, {s0}, lsr #32", + "umlal2.2d v10, v24, v30[3]", + "add {hi1}, {hi1}, {t4}, lsl #1", + "umlal2.2d v11, v24, v30[2]", + "add {t6}, {t3}, {t5}, lsl #3", + "uaddw.2d v1, v1, v24", + "add {t5}, {t3}, {t5}, lsl #2", + "uaddw.2d v2, v2, v23", + "lsr {t3}, {s6}, #32", + "umlal.2d v3, v23, v31[1]", + "lsr {s1}, {s7}, #32", + "uaddw2.2d v12, v12, v23", + "mov {s2:w}, {s4:w}", + "uaddw2.2d v13, v13, v23", + "add {hi0}, {hi0}, {t4}", + "umlal.2d v4, v22, v31[2]", + "add {lo1}, {lo1}, {s3:w}, uxtw #1", + "umlal2.2d v14, v22, v30[2]", + "add {lo0}, {lo0}, {s3:w}, uxtw", + "umlal.2d v5, v22, v31[0]", + "add {t4}, {s2}, {s5:w}, uxtw #3", + "umlal2.2d v15, v22, v31[2]", + "add {s2}, {s2}, {s5:w}, uxtw #2", + "ldp d6, d7, [{rc_ptr}, #64]", + "add {s3}, {s1}, {t3}, lsl #4", + "ushll.2d v16, v21, #5", + "csetm {t1:w}, cc", + "ushll.2d v17, v21, #3", + "add {hi1}, {hi1}, {t6}", + "umlal.2d v0, v25, v30[1]", + "add {hi0}, {hi0}, {t5}, lsl #3", + "umlal2.2d v10, v25, v31[0]", + "mov {t5:w}, {s6:w}", + "umlal.2d v1, v25, v30[3]", + "mov {t6:w}, {s7:w}", + "umlal2.2d v11, v25, v30[1]", + "add {s4}, {t6}, {t5}, lsl #4", + "umlal.2d v2, v24, v30[1]", + "add {t3}, {t3}, {s1}, lsl #7", + "uaddw2.2d v12, v12, v24", + "lsr {s1}, {s8}, #32", + "uaddw.2d v13, v13, v24", + "lsr {s5}, {s9}, #32", + "umlal2.2d v3, v24, v30[1]", + "lsl {t2}, {s0}, #32", + "umlal.2d v4, v23, v31[3]", + "sub {t0}, {t0}, {t1}", + "umlal2.2d v14, v23, v31[1]", + "add {lo1}, {lo1}, {t4}", + "umlal.2d v5, v23, v30[2]", + "add {lo0}, {lo0}, {s2}, lsl #3", + "umlal2.2d v15, v23, v31[3]", + "add {t4}, {t5}, {t6}, lsl #7", + "umlal.2d v6, v22, v30[1]", + "add {hi1}, {hi1}, {s3}, lsl #1", + "umlal2.2d v16, v22, v31[0]", + "add {t5}, {s1}, {s5}, lsl #4", + "umlal.2d v7, v22, v30[3]", + "mov {s0:w}, {s0:w}", + "umlal2.2d v17, v22, v30[1]", + "sub {s0}, {t2}, {s0}", + "ldp d8, d9, [{rc_ptr}, #80]", + "add {lo1}, {lo1}, {s4}, lsl #1", + "ushll.2d v18, v21, #0", + "add {hi0}, {hi0}, {t3}, lsl #1", + "ushll.2d v19, v21, #1", + "mov {t3:w}, {s9:w}", + "umlal.2d v0, v26, v31[2]", + "mov {t6:w}, {s8:w}", + "umlal2.2d v10, v26, v30[2]", + "add {s2}, {t6}, {t3}, lsl #4", + "umlal.2d v1, v26, v31[0]", + "add {s1}, {s5}, {s1}, lsl #9", + "umlal2.2d v11, v26, v31[2]", + "lsr {s3}, {s10}, #32", + "umlal.2d v2, v25, v30[2]", + "lsr {s4}, {s11}, #32", + "umlal2.2d v12, v25, v30[3]", + "adds {s0}, {t0}, {s0}", + "umlal2.2d v13, v25, v30[2]", + "add {lo0}, {lo0}, {t4}, lsl #1", + "uaddw.2d v3, v3, v25", + "add {t3}, {t3}, {t6}, lsl #9", + "uaddw.2d v4, v4, v24", + "add {hi1}, {hi1}, {t5}, lsl #8", + "umlal.2d v5, v24, v31[1]", + "add {t4}, {s3}, {s4}, lsl #13", + "uaddw2.2d v14, v14, v24", + "csetm {t0:w}, cs", + "uaddw2.2d v15, v15, v24", + "add {lo1}, {lo1}, {s2}, lsl #8", + "umlal.2d v6, v23, v31[2]", + "add {hi0}, {hi0}, {s1}, lsl #3", + "umlal2.2d v16, v23, v30[2]", + "mov {t5:w}, {s10:w}", + "umlal.2d v7, v23, v31[0]", + "mov {t6:w}, {s11:w}", + "umlal2.2d v17, v23, v31[2]", + "add {s1}, {t5}, {t6}, lsl #13", + "umlal.2d v8, v22, v30[2]", + "add {s2}, {s4}, {s3}, lsl #6", + "umlal2.2d v18, v22, v30[3]", + "add {s0}, {s0}, {t0}", + "uaddw.2d v9, v9, v22", + "add {lo0}, {lo0}, {t3}, lsl #3", + "umlal2.2d v19, v22, v30[2]", + "add {t3}, {t6}, {t5}, lsl #6", + "add.2d v0, v0, v10", + "add {hi1}, {hi1}, {t4}, lsl #3", + "add.2d v1, v1, v11", + "fmov d20, {s0}", + "umlal.2d v0, v20, v31[3]", + "add {lo1}, {lo1}, {s1}, lsl #3", + "umlal.2d v1, v20, v30[2]", + "add {hi0}, {hi0}, {s2}, lsl #10", + "zip1.2d v22, v0, v1", + "lsr {t4}, {s0}, #32", + "zip2.2d v0, v0, v1", + "add {lo0}, {lo0}, {t3}, lsl #10", + "usra.2d v0, v22, #32", + "add {hi1}, {hi1}, {t4}, lsl #10", + "sli.2d v22, v0, #32", + "mov {t3:w}, {s0:w}", + "uzp2.4s v0, v0, v0", + "add {lo1}, {lo1}, {t3}, lsl #10", + "mov.16b v1, v22", + "add {hi0}, {hi0}, {t4}", + "umlal.2d v22, v0, v30[0]", + "add {lo0}, {lo0}, {t3}", + "cmhi.2d v1, v1, v22", + "lsl {t0}, {hi0}, #32", + "usra.2d v22, v1, #32", + "lsl {t1}, {hi1}, #32", + "fmov {s2}, d22", + "adds {lo0}, {lo0}, {t0}", + "fmov.d {s3}, v22[1]", + "csetm {t0:w}, cs", + "umlal.2d v2, v26, v30[1]", + "adds {lo1}, {lo1}, {t1}", + "umlal2.2d v12, v26, v31[0]", + "csetm {t1:w}, cs", + "umlal.2d v3, v26, v30[3]", + "and {t2}, {hi0}, #0xffffffff00000000", + "umlal2.2d v13, v26, v30[1]", + "and {t3}, {hi1}, #0xffffffff00000000", + "umlal.2d v4, v25, v30[1]", + "lsr {hi0}, {hi0}, #32", + "uaddw2.2d v14, v14, v25", + "lsr {hi1}, {hi1}, #32", + "uaddw.2d v15, v15, v25", + "sub {hi0}, {t2}, {hi0}", + "umlal2.2d v5, v25, v30[1]", + "sub {hi1}, {t3}, {hi1}", + "umlal.2d v6, v24, v31[3]", + "add {lo0}, {lo0}, {t0}", + "umlal2.2d v16, v24, v31[1]", + "add {lo1}, {lo1}, {t1}", + "umlal.2d v7, v24, v30[2]", + "adds {lo0}, {lo0}, {hi0}", + "umlal2.2d v17, v24, v31[3]", + "csetm {t0:w}, cs", + "umlal.2d v8, v23, v30[1]", + "adds {lo1}, {lo1}, {hi1}", + "umlal2.2d v18, v23, v31[0]", + "csetm {t1:w}, cs", + "umlal.2d v9, v23, v30[3]", + "add {s0}, {lo0}, {t0}", + "umlal2.2d v19, v23, v30[1]", + "add {s1}, {lo1}, {t1}", + "add.2d v2, v2, v12", + "add.2d v3, v3, v13", + "umlal.2d v2, v20, v31[2]", + "umlal.2d v3, v20, v31[0]", + mds_reduce_asm!("v2", "v3", "v23", "v30"), + "fmov {s4}, d23", + "fmov.d {s5}, v23[1]", + "umlal.2d v4, v26, v30[2]", + "umlal2.2d v14, v26, v30[3]", + "umlal2.2d v15, v26, v30[2]", + "uaddw.2d v5, v5, v26", + "uaddw.2d v6, v6, v25", + "uaddw2.2d v16, v16, v25", + "uaddw2.2d v17, v17, v25", + "umlal.2d v7, v25, v31[1]", + "umlal.2d v8, v24, v31[2]", + "umlal2.2d v18, v24, v30[2]", + "umlal.2d v9, v24, v31[0]", + "umlal2.2d v19, v24, v31[2]", + "add.2d v4, v4, v14", + "add.2d v5, v5, v15", + "umlal.2d v4, v20, v30[1]", + "umlal.2d v5, v20, v30[3]", + mds_reduce_asm!("v4", "v5", "v24", "v30"), + "fmov {s6}, d24", + "fmov.d {s7}, v24[1]", + "umlal.2d v6, v26, v30[1]", + "uaddw2.2d v16, v16, v26", + "umlal2.2d v17, v26, v30[1]", + "uaddw.2d v7, v7, v26", + "umlal.2d v8, v25, v31[3]", + "umlal2.2d v18, v25, v31[1]", + "umlal.2d v9, v25, v30[2]", + "umlal2.2d v19, v25, v31[3]", + "add.2d v6, v6, v16", + "add.2d v7, v7, v17", + "umlal.2d v6, v20, v30[2]", + "uaddw.2d v7, v7, v20", + mds_reduce_asm!("v6", "v7", "v25", "v30"), + "fmov {s8}, d25", + "fmov.d {s9}, v25[1]", + "uaddw.2d v8, v8, v26", + "uaddw2.2d v18, v18, v26", + "umlal.2d v9, v26, v31[1]", + "uaddw2.2d v19, v19, v26", + "add.2d v8, v8, v18", + "add.2d v9, v9, v19", + "umlal.2d v8, v20, v30[1]", + "uaddw.2d v9, v9, v20", + mds_reduce_asm!("v8", "v9", "v26", "v30"), + "fmov {s10}, d26", + "fmov.d {s11}, v26[1]", + + // Scalar inputs/outputs + // s0 is transformed by the S-box + s0 = inout(reg) state_scalar[0] => res0, + // s1-s6 double as scratch in the MDS matrix multiplication + s1 = inout(reg) state_scalar[1] => res1, + // s2-s11 are copied from the vector inputs/outputs + s2 = inout(reg) state_scalar[2] => res2_scalar, + s3 = inout(reg) state_scalar[3] => res3_scalar, + s4 = inout(reg) state_scalar[4] => res4_scalar, + s5 = inout(reg) state_scalar[5] => res5_scalar, + s6 = inout(reg) state_scalar[6] => res6_scalar, + s7 = inout(reg) state_scalar[7] => res7_scalar, + s8 = inout(reg) state_scalar[8] => res8_scalar, + s9 = inout(reg) state_scalar[9] => res9_scalar, + s10 = inout(reg) state_scalar[10] => res10_scalar, + s11 = inout(reg) state_scalar[11] => res11_scalar, + + // Pointer to the round constants + rc_ptr = in(reg) round_constants.as_ptr(), + + // Scalar MDS multiplication accumulators + lo1 = out(reg) _, + hi1 = out(reg) _, + lo0 = out(reg) _, + hi0 = out(reg) _, + + // Scalar scratch registers + // All are used in the scalar S-box + t0 = out(reg) _, + t1 = out(reg) _, + t2 = out(reg) _, + // t3-t6 are used in the scalar MDS matrix multiplication + t3 = out(reg) _, + t4 = out(reg) _, + t5 = out(reg) _, + t6 = out(reg) _, + + // Vector MDS multiplication accumulators + // v{n} and v1{n} are accumulators for res[n + 2] (we need two to mask latency) + // The low and high 64-bits are accumulators for the low and high results, respectively + out("v0") _, + out("v1") _, + out("v2") _, + out("v3") _, + out("v4") _, + out("v5") _, + out("v6") _, + out("v7") _, + out("v8") _, + out("v9") _, + out("v10") _, + out("v11") _, + out("v12") _, + out("v13") _, + out("v14") _, + out("v15") _, + out("v16") _, + out("v17") _, + out("v18") _, + out("v19") _, + + // Inputs into vector MDS matrix multiplication + // v20 and v21 are sbox(state0) and state1, respectively. They are copied from the scalar + // registers. + out("v20") _, + out("v21") _, + // v22, ..., v26 hold state[2,3], ..., state[10,11] + inout("v22") state_vector[0] => res23, + inout("v23") state_vector[1] => res45, + inout("v24") state_vector[2] => res67, + inout("v25") state_vector[3] => res89, + inout("v26") state_vector[4] => res1011, + + // Useful constants + in("v30") mds_consts0, + in("v31") mds_consts1, + + options(nostack, pure, readonly), + ); + ( + [ + res0, + res1, + res2_scalar, + res3_scalar, + res4_scalar, + res5_scalar, + res6_scalar, + res7_scalar, + res8_scalar, + res9_scalar, + res10_scalar, + res11_scalar, + ], + [res23, res45, res67, res89, res1011], + ) +} +*/ + +// ========================================== GLUE CODE +// =========================================== + +/* +#[inline(always)] +unsafe fn full_round(state: [u64; 12], round_constants: &[u64; WIDTH]) -> [u64; 12] { + let state = sbox_layer_full(state); + mds_layer_full(state, round_constants) +} + +#[inline] +unsafe fn full_rounds( + mut state: [u64; 12], + round_constants: &[u64; WIDTH * HALF_N_FULL_ROUNDS], +) -> [u64; 12] { + for round_constants_chunk in round_constants.chunks_exact(WIDTH) { + state = full_round(state, round_constants_chunk.try_into().unwrap()); + } + state +} + +#[inline(always)] +unsafe fn partial_rounds( + state: [u64; 12], + round_constants: &[u64; WIDTH * N_PARTIAL_ROUNDS], +) -> [u64; 12] { + let mut state = ( + state, + [ + vcombine_u64(vcreate_u64(state[2]), vcreate_u64(state[3])), + vcombine_u64(vcreate_u64(state[4]), vcreate_u64(state[5])), + vcombine_u64(vcreate_u64(state[6]), vcreate_u64(state[7])), + vcombine_u64(vcreate_u64(state[8]), vcreate_u64(state[9])), + vcombine_u64(vcreate_u64(state[10]), vcreate_u64(state[11])), + ], + ); + for round_constants_chunk in round_constants.chunks_exact(WIDTH) { + state = partial_round(state, round_constants_chunk.try_into().unwrap()); + } + state.0 +} +*/ + +#[inline(always)] +fn unwrap_state(state: [GoldilocksField; 12]) -> [u64; 12] { + state.map(|s| s.0) +} + +#[inline(always)] +fn wrap_state(state: [u64; 12]) -> [GoldilocksField; 12] { + state.map(GoldilocksField) +} + +/* +#[inline(always)] +pub unsafe fn poseidon(state: [GoldilocksField; 12]) -> [GoldilocksField; 12] { + let state = unwrap_state(state); + let state = const_layer_full(state, ALL_ROUND_CONSTANTS[0..WIDTH].try_into().unwrap()); + let state = full_rounds( + state, + ALL_ROUND_CONSTANTS[WIDTH..WIDTH * (HALF_N_FULL_ROUNDS + 1)] + .try_into() + .unwrap(), + ); + let state = partial_rounds( + state, + ALL_ROUND_CONSTANTS + [WIDTH * (HALF_N_FULL_ROUNDS + 1)..WIDTH * (HALF_N_FULL_ROUNDS + N_PARTIAL_ROUNDS + 1)] + .try_into() + .unwrap(), + ); + let state = full_rounds(state, &FINAL_ROUND_CONSTANTS); + wrap_state(state) +} +*/ + +#[inline(always)] +pub unsafe fn sbox_layer(state: &mut [GoldilocksField; WIDTH]) { + *state = wrap_state(sbox_layer_full(unwrap_state(*state))); +} + +#[inline(always)] +pub unsafe fn mds_layer(state: &[GoldilocksField; WIDTH]) -> [GoldilocksField; WIDTH] { + let state = unwrap_state(*state); + let state = mds_layer_full(state); + wrap_state(state) +} diff --git a/plonky2/src/hash/arch/aarch64/readme-asm.md b/plonky2/src/hash/arch/aarch64/readme-asm.md new file mode 100644 index 000000000..c83c5868c --- /dev/null +++ b/plonky2/src/hash/arch/aarch64/readme-asm.md @@ -0,0 +1,495 @@ +Partial rounds ASM +================== + +The partial rounds are written in hand-rolled ASM. This was necessary to ensure proper pipelining. Indeed, the ASM shaves 40% off the execution time of the original vector intrinsics-based partial round. + +The partial layer performs two operations: + 1. Apply the S-box to state[0] + 2. Apply an affine transform (MDS matrix + constant layer) to the entire state vector. + +The S-box must be performed in scalar to minimize latency. The MDS matrix is done mostly in vector to maximize throughput. To take advantage of the otherwise idle scalar execution units, MDS matrix multiplication for result[0..2] is done in scalar. Clearly, this necessitates some data movement, as the input state must be available to both scalar and vector execution units. + +This task has plentiful opportunities for pipelining and parallelism. Most immediately, the S-box—with its long latency chain—can be performed simultaneously with most of the MDS matrix multiplication, with the permuted input only available right before the reduction. In addition, the MDS matrix multiplication can be scheduled in a way that interleaves different kinds of operations, masking the latency of the reduction step. + +There are three chains of ASM: + 1. the S-box, + 2. the scalar part of MDS multiplication (for result[0..2]), + 3. the vector part of MDS multiplication (for result[2..12]). +Those chains are explained individually below. They interact sporadically to exchange results. In the compiled file, they have been interleaved. + + +S-box +----- + +The ASM for the S-box is as follows: +```assembly + umulh {t0}, {s0}, {s0} + mul {t1}, {s0}, {s0} + subs {t1}, {t1}, {t0}, lsr #32 + csetm {t2:w}, cc + lsl {t3}, {t0}, #32 + sub {t1}, {t1}, {t2} + mov {t0:w}, {t0:w} + sub {t0}, {t3}, {t0} + adds {t0}, {t1}, {t0} + csetm {t1:w}, cs + add {t0}, {t0}, {t1} + + // t0 now contains state ** 2 + umulh {t1}, {s0}, {t0} + umulh {t2}, {t0}, {t0} + mul {s0}, {s0}, {t0} + mul {t0}, {t0}, {t0} + subs {s0}, {s0}, {t1}, lsr #32 + csetm {t3:w}, cc + subs {t0}, {t0}, {t2}, lsr #32 + csetm {t4:w}, cc + lsl {t5}, {t1}, #32 + lsl {t6}, {t2}, #32 + sub {s0}, {s0}, {t3} + sub {t0}, {t0}, {t4} + mov {t1:w}, {t1:w} + mov {t2:w}, {t2:w} + sub {t1}, {t5}, {t1} + sub {t2}, {t6}, {t2} + adds {t1}, {s0}, {t1} + csetm {s0:w}, cs + adds {t2}, {t0}, {t2} + csetm {t0:w}, cs + add {t1}, {t1}, {s0} + add {t2}, {t2}, {t0} + + // t1 now contains state ** 3 + // t2 now contains state ** 4 + umulh {s0}, {t1}, {t2} + mul {t0}, {t1}, {t2} + subs {t0}, {t0}, {s0}, lsr #32 + csetm {t1:w}, cc + lsl {t2}, {s0}, #32 + sub {t0}, {t0}, {t1} + mov {s0:w}, {s0:w} + sub {s0}, {t2}, {s0} + adds {s0}, {t0}, {s0} + csetm {t0:w}, cs + add {s0}, {s0}, {t0} + + // s0 now contains state **7 + fmov d20, {s0} +``` + +It is merely four repetitions of a block of 11 instructions (the middle two repetitions are interleaved). The input and output are in `s0`. `t0` through `t6` are scratch registers. The `fmov` copies the result to the bottom 64 bits of the vector register v20. + +Trick: `csetm` sets its destination to all 1s if the condition is met. In our case the destination is 32-bits and the condition is overflow/underflow of the previous instruction, so we get EPSILON on over/underflow and 0 otherwise. + +Note: the last multiplication does not use `t3` through `t6`, making them available to scalar MDS multiplication. + + +Scalar MDS multiplication +------------------------- + +The ASM for the scalar MDS multiplication is +```assembly + ldp {lo0}, {lo1}, [{rc_ptr}] + add {lo1}, {lo1}, {s1:w}, uxtw + add {lo0}, {lo0}, {s1:w}, uxtw + lsr {hi0}, {s1}, #32 + lsr {t3}, {s2}, #32 + lsr {t4}, {s3}, #32 + add {hi1}, {hi0}, {t3} + add {hi0}, {hi0}, {t3}, lsl #1 + add {lo1}, {lo1}, {s2:w}, uxtw + add {lo0}, {lo0}, {s2:w}, uxtw #1 + lsr {t3}, {s4}, #32 + lsr {t5}, {s5}, #32 + add {hi1}, {hi1}, {t4}, lsl #1 + add {t6}, {t3}, {t5}, lsl #3 + add {t5}, {t3}, {t5}, lsl #2 + lsr {t3}, {s6}, #32 + lsr {s1}, {s7}, #32 + mov {s2:w}, {s4:w} + add {hi0}, {hi0}, {t4} + add {lo1}, {lo1}, {s3:w}, uxtw #1 + add {lo0}, {lo0}, {s3:w}, uxtw + add {t4}, {s2}, {s5:w}, uxtw #3 + add {s2}, {s2}, {s5:w}, uxtw #2 + add {s3}, {s1}, {t3}, lsl #4 + add {hi1}, {hi1}, {t6} + add {hi0}, {hi0}, {t5}, lsl #3 + mov {t5:w}, {s6:w} + mov {t6:w}, {s7:w} + add {s4}, {t6}, {t5}, lsl #4 + add {t3}, {t3}, {s1}, lsl #7 + lsr {s1}, {s8}, #32 + lsr {s5}, {s9}, #32 + add {lo1}, {lo1}, {t4} + add {lo0}, {lo0}, {s2}, lsl #3 + add {t4}, {t5}, {t6}, lsl #7 + add {hi1}, {hi1}, {s3}, lsl #1 + add {t5}, {s1}, {s5}, lsl #4 + add {lo1}, {lo1}, {s4}, lsl #1 + add {hi0}, {hi0}, {t3}, lsl #1 + mov {t3:w}, {s9:w} + mov {t6:w}, {s8:w} + add {s2}, {t6}, {t3}, lsl #4 + add {s1}, {s5}, {s1}, lsl #9 + lsr {s3}, {s10}, #32 + lsr {s4}, {s11}, #32 + add {lo0}, {lo0}, {t4}, lsl #1 + add {t3}, {t3}, {t6}, lsl #9 + add {hi1}, {hi1}, {t5}, lsl #8 + add {t4}, {s3}, {s4}, lsl #13 + add {lo1}, {lo1}, {s2}, lsl #8 + add {hi0}, {hi0}, {s1}, lsl #3 + mov {t5:w}, {s10:w} + mov {t6:w}, {s11:w} + add {s1}, {t5}, {t6}, lsl #13 + add {s2}, {s4}, {s3}, lsl #6 + add {lo0}, {lo0}, {t3}, lsl #3 + add {t3}, {t6}, {t5}, lsl #6 + add {hi1}, {hi1}, {t4}, lsl #3 + add {lo1}, {lo1}, {s1}, lsl #3 + add {hi0}, {hi0}, {s2}, lsl #10 + lsr {t4}, {s0}, #32 + add {lo0}, {lo0}, {t3}, lsl #10 + add {hi1}, {hi1}, {t4}, lsl #10 + mov {t3:w}, {s0:w} + add {lo1}, {lo1}, {t3}, lsl #10 + add {hi0}, {hi0}, {t4} + add {lo0}, {lo0}, {t3} + + // Reduction + lsl {t0}, {hi0}, #32 + lsl {t1}, {hi1}, #32 + adds {lo0}, {lo0}, {t0} + csetm {t0:w}, cs + adds {lo1}, {lo1}, {t1} + csetm {t1:w}, cs + and {t2}, {hi0}, #0xffffffff00000000 + and {t3}, {hi1}, #0xffffffff00000000 + lsr {hi0}, {hi0}, #32 + lsr {hi1}, {hi1}, #32 + sub {hi0}, {t2}, {hi0} + sub {hi1}, {t3}, {hi1} + add {lo0}, {lo0}, {t0} + add {lo1}, {lo1}, {t1} + adds {lo0}, {lo0}, {hi0} + csetm {t0:w}, cs + adds {lo1}, {lo1}, {hi1} + csetm {t1:w}, cs + add {s0}, {lo0}, {t0} + add {s1}, {lo1}, {t1} +``` + +The MDS multiplication is done separately on the low 32 bits and the high 32 bits of the input, and combined by linearity. Each input is split into the low part and the high part. There are separate accumulators for the low and high parts of the result `lo0`/`lo1`, for result[0] and result[1] respectively, and `hi0`/`hi1`. + +The pointer to the round constants is given in `rc_ptr`. Registers `s0`-`s11` contain the state vector at the start, and are later used as scratch. `t3`-`t6` are temporaries. + +`s1` is assumed to be available first, as it is computed in scalar. `s2`-`s11` are used next. `s0` is assumed to be available last, as it must be transformed by the S-box. + +The reduction is +```assembly + lsl {t0}, {hi0}, #32 + adds {lo0}, {lo0}, {t0} + csetm {t0:w}, cs + and {t2}, {hi0}, #0xffffffff00000000 + lsr {hi0}, {hi0}, #32 + sub {hi0}, {t2}, {hi0} + add {lo0}, {lo0}, {t0} + adds {lo0}, {lo0}, {hi0} + csetm {t0:w}, cs + add {s0}, {lo0}, {t0} +``` +repeated and interleaved. `cset` sets its destination to EPSILON if the previous instruction overflowed. + + +Vector MDS multiplication +------------------------- + +The ASM for the vector MDS multiplication is +```assembly + fmov d21, {s1} + + // res2,3 <- consts,state1 + ldp d0, d1, [{rc_ptr}, #16] + ushll.2d v10, v21, #10 // MDS[11] == 10 + ushll.2d v11, v21, #16 // MDS[10] == 16 + + // res2,3 <- state2,3 + uaddw.2d v0, v0, v22 // MDS[0] == 0 + umlal.2d v1, v22, v31[1] // MDS[11] == 10 + uaddw2.2d v10, v10, v22 // MDS[1] == 0 + uaddw2.2d v11, v11, v22 // MDS[0] == 0 + + // res4,5 <- consts,state1 + ldp d2, d3, [{rc_ptr}, #32] + ushll.2d v12, v21, #3 // MDS[9] == 3 + ushll.2d v13, v21, #12 // MDS[8] == 12 + + // res2,3 <- state4,5 + umlal.2d v0, v23, v30[1] // MDS[2] == 1 + uaddw2.2d v10, v10, v23 // MDS[3] == 0 + uaddw.2d v11, v11, v23 // MDS[1] == 0 + umlal2.2d v1, v23, v30[1] // MDS[2] == 1 + + // res4,5 <- state2,3 + umlal.2d v2, v22, v31[3] // MDS[10] == 16 + umlal2.2d v12, v22, v31[1] // MDS[11] == 10 + umlal.2d v3, v22, v30[2] // MDS[9] == 3 + umlal2.2d v13, v22, v31[3] // MDS[10] == 16 + + // res6,7 <- consts,state1 + ldp d4, d5, [{rc_ptr}, #48] + ushll.2d v14, v21, #8 // MDS[7] == 8 + ushll.2d v15, v21, #1 // MDS[6] == 1 + + // res2,3 <- state6,7 + umlal.2d v0, v24, v30[2] // MDS[4] == 3 + umlal2.2d v10, v24, v30[3] // MDS[5] == 5 + umlal2.2d v11, v24, v30[2] // MDS[4] == 3 + uaddw.2d v1, v1, v24 // MDS[3] == 0 + + // res4,5 <- state4,5 + uaddw.2d v2, v2, v23 // MDS[0] == 0 + umlal.2d v3, v23, v31[1] // MDS[11] == 10 + uaddw2.2d v12, v12, v23 // MDS[1] == 0 + uaddw2.2d v13, v13, v23 // MDS[0] == 0 + + // res6,7 <- state2,3 + umlal.2d v4, v22, v31[2] // MDS[8] == 12 + umlal2.2d v14, v22, v30[2] // MDS[9] == 3 + umlal.2d v5, v22, v31[0] // MDS[7] == 8 + umlal2.2d v15, v22, v31[2] // MDS[8] == 12 + + // res8,9 <- consts,state1 + ldp d6, d7, [{rc_ptr}, #64] + ushll.2d v16, v21, #5 // MDS[5] == 5 + ushll.2d v17, v21, #3 // MDS[4] == 3 + + // res2,3 <- state8,9 + umlal.2d v0, v25, v30[1] // MDS[6] == 1 + umlal2.2d v10, v25, v31[0] // MDS[7] == 8 + umlal.2d v1, v25, v30[3] // MDS[5] == 5 + umlal2.2d v11, v25, v30[1] // MDS[6] == 1 + + // res4,5 <- state6,7 + umlal.2d v2, v24, v30[1] // MDS[2] == 1 + uaddw2.2d v12, v12, v24 // MDS[3] == 0 + uaddw.2d v13, v13, v24 // MDS[1] == 0 + umlal2.2d v3, v24, v30[1] // MDS[2] == 1 + + // res6,7 <- state4,5 + umlal.2d v4, v23, v31[3] // MDS[10] == 16 + umlal2.2d v14, v23, v31[1] // MDS[11] == 10 + umlal.2d v5, v23, v30[2] // MDS[9] == 3 + umlal2.2d v15, v23, v31[3] // MDS[10] == 16 + + // res8,9 <- state2,3 + umlal.2d v6, v22, v30[1] // MDS[6] == 1 + umlal2.2d v16, v22, v31[0] // MDS[7] == 8 + umlal.2d v7, v22, v30[3] // MDS[5] == 5 + umlal2.2d v17, v22, v30[1] // MDS[6] == 1 + + // res10,11 <- consts,state1 + ldp d8, d9, [{rc_ptr}, #80] + ushll.2d v18, v21, #0 // MDS[3] == 0 + ushll.2d v19, v21, #1 // MDS[2] == 1 + + // res2,3 <- state10,11 + umlal.2d v0, v26, v31[2] // MDS[8] == 12 + umlal2.2d v10, v26, v30[2] // MDS[9] == 3 + umlal.2d v1, v26, v31[0] // MDS[7] == 8 + umlal2.2d v11, v26, v31[2] // MDS[8] == 12 + + // res4,5 <- state8,9 + umlal.2d v2, v25, v30[2] // MDS[4] == 3 + umlal2.2d v12, v25, v30[3] // MDS[5] == 5 + umlal2.2d v13, v25, v30[2] // MDS[4] == 3 + uaddw.2d v3, v3, v25 // MDS[3] == 0 + + // res6,7 <- state6,7 + uaddw.2d v4, v4, v24 // MDS[0] == 0 + umlal.2d v5, v24, v31[1] // MDS[11] == 10 + uaddw2.2d v14, v14, v24 // MDS[1] == 0 + uaddw2.2d v15, v15, v24 // MDS[0] == 0 + + // res8,9 <- state4,5 + umlal.2d v6, v23, v31[2] // MDS[8] == 12 + umlal2.2d v16, v23, v30[2] // MDS[9] == 3 + umlal.2d v7, v23, v31[0] // MDS[7] == 8 + umlal2.2d v17, v23, v31[2] // MDS[8] == 12 + + // res10,11 <- state2,3 + umlal.2d v8, v22, v30[2] // MDS[4] == 3 + umlal2.2d v18, v22, v30[3] // MDS[5] == 5 + uaddw.2d v9, v9, v22 // MDS[3] == 0 + umlal2.2d v19, v22, v30[2] // MDS[4] == 3 + + // merge accumulators, res2,3 <- state0, and reduce + add.2d v0, v0, v10 + add.2d v1, v1, v11 + + umlal.2d v0, v20, v31[3] // MDS[10] == 16 + umlal.2d v1, v20, v30[2] // MDS[9] == 3 + mds_reduce_asm(v0, v1, v22) + fmov {s2}, d22 + fmov.d {s3}, v22[1] + + // res4,5 <- state10,11 + umlal.2d v2, v26, v30[1] // MDS[6] == 1 + umlal2.2d v12, v26, v31[0] // MDS[7] == 8 + umlal.2d v3, v26, v30[3] // MDS[5] == 5 + umlal2.2d v13, v26, v30[1] // MDS[6] == 1 + + // res6,7 <- state8,9 + umlal.2d v4, v25, v30[1] // MDS[2] == 1 + uaddw2.2d v14, v14, v25 // MDS[3] == 0 + uaddw.2d v15, v15, v25 // MDS[1] == 0 + umlal2.2d v5, v25, v30[1] // MDS[2] == 1 + + // res8,9 <- state6,7 + umlal.2d v6, v24, v31[3] // MDS[10] == 16 + umlal2.2d v16, v24, v31[1] // MDS[11] == 10 + umlal.2d v7, v24, v30[2] // MDS[9] == 3 + umlal2.2d v17, v24, v31[3] // MDS[10] == 16 + + // res10,11 <- state4,5 + umlal.2d v8, v23, v30[1] // MDS[6] == 1 + umlal2.2d v18, v23, v31[0] // MDS[7] == 8 + umlal.2d v9, v23, v30[3] // MDS[5] == 5 + umlal2.2d v19, v23, v30[1] // MDS[6] == 1 + + // merge accumulators, res4,5 <- state0, and reduce + add.2d v2, v2, v12 + add.2d v3, v3, v13 + + umlal.2d v2, v20, v31[2] // MDS[8] == 12 + umlal.2d v3, v20, v31[0] // MDS[7] == 8 + mds_reduce_asm(v2, v3, v23) + fmov {s4}, d23 + fmov.d {s5}, v23[1] + + // res6,7 <- state10,11 + umlal.2d v4, v26, v30[2] // MDS[4] == 3 + umlal2.2d v14, v26, v30[3] // MDS[5] == 5 + umlal2.2d v15, v26, v30[2] // MDS[4] == 3 + uaddw.2d v5, v5, v26 // MDS[3] == 0 + + // res8,9 <- state8,9 + uaddw.2d v6, v6, v25 // MDS[0] == 0 + uaddw2.2d v16, v16, v25 // MDS[1] == 0 + uaddw2.2d v17, v17, v25 // MDS[0] == 0 + umlal.2d v7, v25, v31[1] // MDS[11] == 10 + + // res10,11 <- state6,7 + umlal.2d v8, v24, v31[2] // MDS[8] == 12 + umlal2.2d v18, v24, v30[2] // MDS[9] == 3 + umlal.2d v9, v24, v31[0] // MDS[7] == 8 + umlal2.2d v19, v24, v31[2] // MDS[8] == 12 + + // merge accumulators, res6,7 <- state0, and reduce + add.2d v4, v4, v14 + add.2d v5, v5, v15 + + umlal.2d v4, v20, v30[1] // MDS[6] == 1 + umlal.2d v5, v20, v30[3] // MDS[5] == 5 + mds_reduce_asm(v4, v5, v24) + fmov {s6}, d24 + fmov.d {s7}, v24[1] + + // res8,9 <- state10,11 + umlal.2d v6, v26, v30[1] // MDS[2] == 1 + uaddw2.2d v16, v16, v26 // MDS[3] == 0 + umlal2.2d v17, v26, v30[1] // MDS[2] == 1 + uaddw.2d v7, v7, v26 // MDS[1] == 0 + + // res10,11 <- state8,9 + umlal.2d v8, v25, v31[3] // MDS[10] == 16 + umlal2.2d v18, v25, v31[1] // MDS[11] == 10 + umlal.2d v9, v25, v30[2] // MDS[9] == 3 + umlal2.2d v19, v25, v31[3] // MDS[10] == 16 + + // merge accumulators, res8,9 <- state0, and reduce + add.2d v6, v6, v16 + add.2d v7, v7, v17 + + umlal.2d v6, v20, v30[2] // MDS[4] == 3 + uaddw.2d v7, v7, v20 // MDS[3] == 0 + mds_reduce_asm(v6, v7, v25) + fmov {s8}, d25 + fmov.d {s9}, v25[1] + + // res10,11 <- state10,11 + uaddw.2d v8, v8, v26 // MDS[0] == 0 + uaddw2.2d v18, v18, v26 // MDS[1] == 0 + umlal.2d v9, v26, v31[1] // MDS[11] == 10 + uaddw2.2d v19, v19, v26 // MDS[0] == 0 + + // merge accumulators, res10,11 <- state0, and reduce + add.2d v8, v8, v18 + add.2d v9, v9, v19 + + umlal.2d v8, v20, v30[1] // MDS[2] == 1 + uaddw.2d v9, v9, v20 // MDS[1] == 0 + mds_reduce_asm(v8, v9, v26) + fmov {s10}, d26 + fmov.d {s11}, v26[1] +``` +where the macro `mds_reduce_asm` is defined as +```assembly + ($c0, $c1, $out) => { + // Swizzle + zip1.2d $out, $c0, $c1 // lo + zip2.2d $c0, $c0, $c1 // hi + + // Reduction from u96 + usra.2d $c0, $out, #32 + sli.2d $out, $c0, #32 + // Extract high 32-bits. + uzp2.4s $c0, $c0, $c0 + // Multiply by EPSILON and accumulate. + mov.16b $c1, $out + umlal.2d $out, $c0, v30[0] + cmhi.2d $c1, $c1, $out + usra.2d $out, $c1, #32 + } +``` + +The order in which inputs are assumed to be available is: +- state[1] +- state[2] and state[3] +- state[4] and state[5] +- state[6] and state[7] +- state[8] and state[9] +- state[10] and state[11] +- state[0] + +The order in which the results are produced is: +- state[2] and state[3] +- state[4] and state[5] +- state[6] and state[7] +- state[8] and state[9] +- state[10] and state[11] + +The order of the instructions in the assembly should be thought of as a setting the relative priority of each instruction; because of CPU reordering, it does not correspond exactly to execution order in time. Ideally, we'd like the MDS matrix multiplication to happen in the following order: + s[1] s[2..4] s[4..6] s[6..8] s[8..10] s[10..12] s[0] + res[2..4] 1 2 4 7 11 16 21 + res[4..6] 3 5 8 12 17 22 26 +output res[6..8] 6 9 13 18 23 27 30 + res[8..10] 10 14 19 24 28 31 33 + res[10..12] 15 20 25 29 32 34 35 + +This is the order in which the operations are ordered in the ASM. It permits the start of one iteration to be interleaved with the end of the previous iteration (CPU reordering means we don't have to do it manually). Reductions, which have high latency, are executed as soon as the unreduced product is available; the pipelining permits them to be executed simultaneously with multiplication/accumulation, masking the latency. + +The registers `v0`-`v19` are used for scratch. `v0` and `v10` are accumulators for res[2], `v1` and `v11` are accumulators for res[3], and so on. The accumulators hold the low result in the low 64 bits and the high result in the high 64 bits (this is convenient as both low and high are always multiplied by the same constant). They must be added before reduction. + +The inputs for state[0] and state[1] are in the low 64 bits of `v20` and `v21`, respectively. The inputs and outputs for state[2..4], ..., state[10..12] are in `v22`, ..., `v26`, respectively. + +`v30` and `v31` contains the constants [EPSILON, 1 << 1, 1 << 3, 1 << 5], [1 << 8, 1 << 10, 1 << 12, 1 << 16]. EPSILON is used in the reduction. The remaining constants are MDS matrix elements (except 1, which is omitted) and are used to form the dot products. + +The instruction `umlal.2d v4, v20, v30[1]` can be read as: +1. take the low 64 bits (`umlal2` for high 64 bits) of `v20` (state[0]), +2. multiply the low and high 32 bits thereof by `v30[1]` (1), +3. add the low and high product to the low and high 64-bits of `v4` respectively, +4. save to `v4`. + +We do not use `umlal` when the MDS coefficient is 1; instead, we use `uaddw` ("widening add") to reduce latency. diff --git a/plonky2/src/hash/arch/mod.rs b/plonky2/src/hash/arch/mod.rs new file mode 100644 index 000000000..1de23f671 --- /dev/null +++ b/plonky2/src/hash/arch/mod.rs @@ -0,0 +1,5 @@ +#[cfg(target_arch = "x86_64")] +pub(crate) mod x86_64; + +#[cfg(target_arch = "aarch64")] +pub(crate) mod aarch64; diff --git a/plonky2/src/hash/arch/x86_64/mod.rs b/plonky2/src/hash/arch/x86_64/mod.rs new file mode 100644 index 000000000..0730b6261 --- /dev/null +++ b/plonky2/src/hash/arch/x86_64/mod.rs @@ -0,0 +1,5 @@ +// // Requires: +// // - AVX2 +// // - BMI2 (for MULX and SHRX) +// #[cfg(all(target_feature = "avx2", target_feature = "bmi2"))] +// pub(crate) mod poseidon_goldilocks_avx2_bmi2; diff --git a/plonky2/src/hash/arch/x86_64/poseidon_goldilocks_avx2_bmi2.rs b/plonky2/src/hash/arch/x86_64/poseidon_goldilocks_avx2_bmi2.rs new file mode 100644 index 000000000..e56fea5ea --- /dev/null +++ b/plonky2/src/hash/arch/x86_64/poseidon_goldilocks_avx2_bmi2.rs @@ -0,0 +1,981 @@ +use core::arch::asm; +use core::arch::x86_64::*; +use core::mem::size_of; + +use static_assertions::const_assert; + +use crate::field::goldilocks_field::GoldilocksField; +use crate::field::types::Field; +use crate::hash::poseidon::{ + Poseidon, ALL_ROUND_CONSTANTS, HALF_N_FULL_ROUNDS, N_PARTIAL_ROUNDS, N_ROUNDS, +}; +use crate::util::branch_hint; + +// WARNING: This code contains tricks that work for the current MDS matrix and round constants, but +// are not guaranteed to work if those are changed. + +// * Constant definitions * + +const WIDTH: usize = 12; + +// These transformed round constants are used where the constant layer is fused with the preceding +// MDS layer. The FUSED_ROUND_CONSTANTS for round i are the ALL_ROUND_CONSTANTS for round i + 1. +// The FUSED_ROUND_CONSTANTS for the very last round are 0, as it is not followed by a constant +// layer. On top of that, all FUSED_ROUND_CONSTANTS are shifted by 2 ** 63 to save a few XORs per +// round. +const fn make_fused_round_constants() -> [u64; WIDTH * N_ROUNDS] { + let mut res = [0x8000000000000000u64; WIDTH * N_ROUNDS]; + let mut i: usize = WIDTH; + while i < WIDTH * N_ROUNDS { + res[i - WIDTH] ^= ALL_ROUND_CONSTANTS[i]; + i += 1; + } + res +} +const FUSED_ROUND_CONSTANTS: [u64; WIDTH * N_ROUNDS] = make_fused_round_constants(); + +// This is the top row of the MDS matrix. Concretely, it's the MDS exps vector at the following +// indices: [0, 11, ..., 1]. +static TOP_ROW_EXPS: [usize; 12] = [0, 10, 16, 3, 12, 8, 1, 5, 3, 0, 1, 0]; + +// * Compile-time checks * + +/// The MDS matrix multiplication ASM is specific to the MDS matrix below. We want this file to +/// fail to compile if it has been changed. +#[allow(dead_code)] +const fn check_mds_matrix() -> bool { + // Can't == two arrays in a const_assert! (: + let mut i = 0; + let wanted_matrix_exps = [0, 0, 1, 0, 3, 5, 1, 8, 12, 3, 16, 10]; + while i < WIDTH { + if ::MDS_MATRIX_EXPS[i] != wanted_matrix_exps[i] { + return false; + } + i += 1; + } + true +} +const_assert!(check_mds_matrix()); + +/// The maximum amount by which the MDS matrix will multiply the input. +/// i.e. max(MDS(state)) <= mds_matrix_inf_norm() * max(state). +const fn mds_matrix_inf_norm() -> u64 { + let mut cumul = 0; + let mut i = 0; + while i < WIDTH { + cumul += 1 << ::MDS_MATRIX_EXPS[i]; + i += 1; + } + cumul +} + +/// Ensure that adding round constants to the low result of the MDS multiplication can never +/// overflow. +#[allow(dead_code)] +const fn check_round_const_bounds_mds() -> bool { + let max_mds_res = mds_matrix_inf_norm() * (u32::MAX as u64); + let mut i = WIDTH; // First const layer is handled specially. + while i < WIDTH * N_ROUNDS { + if ALL_ROUND_CONSTANTS[i].overflowing_add(max_mds_res).1 { + return false; + } + i += 1; + } + true +} +const_assert!(check_round_const_bounds_mds()); + +/// Ensure that the first WIDTH round constants are in canonical form for the vpcmpgtd trick. +#[allow(dead_code)] +const fn check_round_const_bounds_init() -> bool { + let max_permitted_round_const = 0xffffffff00000000; + let mut i = 0; // First const layer is handled specially. + while i < WIDTH { + if ALL_ROUND_CONSTANTS[i] > max_permitted_round_const { + return false; + } + i += 1; + } + true +} +const_assert!(check_round_const_bounds_init()); + +// Preliminary notes: +// 1. AVX does not support addition with carry but 128-bit (2-word) addition can be easily +// emulated. The method recognizes that for a + b overflowed iff (a + b) < a: +// i. res_lo = a_lo + b_lo +// ii. carry_mask = res_lo < a_lo +// iii. res_hi = a_hi + b_hi - carry_mask +// Notice that carry_mask is subtracted, not added. This is because AVX comparison instructions +// return -1 (all bits 1) for true and 0 for false. +// +// 2. AVX does not have unsigned 64-bit comparisons. Those can be emulated with signed comparisons +// by recognizing that a , $v:ident) => { + ($f::<$l>($v.0), $f::<$l>($v.1), $f::<$l>($v.2)) + }; + ($f:ident::<$l:literal>, $v1:ident, $v2:ident) => { + ( + $f::<$l>($v1.0, $v2.0), + $f::<$l>($v1.1, $v2.1), + $f::<$l>($v1.2, $v2.2), + ) + }; + ($f:ident, $v:ident) => { + ($f($v.0), $f($v.1), $f($v.2)) + }; + ($f:ident, $v0:ident, $v1:ident) => { + ($f($v0.0, $v1.0), $f($v0.1, $v1.1), $f($v0.2, $v1.2)) + }; + ($f:ident, $v0:ident, rep $v1:ident) => { + ($f($v0.0, $v1), $f($v0.1, $v1), $f($v0.2, $v1)) + }; +} + +#[inline(always)] +unsafe fn const_layer( + state: (__m256i, __m256i, __m256i), + round_const_arr: &[u64; 12], +) -> (__m256i, __m256i, __m256i) { + let sign_bit = _mm256_set1_epi64x(i64::MIN); + let round_const = ( + _mm256_loadu_si256((&round_const_arr[0..4]).as_ptr().cast::<__m256i>()), + _mm256_loadu_si256((&round_const_arr[4..8]).as_ptr().cast::<__m256i>()), + _mm256_loadu_si256((&round_const_arr[8..12]).as_ptr().cast::<__m256i>()), + ); + let state_s = map3!(_mm256_xor_si256, state, rep sign_bit); // Shift by 2**63. + let res_maybe_wrapped_s = map3!(_mm256_add_epi64, state_s, round_const); + // 32-bit compare is much faster than 64-bit compare on Intel. We can use 32-bit compare here + // as long as we can guarantee that state > res_maybe_wrapped iff state >> 32 > + // res_maybe_wrapped >> 32. Clearly, if state >> 32 > res_maybe_wrapped >> 32, then state > + // res_maybe_wrapped, and similarly for <. + // It remains to show that we can't have state >> 32 == res_maybe_wrapped >> 32 with state > + // res_maybe_wrapped. If state >> 32 == res_maybe_wrapped >> 32, then round_const >> 32 = + // 0xffffffff and the addition of the low doubleword generated a carry bit. This can never + // occur if all round constants are < 0xffffffff00000001 = ORDER: if the high bits are + // 0xffffffff, then the low bits are 0, so the carry bit cannot occur. So this trick is valid + // as long as all the round constants are in canonical form. + // The mask contains 0xffffffff in the high doubleword if wraparound occurred and 0 otherwise. + // We will ignore the low doubleword. + let wraparound_mask = map3!(_mm256_cmpgt_epi32, state_s, res_maybe_wrapped_s); + // wraparound_adjustment contains 0xffffffff = EPSILON if wraparound occurred and 0 otherwise. + let wraparound_adjustment = map3!(_mm256_srli_epi64::<32>, wraparound_mask); + // XOR commutes with the addition below. Placing it here helps mask latency. + let res_maybe_wrapped = map3!(_mm256_xor_si256, res_maybe_wrapped_s, rep sign_bit); + // Add EPSILON = subtract ORDER. + let res = map3!(_mm256_add_epi64, res_maybe_wrapped, wraparound_adjustment); + res +} + +#[inline(always)] +unsafe fn square3( + x: (__m256i, __m256i, __m256i), +) -> ((__m256i, __m256i, __m256i), (__m256i, __m256i, __m256i)) { + let x_hi = { + // Move high bits to low position. The high bits of x_hi are ignored. Swizzle is faster than + // bitshift. This instruction only has a floating-point flavor, so we cast to/from float. + // This is safe and free. + let x_ps = map3!(_mm256_castsi256_ps, x); + let x_hi_ps = map3!(_mm256_movehdup_ps, x_ps); + map3!(_mm256_castps_si256, x_hi_ps) + }; + + // All pairwise multiplications. + let mul_ll = map3!(_mm256_mul_epu32, x, x); + let mul_lh = map3!(_mm256_mul_epu32, x, x_hi); + let mul_hh = map3!(_mm256_mul_epu32, x_hi, x_hi); + + // Bignum addition, but mul_lh is shifted by 33 bits (not 32). + let mul_ll_hi = map3!(_mm256_srli_epi64::<33>, mul_ll); + let t0 = map3!(_mm256_add_epi64, mul_lh, mul_ll_hi); + let t0_hi = map3!(_mm256_srli_epi64::<31>, t0); + let res_hi = map3!(_mm256_add_epi64, mul_hh, t0_hi); + + // Form low result by adding the mul_ll and the low 31 bits of mul_lh (shifted to the high + // position). + let mul_lh_lo = map3!(_mm256_slli_epi64::<33>, mul_lh); + let res_lo = map3!(_mm256_add_epi64, mul_ll, mul_lh_lo); + + (res_lo, res_hi) +} + +#[inline(always)] +unsafe fn mul3( + x: (__m256i, __m256i, __m256i), + y: (__m256i, __m256i, __m256i), +) -> ((__m256i, __m256i, __m256i), (__m256i, __m256i, __m256i)) { + let epsilon = _mm256_set1_epi64x(0xffffffff); + let x_hi = { + // Move high bits to low position. The high bits of x_hi are ignored. Swizzle is faster than + // bitshift. This instruction only has a floating-point flavor, so we cast to/from float. + // This is safe and free. + let x_ps = map3!(_mm256_castsi256_ps, x); + let x_hi_ps = map3!(_mm256_movehdup_ps, x_ps); + map3!(_mm256_castps_si256, x_hi_ps) + }; + let y_hi = { + let y_ps = map3!(_mm256_castsi256_ps, y); + let y_hi_ps = map3!(_mm256_movehdup_ps, y_ps); + map3!(_mm256_castps_si256, y_hi_ps) + }; + + // All four pairwise multiplications + let mul_ll = map3!(_mm256_mul_epu32, x, y); + let mul_lh = map3!(_mm256_mul_epu32, x, y_hi); + let mul_hl = map3!(_mm256_mul_epu32, x_hi, y); + let mul_hh = map3!(_mm256_mul_epu32, x_hi, y_hi); + + // Bignum addition + // Extract high 32 bits of mul_ll and add to mul_hl. This cannot overflow. + let mul_ll_hi = map3!(_mm256_srli_epi64::<32>, mul_ll); + let t0 = map3!(_mm256_add_epi64, mul_hl, mul_ll_hi); + // Extract low 32 bits of t0 and add to mul_lh. Again, this cannot overflow. + // Also, extract high 32 bits of t0 and add to mul_hh. + let t0_lo = map3!(_mm256_and_si256, t0, rep epsilon); + let t0_hi = map3!(_mm256_srli_epi64::<32>, t0); + let t1 = map3!(_mm256_add_epi64, mul_lh, t0_lo); + let t2 = map3!(_mm256_add_epi64, mul_hh, t0_hi); + // Lastly, extract the high 32 bits of t1 and add to t2. + let t1_hi = map3!(_mm256_srli_epi64::<32>, t1); + let res_hi = map3!(_mm256_add_epi64, t2, t1_hi); + + // Form res_lo by combining the low half of mul_ll with the low half of t1 (shifted into high + // position). + let t1_lo = { + let t1_ps = map3!(_mm256_castsi256_ps, t1); + let t1_lo_ps = map3!(_mm256_moveldup_ps, t1_ps); + map3!(_mm256_castps_si256, t1_lo_ps) + }; + let res_lo = map3!(_mm256_blend_epi32::<0xaa>, mul_ll, t1_lo); + + (res_lo, res_hi) +} + +/// Addition, where the second operand is `0 <= y < 0xffffffff00000001`. +#[inline(always)] +unsafe fn add_small( + x_s: (__m256i, __m256i, __m256i), + y: (__m256i, __m256i, __m256i), +) -> (__m256i, __m256i, __m256i) { + let res_wrapped_s = map3!(_mm256_add_epi64, x_s, y); + let mask = map3!(_mm256_cmpgt_epi32, x_s, res_wrapped_s); + let wrapback_amt = map3!(_mm256_srli_epi64::<32>, mask); // EPSILON if overflowed else 0. + let res_s = map3!(_mm256_add_epi64, res_wrapped_s, wrapback_amt); + res_s +} + +#[inline(always)] +unsafe fn maybe_adj_sub(res_wrapped_s: __m256i, mask: __m256i) -> __m256i { + // The subtraction is very unlikely to overflow so we're best off branching. + // The even u32s in `mask` are meaningless, so we want to ignore them. `_mm256_testz_pd` + // branches depending on the sign bit of double-precision (64-bit) floats. Bit cast `mask` to + // floating-point (this is free). + let mask_pd = _mm256_castsi256_pd(mask); + // `_mm256_testz_pd(mask_pd, mask_pd) == 1` iff all sign bits are 0, meaning that underflow + // did not occur for any of the vector elements. + if _mm256_testz_pd(mask_pd, mask_pd) == 1 { + res_wrapped_s + } else { + branch_hint(); + // Highly unlikely: underflow did occur. Find adjustment per element and apply it. + let adj_amount = _mm256_srli_epi64::<32>(mask); // EPSILON if underflow. + _mm256_sub_epi64(res_wrapped_s, adj_amount) + } +} + +/// Addition, where the second operand is much smaller than `0xffffffff00000001`. +#[inline(always)] +unsafe fn sub_tiny( + x_s: (__m256i, __m256i, __m256i), + y: (__m256i, __m256i, __m256i), +) -> (__m256i, __m256i, __m256i) { + let res_wrapped_s = map3!(_mm256_sub_epi64, x_s, y); + let mask = map3!(_mm256_cmpgt_epi32, res_wrapped_s, x_s); + let res_s = map3!(maybe_adj_sub, res_wrapped_s, mask); + res_s +} + +#[inline(always)] +unsafe fn reduce3( + (lo0, hi0): ((__m256i, __m256i, __m256i), (__m256i, __m256i, __m256i)), +) -> (__m256i, __m256i, __m256i) { + let sign_bit = _mm256_set1_epi64x(i64::MIN); + let epsilon = _mm256_set1_epi64x(0xffffffff); + let lo0_s = map3!(_mm256_xor_si256, lo0, rep sign_bit); + let hi_hi0 = map3!(_mm256_srli_epi64::<32>, hi0); + let lo1_s = sub_tiny(lo0_s, hi_hi0); + let t1 = map3!(_mm256_mul_epu32, hi0, rep epsilon); + let lo2_s = add_small(lo1_s, t1); + let lo2 = map3!(_mm256_xor_si256, lo2_s, rep sign_bit); + lo2 +} + +#[inline(always)] +unsafe fn sbox_layer_full(state: (__m256i, __m256i, __m256i)) -> (__m256i, __m256i, __m256i) { + let state2_unreduced = square3(state); + let state2 = reduce3(state2_unreduced); + let state4_unreduced = square3(state2); + let state3_unreduced = mul3(state2, state); + let state4 = reduce3(state4_unreduced); + let state3 = reduce3(state3_unreduced); + let state7_unreduced = mul3(state3, state4); + let state7 = reduce3(state7_unreduced); + state7 +} + +#[inline(always)] +unsafe fn mds_layer_reduce( + lo_s: (__m256i, __m256i, __m256i), + hi: (__m256i, __m256i, __m256i), +) -> (__m256i, __m256i, __m256i) { + // This is done in assembly because, frankly, it's cleaner than intrinsics. We also don't have + // to worry about whether the compiler is doing weird things. This entire routine needs proper + // pipelining so there's no point rewriting this, only to have to rewrite it again. + let res0: __m256i; + let res1: __m256i; + let res2: __m256i; + let epsilon = _mm256_set1_epi64x(0xffffffff); + let sign_bit = _mm256_set1_epi64x(i64::MIN); + asm!( + // The high results are in ymm3, ymm4, ymm5. + // The low results (shifted by 2**63) are in ymm0, ymm1, ymm2 + + // We want to do: ymm0 := ymm0 + (ymm3 * 2**32) in modulo P. + // This can be computed by ymm0 + (ymm3 << 32) + (ymm3 >> 32) * EPSILON, + // where the additions must correct for over/underflow. + + // First, do ymm0 + (ymm3 << 32) (first chain) + "vpsllq ymm6, ymm3, 32", + "vpsllq ymm7, ymm4, 32", + "vpsllq ymm8, ymm5, 32", + "vpaddq ymm6, ymm6, ymm0", + "vpaddq ymm7, ymm7, ymm1", + "vpaddq ymm8, ymm8, ymm2", + "vpcmpgtd ymm0, ymm0, ymm6", + "vpcmpgtd ymm1, ymm1, ymm7", + "vpcmpgtd ymm2, ymm2, ymm8", + + // Now we interleave the chains so this gets a bit uglier. + // Form ymm3 := (ymm3 >> 32) * EPSILON (second chain) + "vpsrlq ymm9, ymm3, 32", + "vpsrlq ymm10, ymm4, 32", + "vpsrlq ymm11, ymm5, 32", + // (first chain again) + "vpsrlq ymm0, ymm0, 32", + "vpsrlq ymm1, ymm1, 32", + "vpsrlq ymm2, ymm2, 32", + // (second chain again) + "vpandn ymm3, ymm14, ymm3", + "vpandn ymm4, ymm14, ymm4", + "vpandn ymm5, ymm14, ymm5", + "vpsubq ymm3, ymm3, ymm9", + "vpsubq ymm4, ymm4, ymm10", + "vpsubq ymm5, ymm5, ymm11", + // (first chain again) + "vpaddq ymm0, ymm6, ymm0", + "vpaddq ymm1, ymm7, ymm1", + "vpaddq ymm2, ymm8, ymm2", + + // Merge two chains (second addition) + "vpaddq ymm3, ymm0, ymm3", + "vpaddq ymm4, ymm1, ymm4", + "vpaddq ymm5, ymm2, ymm5", + "vpcmpgtd ymm0, ymm0, ymm3", + "vpcmpgtd ymm1, ymm1, ymm4", + "vpcmpgtd ymm2, ymm2, ymm5", + "vpsrlq ymm6, ymm0, 32", + "vpsrlq ymm7, ymm1, 32", + "vpsrlq ymm8, ymm2, 32", + "vpxor ymm3, ymm15, ymm3", + "vpxor ymm4, ymm15, ymm4", + "vpxor ymm5, ymm15, ymm5", + "vpaddq ymm0, ymm6, ymm3", + "vpaddq ymm1, ymm7, ymm4", + "vpaddq ymm2, ymm8, ymm5", + inout("ymm0") lo_s.0 => res0, + inout("ymm1") lo_s.1 => res1, + inout("ymm2") lo_s.2 => res2, + inout("ymm3") hi.0 => _, + inout("ymm4") hi.1 => _, + inout("ymm5") hi.2 => _, + out("ymm6") _, out("ymm7") _, out("ymm8") _, out("ymm9") _, out("ymm10") _, out("ymm11") _, + in("ymm14") epsilon, in("ymm15") sign_bit, + options(pure, nomem, preserves_flags, nostack), + ); + (res0, res1, res2) +} + +#[inline(always)] +unsafe fn mds_multiply_and_add_round_const_s( + state: (__m256i, __m256i, __m256i), + (base, index): (*const u64, usize), +) -> ((__m256i, __m256i, __m256i), (__m256i, __m256i, __m256i)) { + // TODO: Would it be faster to save the input to memory and do unaligned + // loads instead of swizzling? It would reduce pressure on port 5 but it + // would also have high latency (no store forwarding). + // TODO: Would it be faster to store the lo and hi inputs and outputs on one + // vector? I.e., we currently operate on [lo(s[0]), lo(s[1]), lo(s[2]), + // lo(s[3])] and [hi(s[0]), hi(s[1]), hi(s[2]), hi(s[3])] separately. Using + // [lo(s[0]), lo(s[1]), hi(s[0]), hi(s[1])] and [lo(s[2]), lo(s[3]), + // hi(s[2]), hi(s[3])] would save us a few swizzles but would also need more + // registers. + // TODO: Plain-vanilla matrix-vector multiplication might also work. We take + // one element of the input (a scalar), multiply a column by it, and + // accumulate. It would require shifts by amounts loaded from memory, but + // would eliminate all swizzles. The downside is that we can no longer + // special-case MDS == 0 and MDS == 1, so we end up with more shifts. + // TODO: Building on the above: FMA? It has high latency (4 cycles) but we + // have enough operands to mask it. The main annoyance will be conversion + // to/from floating-point. + // TODO: Try taking the complex Fourier transform and doing the convolution + // with elementwise Fourier multiplication. Alternatively, try a Fourier + // transform modulo Q, such that the prime field fits the result without + // wraparound (i.e. Q > 0x1_1536_fffe_eac9) and has fast multiplication/- + // reduction. + + // At the end of the matrix-vector multiplication r = Ms, + // - ymm3 holds r[0:4] + // - ymm4 holds r[4:8] + // - ymm5 holds r[8:12] + // - ymm6 holds r[2:6] + // - ymm7 holds r[6:10] + // - ymm8 holds concat(r[10:12], r[0:2]) + // Note that there are duplicates. E.g. r[0] is represented by ymm3[0] and + // ymm8[2]. To obtain the final result, we must sum the duplicate entries: + // ymm3[0:2] += ymm8[2:4] + // ymm3[2:4] += ymm6[0:2] + // ymm4[0:2] += ymm6[2:4] + // ymm4[2:4] += ymm7[0:2] + // ymm5[0:2] += ymm7[2:4] + // ymm5[2:4] += ymm8[0:2] + // Thus, the final result resides in ymm3, ymm4, ymm5. + + // WARNING: This code assumes that sum(1 << exp for exp in MDS_EXPS) * 0xffffffff fits in a + // u64. If this guarantee ceases to hold, then it will no longer be correct. + let (unreduced_lo0_s, unreduced_lo1_s, unreduced_lo2_s): (__m256i, __m256i, __m256i); + let (unreduced_hi0, unreduced_hi1, unreduced_hi2): (__m256i, __m256i, __m256i); + let epsilon = _mm256_set1_epi64x(0xffffffff); + asm!( + // Extract low 32 bits of the word + "vpand ymm9, ymm14, ymm0", + "vpand ymm10, ymm14, ymm1", + "vpand ymm11, ymm14, ymm2", + + "mov eax, 1", + + // Fall through for MDS matrix multiplication on low 32 bits + + // This is a GCC _local label_. For details, see + // https://doc.rust-lang.org/beta/unstable-book/library-features/asm.html#labels + // In short, the assembler makes sure to assign a unique name to replace `2:` with a unique + // name, so the label does not clash with any compiler-generated label. `2:` can appear + // multiple times; to disambiguate, we must refer to it as `2b` or `2f`, specifying the + // direction as _backward_ or _forward_. + "2:", + // NB: This block is run twice: once on the low 32 bits and once for the + // high 32 bits. The 32-bit -> 64-bit matrix multiplication is responsible + // for the majority of the instructions in this routine. By reusing them, + // we decrease the burden on instruction caches by over one third. + + // 32-bit -> 64-bit MDS matrix multiplication + // The scalar loop goes: + // for r in 0..WIDTH { + // let mut res = 0u128; + // for i in 0..WIDTH { + // res += (state[(i + r) % WIDTH] as u128) << MDS_MATRIX_EXPS[i]; + // } + // result[r] = reduce(res); + // } + // + // Here, we swap the loops. Equivalent to: + // let mut res = [0u128; WIDTH]; + // for i in 0..WIDTH { + // let mds_matrix_exp = MDS_MATRIX_EXPS[i]; + // for r in 0..WIDTH { + // res[r] += (state[(i + r) % WIDTH] as u128) << mds_matrix_exp; + // } + // } + // for r in 0..WIDTH { + // result[r] = reduce(res[r]); + // } + // + // Notice that in the lower version, all iterations of the inner loop + // shift by the same amount. In vector, we perform multiple iterations of + // the loop at once, and vector shifts are cheaper when all elements are + // shifted by the same amount. + // + // We use a trick to avoid rotating the state vector many times. We + // have as input the state vector and the state vector rotated by one. We + // also have two accumulators: an unrotated one and one that's rotated by + // two. Rotations by three are achieved by matching an input rotated by + // one with an accumulator rotated by two. Rotations by four are free: + // they are done by using a different register. + + // mds[0 - 0] = 0 not done; would be a move from in0 to ymm3 + // ymm3 not set + // mds[0 - 4] = 12 + "vpsllq ymm4, ymm9, 12", + // mds[0 - 8] = 3 + "vpsllq ymm5, ymm9, 3", + // mds[0 - 2] = 16 + "vpsllq ymm6, ymm9, 16", + // mds[0 - 6] = mds[0 - 10] = 1 + "vpaddq ymm7, ymm9, ymm9", + // ymm8 not written + // ymm3 and ymm8 have not been written to, because those would be unnecessary + // copies. Implicitly, ymm3 := in0 and ymm8 := ymm7. + + // ymm12 := [ymm9[1], ymm9[2], ymm9[3], ymm10[0]] + "vperm2i128 ymm13, ymm9, ymm10, 0x21", + "vshufpd ymm12, ymm9, ymm13, 0x5", + + // ymm3 and ymm8 are not read because they have not been written to + // earlier. Instead, the "current value" of ymm3 is read from ymm9 and the + // "current value" of ymm8 is read from ymm7. + // mds[4 - 0] = 3 + "vpsllq ymm13, ymm10, 3", + "vpaddq ymm3, ymm9, ymm13", + // mds[4 - 4] = 0 + "vpaddq ymm4, ymm4, ymm10", + // mds[4 - 8] = 12 + "vpsllq ymm13, ymm10, 12", + "vpaddq ymm5, ymm5, ymm13", + // mds[4 - 2] = mds[4 - 10] = 1 + "vpaddq ymm13, ymm10, ymm10", + "vpaddq ymm6, ymm6, ymm13", + "vpaddq ymm8, ymm7, ymm13", + // mds[4 - 6] = 16 + "vpsllq ymm13, ymm10, 16", + "vpaddq ymm7, ymm7, ymm13", + + // mds[1 - 0] = 0 + "vpaddq ymm3, ymm3, ymm12", + // mds[1 - 4] = 3 + "vpsllq ymm13, ymm12, 3", + "vpaddq ymm4, ymm4, ymm13", + // mds[1 - 8] = 5 + "vpsllq ymm13, ymm12, 5", + "vpaddq ymm5, ymm5, ymm13", + // mds[1 - 2] = 10 + "vpsllq ymm13, ymm12, 10", + "vpaddq ymm6, ymm6, ymm13", + // mds[1 - 6] = 8 + "vpsllq ymm13, ymm12, 8", + "vpaddq ymm7, ymm7, ymm13", + // mds[1 - 10] = 0 + "vpaddq ymm8, ymm8, ymm12", + + // ymm10 := [ymm10[1], ymm10[2], ymm10[3], ymm11[0]] + "vperm2i128 ymm13, ymm10, ymm11, 0x21", + "vshufpd ymm10, ymm10, ymm13, 0x5", + + // mds[8 - 0] = 12 + "vpsllq ymm13, ymm11, 12", + "vpaddq ymm3, ymm3, ymm13", + // mds[8 - 4] = 3 + "vpsllq ymm13, ymm11, 3", + "vpaddq ymm4, ymm4, ymm13", + // mds[8 - 8] = 0 + "vpaddq ymm5, ymm5, ymm11", + // mds[8 - 2] = mds[8 - 6] = 1 + "vpaddq ymm13, ymm11, ymm11", + "vpaddq ymm6, ymm6, ymm13", + "vpaddq ymm7, ymm7, ymm13", + // mds[8 - 10] = 16 + "vpsllq ymm13, ymm11, 16", + "vpaddq ymm8, ymm8, ymm13", + + // ymm9 := [ymm11[1], ymm11[2], ymm11[3], ymm9[0]] + "vperm2i128 ymm13, ymm11, ymm9, 0x21", + "vshufpd ymm9, ymm11, ymm13, 0x5", + + // mds[5 - 0] = 5 + "vpsllq ymm13, ymm10, 5", + "vpaddq ymm3, ymm3, ymm13", + // mds[5 - 4] = 0 + "vpaddq ymm4, ymm4, ymm10", + // mds[5 - 8] = 3 + "vpsllq ymm13, ymm10, 3", + "vpaddq ymm5, ymm5, ymm13", + // mds[5 - 2] = 0 + "vpaddq ymm6, ymm6, ymm10", + // mds[5 - 6] = 10 + "vpsllq ymm13, ymm10, 10", + "vpaddq ymm7, ymm7, ymm13", + // mds[5 - 10] = 8 + "vpsllq ymm13, ymm10, 8", + "vpaddq ymm8, ymm8, ymm13", + + // mds[9 - 0] = 3 + "vpsllq ymm13, ymm9, 3", + "vpaddq ymm3, ymm3, ymm13", + // mds[9 - 4] = 5 + "vpsllq ymm13, ymm9, 5", + "vpaddq ymm4, ymm4, ymm13", + // mds[9 - 8] = 0 + "vpaddq ymm5, ymm5, ymm9", + // mds[9 - 2] = 8 + "vpsllq ymm13, ymm9, 8", + "vpaddq ymm6, ymm6, ymm13", + // mds[9 - 6] = 0 + "vpaddq ymm7, ymm7, ymm9", + // mds[9 - 10] = 10 + "vpsllq ymm13, ymm9, 10", + "vpaddq ymm8, ymm8, ymm13", + + // Rotate ymm6-ymm8 and add to the corresponding elements of ymm3-ymm5 + "vperm2i128 ymm13, ymm8, ymm6, 0x21", + "vpaddq ymm3, ymm3, ymm13", + "vperm2i128 ymm13, ymm6, ymm7, 0x21", + "vpaddq ymm4, ymm4, ymm13", + "vperm2i128 ymm13, ymm7, ymm8, 0x21", + "vpaddq ymm5, ymm5, ymm13", + + // If this is the first time we have run 2: (low 32 bits) then continue. + // If second time (high 32 bits), then jump to 3:. + "dec eax", + // Jump to the _local label_ (see above) `3:`. `f` for _forward_ specifies the direction. + "jnz 3f", + + // Extract high 32 bits + "vpsrlq ymm9, ymm0, 32", + "vpsrlq ymm10, ymm1, 32", + "vpsrlq ymm11, ymm2, 32", + + // Need to move the low result from ymm3-ymm5 to ymm0-13 so it is not + // overwritten. Save three instructions by combining the move with the constant layer, + // which would otherwise be done in 3:. The round constants include the shift by 2**63, so + // the resulting ymm0,1,2 are also shifted by 2**63. + // It is safe to add the round constants here without checking for overflow. The values in + // ymm3,4,5 are guaranteed to be <= 0x11536fffeeac9. All round constants are < 2**64 + // - 0x11536fffeeac9. + // WARNING: If this guarantee ceases to hold due to a change in the MDS matrix or round + // constants, then this code will no longer be correct. + "vpaddq ymm0, ymm3, [{base} + {index}]", + "vpaddq ymm1, ymm4, [{base} + {index} + 32]", + "vpaddq ymm2, ymm5, [{base} + {index} + 64]", + + // MDS matrix multiplication, again. This time on high 32 bits. + // Jump to the _local label_ (see above) `2:`. `b` for _backward_ specifies the direction. + "jmp 2b", + + // `3:` is a _local label_ (see above). + "3:", + // Just done the MDS matrix multiplication on high 32 bits. + // The high results are in ymm3, ymm4, ymm5. + // The low results (shifted by 2**63 and including the following constant layer) are in + // ymm0, ymm1, ymm2. + base = in(reg) base, + index = in(reg) index, + inout("ymm0") state.0 => unreduced_lo0_s, + inout("ymm1") state.1 => unreduced_lo1_s, + inout("ymm2") state.2 => unreduced_lo2_s, + out("ymm3") unreduced_hi0, + out("ymm4") unreduced_hi1, + out("ymm5") unreduced_hi2, + out("ymm6") _,out("ymm7") _, out("ymm8") _, out("ymm9") _, + out("ymm10") _, out("ymm11") _, out("ymm12") _, out("ymm13") _, + in("ymm14") epsilon, + out("rax") _, + options(pure, nomem, nostack), + ); + ( + (unreduced_lo0_s, unreduced_lo1_s, unreduced_lo2_s), + (unreduced_hi0, unreduced_hi1, unreduced_hi2), + ) +} + +#[inline(always)] +unsafe fn mds_const_layers_full( + state: (__m256i, __m256i, __m256i), + round_constants: (*const u64, usize), +) -> (__m256i, __m256i, __m256i) { + let (unreduced_lo_s, unreduced_hi) = mds_multiply_and_add_round_const_s(state, round_constants); + mds_layer_reduce(unreduced_lo_s, unreduced_hi) +} + +/// Compute x ** 7 +#[inline(always)] +unsafe fn sbox_partial(mut x: u64) -> u64 { + // This is done in assembly to fix LLVM's poor treatment of wraparound addition/subtraction + // and to ensure that multiplication by EPSILON is done with bitshifts, leaving port 1 for + // vector operations. + // TODO: Interleave with MDS multiplication. + asm!( + "mov r9, rdx", + + // rdx := rdx ^ 2 + "mulx rdx, rax, rdx", + "shrx r8, rdx, r15", + "mov r12d, edx", + "shl rdx, 32", + "sub rdx, r12", + // rax - r8, with underflow + "sub rax, r8", + "sbb r8d, r8d", // sets r8 to 2^32 - 1 if subtraction underflowed + "sub rax, r8", + // rdx + rax, with overflow + "add rdx, rax", + "sbb eax, eax", + "add rdx, rax", + + // rax := rdx * r9, rdx := rdx ** 2 + "mulx rax, r11, r9", + "mulx rdx, r12, rdx", + + "shrx r9, rax, r15", + "shrx r10, rdx, r15", + + "sub r11, r9", + "sbb r9d, r9d", + "sub r12, r10", + "sbb r10d, r10d", + "sub r11, r9", + "sub r12, r10", + + "mov r9d, eax", + "mov r10d, edx", + "shl rax, 32", + "shl rdx, 32", + "sub rax, r9", + "sub rdx, r10", + + "add rax, r11", + "sbb r11d, r11d", + "add rdx, r12", + "sbb r12d, r12d", + "add rax, r11", + "add rdx, r12", + + // rax := rax * rdx + "mulx rax, rdx, rax", + "shrx r11, rax, r15", + "mov r12d, eax", + "shl rax, 32", + "sub rax, r12", + // rdx - r11, with underflow + "sub rdx, r11", + "sbb r11d, r11d", // sets r11 to 2^32 - 1 if subtraction underflowed + "sub rdx, r11", + // rdx + rax, with overflow + "add rdx, rax", + "sbb eax, eax", + "add rdx, rax", + inout("rdx") x, + out("rax") _, + out("r8") _, + out("r9") _, + out("r10") _, + out("r11") _, + out("r12") _, + in("r15") 32, + options(pure, nomem, nostack), + ); + x +} + +#[inline(always)] +unsafe fn partial_round( + (state0, state1, state2): (__m256i, __m256i, __m256i), + round_constants: (*const u64, usize), +) -> (__m256i, __m256i, __m256i) { + // Extract the low quadword + let state0ab: __m128i = _mm256_castsi256_si128(state0); + let mut state0a = _mm_cvtsi128_si64(state0ab) as u64; + + // Zero the low quadword + let zero = _mm256_setzero_si256(); + let state0bcd = _mm256_blend_epi32::<0x3>(state0, zero); + + // Scalar exponentiation + state0a = sbox_partial(state0a); + + let epsilon = _mm256_set1_epi64x(0xffffffff); + let ( + (mut unreduced_lo0_s, mut unreduced_lo1_s, mut unreduced_lo2_s), + (mut unreduced_hi0, mut unreduced_hi1, mut unreduced_hi2), + ) = mds_multiply_and_add_round_const_s((state0bcd, state1, state2), round_constants); + asm!( + // Just done the MDS matrix multiplication on high 32 bits. + // The high results are in ymm3, ymm4, ymm5. + // The low results (shifted by 2**63) are in ymm0, ymm1, ymm2 + + // The MDS matrix multiplication was done with state[0] set to 0. + // We must: + // 1. propagate the vector product to state[0], which is stored in rdx. + // 2. offset state[1..12] by the appropriate multiple of rdx + // 3. zero the lowest quadword in the vector registers + "vmovq xmm12, {state0a}", + "vpbroadcastq ymm12, xmm12", + "vpsrlq ymm13, ymm12, 32", + "vpand ymm12, ymm14, ymm12", + + // The current matrix-vector product goes not include state[0] as an input. (Imagine Mv + // multiplication where we've set the first element to 0.) Add the remaining bits now. + // TODO: This is a bit of an afterthought, which is why these constants are loaded 22 + // times... There's likely a better way of merging those results. + "vmovdqu ymm6, [{mds_matrix}]", + "vmovdqu ymm7, [{mds_matrix} + 32]", + "vmovdqu ymm8, [{mds_matrix} + 64]", + "vpsllvq ymm9, ymm13, ymm6", + "vpsllvq ymm10, ymm13, ymm7", + "vpsllvq ymm11, ymm13, ymm8", + "vpsllvq ymm6, ymm12, ymm6", + "vpsllvq ymm7, ymm12, ymm7", + "vpsllvq ymm8, ymm12, ymm8", + "vpaddq ymm3, ymm9, ymm3", + "vpaddq ymm4, ymm10, ymm4", + "vpaddq ymm5, ymm11, ymm5", + "vpaddq ymm0, ymm6, ymm0", + "vpaddq ymm1, ymm7, ymm1", + "vpaddq ymm2, ymm8, ymm2", + // Reduction required. + + state0a = in(reg) state0a, + mds_matrix = in(reg) &TOP_ROW_EXPS, + inout("ymm0") unreduced_lo0_s, + inout("ymm1") unreduced_lo1_s, + inout("ymm2") unreduced_lo2_s, + inout("ymm3") unreduced_hi0, + inout("ymm4") unreduced_hi1, + inout("ymm5") unreduced_hi2, + out("ymm6") _,out("ymm7") _, out("ymm8") _, out("ymm9") _, + out("ymm10") _, out("ymm11") _, out("ymm12") _, out("ymm13") _, + in("ymm14") epsilon, + options(pure, nomem, preserves_flags, nostack), + ); + mds_layer_reduce( + (unreduced_lo0_s, unreduced_lo1_s, unreduced_lo2_s), + (unreduced_hi0, unreduced_hi1, unreduced_hi2), + ) +} + +#[inline(always)] +unsafe fn full_round( + state: (__m256i, __m256i, __m256i), + round_constants: (*const u64, usize), +) -> (__m256i, __m256i, __m256i) { + let state = sbox_layer_full(state); + let state = mds_const_layers_full(state, round_constants); + state +} + +#[inline] // Called twice; permit inlining but don't _require_ it +unsafe fn half_full_rounds( + mut state: (__m256i, __m256i, __m256i), + start_round: usize, +) -> (__m256i, __m256i, __m256i) { + let base = (&FUSED_ROUND_CONSTANTS + [WIDTH * start_round..WIDTH * start_round + WIDTH * HALF_N_FULL_ROUNDS]) + .as_ptr(); + + for i in 0..HALF_N_FULL_ROUNDS { + state = full_round(state, (base, i * WIDTH * size_of::())); + } + state +} + +#[inline(always)] +unsafe fn all_partial_rounds( + mut state: (__m256i, __m256i, __m256i), + start_round: usize, +) -> (__m256i, __m256i, __m256i) { + let base = (&FUSED_ROUND_CONSTANTS + [WIDTH * start_round..WIDTH * start_round + WIDTH * N_PARTIAL_ROUNDS]) + .as_ptr(); + + for i in 0..N_PARTIAL_ROUNDS { + state = partial_round(state, (base, i * WIDTH * size_of::())); + } + state +} + +#[inline(always)] +unsafe fn load_state(state: &[GoldilocksField; 12]) -> (__m256i, __m256i, __m256i) { + ( + _mm256_loadu_si256((&state[0..4]).as_ptr().cast::<__m256i>()), + _mm256_loadu_si256((&state[4..8]).as_ptr().cast::<__m256i>()), + _mm256_loadu_si256((&state[8..12]).as_ptr().cast::<__m256i>()), + ) +} + +#[inline(always)] +unsafe fn store_state(buf: &mut [GoldilocksField; 12], state: (__m256i, __m256i, __m256i)) { + _mm256_storeu_si256((&mut buf[0..4]).as_mut_ptr().cast::<__m256i>(), state.0); + _mm256_storeu_si256((&mut buf[4..8]).as_mut_ptr().cast::<__m256i>(), state.1); + _mm256_storeu_si256((&mut buf[8..12]).as_mut_ptr().cast::<__m256i>(), state.2); +} + +#[inline] +pub unsafe fn poseidon(state: &[GoldilocksField; 12]) -> [GoldilocksField; 12] { + let state = load_state(state); + + // The first constant layer must be done explicitly. The remaining constant layers are fused + // with the preceding MDS layer. + let state = const_layer(state, &ALL_ROUND_CONSTANTS[0..WIDTH].try_into().unwrap()); + + let state = half_full_rounds(state, 0); + let state = all_partial_rounds(state, HALF_N_FULL_ROUNDS); + let state = half_full_rounds(state, HALF_N_FULL_ROUNDS + N_PARTIAL_ROUNDS); + + let mut res = [GoldilocksField::ZERO; 12]; + store_state(&mut res, state); + res +} + +#[inline(always)] +pub unsafe fn constant_layer(state_arr: &mut [GoldilocksField; WIDTH], round_ctr: usize) { + let state = load_state(state_arr); + let round_consts = &ALL_ROUND_CONSTANTS[WIDTH * round_ctr..][..WIDTH] + .try_into() + .unwrap(); + let state = const_layer(state, round_consts); + store_state(state_arr, state); +} + +#[inline(always)] +pub unsafe fn sbox_layer(state_arr: &mut [GoldilocksField; WIDTH]) { + let state = load_state(state_arr); + let state = sbox_layer_full(state); + store_state(state_arr, state); +} + +#[inline(always)] +pub unsafe fn mds_layer(state: &[GoldilocksField; WIDTH]) -> [GoldilocksField; WIDTH] { + let state = load_state(state); + // We want to do an MDS layer without the constant layer. + // The FUSED_ROUND_CONSTANTS for the last round are all 0 (shifted by 2**63 as required). + let round_consts = FUSED_ROUND_CONSTANTS[WIDTH * (N_ROUNDS - 1)..].as_ptr(); + let state = mds_const_layers_full(state, (round_consts, 0)); + let mut res = [GoldilocksField::ZERO; 12]; + store_state(&mut res, state); + res +} diff --git a/plonky2/src/hash/hash_types.rs b/plonky2/src/hash/hash_types.rs new file mode 100644 index 000000000..2140517bf --- /dev/null +++ b/plonky2/src/hash/hash_types.rs @@ -0,0 +1,212 @@ +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + +use anyhow::ensure; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use crate::field::goldilocks_field::GoldilocksField; +use crate::field::types::{Field, PrimeField64, Sample}; +use crate::hash::poseidon::Poseidon; +use crate::iop::target::Target; +use crate::plonk::config::GenericHashOut; + +/// A prime order field with the features we need to use it as a base field in +/// our argument system. +pub trait RichField: PrimeField64 + Poseidon {} + +impl RichField for GoldilocksField {} + +pub const NUM_HASH_OUT_ELTS: usize = 4; + +/// Represents a ~256 bit hash output. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct HashOut { + pub elements: [F; NUM_HASH_OUT_ELTS], +} + +impl HashOut { + pub const ZERO: Self = Self { + elements: [F::ZERO; NUM_HASH_OUT_ELTS], + }; + + // TODO: Switch to a TryFrom impl. + pub fn from_vec(elements: Vec) -> Self { + debug_assert!(elements.len() == NUM_HASH_OUT_ELTS); + Self { + elements: elements.try_into().unwrap(), + } + } + + pub fn from_partial(elements_in: &[F]) -> Self { + let mut elements = [F::ZERO; NUM_HASH_OUT_ELTS]; + elements[0..elements_in.len()].copy_from_slice(elements_in); + Self { elements } + } +} + +impl From<[F; NUM_HASH_OUT_ELTS]> for HashOut { + fn from(elements: [F; NUM_HASH_OUT_ELTS]) -> Self { + Self { elements } + } +} + +impl TryFrom<&[F]> for HashOut { + type Error = anyhow::Error; + + fn try_from(elements: &[F]) -> Result { + ensure!(elements.len() == NUM_HASH_OUT_ELTS); + Ok(Self { + elements: elements.try_into().unwrap(), + }) + } +} + +impl Sample for HashOut +where + F: Field, +{ + #[inline] + fn sample(rng: &mut R) -> Self + where + R: rand::RngCore + ?Sized, + { + Self { + elements: [ + F::sample(rng), + F::sample(rng), + F::sample(rng), + F::sample(rng), + ], + } + } +} + +impl GenericHashOut for HashOut { + fn to_bytes(&self) -> Vec { + self.elements + .into_iter() + .flat_map(|x| x.to_canonical_u64().to_le_bytes()) + .collect() + } + + fn from_bytes(bytes: &[u8]) -> Self { + HashOut { + elements: bytes + .chunks(8) + .take(NUM_HASH_OUT_ELTS) + .map(|x| F::from_canonical_u64(u64::from_le_bytes(x.try_into().unwrap()))) + .collect::>() + .try_into() + .unwrap(), + } + } + + fn to_vec(&self) -> Vec { + self.elements.to_vec() + } +} + +impl Default for HashOut { + fn default() -> Self { + Self::ZERO + } +} + +/// Represents a ~256 bit hash output. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct HashOutTarget { + pub elements: [Target; NUM_HASH_OUT_ELTS], +} + +impl HashOutTarget { + // TODO: Switch to a TryFrom impl. + pub fn from_vec(elements: Vec) -> Self { + debug_assert!(elements.len() == NUM_HASH_OUT_ELTS); + Self { + elements: elements.try_into().unwrap(), + } + } + + pub fn from_partial(elements_in: &[Target], zero: Target) -> Self { + let mut elements = [zero; NUM_HASH_OUT_ELTS]; + elements[0..elements_in.len()].copy_from_slice(elements_in); + Self { elements } + } +} + +impl From<[Target; NUM_HASH_OUT_ELTS]> for HashOutTarget { + fn from(elements: [Target; NUM_HASH_OUT_ELTS]) -> Self { + Self { elements } + } +} + +impl TryFrom<&[Target]> for HashOutTarget { + type Error = anyhow::Error; + + fn try_from(elements: &[Target]) -> Result { + ensure!(elements.len() == NUM_HASH_OUT_ELTS); + Ok(Self { + elements: elements.try_into().unwrap(), + }) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct MerkleCapTarget(pub Vec); + +/// Hash consisting of a byte array. +#[derive(Eq, PartialEq, Copy, Clone, Debug)] +pub struct BytesHash(pub [u8; N]); + +impl Sample for BytesHash { + #[inline] + fn sample(rng: &mut R) -> Self + where + R: rand::RngCore + ?Sized, + { + let mut buf = [0; N]; + rng.fill_bytes(&mut buf); + Self(buf) + } +} + +impl GenericHashOut for BytesHash { + fn to_bytes(&self) -> Vec { + self.0.to_vec() + } + + fn from_bytes(bytes: &[u8]) -> Self { + Self(bytes.try_into().unwrap()) + } + + fn to_vec(&self) -> Vec { + self.0 + // Chunks of 7 bytes since 8 bytes would allow collisions. + .chunks(7) + .map(|bytes| { + let mut arr = [0; 8]; + arr[..bytes.len()].copy_from_slice(bytes); + F::from_canonical_u64(u64::from_le_bytes(arr)) + }) + .collect() + } +} + +impl Serialize for BytesHash { + fn serialize(&self, _serializer: S) -> Result + where + S: Serializer, + { + todo!() + } +} + +impl<'de, const N: usize> Deserialize<'de> for BytesHash { + fn deserialize(_deserializer: D) -> Result + where + D: Deserializer<'de>, + { + todo!() + } +} diff --git a/plonky2/src/hash/hashing.rs b/plonky2/src/hash/hashing.rs new file mode 100644 index 000000000..01d9043da --- /dev/null +++ b/plonky2/src/hash/hashing.rs @@ -0,0 +1,148 @@ +//! Concrete instantiation of a hash function. +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; +use core::fmt::Debug; + +use crate::field::extension::Extendable; +use crate::field::types::Field; +use crate::hash::hash_types::{HashOut, HashOutTarget, RichField, NUM_HASH_OUT_ELTS}; +use crate::iop::target::Target; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::config::AlgebraicHasher; + +impl, const D: usize> CircuitBuilder { + pub fn hash_or_noop>(&mut self, inputs: Vec) -> HashOutTarget { + let zero = self.zero(); + if inputs.len() <= NUM_HASH_OUT_ELTS { + HashOutTarget::from_partial(&inputs, zero) + } else { + self.hash_n_to_hash_no_pad::(inputs) + } + } + + pub fn hash_n_to_hash_no_pad>( + &mut self, + inputs: Vec, + ) -> HashOutTarget { + HashOutTarget::from_vec(self.hash_n_to_m_no_pad::(inputs, NUM_HASH_OUT_ELTS)) + } + + pub fn hash_n_to_m_no_pad>( + &mut self, + inputs: Vec, + num_outputs: usize, + ) -> Vec { + let zero = self.zero(); + let mut state = H::AlgebraicPermutation::new(core::iter::repeat(zero)); + + // Absorb all input chunks. + for input_chunk in inputs.chunks(H::AlgebraicPermutation::RATE) { + // Overwrite the first r elements with the inputs. This differs from a standard + // sponge, where we would xor or add in the inputs. This is a + // well-known variant, though, sometimes called "overwrite mode". + state.set_from_slice(input_chunk, 0); + state = self.permute::(state); + } + + // Squeeze until we have the desired number of outputs. + let mut outputs = Vec::with_capacity(num_outputs); + loop { + for &s in state.squeeze() { + outputs.push(s); + if outputs.len() == num_outputs { + return outputs; + } + } + state = self.permute::(state); + } + } +} + +/// Permutation that can be used in the sponge construction for an algebraic +/// hash. +pub trait PlonkyPermutation: + AsRef<[T]> + Copy + Debug + Default + Eq + Sync + Send +{ + const RATE: usize; + const WIDTH: usize; + + /// Initialises internal state with values from `iter` until + /// `iter` is exhausted or `Self::WIDTH` values have been + /// received; remaining state (if any) initialised with + /// `T::default()`. To initialise remaining elements with a + /// different value, instead of your original `iter` pass + /// `iter.chain(core::iter::repeat(F::from_canonical_u64(12345)))` + /// or similar. + fn new>(iter: I) -> Self; + + /// Set idx-th state element to be `elt`. Panics if `idx >= WIDTH`. + fn set_elt(&mut self, elt: T, idx: usize); + + /// Set state element `i` to be `elts[i] for i = + /// start_idx..start_idx + n` where `n = min(elts.len(), + /// WIDTH-start_idx)`. Panics if `start_idx > WIDTH`. + fn set_from_iter>(&mut self, elts: I, start_idx: usize); + + /// Same semantics as for `set_from_iter` but probably faster than + /// just calling `set_from_iter(elts.iter())`. + fn set_from_slice(&mut self, elts: &[T], start_idx: usize); + + /// Apply permutation to internal state + fn permute(&mut self); + + /// Return a slice of `RATE` elements + fn squeeze(&self) -> &[T]; +} + +/// A one-way compression function which takes two ~256 bit inputs and returns a +/// ~256 bit output. +pub fn compress>(x: HashOut, y: HashOut) -> HashOut { + // TODO: With some refactoring, this function could be implemented as + // hash_n_to_m_no_pad(chain(x.elements, y.elements), NUM_HASH_OUT_ELTS). + + debug_assert_eq!(x.elements.len(), NUM_HASH_OUT_ELTS); + debug_assert_eq!(y.elements.len(), NUM_HASH_OUT_ELTS); + debug_assert!(P::RATE >= NUM_HASH_OUT_ELTS); + + let mut perm = P::new(core::iter::repeat(F::ZERO)); + perm.set_from_slice(&x.elements, 0); + perm.set_from_slice(&y.elements, NUM_HASH_OUT_ELTS); + + perm.permute(); + + HashOut { + elements: perm.squeeze()[..NUM_HASH_OUT_ELTS].try_into().unwrap(), + } +} + +/// Hash a message without any padding step. Note that this can enable +/// length-extension attacks. However, it is still collision-resistant in cases +/// where the input has a fixed length. +pub fn hash_n_to_m_no_pad>( + inputs: &[F], + num_outputs: usize, +) -> Vec { + let mut perm = P::new(core::iter::repeat(F::ZERO)); + + // Absorb all input chunks. + for input_chunk in inputs.chunks(P::RATE) { + perm.set_from_slice(input_chunk, 0); + perm.permute(); + } + + // Squeeze until we have the desired number of outputs. + let mut outputs = Vec::new(); + loop { + for &item in perm.squeeze() { + outputs.push(item); + if outputs.len() == num_outputs { + return outputs; + } + } + perm.permute(); + } +} + +pub fn hash_n_to_hash_no_pad>(inputs: &[F]) -> HashOut { + HashOut::from_vec(hash_n_to_m_no_pad::(inputs, NUM_HASH_OUT_ELTS)) +} diff --git a/plonky2/src/hash/keccak.rs b/plonky2/src/hash/keccak.rs new file mode 100644 index 000000000..6376ca220 --- /dev/null +++ b/plonky2/src/hash/keccak.rs @@ -0,0 +1,127 @@ +#[cfg(not(feature = "std"))] +use alloc::{vec, vec::Vec}; +use core::mem::size_of; + +use itertools::Itertools; +use keccak_hash::keccak; + +use crate::hash::hash_types::{BytesHash, RichField}; +use crate::hash::hashing::PlonkyPermutation; +use crate::plonk::config::Hasher; +use crate::util::serialization::Write; + +pub const SPONGE_RATE: usize = 8; +pub const SPONGE_CAPACITY: usize = 4; +pub const SPONGE_WIDTH: usize = SPONGE_RATE + SPONGE_CAPACITY; + +/// Keccak-256 pseudo-permutation (not necessarily one-to-one) used in the +/// challenger. A state `input: [F; 12]` is sent to the field representation of +/// `H(input) || H(H(input)) || H(H(H(input)))` where `H` is the Keccak-256 +/// hash. +#[derive(Copy, Clone, Default, Debug, PartialEq)] +pub struct KeccakPermutation { + state: [F; SPONGE_WIDTH], +} + +impl Eq for KeccakPermutation {} + +impl AsRef<[F]> for KeccakPermutation { + fn as_ref(&self) -> &[F] { + &self.state + } +} + +// TODO: Several implementations here are copied from +// PoseidonPermutation; they should be refactored. +impl PlonkyPermutation for KeccakPermutation { + const RATE: usize = SPONGE_RATE; + const WIDTH: usize = SPONGE_WIDTH; + + fn new>(elts: I) -> Self { + let mut perm = Self { + state: [F::default(); SPONGE_WIDTH], + }; + perm.set_from_iter(elts, 0); + perm + } + + fn set_elt(&mut self, elt: F, idx: usize) { + self.state[idx] = elt; + } + + fn set_from_slice(&mut self, elts: &[F], start_idx: usize) { + let begin = start_idx; + let end = start_idx + elts.len(); + self.state[begin..end].copy_from_slice(elts); + } + + fn set_from_iter>(&mut self, elts: I, start_idx: usize) { + for (s, e) in self.state[start_idx..].iter_mut().zip(elts) { + *s = e; + } + } + + fn permute(&mut self) { + let mut state_bytes = vec![0u8; SPONGE_WIDTH * size_of::()]; + for i in 0..SPONGE_WIDTH { + state_bytes[i * size_of::()..(i + 1) * size_of::()] + .copy_from_slice(&self.state[i].to_canonical_u64().to_le_bytes()); + } + + let hash_onion = core::iter::repeat_with(|| { + let output = keccak(state_bytes.clone()).to_fixed_bytes(); + state_bytes = output.to_vec(); + output + }); + + let hash_onion_u64s = hash_onion.flat_map(|output| { + output + .chunks_exact(size_of::()) + .map(|word| u64::from_le_bytes(word.try_into().unwrap())) + .collect_vec() + }); + + // Parse field elements from u64 stream, using rejection sampling such that + // words that don't fit in F are ignored. + let hash_onion_elems = hash_onion_u64s + .filter(|&word| word < F::ORDER) + .map(F::from_canonical_u64); + + self.state = hash_onion_elems + .take(SPONGE_WIDTH) + .collect_vec() + .try_into() + .unwrap(); + } + + fn squeeze(&self) -> &[F] { + &self.state[..Self::RATE] + } +} + +/// Keccak-256 hash function. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct KeccakHash; +impl Hasher for KeccakHash { + const HASH_SIZE: usize = N; + type Hash = BytesHash; + type Permutation = KeccakPermutation; + + fn hash_no_pad(input: &[F]) -> Self::Hash { + let mut buffer = Vec::with_capacity(input.len()); + buffer.write_field_vec(input).unwrap(); + let mut arr = [0; N]; + let hash_bytes = keccak(buffer).0; + arr.copy_from_slice(&hash_bytes[..N]); + BytesHash(arr) + } + + fn two_to_one(left: Self::Hash, right: Self::Hash) -> Self::Hash { + let mut v = vec![0; N * 2]; + v[0..N].copy_from_slice(&left.0); + v[N..].copy_from_slice(&right.0); + let mut arr = [0; N]; + arr.copy_from_slice(&keccak(v).0[..N]); + BytesHash(arr) + } +} diff --git a/plonky2/src/hash/merkle_proofs.rs b/plonky2/src/hash/merkle_proofs.rs new file mode 100644 index 000000000..e2e360bab --- /dev/null +++ b/plonky2/src/hash/merkle_proofs.rs @@ -0,0 +1,240 @@ +#[cfg(not(feature = "std"))] +use alloc::{vec, vec::Vec}; + +use anyhow::{ensure, Result}; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; + +use crate::field::extension::Extendable; +use crate::hash::hash_types::{HashOutTarget, MerkleCapTarget, RichField, NUM_HASH_OUT_ELTS}; +use crate::hash::hashing::PlonkyPermutation; +use crate::hash::merkle_tree::MerkleCap; +use crate::iop::target::{BoolTarget, Target}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::VerifierCircuitTarget; +use crate::plonk::config::{AlgebraicHasher, Hasher}; + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] +#[serde(bound = "")] +pub struct MerkleProof> { + /// The Merkle digest of each sibling subtree, staying from the bottommost + /// layer. + pub siblings: Vec, +} + +impl> MerkleProof { + pub fn len(&self) -> usize { + self.siblings.len() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct MerkleProofTarget { + /// The Merkle digest of each sibling subtree, staying from the bottommost + /// layer. + pub siblings: Vec, +} + +/// Verifies that the given leaf data is present at the given index in the +/// Merkle tree with the given root. +pub fn verify_merkle_proof>( + leaf_data: Vec, + leaf_index: usize, + merkle_root: H::Hash, + proof: &MerkleProof, +) -> Result<()> { + let merkle_cap = MerkleCap(vec![merkle_root]); + verify_merkle_proof_to_cap(leaf_data, leaf_index, &merkle_cap, proof) +} + +/// Verifies that the given leaf data is present at the given index in the +/// Merkle tree with the given cap. +pub fn verify_merkle_proof_to_cap>( + leaf_data: Vec, + leaf_index: usize, + merkle_cap: &MerkleCap, + proof: &MerkleProof, +) -> Result<()> { + let mut index = leaf_index; + let mut current_digest = H::hash_or_noop(&leaf_data); + for &sibling_digest in proof.siblings.iter() { + let bit = index & 1; + index >>= 1; + current_digest = if bit == 1 { + H::two_to_one(sibling_digest, current_digest) + } else { + H::two_to_one(current_digest, sibling_digest) + } + } + ensure!( + current_digest == merkle_cap.0[index], + "Invalid Merkle proof." + ); + + Ok(()) +} + +impl, const D: usize> CircuitBuilder { + /// Verifies that the given leaf data is present at the given index in the + /// Merkle tree with the given root. The index is given by its + /// little-endian bits. + pub fn verify_merkle_proof>( + &mut self, + leaf_data: Vec, + leaf_index_bits: &[BoolTarget], + merkle_root: HashOutTarget, + proof: &MerkleProofTarget, + ) { + let merkle_cap = MerkleCapTarget(vec![merkle_root]); + self.verify_merkle_proof_to_cap::(leaf_data, leaf_index_bits, &merkle_cap, proof); + } + + /// Verifies that the given leaf data is present at the given index in the + /// Merkle tree with the given cap. The index is given by its + /// little-endian bits. + pub fn verify_merkle_proof_to_cap>( + &mut self, + leaf_data: Vec, + leaf_index_bits: &[BoolTarget], + merkle_cap: &MerkleCapTarget, + proof: &MerkleProofTarget, + ) { + let cap_index = self.le_sum(leaf_index_bits[proof.siblings.len()..].iter().copied()); + self.verify_merkle_proof_to_cap_with_cap_index::( + leaf_data, + leaf_index_bits, + cap_index, + merkle_cap, + proof, + ); + } + + /// Same as `verify_merkle_proof_to_cap`, except with the final "cap index" + /// as separate parameter, rather than being contained in + /// `leaf_index_bits`. + pub(crate) fn verify_merkle_proof_to_cap_with_cap_index>( + &mut self, + leaf_data: Vec, + leaf_index_bits: &[BoolTarget], + cap_index: Target, + merkle_cap: &MerkleCapTarget, + proof: &MerkleProofTarget, + ) { + debug_assert!(H::AlgebraicPermutation::RATE >= NUM_HASH_OUT_ELTS); + + let zero = self.zero(); + let mut state: HashOutTarget = self.hash_or_noop::(leaf_data); + debug_assert_eq!(state.elements.len(), NUM_HASH_OUT_ELTS); + + for (&bit, &sibling) in leaf_index_bits.iter().zip(&proof.siblings) { + debug_assert_eq!(sibling.elements.len(), NUM_HASH_OUT_ELTS); + + let mut perm_inputs = H::AlgebraicPermutation::default(); + perm_inputs.set_from_slice(&state.elements, 0); + perm_inputs.set_from_slice(&sibling.elements, NUM_HASH_OUT_ELTS); + // Ensure the rest of the state, if any, is zero: + perm_inputs.set_from_iter(core::iter::repeat(zero), 2 * NUM_HASH_OUT_ELTS); + let perm_outs = self.permute_swapped::(perm_inputs, bit); + let hash_outs = perm_outs.squeeze()[0..NUM_HASH_OUT_ELTS] + .try_into() + .unwrap(); + state = HashOutTarget { + elements: hash_outs, + }; + } + + for i in 0..NUM_HASH_OUT_ELTS { + let result = self.random_access( + cap_index, + merkle_cap.0.iter().map(|h| h.elements[i]).collect(), + ); + self.connect(result, state.elements[i]); + } + } + + pub fn connect_hashes(&mut self, x: HashOutTarget, y: HashOutTarget) { + for i in 0..NUM_HASH_OUT_ELTS { + self.connect(x.elements[i], y.elements[i]); + } + } + + pub fn connect_merkle_caps(&mut self, x: &MerkleCapTarget, y: &MerkleCapTarget) { + for (h0, h1) in x.0.iter().zip_eq(&y.0) { + self.connect_hashes(*h0, *h1); + } + } + + pub fn connect_verifier_data(&mut self, x: &VerifierCircuitTarget, y: &VerifierCircuitTarget) { + self.connect_merkle_caps(&x.constants_sigmas_cap, &y.constants_sigmas_cap); + self.connect_hashes(x.circuit_digest, y.circuit_digest); + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + use rand::rngs::OsRng; + use rand::Rng; + + use super::*; + use crate::field::types::Field; + use crate::hash::merkle_tree::MerkleTree; + use crate::iop::witness::{PartialWitness, WitnessWrite}; + use crate::plonk::circuit_builder::CircuitBuilder; + use crate::plonk::circuit_data::CircuitConfig; + use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + use crate::plonk::verifier::verify; + + fn random_data(n: usize, k: usize) -> Vec> { + (0..n).map(|_| F::rand_vec(k)).collect() + } + + #[test] + fn test_recursive_merkle_proof() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + let config = CircuitConfig::standard_recursion_config(); + let mut pw = PartialWitness::new(); + let mut builder = CircuitBuilder::::new(config); + + let log_n = 8; + let n = 1 << log_n; + let cap_height = 1; + let leaves = random_data::(n, 7); + let tree = MerkleTree::>::Hasher>::new(leaves, cap_height); + let i: usize = OsRng.gen_range(0..n); + let proof = tree.prove(i); + + let proof_t = MerkleProofTarget { + siblings: builder.add_virtual_hashes(proof.siblings.len()), + }; + for i in 0..proof.siblings.len() { + pw.set_hash_target(proof_t.siblings[i], proof.siblings[i]); + } + + let cap_t = builder.add_virtual_cap(cap_height); + pw.set_cap_target(&cap_t, &tree.cap); + + let i_c = builder.constant(F::from_canonical_usize(i)); + let i_bits = builder.split_le(i_c, log_n); + + let data = builder.add_virtual_targets(tree.leaves[i].len()); + for j in 0..data.len() { + pw.set_target(data[j], tree.leaves[i][j]); + } + + builder.verify_merkle_proof_to_cap::<>::InnerHasher>( + data, &i_bits, &cap_t, &proof_t, + ); + + let data = builder.build::(); + let proof = data.prove(pw)?; + + verify(proof, &data.verifier_only, &data.common) + } +} diff --git a/plonky2/src/hash/merkle_tree.rs b/plonky2/src/hash/merkle_tree.rs new file mode 100644 index 000000000..6a034577b --- /dev/null +++ b/plonky2/src/hash/merkle_tree.rs @@ -0,0 +1,306 @@ +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; +use core::mem::MaybeUninit; +use core::slice; + +use plonky2_maybe_rayon::*; +use serde::{Deserialize, Serialize}; + +use crate::hash::hash_types::RichField; +use crate::hash::merkle_proofs::MerkleProof; +use crate::plonk::config::{GenericHashOut, Hasher}; +use crate::util::log2_strict; + +/// The Merkle cap of height `h` of a Merkle tree is the `h`-th layer (from the +/// root) of the tree. It can be used in place of the root to verify Merkle +/// paths, which are `h` elements shorter. +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] +#[serde(bound = "")] +// TODO: Change H to GenericHashOut, since this only cares about the hash, +// not the hasher. +pub struct MerkleCap>(pub Vec); + +impl> Default for MerkleCap { + fn default() -> Self { + Self(Vec::new()) + } +} + +impl> MerkleCap { + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn height(&self) -> usize { + log2_strict(self.len()) + } + + pub fn flatten(&self) -> Vec { + self.0.iter().flat_map(|&h| h.to_vec()).collect() + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct MerkleTree> { + /// The data in the leaves of the Merkle tree. + pub leaves: Vec>, + + /// The digests in the tree. Consists of `cap.len()` sub-trees, each + /// corresponding to one element in `cap`. Each subtree is contiguous + /// and located at `digests[digests.len() / cap.len() * i..digests.len() + /// / cap.len() * (i + 1)]`. Within each subtree, siblings are stored + /// next to each other. The layout is, left_child_subtree || + /// left_child_digest || right_child_digest || right_child_subtree, where + /// left_child_digest and right_child_digest are H::Hash and + /// left_child_subtree and right_child_subtree recurse. Observe that the + /// digest of a node is stored by its _parent_. Consequently, the + /// digests of the roots are not stored here (they can be found in `cap`). + pub digests: Vec, + + /// The Merkle cap. + pub cap: MerkleCap, +} + +impl> Default for MerkleTree { + fn default() -> Self { + Self { + leaves: Vec::new(), + digests: Vec::new(), + cap: MerkleCap::default(), + } + } +} + +fn capacity_up_to_mut(v: &mut Vec, len: usize) -> &mut [MaybeUninit] { + assert!(v.capacity() >= len); + let v_ptr = v.as_mut_ptr().cast::>(); + unsafe { + // SAFETY: `v_ptr` is a valid pointer to a buffer of length at least `len`. Upon + // return, the lifetime will be bound to that of `v`. The underlying + // memory will not be deallocated as we hold the sole mutable reference + // to `v`. The contents of the slice may be uninitialized, but the + // `MaybeUninit` makes it safe. + slice::from_raw_parts_mut(v_ptr, len) + } +} + +fn fill_subtree>( + digests_buf: &mut [MaybeUninit], + leaves: &[Vec], +) -> H::Hash { + assert_eq!(leaves.len(), digests_buf.len() / 2 + 1); + if digests_buf.is_empty() { + H::hash_or_noop(&leaves[0]) + } else { + // Layout is: left recursive output || left child digest + // || right child digest || right recursive output. + // Split `digests_buf` into the two recursive outputs (slices) and two child + // digests (references). + let (left_digests_buf, right_digests_buf) = digests_buf.split_at_mut(digests_buf.len() / 2); + let (left_digest_mem, left_digests_buf) = left_digests_buf.split_last_mut().unwrap(); + let (right_digest_mem, right_digests_buf) = right_digests_buf.split_first_mut().unwrap(); + // Split `leaves` between both children. + let (left_leaves, right_leaves) = leaves.split_at(leaves.len() / 2); + + let (left_digest, right_digest) = plonky2_maybe_rayon::join( + || fill_subtree::(left_digests_buf, left_leaves), + || fill_subtree::(right_digests_buf, right_leaves), + ); + + left_digest_mem.write(left_digest); + right_digest_mem.write(right_digest); + H::two_to_one(left_digest, right_digest) + } +} + +fn fill_digests_buf>( + digests_buf: &mut [MaybeUninit], + cap_buf: &mut [MaybeUninit], + leaves: &[Vec], + cap_height: usize, +) { + // Special case of a tree that's all cap. The usual case will panic because + // we'll try to split an empty slice into chunks of `0`. (We would not need + // this if there was a way to split into `blah` chunks as opposed to chunks + // _of_ `blah`.) + if digests_buf.is_empty() { + debug_assert_eq!(cap_buf.len(), leaves.len()); + cap_buf + .par_iter_mut() + .zip(leaves) + .for_each(|(cap_buf, leaf)| { + cap_buf.write(H::hash_or_noop(leaf)); + }); + return; + } + + let subtree_digests_len = digests_buf.len() >> cap_height; + let subtree_leaves_len = leaves.len() >> cap_height; + let digests_chunks = digests_buf.par_chunks_exact_mut(subtree_digests_len); + let leaves_chunks = leaves.par_chunks_exact(subtree_leaves_len); + assert_eq!(digests_chunks.len(), cap_buf.len()); + assert_eq!(digests_chunks.len(), leaves_chunks.len()); + digests_chunks.zip(cap_buf).zip(leaves_chunks).for_each( + |((subtree_digests, subtree_cap), subtree_leaves)| { + // We have `1 << cap_height` sub-trees, one for each entry in `cap`. They are + // totally independent, so we schedule one task for each. + // `digests_buf` and `leaves` are split into `1 << cap_height` + // slices, one for each sub-tree. + subtree_cap.write(fill_subtree::(subtree_digests, subtree_leaves)); + }, + ); +} + +impl> MerkleTree { + pub fn new(leaves: Vec>, cap_height: usize) -> Self { + let log2_leaves_len = log2_strict(leaves.len()); + assert!( + cap_height <= log2_leaves_len, + "cap_height={} should be at most log2(leaves.len())={}", + cap_height, + log2_leaves_len + ); + + let num_digests = 2 * (leaves.len() - (1 << cap_height)); + let mut digests = Vec::with_capacity(num_digests); + + let len_cap = 1 << cap_height; + let mut cap = Vec::with_capacity(len_cap); + + let digests_buf = capacity_up_to_mut(&mut digests, num_digests); + let cap_buf = capacity_up_to_mut(&mut cap, len_cap); + fill_digests_buf::(digests_buf, cap_buf, &leaves[..], cap_height); + + unsafe { + // SAFETY: `fill_digests_buf` and `cap` initialized the spare capacity up to + // `num_digests` and `len_cap`, resp. + digests.set_len(num_digests); + cap.set_len(len_cap); + } + + Self { + leaves, + digests, + cap: MerkleCap(cap), + } + } + + pub fn get(&self, i: usize) -> &[F] { + &self.leaves[i] + } + + /// Create a Merkle proof from a leaf index. + pub fn prove(&self, leaf_index: usize) -> MerkleProof { + let cap_height = log2_strict(self.cap.len()); + let num_layers = log2_strict(self.leaves.len()) - cap_height; + debug_assert_eq!(leaf_index >> (cap_height + num_layers), 0); + + let digest_tree = { + let tree_index = leaf_index >> num_layers; + let tree_len = self.digests.len() >> cap_height; + &self.digests[tree_len * tree_index..tree_len * (tree_index + 1)] + }; + + // Mask out high bits to get the index within the sub-tree. + let mut pair_index = leaf_index & ((1 << num_layers) - 1); + let siblings = (0..num_layers) + .map(|i| { + let parity = pair_index & 1; + pair_index >>= 1; + + // The layers' data is interleaved as follows: + // [layer 0, layer 1, layer 0, layer 2, layer 0, layer 1, layer 0, layer 3, + // ...]. Each of the above is a pair of siblings. + // `pair_index` is the index of the pair within layer `i`. + // The index of that the pair within `digests` is + // `pair_index * 2 ** (i + 1) + (2 ** i - 1)`. + let siblings_index = (pair_index << (i + 1)) + (1 << i) - 1; + // We have an index for the _pair_, but we want the index of the _sibling_. + // Double the pair index to get the index of the left sibling. Conditionally add + // `1` if we are to retrieve the right sibling. + let sibling_index = 2 * siblings_index + (1 - parity); + digest_tree[sibling_index] + }) + .collect(); + + MerkleProof { siblings } + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + + use super::*; + use crate::field::extension::Extendable; + use crate::hash::merkle_proofs::verify_merkle_proof_to_cap; + use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + + fn random_data(n: usize, k: usize) -> Vec> { + (0..n).map(|_| F::rand_vec(k)).collect() + } + + fn verify_all_leaves< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, + >( + leaves: Vec>, + cap_height: usize, + ) -> Result<()> { + let tree = MerkleTree::::new(leaves.clone(), cap_height); + for (i, leaf) in leaves.into_iter().enumerate() { + let proof = tree.prove(i); + verify_merkle_proof_to_cap(leaf, i, &tree.cap, &proof)?; + } + Ok(()) + } + + #[test] + #[should_panic] + fn test_cap_height_too_big() { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + + let log_n = 8; + let cap_height = log_n + 1; // Should panic if `cap_height > len_n`. + + let leaves = random_data::(1 << log_n, 7); + let _ = MerkleTree::>::Hasher>::new(leaves, cap_height); + } + + #[test] + fn test_cap_height_eq_log2_len() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + + let log_n = 8; + let n = 1 << log_n; + let leaves = random_data::(n, 7); + + verify_all_leaves::(leaves, log_n)?; + + Ok(()) + } + + #[test] + fn test_merkle_trees() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + + let log_n = 8; + let n = 1 << log_n; + let leaves = random_data::(n, 7); + + verify_all_leaves::(leaves, 1)?; + + Ok(()) + } +} diff --git a/plonky2/src/hash/mod.rs b/plonky2/src/hash/mod.rs new file mode 100644 index 000000000..c98c57069 --- /dev/null +++ b/plonky2/src/hash/mod.rs @@ -0,0 +1,12 @@ +//! plonky2 hashing logic for in-circuit hashing and Merkle proof verification +//! as well as specific hash functions implementation. + +mod arch; +pub mod hash_types; +pub mod hashing; +pub mod keccak; +pub mod merkle_proofs; +pub mod merkle_tree; +pub mod path_compression; +pub mod poseidon; +pub mod poseidon_goldilocks; diff --git a/plonky2/src/hash/path_compression.rs b/plonky2/src/hash/path_compression.rs new file mode 100644 index 000000000..32793705b --- /dev/null +++ b/plonky2/src/hash/path_compression.rs @@ -0,0 +1,164 @@ +#[cfg(not(feature = "std"))] +use alloc::{vec, vec::Vec}; + +use hashbrown::HashMap; +use num::Integer; + +use crate::hash::hash_types::RichField; +use crate::hash::merkle_proofs::MerkleProof; +use crate::plonk::config::Hasher; + +/// Compress multiple Merkle proofs on the same tree by removing redundancy in +/// the Merkle paths. +pub(crate) fn compress_merkle_proofs>( + cap_height: usize, + indices: &[usize], + proofs: &[MerkleProof], +) -> Vec> { + assert!(!proofs.is_empty()); + let height = cap_height + proofs[0].siblings.len(); + let num_leaves = 1 << height; + let mut compressed_proofs = Vec::with_capacity(proofs.len()); + // Holds the known nodes in the tree at a given time. The root is at index 1. + // Valid indices are 1 through n, and each element at index `i` has + // children at indices `2i` and `2i +1` its parent at index `floor(i ∕ 2)`. + let mut known = vec![false; 2 * num_leaves]; + for &i in indices { + // The path from a leaf to the cap is known. + for j in 0..(height - cap_height) { + known[(i + num_leaves) >> j] = true; + } + } + // For each proof collect all the unknown proof elements. + for (&i, p) in indices.iter().zip(proofs) { + let mut compressed_proof = MerkleProof { + siblings: Vec::new(), + }; + let mut index = i + num_leaves; + for &sibling in &p.siblings { + let sibling_index = index ^ 1; + if !known[sibling_index] { + // If the sibling is not yet known, add it to the proof and set it to known. + compressed_proof.siblings.push(sibling); + known[sibling_index] = true; + } + // Go up the tree and set the parent to known. + index >>= 1; + known[index] = true; + } + compressed_proofs.push(compressed_proof); + } + + compressed_proofs +} + +/// Decompress compressed Merkle proofs. +/// Note: The data and indices must be in the same order as in +/// `compress_merkle_proofs`. +pub(crate) fn decompress_merkle_proofs>( + leaves_data: &[Vec], + leaves_indices: &[usize], + compressed_proofs: &[MerkleProof], + height: usize, + cap_height: usize, +) -> Vec> { + let num_leaves = 1 << height; + let compressed_proofs = compressed_proofs.to_vec(); + let mut decompressed_proofs = Vec::with_capacity(compressed_proofs.len()); + // Holds the already seen nodes in the tree along with their value. + let mut seen = HashMap::new(); + + for (&i, v) in leaves_indices.iter().zip(leaves_data) { + // Observe the leaves. + seen.insert(i + num_leaves, H::hash_or_noop(v)); + } + + // Iterators over the siblings. + let mut siblings = compressed_proofs + .iter() + .map(|p| p.siblings.iter()) + .collect::>(); + // Fill the `seen` map from the bottom of the tree to the cap. + for layer_height in 0..height - cap_height { + for (&i, p) in leaves_indices.iter().zip(siblings.iter_mut()) { + let index = (i + num_leaves) >> layer_height; + let current_hash = seen[&index]; + let sibling_index = index ^ 1; + let sibling_hash = *seen + .entry(sibling_index) + .or_insert_with(|| *p.next().unwrap()); + let parent_hash = if index.is_even() { + H::two_to_one(current_hash, sibling_hash) + } else { + H::two_to_one(sibling_hash, current_hash) + }; + seen.insert(index >> 1, parent_hash); + } + } + // For every index, go up the tree by querying `seen` to get node values. + for &i in leaves_indices { + let mut decompressed_proof = MerkleProof { + siblings: Vec::new(), + }; + let mut index = i + num_leaves; + for _ in 0..height - cap_height { + let sibling_index = index ^ 1; + let h = seen[&sibling_index]; + decompressed_proof.siblings.push(h); + index >>= 1; + } + + decompressed_proofs.push(decompressed_proof); + } + + decompressed_proofs +} + +#[cfg(test)] +mod tests { + use rand::rngs::OsRng; + use rand::Rng; + + use super::*; + use crate::field::types::Sample; + use crate::hash::merkle_tree::MerkleTree; + use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + + #[test] + fn test_path_compression() { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + let h = 10; + let cap_height = 3; + let vs = (0..1 << h).map(|_| vec![F::rand()]).collect::>(); + let mt = MerkleTree::>::Hasher>::new(vs.clone(), cap_height); + + let mut rng = OsRng; + let k = rng.gen_range(1..=1 << h); + let indices = (0..k).map(|_| rng.gen_range(0..1 << h)).collect::>(); + let proofs = indices.iter().map(|&i| mt.prove(i)).collect::>(); + + let compressed_proofs = compress_merkle_proofs(cap_height, &indices, &proofs); + let decompressed_proofs = decompress_merkle_proofs( + &indices.iter().map(|&i| vs[i].clone()).collect::>(), + &indices, + &compressed_proofs, + h, + cap_height, + ); + + assert_eq!(proofs, decompressed_proofs); + + #[cfg(feature = "std")] + { + let compressed_proof_bytes = serde_cbor::to_vec(&compressed_proofs).unwrap(); + println!( + "Compressed proof length: {} bytes", + compressed_proof_bytes.len() + ); + let proof_bytes = serde_cbor::to_vec(&proofs).unwrap(); + println!("Proof length: {} bytes", proof_bytes.len()); + } + } +} diff --git a/plonky2/src/hash/poseidon.rs b/plonky2/src/hash/poseidon.rs new file mode 100644 index 000000000..11d4c3be6 --- /dev/null +++ b/plonky2/src/hash/poseidon.rs @@ -0,0 +1,791 @@ +//! Implementation of the Poseidon hash function, as described in +//! + +#[cfg(not(feature = "std"))] +use alloc::{vec, vec::Vec}; +use core::fmt::Debug; + +use unroll::unroll_for_loops; + +use crate::field::extension::{Extendable, FieldExtension}; +use crate::field::types::{Field, PrimeField64}; +use crate::gates::gate::Gate; +use crate::gates::poseidon::PoseidonGate; +use crate::gates::poseidon_mds::PoseidonMdsGate; +use crate::hash::hash_types::{HashOut, RichField}; +use crate::hash::hashing::{compress, hash_n_to_hash_no_pad, PlonkyPermutation}; +use crate::iop::ext_target::ExtensionTarget; +use crate::iop::target::{BoolTarget, Target}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::config::{AlgebraicHasher, Hasher}; + +pub const SPONGE_RATE: usize = 8; +pub const SPONGE_CAPACITY: usize = 4; +pub const SPONGE_WIDTH: usize = SPONGE_RATE + SPONGE_CAPACITY; + +// The number of full rounds and partial rounds is given by the +// calc_round_numbers.py script. They happen to be the same for both +// width 8 and width 12 with s-box x^7. +// +// NB: Changing any of these values will require regenerating all of +// the precomputed constant arrays in this file. +pub const HALF_N_FULL_ROUNDS: usize = 4; +pub(crate) const N_FULL_ROUNDS_TOTAL: usize = 2 * HALF_N_FULL_ROUNDS; +pub const N_PARTIAL_ROUNDS: usize = 22; +pub const N_ROUNDS: usize = N_FULL_ROUNDS_TOTAL + N_PARTIAL_ROUNDS; +const MAX_WIDTH: usize = 12; // we only have width 8 and 12, and 12 is bigger. :) + +#[inline(always)] +const fn add_u160_u128((x_lo, x_hi): (u128, u32), y: u128) -> (u128, u32) { + let (res_lo, over) = x_lo.overflowing_add(y); + let res_hi = x_hi + (over as u32); + (res_lo, res_hi) +} + +#[inline(always)] +fn reduce_u160((n_lo, n_hi): (u128, u32)) -> F { + let n_lo_hi = (n_lo >> 64) as u64; + let n_lo_lo = n_lo as u64; + let reduced_hi: u64 = F::from_noncanonical_u96((n_lo_hi, n_hi)).to_noncanonical_u64(); + let reduced128: u128 = ((reduced_hi as u128) << 64) + (n_lo_lo as u128); + F::from_noncanonical_u128(reduced128) +} + +/// Note that these work for the Goldilocks field, but not necessarily others. See +/// `generate_constants` about how these were generated. We include enough for a width of 12; +/// smaller widths just use a subset. +#[rustfmt::skip] +pub const ALL_ROUND_CONSTANTS: [u64; MAX_WIDTH * N_ROUNDS] = [ + // WARNING: The AVX2 Goldilocks specialization relies on all round constants being in + // 0..0xfffeeac900011537. If these constants are randomly regenerated, there is a ~.6% chance + // that this condition will no longer hold. + // + // WARNING: If these are changed in any way, then all the + // implementations of Poseidon must be regenerated. See comments + // in `poseidon_goldilocks.rs`. + 0xb585f766f2144405, 0x7746a55f43921ad7, 0xb2fb0d31cee799b4, 0x0f6760a4803427d7, + 0xe10d666650f4e012, 0x8cae14cb07d09bf1, 0xd438539c95f63e9f, 0xef781c7ce35b4c3d, + 0xcdc4a239b0c44426, 0x277fa208bf337bff, 0xe17653a29da578a1, 0xc54302f225db2c76, + 0x86287821f722c881, 0x59cd1a8a41c18e55, 0xc3b919ad495dc574, 0xa484c4c5ef6a0781, + 0x308bbd23dc5416cc, 0x6e4a40c18f30c09c, 0x9a2eedb70d8f8cfa, 0xe360c6e0ae486f38, + 0xd5c7718fbfc647fb, 0xc35eae071903ff0b, 0x849c2656969c4be7, 0xc0572c8c08cbbbad, + 0xe9fa634a21de0082, 0xf56f6d48959a600d, 0xf7d713e806391165, 0x8297132b32825daf, + 0xad6805e0e30b2c8a, 0xac51d9f5fcf8535e, 0x502ad7dc18c2ad87, 0x57a1550c110b3041, + 0x66bbd30e6ce0e583, 0x0da2abef589d644e, 0xf061274fdb150d61, 0x28b8ec3ae9c29633, + 0x92a756e67e2b9413, 0x70e741ebfee96586, 0x019d5ee2af82ec1c, 0x6f6f2ed772466352, + 0x7cf416cfe7e14ca1, 0x61df517b86a46439, 0x85dc499b11d77b75, 0x4b959b48b9c10733, + 0xe8be3e5da8043e57, 0xf5c0bc1de6da8699, 0x40b12cbf09ef74bf, 0xa637093ecb2ad631, + 0x3cc3f892184df408, 0x2e479dc157bf31bb, 0x6f49de07a6234346, 0x213ce7bede378d7b, + 0x5b0431345d4dea83, 0xa2de45780344d6a1, 0x7103aaf94a7bf308, 0x5326fc0d97279301, + 0xa9ceb74fec024747, 0x27f8ec88bb21b1a3, 0xfceb4fda1ded0893, 0xfac6ff1346a41675, + 0x7131aa45268d7d8c, 0x9351036095630f9f, 0xad535b24afc26bfb, 0x4627f5c6993e44be, + 0x645cf794b8f1cc58, 0x241c70ed0af61617, 0xacb8e076647905f1, 0x3737e9db4c4f474d, + 0xe7ea5e33e75fffb6, 0x90dee49fc9bfc23a, 0xd1b1edf76bc09c92, 0x0b65481ba645c602, + 0x99ad1aab0814283b, 0x438a7c91d416ca4d, 0xb60de3bcc5ea751c, 0xc99cab6aef6f58bc, + 0x69a5ed92a72ee4ff, 0x5e7b329c1ed4ad71, 0x5fc0ac0800144885, 0x32db829239774eca, + 0x0ade699c5830f310, 0x7cc5583b10415f21, 0x85df9ed2e166d64f, 0x6604df4fee32bcb1, + 0xeb84f608da56ef48, 0xda608834c40e603d, 0x8f97fe408061f183, 0xa93f485c96f37b89, + 0x6704e8ee8f18d563, 0xcee3e9ac1e072119, 0x510d0e65e2b470c1, 0xf6323f486b9038f0, + 0x0b508cdeffa5ceef, 0xf2417089e4fb3cbd, 0x60e75c2890d15730, 0xa6217d8bf660f29c, + 0x7159cd30c3ac118e, 0x839b4e8fafead540, 0x0d3f3e5e82920adc, 0x8f7d83bddee7bba8, + 0x780f2243ea071d06, 0xeb915845f3de1634, 0xd19e120d26b6f386, 0x016ee53a7e5fecc6, + 0xcb5fd54e7933e477, 0xacb8417879fd449f, 0x9c22190be7f74732, 0x5d693c1ba3ba3621, + 0xdcef0797c2b69ec7, 0x3d639263da827b13, 0xe273fd971bc8d0e7, 0x418f02702d227ed5, + 0x8c25fda3b503038c, 0x2cbaed4daec8c07c, 0x5f58e6afcdd6ddc2, 0x284650ac5e1b0eba, + 0x635b337ee819dab5, 0x9f9a036ed4f2d49f, 0xb93e260cae5c170e, 0xb0a7eae879ddb76d, + 0xd0762cbc8ca6570c, 0x34c6efb812b04bf5, 0x40bf0ab5fa14c112, 0xb6b570fc7c5740d3, + 0x5a27b9002de33454, 0xb1a5b165b6d2b2d2, 0x8722e0ace9d1be22, 0x788ee3b37e5680fb, + 0x14a726661551e284, 0x98b7672f9ef3b419, 0xbb93ae776bb30e3a, 0x28fd3b046380f850, + 0x30a4680593258387, 0x337dc00c61bd9ce1, 0xd5eca244c7a4ff1d, 0x7762638264d279bd, + 0xc1e434bedeefd767, 0x0299351a53b8ec22, 0xb2d456e4ad251b80, 0x3e9ed1fda49cea0b, + 0x2972a92ba450bed8, 0x20216dd77be493de, 0xadffe8cf28449ec6, 0x1c4dbb1c4c27d243, + 0x15a16a8a8322d458, 0x388a128b7fd9a609, 0x2300e5d6baedf0fb, 0x2f63aa8647e15104, + 0xf1c36ce86ecec269, 0x27181125183970c9, 0xe584029370dca96d, 0x4d9bbc3e02f1cfb2, + 0xea35bc29692af6f8, 0x18e21b4beabb4137, 0x1e3b9fc625b554f4, 0x25d64362697828fd, + 0x5a3f1bb1c53a9645, 0xdb7f023869fb8d38, 0xb462065911d4e1fc, 0x49c24ae4437d8030, + 0xd793862c112b0566, 0xaadd1106730d8feb, 0xc43b6e0e97b0d568, 0xe29024c18ee6fca2, + 0x5e50c27535b88c66, 0x10383f20a4ff9a87, 0x38e8ee9d71a45af8, 0xdd5118375bf1a9b9, + 0x775005982d74d7f7, 0x86ab99b4dde6c8b0, 0xb1204f603f51c080, 0xef61ac8470250ecf, + 0x1bbcd90f132c603f, 0x0cd1dabd964db557, 0x11a3ae5beb9d1ec9, 0xf755bfeea585d11d, + 0xa3b83250268ea4d7, 0x516306f4927c93af, 0xddb4ac49c9efa1da, 0x64bb6dec369d4418, + 0xf9cc95c22b4c1fcc, 0x08d37f755f4ae9f6, 0xeec49b613478675b, 0xf143933aed25e0b0, + 0xe4c5dd8255dfc622, 0xe7ad7756f193198e, 0x92c2318b87fff9cb, 0x739c25f8fd73596d, + 0x5636cac9f16dfed0, 0xdd8f909a938e0172, 0xc6401fe115063f5b, 0x8ad97b33f1ac1455, + 0x0c49366bb25e8513, 0x0784d3d2f1698309, 0x530fb67ea1809a81, 0x410492299bb01f49, + 0x139542347424b9ac, 0x9cb0bd5ea1a1115e, 0x02e3f615c38f49a1, 0x985d4f4a9c5291ef, + 0x775b9feafdcd26e7, 0x304265a6384f0f2d, 0x593664c39773012c, 0x4f0a2e5fb028f2ce, + 0xdd611f1000c17442, 0xd8185f9adfea4fd0, 0xef87139ca9a3ab1e, 0x3ba71336c34ee133, + 0x7d3a455d56b70238, 0x660d32e130182684, 0x297a863f48cd1f43, 0x90e0a736a751ebb7, + 0x549f80ce550c4fd3, 0x0f73b2922f38bd64, 0x16bf1f73fb7a9c3f, 0x6d1f5a59005bec17, + 0x02ff876fa5ef97c4, 0xc5cb72a2a51159b0, 0x8470f39d2d5c900e, 0x25abb3f1d39fcb76, + 0x23eb8cc9b372442f, 0xd687ba55c64f6364, 0xda8d9e90fd8ff158, 0xe3cbdc7d2fe45ea7, + 0xb9a8c9b3aee52297, 0xc0d28a5c10960bd3, 0x45d7ac9b68f71a34, 0xeeb76e397069e804, + 0x3d06c8bd1514e2d9, 0x9c9c98207cb10767, 0x65700b51aedfb5ef, 0x911f451539869408, + 0x7ae6849fbc3a0ec6, 0x3bb340eba06afe7e, 0xb46e9d8b682ea65e, 0x8dcf22f9a3b34356, + 0x77bdaeda586257a7, 0xf19e400a5104d20d, 0xc368a348e46d950f, 0x9ef1cd60e679f284, + 0xe89cd854d5d01d33, 0x5cd377dc8bb882a2, 0xa7b0fb7883eee860, 0x7684403ec392950d, + 0x5fa3f06f4fed3b52, 0x8df57ac11bc04831, 0x2db01efa1e1e1897, 0x54846de4aadb9ca2, + 0xba6745385893c784, 0x541d496344d2c75b, 0xe909678474e687fe, 0xdfe89923f6c9c2ff, + 0xece5a71e0cfedc75, 0x5ff98fd5d51fe610, 0x83e8941918964615, 0x5922040b47f150c1, + 0xf97d750e3dd94521, 0x5080d4c2b86f56d7, 0xa7de115b56c78d70, 0x6a9242ac87538194, + 0xf7856ef7f9173e44, 0x2265fc92feb0dc09, 0x17dfc8e4f7ba8a57, 0x9001a64209f21db8, + 0x90004c1371b893c5, 0xb932b7cf752e5545, 0xa0b1df81b6fe59fc, 0x8ef1dd26770af2c2, + 0x0541a4f9cfbeed35, 0x9e61106178bfc530, 0xb3767e80935d8af2, 0x0098d5782065af06, + 0x31d191cd5c1466c7, 0x410fefafa319ac9d, 0xbdf8f242e316c4ab, 0x9e8cd55b57637ed0, + 0xde122bebe9a39368, 0x4d001fd58f002526, 0xca6637000eb4a9f8, 0x2f2339d624f91f78, + 0x6d1a7918c80df518, 0xdf9a4939342308e9, 0xebc2151ee6c8398c, 0x03cc2ba8a1116515, + 0xd341d037e840cf83, 0x387cb5d25af4afcc, 0xbba2515f22909e87, 0x7248fe7705f38e47, + 0x4d61e56a525d225a, 0x262e963c8da05d3d, 0x59e89b094d220ec2, 0x055d5b52b78b9c5e, + 0x82b27eb33514ef99, 0xd30094ca96b7ce7b, 0xcf5cb381cd0a1535, 0xfeed4db6919e5a7c, + 0x41703f53753be59f, 0x5eeea940fcde8b6f, 0x4cd1f1b175100206, 0x4a20358574454ec0, + 0x1478d361dbbf9fac, 0x6f02dc07d141875c, 0x296a202ed8e556a2, 0x2afd67999bf32ee5, + 0x7acfd96efa95491d, 0x6798ba0c0abb2c6d, 0x34c6f57b26c92122, 0x5736e1bad206b5de, + 0x20057d2a0056521b, 0x3dea5bd5d0578bd7, 0x16e50d897d4634ac, 0x29bff3ecb9b7a6e3, + 0x475cd3205a3bdcde, 0x18a42105c31b7e88, 0x023e7414af663068, 0x15147108121967d7, + 0xe4a3dff1d7d6fef9, 0x01a8d1a588085737, 0x11b4c74eda62beef, 0xe587cc0d69a73346, + 0x1ff7327017aa2a6e, 0x594e29c42473d06b, 0xf6f31db1899b12d5, 0xc02ac5e47312d3ca, + 0xe70201e960cb78b8, 0x6f90ff3b6a65f108, 0x42747a7245e7fa84, 0xd1f507e43ab749b2, + 0x1c86d265f15750cd, 0x3996ce73dd832c1c, 0x8e7fba02983224bd, 0xba0dec7103255dd4, + 0x9e9cbd781628fc5b, 0xdae8645996edd6a5, 0xdebe0853b1a1d378, 0xa49229d24d014343, + 0x7be5b9ffda905e1c, 0xa3c95eaec244aa30, 0x0230bca8f4df0544, 0x4135c2bebfe148c6, + 0x166fc0cc438a3c72, 0x3762b59a8ae83efa, 0xe8928a4c89114750, 0x2a440b51a4945ee5, + 0x80cefd2b7d99ff83, 0xbb9879c6e61fd62a, 0x6e7c8f1a84265034, 0x164bb2de1bbeddc8, + 0xf3c12fe54d5c653b, 0x40b9e922ed9771e2, 0x551f5b0fbe7b1840, 0x25032aa7c4cb1811, + 0xaaed34074b164346, 0x8ffd96bbf9c9c81d, 0x70fc91eb5937085c, 0x7f795e2a5f915440, + 0x4543d9df5476d3cb, 0xf172d73e004fc90d, 0xdfd1c4febcc81238, 0xbc8dfb627fe558fc, +]; + +pub trait Poseidon: PrimeField64 { + // Total number of round constants required: width of the input + // times number of rounds. + const N_ROUND_CONSTANTS: usize = SPONGE_WIDTH * N_ROUNDS; + + // The MDS matrix we use is C + D, where C is the circulant matrix whose first + // row is given by `MDS_MATRIX_CIRC`, and D is the diagonal matrix whose + // diagonal is given by `MDS_MATRIX_DIAG`. + const MDS_MATRIX_CIRC: [u64; SPONGE_WIDTH]; + const MDS_MATRIX_DIAG: [u64; SPONGE_WIDTH]; + + // Precomputed constants for the fast Poseidon calculation. See + // the paper. + const FAST_PARTIAL_FIRST_ROUND_CONSTANT: [u64; SPONGE_WIDTH]; + const FAST_PARTIAL_ROUND_CONSTANTS: [u64; N_PARTIAL_ROUNDS]; + const FAST_PARTIAL_ROUND_VS: [[u64; SPONGE_WIDTH - 1]; N_PARTIAL_ROUNDS]; + const FAST_PARTIAL_ROUND_W_HATS: [[u64; SPONGE_WIDTH - 1]; N_PARTIAL_ROUNDS]; + const FAST_PARTIAL_ROUND_INITIAL_MATRIX: [[u64; SPONGE_WIDTH - 1]; SPONGE_WIDTH - 1]; + + #[inline(always)] + #[unroll_for_loops] + fn mds_row_shf(r: usize, v: &[u64; SPONGE_WIDTH]) -> u128 { + debug_assert!(r < SPONGE_WIDTH); + // The values of `MDS_MATRIX_CIRC` and `MDS_MATRIX_DIAG` are + // known to be small, so we can accumulate all the products for + // each row and reduce just once at the end (done by the + // caller). + + // NB: Unrolling this, calculating each term independently, and + // summing at the end, didn't improve performance for me. + let mut res = 0u128; + + // This is a hacky way of fully unrolling the loop. + for i in 0..12 { + if i < SPONGE_WIDTH { + res += (v[(i + r) % SPONGE_WIDTH] as u128) * (Self::MDS_MATRIX_CIRC[i] as u128); + } + } + res += (v[r] as u128) * (Self::MDS_MATRIX_DIAG[r] as u128); + + res + } + + /// Same as `mds_row_shf` for field extensions of `Self`. + fn mds_row_shf_field, const D: usize>( + r: usize, + v: &[F; SPONGE_WIDTH], + ) -> F { + debug_assert!(r < SPONGE_WIDTH); + let mut res = F::ZERO; + + for i in 0..SPONGE_WIDTH { + res += v[(i + r) % SPONGE_WIDTH] * F::from_canonical_u64(Self::MDS_MATRIX_CIRC[i]); + } + res += v[r] * F::from_canonical_u64(Self::MDS_MATRIX_DIAG[r]); + + res + } + + /// Recursive version of `mds_row_shf`. + fn mds_row_shf_circuit( + builder: &mut CircuitBuilder, + r: usize, + v: &[ExtensionTarget; SPONGE_WIDTH], + ) -> ExtensionTarget + where + Self: RichField + Extendable, + { + debug_assert!(r < SPONGE_WIDTH); + let mut res = builder.zero_extension(); + + for i in 0..SPONGE_WIDTH { + let c = Self::from_canonical_u64(::MDS_MATRIX_CIRC[i]); + res = builder.mul_const_add_extension(c, v[(i + r) % SPONGE_WIDTH], res); + } + { + let c = Self::from_canonical_u64(::MDS_MATRIX_DIAG[r]); + res = builder.mul_const_add_extension(c, v[r], res); + } + + res + } + + #[inline(always)] + #[unroll_for_loops] + fn mds_layer(state_: &[Self; SPONGE_WIDTH]) -> [Self; SPONGE_WIDTH] { + let mut result = [Self::ZERO; SPONGE_WIDTH]; + + let mut state = [0u64; SPONGE_WIDTH]; + for r in 0..SPONGE_WIDTH { + state[r] = state_[r].to_noncanonical_u64(); + } + + // This is a hacky way of fully unrolling the loop. + for r in 0..12 { + if r < SPONGE_WIDTH { + let sum = Self::mds_row_shf(r, &state); + let sum_lo = sum as u64; + let sum_hi = (sum >> 64) as u32; + result[r] = Self::from_noncanonical_u96((sum_lo, sum_hi)); + } + } + + result + } + + /// Same as `mds_layer` for field extensions of `Self`. + fn mds_layer_field, const D: usize>( + state: &[F; SPONGE_WIDTH], + ) -> [F; SPONGE_WIDTH] { + let mut result = [F::ZERO; SPONGE_WIDTH]; + + for r in 0..SPONGE_WIDTH { + result[r] = Self::mds_row_shf_field(r, state); + } + + result + } + + /// Recursive version of `mds_layer`. + fn mds_layer_circuit( + builder: &mut CircuitBuilder, + state: &[ExtensionTarget; SPONGE_WIDTH], + ) -> [ExtensionTarget; SPONGE_WIDTH] + where + Self: RichField + Extendable, + { + // If we have enough routed wires, we will use PoseidonMdsGate. + let mds_gate = PoseidonMdsGate::::new(); + if builder.config.num_routed_wires >= mds_gate.num_wires() { + let index = builder.add_gate(mds_gate, vec![]); + for i in 0..SPONGE_WIDTH { + let input_wire = PoseidonMdsGate::::wires_input(i); + builder.connect_extension(state[i], ExtensionTarget::from_range(index, input_wire)); + } + (0..SPONGE_WIDTH) + .map(|i| { + let output_wire = PoseidonMdsGate::::wires_output(i); + ExtensionTarget::from_range(index, output_wire) + }) + .collect::>() + .try_into() + .unwrap() + } else { + let mut result = [builder.zero_extension(); SPONGE_WIDTH]; + + for r in 0..SPONGE_WIDTH { + result[r] = Self::mds_row_shf_circuit(builder, r, state); + } + + result + } + } + + #[inline(always)] + #[unroll_for_loops] + fn partial_first_constant_layer, const D: usize>( + state: &mut [F; SPONGE_WIDTH], + ) { + for i in 0..12 { + if i < SPONGE_WIDTH { + state[i] += F::from_canonical_u64(Self::FAST_PARTIAL_FIRST_ROUND_CONSTANT[i]); + } + } + } + + /// Recursive version of `partial_first_constant_layer`. + fn partial_first_constant_layer_circuit( + builder: &mut CircuitBuilder, + state: &mut [ExtensionTarget; SPONGE_WIDTH], + ) where + Self: RichField + Extendable, + { + for i in 0..SPONGE_WIDTH { + let c = ::FAST_PARTIAL_FIRST_ROUND_CONSTANT[i]; + let c = Self::Extension::from_canonical_u64(c); + let c = builder.constant_extension(c); + state[i] = builder.add_extension(state[i], c); + } + } + + #[inline(always)] + #[unroll_for_loops] + fn mds_partial_layer_init, const D: usize>( + state: &[F; SPONGE_WIDTH], + ) -> [F; SPONGE_WIDTH] { + let mut result = [F::ZERO; SPONGE_WIDTH]; + + // Initial matrix has first row/column = [1, 0, ..., 0]; + + // c = 0 + result[0] = state[0]; + + for r in 1..12 { + if r < SPONGE_WIDTH { + for c in 1..12 { + if c < SPONGE_WIDTH { + // NB: FAST_PARTIAL_ROUND_INITIAL_MATRIX is stored in + // row-major order so that this dot product is cache + // friendly. + let t = F::from_canonical_u64( + Self::FAST_PARTIAL_ROUND_INITIAL_MATRIX[r - 1][c - 1], + ); + result[c] += state[r] * t; + } + } + } + } + result + } + + /// Recursive version of `mds_partial_layer_init`. + fn mds_partial_layer_init_circuit( + builder: &mut CircuitBuilder, + state: &[ExtensionTarget; SPONGE_WIDTH], + ) -> [ExtensionTarget; SPONGE_WIDTH] + where + Self: RichField + Extendable, + { + let mut result = [builder.zero_extension(); SPONGE_WIDTH]; + + result[0] = state[0]; + + for r in 1..SPONGE_WIDTH { + for c in 1..SPONGE_WIDTH { + let t = ::FAST_PARTIAL_ROUND_INITIAL_MATRIX[r - 1][c - 1]; + let t = Self::Extension::from_canonical_u64(t); + let t = builder.constant_extension(t); + result[c] = builder.mul_add_extension(t, state[r], result[c]); + } + } + result + } + + /// Computes s*A where s is the state row vector and A is the matrix + /// + /// [ M_00 | v ] + /// [ ------+--- ] + /// [ w_hat | Id ] + /// + /// M_00 is a scalar, v is 1x(t-1), w_hat is (t-1)x1 and Id is the + /// (t-1)x(t-1) identity matrix. + #[inline(always)] + #[unroll_for_loops] + fn mds_partial_layer_fast(state: &[Self; SPONGE_WIDTH], r: usize) -> [Self; SPONGE_WIDTH] { + // Set d = [M_00 | w^] dot [state] + + let mut d_sum = (0u128, 0u32); // u160 accumulator + for i in 1..12 { + if i < SPONGE_WIDTH { + let t = Self::FAST_PARTIAL_ROUND_W_HATS[r][i - 1] as u128; + let si = state[i].to_noncanonical_u64() as u128; + d_sum = add_u160_u128(d_sum, si * t); + } + } + let s0 = state[0].to_noncanonical_u64() as u128; + let mds0to0 = (Self::MDS_MATRIX_CIRC[0] + Self::MDS_MATRIX_DIAG[0]) as u128; + d_sum = add_u160_u128(d_sum, s0 * mds0to0); + let d = reduce_u160::(d_sum); + + // result = [d] concat [state[0] * v + state[shift up by 1]] + let mut result = [Self::ZERO; SPONGE_WIDTH]; + result[0] = d; + for i in 1..12 { + if i < SPONGE_WIDTH { + let t = Self::from_canonical_u64(Self::FAST_PARTIAL_ROUND_VS[r][i - 1]); + result[i] = state[i].multiply_accumulate(state[0], t); + } + } + result + } + + /// Same as `mds_partial_layer_fast` for field extensions of `Self`. + fn mds_partial_layer_fast_field, const D: usize>( + state: &[F; SPONGE_WIDTH], + r: usize, + ) -> [F; SPONGE_WIDTH] { + let s0 = state[0]; + let mds0to0 = Self::MDS_MATRIX_CIRC[0] + Self::MDS_MATRIX_DIAG[0]; + let mut d = s0 * F::from_canonical_u64(mds0to0); + for i in 1..SPONGE_WIDTH { + let t = F::from_canonical_u64(Self::FAST_PARTIAL_ROUND_W_HATS[r][i - 1]); + d += state[i] * t; + } + + // result = [d] concat [state[0] * v + state[shift up by 1]] + let mut result = [F::ZERO; SPONGE_WIDTH]; + result[0] = d; + for i in 1..SPONGE_WIDTH { + let t = F::from_canonical_u64(Self::FAST_PARTIAL_ROUND_VS[r][i - 1]); + result[i] = state[0] * t + state[i]; + } + result + } + + /// Recursive version of `mds_partial_layer_fast`. + fn mds_partial_layer_fast_circuit( + builder: &mut CircuitBuilder, + state: &[ExtensionTarget; SPONGE_WIDTH], + r: usize, + ) -> [ExtensionTarget; SPONGE_WIDTH] + where + Self: RichField + Extendable, + { + let s0 = state[0]; + let mds0to0 = Self::MDS_MATRIX_CIRC[0] + Self::MDS_MATRIX_DIAG[0]; + let mut d = builder.mul_const_extension(Self::from_canonical_u64(mds0to0), s0); + for i in 1..SPONGE_WIDTH { + let t = ::FAST_PARTIAL_ROUND_W_HATS[r][i - 1]; + let t = Self::Extension::from_canonical_u64(t); + let t = builder.constant_extension(t); + d = builder.mul_add_extension(t, state[i], d); + } + + let mut result = [builder.zero_extension(); SPONGE_WIDTH]; + result[0] = d; + for i in 1..SPONGE_WIDTH { + let t = ::FAST_PARTIAL_ROUND_VS[r][i - 1]; + let t = Self::Extension::from_canonical_u64(t); + let t = builder.constant_extension(t); + result[i] = builder.mul_add_extension(t, state[0], state[i]); + } + result + } + + #[inline(always)] + #[unroll_for_loops] + fn constant_layer(state: &mut [Self; SPONGE_WIDTH], round_ctr: usize) { + for i in 0..12 { + if i < SPONGE_WIDTH { + let round_constant = ALL_ROUND_CONSTANTS[i + SPONGE_WIDTH * round_ctr]; + unsafe { + state[i] = state[i].add_canonical_u64(round_constant); + } + } + } + } + + /// Same as `constant_layer` for field extensions of `Self`. + fn constant_layer_field, const D: usize>( + state: &mut [F; SPONGE_WIDTH], + round_ctr: usize, + ) { + for i in 0..SPONGE_WIDTH { + state[i] += F::from_canonical_u64(ALL_ROUND_CONSTANTS[i + SPONGE_WIDTH * round_ctr]); + } + } + + /// Recursive version of `constant_layer`. + fn constant_layer_circuit( + builder: &mut CircuitBuilder, + state: &mut [ExtensionTarget; SPONGE_WIDTH], + round_ctr: usize, + ) where + Self: RichField + Extendable, + { + for i in 0..SPONGE_WIDTH { + let c = ALL_ROUND_CONSTANTS[i + SPONGE_WIDTH * round_ctr]; + let c = Self::Extension::from_canonical_u64(c); + let c = builder.constant_extension(c); + state[i] = builder.add_extension(state[i], c); + } + } + + #[inline(always)] + fn sbox_monomial, const D: usize>(x: F) -> F { + // x |--> x^7 + let x2 = x.square(); + let x4 = x2.square(); + let x3 = x * x2; + x3 * x4 + } + + /// Recursive version of `sbox_monomial`. + fn sbox_monomial_circuit( + builder: &mut CircuitBuilder, + x: ExtensionTarget, + ) -> ExtensionTarget + where + Self: RichField + Extendable, + { + // x |--> x^7 + builder.exp_u64_extension(x, 7) + } + + #[inline(always)] + #[unroll_for_loops] + fn sbox_layer(state: &mut [Self; SPONGE_WIDTH]) { + for i in 0..12 { + if i < SPONGE_WIDTH { + state[i] = Self::sbox_monomial(state[i]); + } + } + } + + /// Same as `sbox_layer` for field extensions of `Self`. + fn sbox_layer_field, const D: usize>( + state: &mut [F; SPONGE_WIDTH], + ) { + for i in 0..SPONGE_WIDTH { + state[i] = Self::sbox_monomial(state[i]); + } + } + + /// Recursive version of `sbox_layer`. + fn sbox_layer_circuit( + builder: &mut CircuitBuilder, + state: &mut [ExtensionTarget; SPONGE_WIDTH], + ) where + Self: RichField + Extendable, + { + for i in 0..SPONGE_WIDTH { + state[i] = ::sbox_monomial_circuit(builder, state[i]); + } + } + + #[inline] + fn full_rounds(state: &mut [Self; SPONGE_WIDTH], round_ctr: &mut usize) { + for _ in 0..HALF_N_FULL_ROUNDS { + Self::constant_layer(state, *round_ctr); + Self::sbox_layer(state); + *state = Self::mds_layer(state); + *round_ctr += 1; + } + } + + #[inline] + fn partial_rounds(state: &mut [Self; SPONGE_WIDTH], round_ctr: &mut usize) { + Self::partial_first_constant_layer(state); + *state = Self::mds_partial_layer_init(state); + + for i in 0..N_PARTIAL_ROUNDS { + state[0] = Self::sbox_monomial(state[0]); + unsafe { + state[0] = state[0].add_canonical_u64(Self::FAST_PARTIAL_ROUND_CONSTANTS[i]); + } + *state = Self::mds_partial_layer_fast(state, i); + } + *round_ctr += N_PARTIAL_ROUNDS; + } + + #[inline] + fn poseidon(input: [Self; SPONGE_WIDTH]) -> [Self; SPONGE_WIDTH] { + let mut state = input; + let mut round_ctr = 0; + + Self::full_rounds(&mut state, &mut round_ctr); + Self::partial_rounds(&mut state, &mut round_ctr); + Self::full_rounds(&mut state, &mut round_ctr); + debug_assert_eq!(round_ctr, N_ROUNDS); + + state + } + + // For testing only, to ensure that various tricks are correct. + #[inline] + fn partial_rounds_naive(state: &mut [Self; SPONGE_WIDTH], round_ctr: &mut usize) { + for _ in 0..N_PARTIAL_ROUNDS { + Self::constant_layer(state, *round_ctr); + state[0] = Self::sbox_monomial(state[0]); + *state = Self::mds_layer(state); + *round_ctr += 1; + } + } + + #[inline] + fn poseidon_naive(input: [Self; SPONGE_WIDTH]) -> [Self; SPONGE_WIDTH] { + let mut state = input; + let mut round_ctr = 0; + + Self::full_rounds(&mut state, &mut round_ctr); + Self::partial_rounds_naive(&mut state, &mut round_ctr); + Self::full_rounds(&mut state, &mut round_ctr); + debug_assert_eq!(round_ctr, N_ROUNDS); + + state + } +} + +#[derive(Copy, Clone, Default, Debug, PartialEq)] +pub struct PoseidonPermutation { + state: [T; SPONGE_WIDTH], +} + +impl Eq for PoseidonPermutation {} + +impl AsRef<[T]> for PoseidonPermutation { + fn as_ref(&self) -> &[T] { + &self.state + } +} + +trait Permuter: Sized { + fn permute(input: [Self; SPONGE_WIDTH]) -> [Self; SPONGE_WIDTH]; +} + +impl Permuter for F { + fn permute(input: [Self; SPONGE_WIDTH]) -> [Self; SPONGE_WIDTH] { + ::poseidon(input) + } +} + +impl Permuter for Target { + fn permute(_input: [Self; SPONGE_WIDTH]) -> [Self; SPONGE_WIDTH] { + panic!("Call `permute_swapped()` instead of `permute()`"); + } +} + +impl PlonkyPermutation + for PoseidonPermutation +{ + const RATE: usize = SPONGE_RATE; + const WIDTH: usize = SPONGE_WIDTH; + + fn new>(elts: I) -> Self { + let mut perm = Self { + state: [T::default(); SPONGE_WIDTH], + }; + perm.set_from_iter(elts, 0); + perm + } + + fn set_elt(&mut self, elt: T, idx: usize) { + self.state[idx] = elt; + } + + fn set_from_slice(&mut self, elts: &[T], start_idx: usize) { + let begin = start_idx; + let end = start_idx + elts.len(); + self.state[begin..end].copy_from_slice(elts); + } + + fn set_from_iter>(&mut self, elts: I, start_idx: usize) { + for (s, e) in self.state[start_idx..].iter_mut().zip(elts) { + *s = e; + } + } + + fn permute(&mut self) { + self.state = T::permute(self.state); + } + + fn squeeze(&self) -> &[T] { + &self.state[..Self::RATE] + } +} + +/// Poseidon hash function. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct PoseidonHash; +impl Hasher for PoseidonHash { + const HASH_SIZE: usize = 4 * 8; + type Hash = HashOut; + type Permutation = PoseidonPermutation; + + fn hash_no_pad(input: &[F]) -> Self::Hash { + hash_n_to_hash_no_pad::(input) + } + + fn two_to_one(left: Self::Hash, right: Self::Hash) -> Self::Hash { + compress::(left, right) + } +} + +impl AlgebraicHasher for PoseidonHash { + type AlgebraicPermutation = PoseidonPermutation; + + fn permute_swapped( + inputs: Self::AlgebraicPermutation, + swap: BoolTarget, + builder: &mut CircuitBuilder, + ) -> Self::AlgebraicPermutation + where + F: RichField + Extendable, + { + let gate_type = PoseidonGate::::new(); + let gate = builder.add_gate(gate_type, vec![]); + + let swap_wire = PoseidonGate::::WIRE_SWAP; + let swap_wire = Target::wire(gate, swap_wire); + builder.connect(swap.target, swap_wire); + + // Route input wires. + let inputs = inputs.as_ref(); + for i in 0..SPONGE_WIDTH { + let in_wire = PoseidonGate::::wire_input(i); + let in_wire = Target::wire(gate, in_wire); + builder.connect(inputs[i], in_wire); + } + + // Collect output wires. + Self::AlgebraicPermutation::new( + (0..SPONGE_WIDTH).map(|i| Target::wire(gate, PoseidonGate::::wire_output(i))), + ) + } +} + +#[cfg(test)] +pub(crate) mod test_helpers { + use super::*; + + pub(crate) fn check_test_vectors( + test_vectors: Vec<([u64; SPONGE_WIDTH], [u64; SPONGE_WIDTH])>, + ) where + F: Poseidon, + { + for (input_, expected_output_) in test_vectors.into_iter() { + let mut input = [F::ZERO; SPONGE_WIDTH]; + for i in 0..SPONGE_WIDTH { + input[i] = F::from_canonical_u64(input_[i]); + } + let output = F::poseidon(input); + for i in 0..SPONGE_WIDTH { + let ex_output = F::from_canonical_u64(expected_output_[i]); + assert_eq!(output[i], ex_output); + } + } + } + + pub(crate) fn check_consistency() + where + F: Poseidon, + { + let mut input = [F::ZERO; SPONGE_WIDTH]; + for i in 0..SPONGE_WIDTH { + input[i] = F::from_canonical_u64(i as u64); + } + let output = F::poseidon(input); + let output_naive = F::poseidon_naive(input); + for i in 0..SPONGE_WIDTH { + assert_eq!(output[i], output_naive[i]); + } + } +} diff --git a/plonky2/src/hash/poseidon_crandall.rs b/plonky2/src/hash/poseidon_crandall.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/plonky2/src/hash/poseidon_crandall.rs @@ -0,0 +1 @@ + diff --git a/plonky2/src/hash/poseidon_goldilocks.rs b/plonky2/src/hash/poseidon_goldilocks.rs new file mode 100644 index 000000000..3a06a3c9b --- /dev/null +++ b/plonky2/src/hash/poseidon_goldilocks.rs @@ -0,0 +1,502 @@ +//! Implementations for Poseidon over Goldilocks field of widths 8 and 12. +//! +//! These contents of the implementations *must* be generated using the +//! `poseidon_constants.sage` script in the `0xPolygonZero/hash-constants` +//! repository. + +#[cfg(not(all(target_arch = "aarch64", target_feature = "neon")))] +use plonky2_field::types::Field; + +use crate::field::goldilocks_field::GoldilocksField; +use crate::hash::poseidon::{Poseidon, N_PARTIAL_ROUNDS}; + +#[rustfmt::skip] +impl Poseidon for GoldilocksField { + // The MDS matrix we use is C + D, where C is the circulant matrix whose first row is given by + // `MDS_MATRIX_CIRC`, and D is the diagonal matrix whose diagonal is given by `MDS_MATRIX_DIAG`. + // + // WARNING: If the MDS matrix is changed, then the following + // constants need to be updated accordingly: + // - FAST_PARTIAL_ROUND_CONSTANTS + // - FAST_PARTIAL_ROUND_VS + // - FAST_PARTIAL_ROUND_W_HATS + // - FAST_PARTIAL_ROUND_INITIAL_MATRIX + const MDS_MATRIX_CIRC: [u64; 12] = [17, 15, 41, 16, 2, 28, 13, 13, 39, 18, 34, 20]; + const MDS_MATRIX_DIAG: [u64; 12] = [8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + const FAST_PARTIAL_FIRST_ROUND_CONSTANT: [u64; 12] = [ + 0x3cc3f892184df408, 0xe993fd841e7e97f1, 0xf2831d3575f0f3af, 0xd2500e0a350994ca, + 0xc5571f35d7288633, 0x91d89c5184109a02, 0xf37f925d04e5667b, 0x2d6e448371955a69, + 0x740ef19ce01398a1, 0x694d24c0752fdf45, 0x60936af96ee2f148, 0xc33448feadc78f0c, + ]; + + const FAST_PARTIAL_ROUND_CONSTANTS: [u64; N_PARTIAL_ROUNDS] = [ + 0x74cb2e819ae421ab, 0xd2559d2370e7f663, 0x62bf78acf843d17c, 0xd5ab7b67e14d1fb4, + 0xb9fe2ae6e0969bdc, 0xe33fdf79f92a10e8, 0x0ea2bb4c2b25989b, 0xca9121fbf9d38f06, + 0xbdd9b0aa81f58fa4, 0x83079fa4ecf20d7e, 0x650b838edfcc4ad3, 0x77180c88583c76ac, + 0xaf8c20753143a180, 0xb8ccfe9989a39175, 0x954a1729f60cc9c5, 0xdeb5b550c4dca53b, + 0xf01bb0b00f77011e, 0xa1ebb404b676afd9, 0x860b6e1597a0173e, 0x308bb65a036acbce, + 0x1aca78f31c97c876, 0x0, + ]; + + const FAST_PARTIAL_ROUND_VS: [[u64; 12 - 1]; N_PARTIAL_ROUNDS] = [ + [0x94877900674181c3, 0xc6c67cc37a2a2bbd, 0xd667c2055387940f, 0x0ba63a63e94b5ff0, + 0x99460cc41b8f079f, 0x7ff02375ed524bb3, 0xea0870b47a8caf0e, 0xabcad82633b7bc9d, + 0x3b8d135261052241, 0xfb4515f5e5b0d539, 0x3ee8011c2b37f77c, ], + [0x0adef3740e71c726, 0xa37bf67c6f986559, 0xc6b16f7ed4fa1b00, 0x6a065da88d8bfc3c, + 0x4cabc0916844b46f, 0x407faac0f02e78d1, 0x07a786d9cf0852cf, 0x42433fb6949a629a, + 0x891682a147ce43b0, 0x26cfd58e7b003b55, 0x2bbf0ed7b657acb3, ], + [0x481ac7746b159c67, 0xe367de32f108e278, 0x73f260087ad28bec, 0x5cfc82216bc1bdca, + 0xcaccc870a2663a0e, 0xdb69cd7b4298c45d, 0x7bc9e0c57243e62d, 0x3cc51c5d368693ae, + 0x366b4e8cc068895b, 0x2bd18715cdabbca4, 0xa752061c4f33b8cf, ], + [0xb22d2432b72d5098, 0x9e18a487f44d2fe4, 0x4b39e14ce22abd3c, 0x9e77fde2eb315e0d, + 0xca5e0385fe67014d, 0x0c2cb99bf1b6bddb, 0x99ec1cd2a4460bfe, 0x8577a815a2ff843f, + 0x7d80a6b4fd6518a5, 0xeb6c67123eab62cb, 0x8f7851650eca21a5, ], + [0x11ba9a1b81718c2a, 0x9f7d798a3323410c, 0xa821855c8c1cf5e5, 0x535e8d6fac0031b2, + 0x404e7c751b634320, 0xa729353f6e55d354, 0x4db97d92e58bb831, 0xb53926c27897bf7d, + 0x965040d52fe115c5, 0x9565fa41ebd31fd7, 0xaae4438c877ea8f4, ], + [0x37f4e36af6073c6e, 0x4edc0918210800e9, 0xc44998e99eae4188, 0x9f4310d05d068338, + 0x9ec7fe4350680f29, 0xc5b2c1fdc0b50874, 0xa01920c5ef8b2ebe, 0x59fa6f8bd91d58ba, + 0x8bfc9eb89b515a82, 0xbe86a7a2555ae775, 0xcbb8bbaa3810babf, ], + [0x577f9a9e7ee3f9c2, 0x88c522b949ace7b1, 0x82f07007c8b72106, 0x8283d37c6675b50e, + 0x98b074d9bbac1123, 0x75c56fb7758317c1, 0xfed24e206052bc72, 0x26d7c3d1bc07dae5, + 0xf88c5e441e28dbb4, 0x4fe27f9f96615270, 0x514d4ba49c2b14fe, ], + [0xf02a3ac068ee110b, 0x0a3630dafb8ae2d7, 0xce0dc874eaf9b55c, 0x9a95f6cff5b55c7e, + 0x626d76abfed00c7b, 0xa0c1cf1251c204ad, 0xdaebd3006321052c, 0x3d4bd48b625a8065, + 0x7f1e584e071f6ed2, 0x720574f0501caed3, 0xe3260ba93d23540a, ], + [0xab1cbd41d8c1e335, 0x9322ed4c0bc2df01, 0x51c3c0983d4284e5, 0x94178e291145c231, + 0xfd0f1a973d6b2085, 0xd427ad96e2b39719, 0x8a52437fecaac06b, 0xdc20ee4b8c4c9a80, + 0xa2c98e9549da2100, 0x1603fe12613db5b6, 0x0e174929433c5505, ], + [0x3d4eab2b8ef5f796, 0xcfff421583896e22, 0x4143cb32d39ac3d9, 0x22365051b78a5b65, + 0x6f7fd010d027c9b6, 0xd9dd36fba77522ab, 0xa44cf1cb33e37165, 0x3fc83d3038c86417, + 0xc4588d418e88d270, 0xce1320f10ab80fe2, 0xdb5eadbbec18de5d, ], + [0x1183dfce7c454afd, 0x21cea4aa3d3ed949, 0x0fce6f70303f2304, 0x19557d34b55551be, + 0x4c56f689afc5bbc9, 0xa1e920844334f944, 0xbad66d423d2ec861, 0xf318c785dc9e0479, + 0x99e2032e765ddd81, 0x400ccc9906d66f45, 0xe1197454db2e0dd9, ], + [0x84d1ecc4d53d2ff1, 0xd8af8b9ceb4e11b6, 0x335856bb527b52f4, 0xc756f17fb59be595, + 0xc0654e4ea5553a78, 0x9e9a46b61f2ea942, 0x14fc8b5b3b809127, 0xd7009f0f103be413, + 0x3e0ee7b7a9fb4601, 0xa74e888922085ed7, 0xe80a7cde3d4ac526, ], + [0x238aa6daa612186d, 0x9137a5c630bad4b4, 0xc7db3817870c5eda, 0x217e4f04e5718dc9, + 0xcae814e2817bd99d, 0xe3292e7ab770a8ba, 0x7bb36ef70b6b9482, 0x3c7835fb85bca2d3, + 0xfe2cdf8ee3c25e86, 0x61b3915ad7274b20, 0xeab75ca7c918e4ef, ], + [0xd6e15ffc055e154e, 0xec67881f381a32bf, 0xfbb1196092bf409c, 0xdc9d2e07830ba226, + 0x0698ef3245ff7988, 0x194fae2974f8b576, 0x7a5d9bea6ca4910e, 0x7aebfea95ccdd1c9, + 0xf9bd38a67d5f0e86, 0xfa65539de65492d8, 0xf0dfcbe7653ff787, ], + [0x0bd87ad390420258, 0x0ad8617bca9e33c8, 0x0c00ad377a1e2666, 0x0ac6fc58b3f0518f, + 0x0c0cc8a892cc4173, 0x0c210accb117bc21, 0x0b73630dbb46ca18, 0x0c8be4920cbd4a54, + 0x0bfe877a21be1690, 0x0ae790559b0ded81, 0x0bf50db2f8d6ce31, ], + [0x000cf29427ff7c58, 0x000bd9b3cf49eec8, 0x000d1dc8aa81fb26, 0x000bc792d5c394ef, + 0x000d2ae0b2266453, 0x000d413f12c496c1, 0x000c84128cfed618, 0x000db5ebd48fc0d4, + 0x000d1b77326dcb90, 0x000beb0ccc145421, 0x000d10e5b22b11d1, ], + [0x00000e24c99adad8, 0x00000cf389ed4bc8, 0x00000e580cbf6966, 0x00000cde5fd7e04f, + 0x00000e63628041b3, 0x00000e7e81a87361, 0x00000dabe78f6d98, 0x00000efb14cac554, + 0x00000e5574743b10, 0x00000d05709f42c1, 0x00000e4690c96af1, ], + [0x0000000f7157bc98, 0x0000000e3006d948, 0x0000000fa65811e6, 0x0000000e0d127e2f, + 0x0000000fc18bfe53, 0x0000000fd002d901, 0x0000000eed6461d8, 0x0000001068562754, + 0x0000000fa0236f50, 0x0000000e3af13ee1, 0x0000000fa460f6d1, ], + [0x0000000011131738, 0x000000000f56d588, 0x0000000011050f86, 0x000000000f848f4f, + 0x00000000111527d3, 0x00000000114369a1, 0x00000000106f2f38, 0x0000000011e2ca94, + 0x00000000110a29f0, 0x000000000fa9f5c1, 0x0000000010f625d1, ], + [0x000000000011f718, 0x000000000010b6c8, 0x0000000000134a96, 0x000000000010cf7f, + 0x0000000000124d03, 0x000000000013f8a1, 0x0000000000117c58, 0x0000000000132c94, + 0x0000000000134fc0, 0x000000000010a091, 0x0000000000128961, ], + [0x0000000000001300, 0x0000000000001750, 0x000000000000114e, 0x000000000000131f, + 0x000000000000167b, 0x0000000000001371, 0x0000000000001230, 0x000000000000182c, + 0x0000000000001368, 0x0000000000000f31, 0x00000000000015c9, ], + [0x0000000000000014, 0x0000000000000022, 0x0000000000000012, 0x0000000000000027, + 0x000000000000000d, 0x000000000000000d, 0x000000000000001c, 0x0000000000000002, + 0x0000000000000010, 0x0000000000000029, 0x000000000000000f, ], + ]; + + const FAST_PARTIAL_ROUND_W_HATS: [[u64; 12 - 1]; N_PARTIAL_ROUNDS] = [ + [0x3d999c961b7c63b0, 0x814e82efcd172529, 0x2421e5d236704588, 0x887af7d4dd482328, + 0xa5e9c291f6119b27, 0xbdc52b2676a4b4aa, 0x64832009d29bcf57, 0x09c4155174a552cc, + 0x463f9ee03d290810, 0xc810936e64982542, 0x043b1c289f7bc3ac, ], + [0x673655aae8be5a8b, 0xd510fe714f39fa10, 0x2c68a099b51c9e73, 0xa667bfa9aa96999d, + 0x4d67e72f063e2108, 0xf84dde3e6acda179, 0x40f9cc8c08f80981, 0x5ead032050097142, + 0x6591b02092d671bb, 0x00e18c71963dd1b7, 0x8a21bcd24a14218a, ], + [0x202800f4addbdc87, 0xe4b5bdb1cc3504ff, 0xbe32b32a825596e7, 0x8e0f68c5dc223b9a, + 0x58022d9e1c256ce3, 0x584d29227aa073ac, 0x8b9352ad04bef9e7, 0xaead42a3f445ecbf, + 0x3c667a1d833a3cca, 0xda6f61838efa1ffe, 0xe8f749470bd7c446, ], + [0xc5b85bab9e5b3869, 0x45245258aec51cf7, 0x16e6b8e68b931830, 0xe2ae0f051418112c, + 0x0470e26a0093a65b, 0x6bef71973a8146ed, 0x119265be51812daf, 0xb0be7356254bea2e, + 0x8584defff7589bd7, 0x3c5fe4aeb1fb52ba, 0x9e7cd88acf543a5e, ], + [0x179be4bba87f0a8c, 0xacf63d95d8887355, 0x6696670196b0074f, 0xd99ddf1fe75085f9, + 0xc2597881fef0283b, 0xcf48395ee6c54f14, 0x15226a8e4cd8d3b6, 0xc053297389af5d3b, + 0x2c08893f0d1580e2, 0x0ed3cbcff6fcc5ba, 0xc82f510ecf81f6d0, ], + [0x94b06183acb715cc, 0x500392ed0d431137, 0x861cc95ad5c86323, 0x05830a443f86c4ac, + 0x3b68225874a20a7c, 0x10b3309838e236fb, 0x9b77fc8bcd559e2c, 0xbdecf5e0cb9cb213, + 0x30276f1221ace5fa, 0x7935dd342764a144, 0xeac6db520bb03708, ], + [0x7186a80551025f8f, 0x622247557e9b5371, 0xc4cbe326d1ad9742, 0x55f1523ac6a23ea2, + 0xa13dfe77a3d52f53, 0xe30750b6301c0452, 0x08bd488070a3a32b, 0xcd800caef5b72ae3, + 0x83329c90f04233ce, 0xb5b99e6664a0a3ee, 0x6b0731849e200a7f, ], + [0xec3fabc192b01799, 0x382b38cee8ee5375, 0x3bfb6c3f0e616572, 0x514abd0cf6c7bc86, + 0x47521b1361dcc546, 0x178093843f863d14, 0xad1003c5d28918e7, 0x738450e42495bc81, + 0xaf947c59af5e4047, 0x4653fb0685084ef2, 0x057fde2062ae35bf, ], + [0xe376678d843ce55e, 0x66f3860d7514e7fc, 0x7817f3dfff8b4ffa, 0x3929624a9def725b, + 0x0126ca37f215a80a, 0xfce2f5d02762a303, 0x1bc927375febbad7, 0x85b481e5243f60bf, + 0x2d3c5f42a39c91a0, 0x0811719919351ae8, 0xf669de0add993131, ], + [0x7de38bae084da92d, 0x5b848442237e8a9b, 0xf6c705da84d57310, 0x31e6a4bdb6a49017, + 0x889489706e5c5c0f, 0x0e4a205459692a1b, 0xbac3fa75ee26f299, 0x5f5894f4057d755e, + 0xb0dc3ecd724bb076, 0x5e34d8554a6452ba, 0x04f78fd8c1fdcc5f, ], + [0x4dd19c38779512ea, 0xdb79ba02704620e9, 0x92a29a3675a5d2be, 0xd5177029fe495166, + 0xd32b3298a13330c1, 0x251c4a3eb2c5f8fd, 0xe1c48b26e0d98825, 0x3301d3362a4ffccb, + 0x09bb6c88de8cd178, 0xdc05b676564f538a, 0x60192d883e473fee, ], + [0x16b9774801ac44a0, 0x3cb8411e786d3c8e, 0xa86e9cf505072491, 0x0178928152e109ae, + 0x5317b905a6e1ab7b, 0xda20b3be7f53d59f, 0xcb97dedecebee9ad, 0x4bd545218c59f58d, + 0x77dc8d856c05a44a, 0x87948589e4f243fd, 0x7e5217af969952c2, ], + [0xbc58987d06a84e4d, 0x0b5d420244c9cae3, 0xa3c4711b938c02c0, 0x3aace640a3e03990, + 0x865a0f3249aacd8a, 0x8d00b2a7dbed06c7, 0x6eacb905beb7e2f8, 0x045322b216ec3ec7, + 0xeb9de00d594828e6, 0x088c5f20df9e5c26, 0xf555f4112b19781f, ], + [0xa8cedbff1813d3a7, 0x50dcaee0fd27d164, 0xf1cb02417e23bd82, 0xfaf322786e2abe8b, + 0x937a4315beb5d9b6, 0x1b18992921a11d85, 0x7d66c4368b3c497b, 0x0e7946317a6b4e99, + 0xbe4430134182978b, 0x3771e82493ab262d, 0xa671690d8095ce82, ], + [0xb035585f6e929d9d, 0xba1579c7e219b954, 0xcb201cf846db4ba3, 0x287bf9177372cf45, + 0xa350e4f61147d0a6, 0xd5d0ecfb50bcff99, 0x2e166aa6c776ed21, 0xe1e66c991990e282, + 0x662b329b01e7bb38, 0x8aa674b36144d9a9, 0xcbabf78f97f95e65, ], + [0xeec24b15a06b53fe, 0xc8a7aa07c5633533, 0xefe9c6fa4311ad51, 0xb9173f13977109a1, + 0x69ce43c9cc94aedc, 0xecf623c9cd118815, 0x28625def198c33c7, 0xccfc5f7de5c3636a, + 0xf5e6c40f1621c299, 0xcec0e58c34cb64b1, 0xa868ea113387939f, ], + [0xd8dddbdc5ce4ef45, 0xacfc51de8131458c, 0x146bb3c0fe499ac0, 0x9e65309f15943903, + 0x80d0ad980773aa70, 0xf97817d4ddbf0607, 0xe4626620a75ba276, 0x0dfdc7fd6fc74f66, + 0xf464864ad6f2bb93, 0x02d55e52a5d44414, 0xdd8de62487c40925, ], + [0xc15acf44759545a3, 0xcbfdcf39869719d4, 0x33f62042e2f80225, 0x2599c5ead81d8fa3, + 0x0b306cb6c1d7c8d0, 0x658c80d3df3729b1, 0xe8d1b2b21b41429c, 0xa1b67f09d4b3ccb8, + 0x0e1adf8b84437180, 0x0d593a5e584af47b, 0xa023d94c56e151c7, ], + [0x49026cc3a4afc5a6, 0xe06dff00ab25b91b, 0x0ab38c561e8850ff, 0x92c3c8275e105eeb, + 0xb65256e546889bd0, 0x3c0468236ea142f6, 0xee61766b889e18f2, 0xa206f41b12c30415, + 0x02fe9d756c9f12d1, 0xe9633210630cbf12, 0x1ffea9fe85a0b0b1, ], + [0x81d1ae8cc50240f3, 0xf4c77a079a4607d7, 0xed446b2315e3efc1, 0x0b0a6b70915178c3, + 0xb11ff3e089f15d9a, 0x1d4dba0b7ae9cc18, 0x65d74e2f43b48d05, 0xa2df8c6b8ae0804a, + 0xa4e6f0a8c33348a6, 0xc0a26efc7be5669b, 0xa6b6582c547d0d60, ], + [0x84afc741f1c13213, 0x2f8f43734fc906f3, 0xde682d72da0a02d9, 0x0bb005236adb9ef2, + 0x5bdf35c10a8b5624, 0x0739a8a343950010, 0x52f515f44785cfbc, 0xcbaf4e5d82856c60, + 0xac9ea09074e3e150, 0x8f0fa011a2035fb0, 0x1a37905d8450904a, ], + [0x3abeb80def61cc85, 0x9d19c9dd4eac4133, 0x075a652d9641a985, 0x9daf69ae1b67e667, + 0x364f71da77920a18, 0x50bd769f745c95b1, 0xf223d1180dbbf3fc, 0x2f885e584e04aa99, + 0xb69a0fa70aea684a, 0x09584acaa6e062a0, 0x0bc051640145b19b, ], + ]; + + // NB: This is in ROW-major order to support cache-friendly pre-multiplication. + const FAST_PARTIAL_ROUND_INITIAL_MATRIX: [[u64; 12 - 1]; 12 - 1] = [ + [0x80772dc2645b280b, 0xdc927721da922cf8, 0xc1978156516879ad, 0x90e80c591f48b603, + 0x3a2432625475e3ae, 0x00a2d4321cca94fe, 0x77736f524010c932, 0x904d3f2804a36c54, + 0xbf9b39e28a16f354, 0x3a1ded54a6cd058b, 0x42392870da5737cf, ], + [0xe796d293a47a64cb, 0xb124c33152a2421a, 0x0ee5dc0ce131268a, 0xa9032a52f930fae6, + 0x7e33ca8c814280de, 0xad11180f69a8c29e, 0xc75ac6d5b5a10ff3, 0xf0674a8dc5a387ec, + 0xb36d43120eaa5e2b, 0x6f232aab4b533a25, 0x3a1ded54a6cd058b, ], + [0xdcedab70f40718ba, 0x14a4a64da0b2668f, 0x4715b8e5ab34653b, 0x1e8916a99c93a88e, + 0xbba4b5d86b9a3b2c, 0xe76649f9bd5d5c2e, 0xaf8e2518a1ece54d, 0xdcda1344cdca873f, + 0xcd080204256088e5, 0xb36d43120eaa5e2b, 0xbf9b39e28a16f354, ], + [0xf4a437f2888ae909, 0xc537d44dc2875403, 0x7f68007619fd8ba9, 0xa4911db6a32612da, + 0x2f7e9aade3fdaec1, 0xe7ffd578da4ea43d, 0x43a608e7afa6b5c2, 0xca46546aa99e1575, + 0xdcda1344cdca873f, 0xf0674a8dc5a387ec, 0x904d3f2804a36c54, ], + [0xf97abba0dffb6c50, 0x5e40f0c9bb82aab5, 0x5996a80497e24a6b, 0x07084430a7307c9a, + 0xad2f570a5b8545aa, 0xab7f81fef4274770, 0xcb81f535cf98c9e9, 0x43a608e7afa6b5c2, + 0xaf8e2518a1ece54d, 0xc75ac6d5b5a10ff3, 0x77736f524010c932, ], + [0x7f8e41e0b0a6cdff, 0x4b1ba8d40afca97d, 0x623708f28fca70e8, 0xbf150dc4914d380f, + 0xc26a083554767106, 0x753b8b1126665c22, 0xab7f81fef4274770, 0xe7ffd578da4ea43d, + 0xe76649f9bd5d5c2e, 0xad11180f69a8c29e, 0x00a2d4321cca94fe, ], + [0x726af914971c1374, 0x1d7f8a2cce1a9d00, 0x18737784700c75cd, 0x7fb45d605dd82838, + 0x862361aeab0f9b6e, 0xc26a083554767106, 0xad2f570a5b8545aa, 0x2f7e9aade3fdaec1, + 0xbba4b5d86b9a3b2c, 0x7e33ca8c814280de, 0x3a2432625475e3ae, ], + [0x64dd936da878404d, 0x4db9a2ead2bd7262, 0xbe2e19f6d07f1a83, 0x02290fe23c20351a, + 0x7fb45d605dd82838, 0xbf150dc4914d380f, 0x07084430a7307c9a, 0xa4911db6a32612da, + 0x1e8916a99c93a88e, 0xa9032a52f930fae6, 0x90e80c591f48b603, ], + [0x85418a9fef8a9890, 0xd8a2eb7ef5e707ad, 0xbfe85ababed2d882, 0xbe2e19f6d07f1a83, + 0x18737784700c75cd, 0x623708f28fca70e8, 0x5996a80497e24a6b, 0x7f68007619fd8ba9, + 0x4715b8e5ab34653b, 0x0ee5dc0ce131268a, 0xc1978156516879ad, ], + [0x156048ee7a738154, 0x91f7562377e81df5, 0xd8a2eb7ef5e707ad, 0x4db9a2ead2bd7262, + 0x1d7f8a2cce1a9d00, 0x4b1ba8d40afca97d, 0x5e40f0c9bb82aab5, 0xc537d44dc2875403, + 0x14a4a64da0b2668f, 0xb124c33152a2421a, 0xdc927721da922cf8, ], + [0xd841e8ef9dde8ba0, 0x156048ee7a738154, 0x85418a9fef8a9890, 0x64dd936da878404d, + 0x726af914971c1374, 0x7f8e41e0b0a6cdff, 0xf97abba0dffb6c50, 0xf4a437f2888ae909, + 0xdcedab70f40718ba, 0xe796d293a47a64cb, 0x80772dc2645b280b, ], + ]; + + #[cfg(not(all(target_arch = "aarch64", target_feature = "neon")))] + #[inline(always)] + #[unroll::unroll_for_loops] + fn mds_layer(state: &[Self; 12]) -> [Self; 12] { + let mut result = [GoldilocksField::ZERO; 12]; + + // Using the linearity of the operations we can split the state into a low||high decomposition + // and operate on each with no overflow and then combine/reduce the result to a field element. + let mut state_l = [0u64; 12]; + let mut state_h = [0u64; 12]; + + for r in 0..12 { + let s = state[r].0; + state_h[r] = s >> 32; + state_l[r] = (s as u32) as u64; + } + + let state_h = poseidon12_mds::mds_multiply_freq(state_h); + let state_l = poseidon12_mds::mds_multiply_freq(state_l); + + for r in 0..12 { + let s = state_l[r] as u128 + ((state_h[r] as u128) << 32); + + result[r] = GoldilocksField::from_noncanonical_u96((s as u64, (s >> 64) as u32)); + } + + // Add first element with the only non-zero diagonal matrix coefficient. + let s = Self::MDS_MATRIX_DIAG[0] as u128 * (state[0].0 as u128); + result[0] += GoldilocksField::from_noncanonical_u96((s as u64, (s >> 64) as u32)); + + result + } + + // #[cfg(all(target_arch="x86_64", target_feature="avx2", target_feature="bmi2"))] + // #[inline] + // fn poseidon(input: [Self; 12]) -> [Self; 12] { + // unsafe { + // crate::hash::arch::x86_64::poseidon_goldilocks_avx2_bmi2::poseidon(&input) + // } + // } + + // #[cfg(all(target_arch="x86_64", target_feature="avx2", target_feature="bmi2"))] + // #[inline(always)] + // fn constant_layer(state: &mut [Self; 12], round_ctr: usize) { + // unsafe { + // crate::hash::arch::x86_64::poseidon_goldilocks_avx2_bmi2::constant_layer(state, round_ctr); + // } + // } + + // #[cfg(all(target_arch="x86_64", target_feature="avx2", target_feature="bmi2"))] + // #[inline(always)] + // fn sbox_layer(state: &mut [Self; 12]) { + // unsafe { + // crate::hash::arch::x86_64::poseidon_goldilocks_avx2_bmi2::sbox_layer(state); + // } + // } + + // #[cfg(all(target_arch="x86_64", target_feature="avx2", target_feature="bmi2"))] + // #[inline(always)] + // fn mds_layer(state: &[Self; 12]) -> [Self; 12] { + // unsafe { + // crate::hash::arch::x86_64::poseidon_goldilocks_avx2_bmi2::mds_layer(state) + // } + // } + + // #[cfg(all(target_arch="aarch64", target_feature="neon"))] + // #[inline] + // fn poseidon(input: [Self; 12]) -> [Self; 12] { + // unsafe { + // crate::hash::arch::aarch64::poseidon_goldilocks_neon::poseidon(input) + // } + // } + + #[cfg(all(target_arch="aarch64", target_feature="neon"))] + #[inline(always)] + fn sbox_layer(state: &mut [Self; 12]) { + unsafe { + crate::hash::arch::aarch64::poseidon_goldilocks_neon::sbox_layer(state); + } + } + + #[cfg(all(target_arch="aarch64", target_feature="neon"))] + #[inline(always)] + fn mds_layer(state: &[Self; 12]) -> [Self; 12] { + unsafe { + crate::hash::arch::aarch64::poseidon_goldilocks_neon::mds_layer(state) + } + } +} + +// MDS layer helper methods +// The following code has been adapted from +// winterfell/crypto/src/hash/mds/mds_f64_12x12.rs located at https://github.com/facebook/winterfell. +#[cfg(not(all(target_arch = "aarch64", target_feature = "neon")))] +mod poseidon12_mds { + const MDS_FREQ_BLOCK_ONE: [i64; 3] = [16, 32, 16]; + const MDS_FREQ_BLOCK_TWO: [(i64, i64); 3] = [(2, -1), (-4, 1), (16, 1)]; + const MDS_FREQ_BLOCK_THREE: [i64; 3] = [-1, -8, 2]; + + /// Split 3 x 4 FFT-based MDS vector-multiplication with the Poseidon + /// circulant MDS matrix. + #[inline(always)] + pub(crate) const fn mds_multiply_freq(state: [u64; 12]) -> [u64; 12] { + let [s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11] = state; + + let (u0, u1, u2) = fft4_real([s0, s3, s6, s9]); + let (u4, u5, u6) = fft4_real([s1, s4, s7, s10]); + let (u8, u9, u10) = fft4_real([s2, s5, s8, s11]); + + // This where the multiplication in frequency domain is done. More precisely, + // and with the appropriate permutations in between, the sequence of + // 3-point FFTs --> multiplication by twiddle factors --> Hadamard + // multiplication --> 3 point iFFTs --> multiplication by (inverse) + // twiddle factors is "squashed" into one step composed of the functions + // "block1", "block2" and "block3". The expressions in the + // aforementioned functions are the result of explicit computations + // combined with the Karatsuba trick for the multiplication of complex numbers. + + let [v0, v4, v8] = block1([u0, u4, u8], MDS_FREQ_BLOCK_ONE); + let [v1, v5, v9] = block2([u1, u5, u9], MDS_FREQ_BLOCK_TWO); + let [v2, v6, v10] = block3([u2, u6, u10], MDS_FREQ_BLOCK_THREE); + // The 4th block is not computed as it is similar to the 2nd one, up to complex + // conjugation. + + let [s0, s3, s6, s9] = ifft4_real_unreduced((v0, v1, v2)); + let [s1, s4, s7, s10] = ifft4_real_unreduced((v4, v5, v6)); + let [s2, s5, s8, s11] = ifft4_real_unreduced((v8, v9, v10)); + + [s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11] + } + + #[inline(always)] + const fn block1(x: [i64; 3], y: [i64; 3]) -> [i64; 3] { + let [x0, x1, x2] = x; + let [y0, y1, y2] = y; + let z0 = x0 * y0 + x1 * y2 + x2 * y1; + let z1 = x0 * y1 + x1 * y0 + x2 * y2; + let z2 = x0 * y2 + x1 * y1 + x2 * y0; + + [z0, z1, z2] + } + + #[inline(always)] + const fn block2(x: [(i64, i64); 3], y: [(i64, i64); 3]) -> [(i64, i64); 3] { + let [(x0r, x0i), (x1r, x1i), (x2r, x2i)] = x; + let [(y0r, y0i), (y1r, y1i), (y2r, y2i)] = y; + let x0s = x0r + x0i; + let x1s = x1r + x1i; + let x2s = x2r + x2i; + let y0s = y0r + y0i; + let y1s = y1r + y1i; + let y2s = y2r + y2i; + + // Compute x0​y0 ​− ix1​y2​ − ix2​y1​ using Karatsuba for complex numbers + // multiplication + let m0 = (x0r * y0r, x0i * y0i); + let m1 = (x1r * y2r, x1i * y2i); + let m2 = (x2r * y1r, x2i * y1i); + let z0r = (m0.0 - m0.1) + (x1s * y2s - m1.0 - m1.1) + (x2s * y1s - m2.0 - m2.1); + let z0i = (x0s * y0s - m0.0 - m0.1) + (-m1.0 + m1.1) + (-m2.0 + m2.1); + let z0 = (z0r, z0i); + + // Compute x0​y1​ + x1​y0​ − ix2​y2 using Karatsuba for complex numbers + // multiplication + let m0 = (x0r * y1r, x0i * y1i); + let m1 = (x1r * y0r, x1i * y0i); + let m2 = (x2r * y2r, x2i * y2i); + let z1r = (m0.0 - m0.1) + (m1.0 - m1.1) + (x2s * y2s - m2.0 - m2.1); + let z1i = (x0s * y1s - m0.0 - m0.1) + (x1s * y0s - m1.0 - m1.1) + (-m2.0 + m2.1); + let z1 = (z1r, z1i); + + // Compute x0​y2​ + x1​y1 ​+ x2​y0​ using Karatsuba for complex numbers multiplication + let m0 = (x0r * y2r, x0i * y2i); + let m1 = (x1r * y1r, x1i * y1i); + let m2 = (x2r * y0r, x2i * y0i); + let z2r = (m0.0 - m0.1) + (m1.0 - m1.1) + (m2.0 - m2.1); + let z2i = (x0s * y2s - m0.0 - m0.1) + (x1s * y1s - m1.0 - m1.1) + (x2s * y0s - m2.0 - m2.1); + let z2 = (z2r, z2i); + + [z0, z1, z2] + } + + #[inline(always)] + const fn block3(x: [i64; 3], y: [i64; 3]) -> [i64; 3] { + let [x0, x1, x2] = x; + let [y0, y1, y2] = y; + let z0 = x0 * y0 - x1 * y2 - x2 * y1; + let z1 = x0 * y1 + x1 * y0 - x2 * y2; + let z2 = x0 * y2 + x1 * y1 + x2 * y0; + + [z0, z1, z2] + } + + /// Real 2-FFT over u64 integers. + #[inline(always)] + pub(crate) const fn fft2_real(x: [u64; 2]) -> [i64; 2] { + [(x[0] as i64 + x[1] as i64), (x[0] as i64 - x[1] as i64)] + } + + /// Real 2-iFFT over u64 integers. + /// Division by two to complete the inverse FFT is not performed here. + #[inline(always)] + pub(crate) const fn ifft2_real_unreduced(y: [i64; 2]) -> [u64; 2] { + [(y[0] + y[1]) as u64, (y[0] - y[1]) as u64] + } + + /// Real 4-FFT over u64 integers. + #[inline(always)] + pub(crate) const fn fft4_real(x: [u64; 4]) -> (i64, (i64, i64), i64) { + let [z0, z2] = fft2_real([x[0], x[2]]); + let [z1, z3] = fft2_real([x[1], x[3]]); + let y0 = z0 + z1; + let y1 = (z2, -z3); + let y2 = z0 - z1; + (y0, y1, y2) + } + + /// Real 4-iFFT over u64 integers. + /// Division by four to complete the inverse FFT is not performed here. + #[inline(always)] + pub(crate) const fn ifft4_real_unreduced(y: (i64, (i64, i64), i64)) -> [u64; 4] { + let z0 = y.0 + y.2; + let z1 = y.0 - y.2; + let z2 = y.1 .0; + let z3 = -y.1 .1; + + let [x0, x2] = ifft2_real_unreduced([z0, z2]); + let [x1, x3] = ifft2_real_unreduced([z1, z3]); + + [x0, x1, x2, x3] + } +} + +#[cfg(test)] +mod tests { + #[cfg(not(feature = "std"))] + use alloc::{vec, vec::Vec}; + + use crate::field::goldilocks_field::GoldilocksField as F; + use crate::field::types::{Field, PrimeField64}; + use crate::hash::poseidon::test_helpers::{check_consistency, check_test_vectors}; + + #[test] + fn test_vectors() { + // Test inputs are: + // 1. all zeros + // 2. range 0..WIDTH + // 3. all -1's + // 4. random elements of GoldilocksField. + // expected output calculated with (modified) hadeshash reference + // implementation. + + let neg_one: u64 = F::NEG_ONE.to_canonical_u64(); + + #[rustfmt::skip] + let test_vectors12: Vec<([u64; 12], [u64; 12])> = vec![ + ([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ], + [0x3c18a9786cb0b359, 0xc4055e3364a246c3, 0x7953db0ab48808f4, 0xc71603f33a1144ca, + 0xd7709673896996dc, 0x46a84e87642f44ed, 0xd032648251ee0b3c, 0x1c687363b207df62, + 0xdf8565563e8045fe, 0x40f5b37ff4254dae, 0xd070f637b431067c, 0x1792b1c4342109d7, ]), + ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ], + [0xd64e1e3efc5b8e9e, 0x53666633020aaa47, 0xd40285597c6a8825, 0x613a4f81e81231d2, + 0x414754bfebd051f0, 0xcb1f8980294a023f, 0x6eb2a9e4d54a9d0f, 0x1902bc3af467e056, + 0xf045d5eafdc6021f, 0xe4150f77caaa3be5, 0xc9bfd01d39b50cce, 0x5c0a27fcb0e1459b, ]), + ([neg_one, neg_one, neg_one, neg_one, + neg_one, neg_one, neg_one, neg_one, + neg_one, neg_one, neg_one, neg_one, ], + [0xbe0085cfc57a8357, 0xd95af71847d05c09, 0xcf55a13d33c1c953, 0x95803a74f4530e82, + 0xfcd99eb30a135df1, 0xe095905e913a3029, 0xde0392461b42919b, 0x7d3260e24e81d031, + 0x10d3d0465d9deaa0, 0xa87571083dfc2a47, 0xe18263681e9958f8, 0xe28e96f1ae5e60d3, ]), + ([0x8ccbbbea4fe5d2b7, 0xc2af59ee9ec49970, 0x90f7e1a9e658446a, 0xdcc0630a3ab8b1b8, + 0x7ff8256bca20588c, 0x5d99a7ca0c44ecfb, 0x48452b17a70fbee3, 0xeb09d654690b6c88, + 0x4a55d3a39c676a88, 0xc0407a38d2285139, 0xa234bac9356386d1, 0xe1633f2bad98a52f, ], + [0xa89280105650c4ec, 0xab542d53860d12ed, 0x5704148e9ccab94f, 0xd3a826d4b62da9f5, + 0x8a7a6ca87892574f, 0xc7017e1cad1a674e, 0x1f06668922318e34, 0xa3b203bc8102676f, + 0xfcc781b0ce382bf2, 0x934c69ff3ed14ba5, 0x504688a5996e8f13, 0x401f3f2ed524a2ba, ]), + ]; + + check_test_vectors::(test_vectors12); + } + + #[test] + fn consistency() { + check_consistency::(); + } +} diff --git a/plonky2/src/iop/challenger.rs b/plonky2/src/iop/challenger.rs new file mode 100644 index 000000000..78af6ccd5 --- /dev/null +++ b/plonky2/src/iop/challenger.rs @@ -0,0 +1,381 @@ +#[cfg(not(feature = "std"))] +use alloc::{vec, vec::Vec}; +use core::marker::PhantomData; + +use crate::field::extension::{Extendable, FieldExtension}; +use crate::hash::hash_types::{HashOut, HashOutTarget, MerkleCapTarget, RichField}; +use crate::hash::hashing::PlonkyPermutation; +use crate::hash::merkle_tree::MerkleCap; +use crate::iop::ext_target::ExtensionTarget; +use crate::iop::target::Target; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::config::{AlgebraicHasher, GenericHashOut, Hasher}; + +/// Observes prover messages, and generates challenges by hashing the +/// transcript, a la Fiat-Shamir. +#[derive(Clone)] +pub struct Challenger> { + pub(crate) sponge_state: H::Permutation, + pub(crate) input_buffer: Vec, + output_buffer: Vec, +} + +/// Observes prover messages, and generates verifier challenges based on the +/// transcript. +/// +/// The implementation is roughly based on a duplex sponge with a Rescue +/// permutation. Note that in each round, our sponge can absorb an arbitrary +/// number of prover messages and generate an arbitrary number of verifier +/// challenges. This might appear to diverge from the duplex sponge design, but +/// it can be viewed as a duplex sponge whose inputs are sometimes zero (when we +/// perform multiple squeezes) and whose outputs are sometimes ignored (when we +/// perform multiple absorptions). Thus the security properties of a duplex +/// sponge still apply to our design. +impl> Challenger { + pub fn new() -> Challenger { + Challenger { + sponge_state: H::Permutation::new(core::iter::repeat(F::ZERO)), + input_buffer: Vec::with_capacity(H::Permutation::RATE), + output_buffer: Vec::with_capacity(H::Permutation::RATE), + } + } + + pub fn observe_element(&mut self, element: F) { + // Any buffered outputs are now invalid, since they wouldn't reflect this input. + self.output_buffer.clear(); + + self.input_buffer.push(element); + + if self.input_buffer.len() == H::Permutation::RATE { + self.duplexing(); + } + } + + pub fn observe_extension_element(&mut self, element: &F::Extension) + where + F: RichField + Extendable, + { + self.observe_elements(&element.to_basefield_array()); + } + + pub fn observe_elements(&mut self, elements: &[F]) { + for &element in elements { + self.observe_element(element); + } + } + + pub fn observe_extension_elements(&mut self, elements: &[F::Extension]) + where + F: RichField + Extendable, + { + for element in elements { + self.observe_extension_element(element); + } + } + + pub fn observe_hash>(&mut self, hash: OH::Hash) { + self.observe_elements(&hash.to_vec()) + } + + pub fn observe_cap>(&mut self, cap: &MerkleCap) { + for &hash in &cap.0 { + self.observe_hash::(hash); + } + } + + pub fn get_challenge(&mut self) -> F { + // If we have buffered inputs, we must perform a duplexing so that the challenge + // will reflect them. Or if we've run out of outputs, we must perform a + // duplexing to get more. + if !self.input_buffer.is_empty() || self.output_buffer.is_empty() { + self.duplexing(); + } + + self.output_buffer + .pop() + .expect("Output buffer should be non-empty") + } + + pub fn get_n_challenges(&mut self, n: usize) -> Vec { + (0..n).map(|_| self.get_challenge()).collect() + } + + pub fn get_hash(&mut self) -> HashOut { + HashOut { + elements: [ + self.get_challenge(), + self.get_challenge(), + self.get_challenge(), + self.get_challenge(), + ], + } + } + + pub fn get_extension_challenge(&mut self) -> F::Extension + where + F: RichField + Extendable, + { + let mut arr = [F::ZERO; D]; + arr.copy_from_slice(&self.get_n_challenges(D)); + F::Extension::from_basefield_array(arr) + } + + pub fn get_n_extension_challenges(&mut self, n: usize) -> Vec + where + F: RichField + Extendable, + { + (0..n) + .map(|_| self.get_extension_challenge::()) + .collect() + } + + /// Absorb any buffered inputs. After calling this, the input buffer will be + /// empty, and the output buffer will be full. + fn duplexing(&mut self) { + assert!(self.input_buffer.len() <= H::Permutation::RATE); + + // Overwrite the first r elements with the inputs. This differs from a standard + // sponge, where we would xor or add in the inputs. This is a well-known + // variant, though, sometimes called "overwrite mode". + self.sponge_state + .set_from_iter(self.input_buffer.drain(..), 0); + + // Apply the permutation. + self.sponge_state.permute(); + + self.output_buffer.clear(); + self.output_buffer + .extend_from_slice(self.sponge_state.squeeze()); + } + + pub fn compact(&mut self) -> H::Permutation { + if !self.input_buffer.is_empty() { + self.duplexing(); + } + self.output_buffer.clear(); + self.sponge_state + } +} + +impl> Default for Challenger { + fn default() -> Self { + Self::new() + } +} + +/// A recursive version of `Challenger`. The main difference is that +/// `RecursiveChallenger`'s input buffer can grow beyond `H::Permutation::RATE`. +/// This is so that `observe_element` etc do not need access +/// to the `CircuitBuilder`. +pub struct RecursiveChallenger, H: AlgebraicHasher, const D: usize> +{ + sponge_state: H::AlgebraicPermutation, + input_buffer: Vec, + output_buffer: Vec, + __: PhantomData<(F, H)>, +} + +impl, H: AlgebraicHasher, const D: usize> + RecursiveChallenger +{ + pub fn new(builder: &mut CircuitBuilder) -> Self { + let zero = builder.zero(); + Self { + sponge_state: H::AlgebraicPermutation::new(core::iter::repeat(zero)), + input_buffer: Vec::new(), + output_buffer: Vec::new(), + __: PhantomData, + } + } + + pub fn from_state(sponge_state: H::AlgebraicPermutation) -> Self { + Self { + sponge_state, + input_buffer: vec![], + output_buffer: vec![], + __: PhantomData, + } + } + + pub fn observe_element(&mut self, target: Target) { + // Any buffered outputs are now invalid, since they wouldn't reflect this input. + self.output_buffer.clear(); + + self.input_buffer.push(target); + } + + pub fn observe_elements(&mut self, targets: &[Target]) { + for &target in targets { + self.observe_element(target); + } + } + + pub fn observe_hash(&mut self, hash: &HashOutTarget) { + self.observe_elements(&hash.elements) + } + + pub fn observe_cap(&mut self, cap: &MerkleCapTarget) { + for hash in &cap.0 { + self.observe_hash(hash) + } + } + + pub fn observe_extension_element(&mut self, element: ExtensionTarget) { + self.observe_elements(&element.0); + } + + pub fn observe_extension_elements(&mut self, elements: &[ExtensionTarget]) { + for &element in elements { + self.observe_extension_element(element); + } + } + + pub fn get_challenge(&mut self, builder: &mut CircuitBuilder) -> Target { + self.absorb_buffered_inputs(builder); + + if self.output_buffer.is_empty() { + // Evaluate the permutation to produce `r` new outputs. + self.sponge_state = builder.permute::(self.sponge_state); + self.output_buffer = self.sponge_state.squeeze().to_vec(); + } + + self.output_buffer + .pop() + .expect("Output buffer should be non-empty") + } + + pub fn get_n_challenges( + &mut self, + builder: &mut CircuitBuilder, + n: usize, + ) -> Vec { + (0..n).map(|_| self.get_challenge(builder)).collect() + } + + pub fn get_hash(&mut self, builder: &mut CircuitBuilder) -> HashOutTarget { + HashOutTarget { + elements: [ + self.get_challenge(builder), + self.get_challenge(builder), + self.get_challenge(builder), + self.get_challenge(builder), + ], + } + } + + pub fn get_extension_challenge( + &mut self, + builder: &mut CircuitBuilder, + ) -> ExtensionTarget { + self.get_n_challenges(builder, D).try_into().unwrap() + } + + /// Absorb any buffered inputs. After calling this, the input buffer will be + /// empty, and the output buffer will be full. + fn absorb_buffered_inputs(&mut self, builder: &mut CircuitBuilder) { + if self.input_buffer.is_empty() { + return; + } + + for input_chunk in self.input_buffer.chunks(H::AlgebraicPermutation::RATE) { + // Overwrite the first r elements with the inputs. This differs from a standard + // sponge, where we would xor or add in the inputs. This is a + // well-known variant, though, sometimes called "overwrite mode". + self.sponge_state.set_from_slice(input_chunk, 0); + self.sponge_state = builder.permute::(self.sponge_state); + } + + self.output_buffer = self.sponge_state.squeeze().to_vec(); + + self.input_buffer.clear(); + } + + pub fn compact(&mut self, builder: &mut CircuitBuilder) -> H::AlgebraicPermutation { + self.absorb_buffered_inputs(builder); + self.output_buffer.clear(); + self.sponge_state + } +} + +#[cfg(test)] +mod tests { + #[cfg(not(feature = "std"))] + use alloc::vec::Vec; + + use crate::field::types::Sample; + use crate::iop::challenger::{Challenger, RecursiveChallenger}; + use crate::iop::generator::generate_partial_witness; + use crate::iop::target::Target; + use crate::iop::witness::{PartialWitness, Witness}; + use crate::plonk::circuit_builder::CircuitBuilder; + use crate::plonk::circuit_data::CircuitConfig; + use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + + #[test] + fn no_duplicate_challenges() { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + let mut challenger = Challenger::>::InnerHasher>::new(); + let mut challenges = Vec::new(); + + for i in 1..10 { + challenges.extend(challenger.get_n_challenges(i)); + challenger.observe_element(F::rand()); + } + + let dedup_challenges = { + let mut dedup = challenges.clone(); + dedup.dedup(); + dedup + }; + assert_eq!(dedup_challenges, challenges); + } + + /// Tests for consistency between `Challenger` and `RecursiveChallenger`. + #[test] + fn test_consistency() { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + + // These are mostly arbitrary, but we want to test some rounds with enough + // inputs/outputs to trigger multiple absorptions/squeezes. + let num_inputs_per_round = [2, 5, 3]; + let num_outputs_per_round = [1, 2, 4]; + + // Generate random input messages. + let inputs_per_round: Vec> = num_inputs_per_round + .iter() + .map(|&n| F::rand_vec(n)) + .collect(); + + let mut challenger = Challenger::>::InnerHasher>::new(); + let mut outputs_per_round: Vec> = Vec::new(); + for (r, inputs) in inputs_per_round.iter().enumerate() { + challenger.observe_elements(inputs); + outputs_per_round.push(challenger.get_n_challenges(num_outputs_per_round[r])); + } + + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + let mut recursive_challenger = + RecursiveChallenger::>::InnerHasher, D>::new(&mut builder); + let mut recursive_outputs_per_round: Vec> = Vec::new(); + for (r, inputs) in inputs_per_round.iter().enumerate() { + recursive_challenger.observe_elements(&builder.constants(inputs)); + recursive_outputs_per_round.push( + recursive_challenger.get_n_challenges(&mut builder, num_outputs_per_round[r]), + ); + } + let circuit = builder.build::(); + let inputs = PartialWitness::new(); + let witness = generate_partial_witness(inputs, &circuit.prover_only, &circuit.common); + let recursive_output_values_per_round: Vec> = recursive_outputs_per_round + .iter() + .map(|outputs| witness.get_targets(outputs)) + .collect(); + + assert_eq!(outputs_per_round, recursive_output_values_per_round); + } +} diff --git a/plonky2/src/iop/ext_target.rs b/plonky2/src/iop/ext_target.rs new file mode 100644 index 000000000..5709377df --- /dev/null +++ b/plonky2/src/iop/ext_target.rs @@ -0,0 +1,159 @@ +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; +use core::ops::Range; + +use crate::field::extension::algebra::ExtensionAlgebra; +use crate::field::extension::{Extendable, FieldExtension, OEF}; +use crate::field::types::Field; +use crate::hash::hash_types::RichField; +use crate::iop::target::Target; +use crate::plonk::circuit_builder::CircuitBuilder; + +/// `Target`s representing an element of an extension field. +/// +/// This is typically used in recursion settings, where the outer circuit must +/// verify a proof satisfying an inner circuit's statement, which is verified +/// using arithmetic in an extension of the base field. +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub struct ExtensionTarget(pub [Target; D]); + +impl Default for ExtensionTarget { + fn default() -> Self { + Self([Target::default(); D]) + } +} + +impl ExtensionTarget { + pub const fn to_target_array(&self) -> [Target; D] { + self.0 + } + + pub fn frobenius>( + &self, + builder: &mut CircuitBuilder, + ) -> Self { + self.repeated_frobenius(1, builder) + } + + pub fn repeated_frobenius>( + &self, + count: usize, + builder: &mut CircuitBuilder, + ) -> Self { + if count == 0 { + return *self; + } else if count >= D { + return self.repeated_frobenius(count % D, builder); + } + let arr = self.to_target_array(); + let k = (F::order() - 1u32) / (D as u64); + let z0 = F::Extension::W.exp_biguint(&(k * count as u64)); + #[allow(clippy::needless_collect)] + let zs = z0 + .powers() + .take(D) + .map(|z| builder.constant(z)) + .collect::>(); + + let mut res = Vec::with_capacity(D); + for (z, a) in zs.into_iter().zip(arr) { + res.push(builder.mul(z, a)); + } + + res.try_into().unwrap() + } + + pub fn from_range(row: usize, range: Range) -> Self { + debug_assert_eq!(range.end - range.start, D); + Target::wires_from_range(row, range).try_into().unwrap() + } +} + +impl TryFrom> for ExtensionTarget { + type Error = Vec; + + fn try_from(value: Vec) -> Result { + Ok(Self(value.try_into()?)) + } +} + +/// `Target`s representing an element of an extension of an extension field. +#[derive(Copy, Clone, Debug)] +pub struct ExtensionAlgebraTarget(pub [ExtensionTarget; D]); + +impl ExtensionAlgebraTarget { + pub const fn to_ext_target_array(&self) -> [ExtensionTarget; D] { + self.0 + } +} + +impl, const D: usize> CircuitBuilder { + pub fn constant_extension(&mut self, c: F::Extension) -> ExtensionTarget { + let c_parts = c.to_basefield_array(); + let mut parts = [self.zero(); D]; + for i in 0..D { + parts[i] = self.constant(c_parts[i]); + } + ExtensionTarget(parts) + } + + pub fn constant_ext_algebra( + &mut self, + c: ExtensionAlgebra, + ) -> ExtensionAlgebraTarget { + let c_parts = c.to_basefield_array(); + let mut parts = [self.zero_extension(); D]; + for i in 0..D { + parts[i] = self.constant_extension(c_parts[i]); + } + ExtensionAlgebraTarget(parts) + } + + pub fn zero_extension(&mut self) -> ExtensionTarget { + self.constant_extension(F::Extension::ZERO) + } + + pub fn one_extension(&mut self) -> ExtensionTarget { + self.constant_extension(F::Extension::ONE) + } + + pub fn two_extension(&mut self) -> ExtensionTarget { + self.constant_extension(F::Extension::TWO) + } + + pub fn neg_one_extension(&mut self) -> ExtensionTarget { + self.constant_extension(F::Extension::NEG_ONE) + } + + pub fn zero_ext_algebra(&mut self) -> ExtensionAlgebraTarget { + self.constant_ext_algebra(ExtensionAlgebra::ZERO) + } + + pub fn convert_to_ext(&mut self, t: Target) -> ExtensionTarget { + let zero = self.zero(); + t.to_ext_target(zero) + } + + pub fn convert_to_ext_algebra(&mut self, et: ExtensionTarget) -> ExtensionAlgebraTarget { + let zero = self.zero_extension(); + let mut arr = [zero; D]; + arr[0] = et; + ExtensionAlgebraTarget(arr) + } +} + +/// Flatten the slice by sending every extension target to its D-sized canonical +/// representation. +pub fn flatten_target(l: &[ExtensionTarget]) -> Vec { + l.iter() + .flat_map(|x| x.to_target_array().to_vec()) + .collect() +} + +/// Batch every D-sized chunks into extension targets. +pub fn unflatten_target(l: &[Target]) -> Vec> { + debug_assert_eq!(l.len() % D, 0); + l.chunks_exact(D) + .map(|c| c.to_vec().try_into().unwrap()) + .collect() +} diff --git a/plonky2/src/iop/generator.rs b/plonky2/src/iop/generator.rs new file mode 100644 index 000000000..a406210ad --- /dev/null +++ b/plonky2/src/iop/generator.rs @@ -0,0 +1,424 @@ +#[cfg(not(feature = "std"))] +use alloc::{ + boxed::Box, + string::{String, ToString}, + vec, + vec::Vec, +}; +use core::fmt::Debug; +use core::marker::PhantomData; + +use crate::field::extension::Extendable; +use crate::field::types::Field; +use crate::hash::hash_types::RichField; +use crate::iop::ext_target::ExtensionTarget; +use crate::iop::target::Target; +use crate::iop::wire::Wire; +use crate::iop::witness::{PartialWitness, PartitionWitness, Witness, WitnessWrite}; +use crate::plonk::circuit_data::{CommonCircuitData, ProverOnlyCircuitData}; +use crate::plonk::config::GenericConfig; +use crate::util::serialization::{Buffer, IoResult, Read, Write}; + +/// Given a `PartitionWitness` that has only inputs set, populates the rest of +/// the witness using the given set of generators. +pub fn generate_partial_witness< + 'a, + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +>( + inputs: PartialWitness, + prover_data: &'a ProverOnlyCircuitData, + common_data: &'a CommonCircuitData, +) -> PartitionWitness<'a, F> { + let config = &common_data.config; + let generators = &prover_data.generators; + let generator_indices_by_watches = &prover_data.generator_indices_by_watches; + + let mut witness = PartitionWitness::new( + config.num_wires, + common_data.degree(), + &prover_data.representative_map, + ); + + for (t, v) in inputs.target_values.into_iter() { + witness.set_target(t, v); + } + + // Build a list of "pending" generators which are queued to be run. Initially, + // all generators are queued. + let mut pending_generator_indices: Vec<_> = (0..generators.len()).collect(); + + // We also track a list of "expired" generators which have already returned + // false. + let mut generator_is_expired = vec![false; generators.len()]; + let mut remaining_generators = generators.len(); + + let mut buffer = GeneratedValues::empty(); + + // Keep running generators until we fail to make progress. + while !pending_generator_indices.is_empty() { + let mut next_pending_generator_indices = Vec::new(); + + for &generator_idx in &pending_generator_indices { + if generator_is_expired[generator_idx] { + continue; + } + + let finished = generators[generator_idx].0.run(&witness, &mut buffer); + if finished { + generator_is_expired[generator_idx] = true; + remaining_generators -= 1; + } + + // Merge any generated values into our witness, and get a list of + // newly-populated targets' representatives. + let new_target_reps = buffer + .target_values + .drain(..) + .flat_map(|(t, v)| witness.set_target_returning_rep(t, v)); + + // Enqueue unfinished generators that were watching one of the newly populated + // targets. + for watch in new_target_reps { + let opt_watchers = generator_indices_by_watches.get(&watch); + if let Some(watchers) = opt_watchers { + for &watching_generator_idx in watchers { + if !generator_is_expired[watching_generator_idx] { + next_pending_generator_indices.push(watching_generator_idx); + } + } + } + } + } + + pending_generator_indices = next_pending_generator_indices; + } + + assert_eq!( + remaining_generators, 0, + "{} generators weren't run", + remaining_generators, + ); + + witness +} + +/// A generator participates in the generation of the witness. +pub trait WitnessGenerator, const D: usize>: + 'static + Send + Sync + Debug +{ + fn id(&self) -> String; + + /// Targets to be "watched" by this generator. Whenever a target in the + /// watch list is populated, the generator will be queued to run. + fn watch_list(&self) -> Vec; + + /// Run this generator, returning a flag indicating whether the generator is + /// finished. If the flag is true, the generator will never be run + /// again, otherwise it will be queued for another run next time a + /// target in its watch list is populated. + fn run(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) -> bool; + + fn serialize(&self, dst: &mut Vec, common_data: &CommonCircuitData) -> IoResult<()>; + + fn deserialize(src: &mut Buffer, common_data: &CommonCircuitData) -> IoResult + where + Self: Sized; +} + +/// A wrapper around an `Box` which implements `PartialEq` +/// and `Eq` based on generator IDs. +pub struct WitnessGeneratorRef, const D: usize>( + pub Box>, +); + +impl, const D: usize> WitnessGeneratorRef { + pub fn new>(generator: G) -> WitnessGeneratorRef { + WitnessGeneratorRef(Box::new(generator)) + } +} + +impl, const D: usize> PartialEq for WitnessGeneratorRef { + fn eq(&self, other: &Self) -> bool { + self.0.id() == other.0.id() + } +} + +impl, const D: usize> Eq for WitnessGeneratorRef {} + +impl, const D: usize> Debug for WitnessGeneratorRef { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.0.id()) + } +} + +/// Values generated by a generator invocation. +#[derive(Debug)] +pub struct GeneratedValues { + pub target_values: Vec<(Target, F)>, +} + +impl From> for GeneratedValues { + fn from(target_values: Vec<(Target, F)>) -> Self { + Self { target_values } + } +} + +impl WitnessWrite for GeneratedValues { + fn set_target(&mut self, target: Target, value: F) { + self.target_values.push((target, value)); + } +} + +impl GeneratedValues { + pub fn with_capacity(capacity: usize) -> Self { + Vec::with_capacity(capacity).into() + } + + pub fn empty() -> Self { + Vec::new().into() + } + + pub fn singleton_wire(wire: Wire, value: F) -> Self { + Self::singleton_target(Target::Wire(wire), value) + } + + pub fn singleton_target(target: Target, value: F) -> Self { + vec![(target, value)].into() + } + + pub fn singleton_extension_target( + et: ExtensionTarget, + value: F::Extension, + ) -> Self + where + F: RichField + Extendable, + { + let mut witness = Self::with_capacity(D); + witness.set_extension_target(et, value); + witness + } +} + +/// A generator which runs once after a list of dependencies is present in the +/// witness. +pub trait SimpleGenerator, const D: usize>: + 'static + Send + Sync + Debug +{ + fn id(&self) -> String; + + fn dependencies(&self) -> Vec; + + fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues); + + fn adapter(self) -> SimpleGeneratorAdapter + where + Self: Sized, + { + SimpleGeneratorAdapter { + inner: self, + _phantom: PhantomData, + } + } + + fn serialize(&self, dst: &mut Vec, common_data: &CommonCircuitData) -> IoResult<()>; + + fn deserialize(src: &mut Buffer, common_data: &CommonCircuitData) -> IoResult + where + Self: Sized; +} + +#[derive(Debug)] +pub struct SimpleGeneratorAdapter< + F: RichField + Extendable, + SG: SimpleGenerator + ?Sized, + const D: usize, +> { + _phantom: PhantomData, + inner: SG, +} + +impl, SG: SimpleGenerator, const D: usize> WitnessGenerator + for SimpleGeneratorAdapter +{ + fn id(&self) -> String { + self.inner.id() + } + + fn watch_list(&self) -> Vec { + self.inner.dependencies() + } + + fn run(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) -> bool { + if witness.contains_all(&self.inner.dependencies()) { + self.inner.run_once(witness, out_buffer); + true + } else { + false + } + } + + fn serialize(&self, dst: &mut Vec, common_data: &CommonCircuitData) -> IoResult<()> { + self.inner.serialize(dst, common_data) + } + + fn deserialize(src: &mut Buffer, common_data: &CommonCircuitData) -> IoResult { + Ok(Self { + inner: SG::deserialize(src, common_data)?, + _phantom: PhantomData, + }) + } +} + +/// A generator which copies one wire to another. +#[derive(Debug, Default)] +pub struct CopyGenerator { + pub(crate) src: Target, + pub(crate) dst: Target, +} + +impl, const D: usize> SimpleGenerator for CopyGenerator { + fn id(&self) -> String { + "CopyGenerator".to_string() + } + + fn dependencies(&self) -> Vec { + vec![self.src] + } + + fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + let value = witness.get_target(self.src); + out_buffer.set_target(self.dst, value); + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_target(self.src)?; + dst.write_target(self.dst) + } + + fn deserialize(source: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let src = source.read_target()?; + let dst = source.read_target()?; + Ok(Self { src, dst }) + } +} + +/// A generator for including a random value +#[derive(Debug, Default)] +pub struct RandomValueGenerator { + pub(crate) target: Target, +} + +impl, const D: usize> SimpleGenerator for RandomValueGenerator { + fn id(&self) -> String { + "RandomValueGenerator".to_string() + } + + fn dependencies(&self) -> Vec { + Vec::new() + } + + fn run_once(&self, _witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + let random_value = F::rand(); + out_buffer.set_target(self.target, random_value); + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_target(self.target) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let target = src.read_target()?; + Ok(Self { target }) + } +} + +/// A generator for testing if a value equals zero +#[derive(Debug, Default)] +pub struct NonzeroTestGenerator { + pub(crate) to_test: Target, + pub(crate) dummy: Target, +} + +impl, const D: usize> SimpleGenerator for NonzeroTestGenerator { + fn id(&self) -> String { + "NonzeroTestGenerator".to_string() + } + + fn dependencies(&self) -> Vec { + vec![self.to_test] + } + + fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + let to_test_value = witness.get_target(self.to_test); + + let dummy_value = if to_test_value == F::ZERO { + F::ONE + } else { + to_test_value.inverse() + }; + + out_buffer.set_target(self.dummy, dummy_value); + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_target(self.to_test)?; + dst.write_target(self.dummy) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let to_test = src.read_target()?; + let dummy = src.read_target()?; + Ok(Self { to_test, dummy }) + } +} + +/// Generator used to fill an extra constant. +#[derive(Debug, Clone, Default)] +pub struct ConstantGenerator { + pub row: usize, + pub constant_index: usize, + pub wire_index: usize, + pub constant: F, +} + +impl ConstantGenerator { + pub fn set_constant(&mut self, c: F) { + self.constant = c; + } +} + +impl, const D: usize> SimpleGenerator for ConstantGenerator { + fn id(&self) -> String { + "ConstantGenerator".to_string() + } + + fn dependencies(&self) -> Vec { + vec![] + } + + fn run_once(&self, _witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + out_buffer.set_target(Target::wire(self.row, self.wire_index), self.constant); + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.row)?; + dst.write_usize(self.constant_index)?; + dst.write_usize(self.wire_index)?; + dst.write_field(self.constant) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let row = src.read_usize()?; + let constant_index = src.read_usize()?; + let wire_index = src.read_usize()?; + let constant = src.read_field()?; + Ok(Self { + row, + constant_index, + wire_index, + constant, + }) + } +} diff --git a/plonky2/src/iop/mod.rs b/plonky2/src/iop/mod.rs new file mode 100644 index 000000000..47642edc1 --- /dev/null +++ b/plonky2/src/iop/mod.rs @@ -0,0 +1,8 @@ +//! Logic common to multiple IOPs. + +pub mod challenger; +pub mod ext_target; +pub mod generator; +pub mod target; +pub mod wire; +pub mod witness; diff --git a/plonky2/src/iop/target.rs b/plonky2/src/iop/target.rs new file mode 100644 index 000000000..86dcec50b --- /dev/null +++ b/plonky2/src/iop/target.rs @@ -0,0 +1,93 @@ +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; +use core::ops::Range; + +use serde::{Deserialize, Serialize}; + +use crate::iop::ext_target::ExtensionTarget; +use crate::iop::wire::Wire; +use crate::plonk::circuit_data::CircuitConfig; + +/// A location in the witness. +/// +/// Targets can either be placed at a specific location, or be "floating" +/// around, serving as intermediary value holders, and copied to other locations +/// whenever needed. +/// +/// When generating a proof for a given circuit, the prover will "set" the +/// values of some (or all) targets, so that they satisfy the circuit +/// constraints. This is done through +/// the [PartialWitness](crate::iop::witness::PartialWitness) interface. +/// +/// There are different "variants" of the `Target` type, namely +/// [`ExtensionTarget`], +/// [ExtensionAlgebraTarget](crate::iop::ext_target::ExtensionAlgebraTarget). +/// The `Target` type is the default one for most circuits verifying some simple +/// statement. +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)] +pub enum Target { + /// A target that has a fixed location in the witness (seen as a `degree x + /// num_wires` grid). + Wire(Wire), + /// A target that doesn't have any inherent location in the witness (but it + /// can be copied to another target that does). This is useful for + /// representing intermediate values in witness generation. + VirtualTarget { index: usize }, +} + +impl Default for Target { + fn default() -> Self { + Self::VirtualTarget { index: 0 } + } +} + +impl Target { + pub const fn wire(row: usize, column: usize) -> Self { + Self::Wire(Wire { row, column }) + } + + pub const fn is_routable(&self, config: &CircuitConfig) -> bool { + match self { + Target::Wire(wire) => wire.is_routable(config), + Target::VirtualTarget { .. } => true, + } + } + + pub fn wires_from_range(row: usize, range: Range) -> Vec { + range.map(|i| Self::wire(row, i)).collect() + } + + pub fn index(&self, num_wires: usize, degree: usize) -> usize { + match self { + Target::Wire(Wire { row, column }) => row * num_wires + column, + Target::VirtualTarget { index } => degree * num_wires + index, + } + } + + /// Conversion to an `ExtensionTarget`. + pub const fn to_ext_target(self, zero: Self) -> ExtensionTarget { + let mut arr = [zero; D]; + arr[0] = self; + ExtensionTarget(arr) + } +} + +/// A `Target` which has already been constrained such that it can only be 0 or +/// 1. +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +#[allow(clippy::manual_non_exhaustive)] +pub struct BoolTarget { + pub target: Target, + /// This private field is here to force all instantiations to go through + /// `new_unsafe`. + _private: (), +} + +impl BoolTarget { + pub const fn new_unsafe(target: Target) -> BoolTarget { + BoolTarget { + target, + _private: (), + } + } +} diff --git a/plonky2/src/iop/wire.rs b/plonky2/src/iop/wire.rs new file mode 100644 index 000000000..cfa69755d --- /dev/null +++ b/plonky2/src/iop/wire.rs @@ -0,0 +1,31 @@ +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; +use core::ops::Range; + +use serde::{Deserialize, Serialize}; + +use crate::plonk::circuit_data::CircuitConfig; + +/// Represents a wire in the circuit, seen as a `degree x num_wires` table. +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)] +pub struct Wire { + /// Row index of the wire. + pub row: usize, + /// Column index of the wire. + pub column: usize, +} + +impl Wire { + pub const fn is_routable(&self, config: &CircuitConfig) -> bool { + self.column < config.num_routed_wires + } + + pub fn from_range(gate: usize, range: Range) -> Vec { + range + .map(|i| Wire { + row: gate, + column: i, + }) + .collect() + } +} diff --git a/plonky2/src/iop/witness.rs b/plonky2/src/iop/witness.rs new file mode 100644 index 000000000..9c6d60ff8 --- /dev/null +++ b/plonky2/src/iop/witness.rs @@ -0,0 +1,368 @@ +#[cfg(not(feature = "std"))] +use alloc::{vec, vec::Vec}; + +use hashbrown::HashMap; +use itertools::{zip_eq, Itertools}; + +use crate::field::extension::{Extendable, FieldExtension}; +use crate::field::types::Field; +use crate::fri::structure::{FriOpenings, FriOpeningsTarget}; +use crate::fri::witness_util::set_fri_proof_target; +use crate::hash::hash_types::{HashOut, HashOutTarget, MerkleCapTarget, RichField}; +use crate::hash::merkle_tree::MerkleCap; +use crate::iop::ext_target::ExtensionTarget; +use crate::iop::target::{BoolTarget, Target}; +use crate::iop::wire::Wire; +use crate::plonk::circuit_data::{VerifierCircuitTarget, VerifierOnlyCircuitData}; +use crate::plonk::config::{AlgebraicHasher, GenericConfig, Hasher}; +use crate::plonk::proof::{Proof, ProofTarget, ProofWithPublicInputs, ProofWithPublicInputsTarget}; + +pub trait WitnessWrite { + fn set_target(&mut self, target: Target, value: F); + + fn set_hash_target(&mut self, ht: HashOutTarget, value: HashOut) { + ht.elements + .iter() + .zip(value.elements) + .for_each(|(&t, x)| self.set_target(t, x)); + } + + fn set_cap_target>( + &mut self, + ct: &MerkleCapTarget, + value: &MerkleCap, + ) where + F: RichField, + { + for (ht, h) in ct.0.iter().zip(&value.0) { + self.set_hash_target(*ht, *h); + } + } + + fn set_extension_target(&mut self, et: ExtensionTarget, value: F::Extension) + where + F: RichField + Extendable, + { + self.set_target_arr(&et.0, &value.to_basefield_array()); + } + + fn set_target_arr(&mut self, targets: &[Target], values: &[F]) { + zip_eq(targets, values).for_each(|(&target, &value)| self.set_target(target, value)); + } + + fn set_extension_targets( + &mut self, + ets: &[ExtensionTarget], + values: &[F::Extension], + ) where + F: RichField + Extendable, + { + debug_assert_eq!(ets.len(), values.len()); + ets.iter() + .zip(values) + .for_each(|(&et, &v)| self.set_extension_target(et, v)); + } + + fn set_bool_target(&mut self, target: BoolTarget, value: bool) { + self.set_target(target.target, F::from_bool(value)) + } + + /// Set the targets in a `ProofWithPublicInputsTarget` to their + /// corresponding values in a `ProofWithPublicInputs`. + fn set_proof_with_pis_target, const D: usize>( + &mut self, + proof_with_pis_target: &ProofWithPublicInputsTarget, + proof_with_pis: &ProofWithPublicInputs, + ) where + F: RichField + Extendable, + C::Hasher: AlgebraicHasher, + { + let ProofWithPublicInputs { + proof, + public_inputs, + } = proof_with_pis; + let ProofWithPublicInputsTarget { + proof: pt, + public_inputs: pi_targets, + } = proof_with_pis_target; + + // Set public inputs. + for (&pi_t, &pi) in pi_targets.iter().zip_eq(public_inputs) { + self.set_target(pi_t, pi); + } + + self.set_proof_target(pt, proof); + } + + /// Set the targets in a `ProofTarget` to their corresponding values in a + /// `Proof`. + fn set_proof_target, const D: usize>( + &mut self, + proof_target: &ProofTarget, + proof: &Proof, + ) where + F: RichField + Extendable, + C::Hasher: AlgebraicHasher, + { + self.set_cap_target(&proof_target.wires_cap, &proof.wires_cap); + self.set_cap_target( + &proof_target.plonk_zs_partial_products_cap, + &proof.plonk_zs_partial_products_cap, + ); + self.set_cap_target(&proof_target.quotient_polys_cap, &proof.quotient_polys_cap); + + self.set_fri_openings( + &proof_target.openings.to_fri_openings(), + &proof.openings.to_fri_openings(), + ); + + set_fri_proof_target(self, &proof_target.opening_proof, &proof.opening_proof); + } + + fn set_fri_openings( + &mut self, + fri_openings_target: &FriOpeningsTarget, + fri_openings: &FriOpenings, + ) where + F: RichField + Extendable, + { + for (batch_target, batch) in fri_openings_target + .batches + .iter() + .zip_eq(&fri_openings.batches) + { + self.set_extension_targets(&batch_target.values, &batch.values); + } + } + + fn set_verifier_data_target, const D: usize>( + &mut self, + vdt: &VerifierCircuitTarget, + vd: &VerifierOnlyCircuitData, + ) where + F: RichField + Extendable, + C::Hasher: AlgebraicHasher, + { + self.set_cap_target(&vdt.constants_sigmas_cap, &vd.constants_sigmas_cap); + self.set_hash_target(vdt.circuit_digest, vd.circuit_digest); + } + + fn set_wire(&mut self, wire: Wire, value: F) { + self.set_target(Target::Wire(wire), value) + } + + fn set_wires(&mut self, wires: W, values: &[F]) + where + W: IntoIterator, + { + // If we used itertools, we could use zip_eq for extra safety. + for (wire, &value) in wires.into_iter().zip(values) { + self.set_wire(wire, value); + } + } + + fn set_ext_wires(&mut self, wires: W, value: F::Extension) + where + F: RichField + Extendable, + W: IntoIterator, + { + self.set_wires(wires, &value.to_basefield_array()); + } + + fn extend>(&mut self, pairs: I) { + for (t, v) in pairs { + self.set_target(t, v); + } + } +} + +/// A witness holds information on the values of targets in a circuit. +pub trait Witness: WitnessWrite { + fn try_get_target(&self, target: Target) -> Option; + + fn get_target(&self, target: Target) -> F { + self.try_get_target(target).unwrap() + } + + fn get_targets(&self, targets: &[Target]) -> Vec { + targets.iter().map(|&t| self.get_target(t)).collect() + } + + fn get_extension_target(&self, et: ExtensionTarget) -> F::Extension + where + F: RichField + Extendable, + { + F::Extension::from_basefield_array( + self.get_targets(&et.to_target_array()).try_into().unwrap(), + ) + } + + fn get_extension_targets(&self, ets: &[ExtensionTarget]) -> Vec + where + F: RichField + Extendable, + { + ets.iter() + .map(|&et| self.get_extension_target(et)) + .collect() + } + + fn get_bool_target(&self, target: BoolTarget) -> bool { + let value = self.get_target(target.target); + if value.is_zero() { + return false; + } + if value.is_one() { + return true; + } + panic!("not a bool") + } + + fn get_hash_target(&self, ht: HashOutTarget) -> HashOut { + HashOut { + elements: self.get_targets(&ht.elements).try_into().unwrap(), + } + } + + fn get_merkle_cap_target>(&self, cap_target: MerkleCapTarget) -> MerkleCap + where + F: RichField, + H: AlgebraicHasher, + { + let cap = cap_target + .0 + .iter() + .map(|hash_target| self.get_hash_target(*hash_target)) + .collect(); + MerkleCap(cap) + } + + fn get_wire(&self, wire: Wire) -> F { + self.get_target(Target::Wire(wire)) + } + + fn try_get_wire(&self, wire: Wire) -> Option { + self.try_get_target(Target::Wire(wire)) + } + + fn contains(&self, target: Target) -> bool { + self.try_get_target(target).is_some() + } + + fn contains_all(&self, targets: &[Target]) -> bool { + targets.iter().all(|&t| self.contains(t)) + } +} + +#[derive(Clone, Debug)] +pub struct MatrixWitness { + pub(crate) wire_values: Vec>, +} + +impl MatrixWitness { + pub fn get_wire(&self, gate: usize, input: usize) -> F { + self.wire_values[input][gate] + } +} + +#[derive(Clone, Debug, Default)] +pub struct PartialWitness { + pub target_values: HashMap, +} + +impl PartialWitness { + pub fn new() -> Self { + Self { + target_values: HashMap::new(), + } + } +} + +impl WitnessWrite for PartialWitness { + fn set_target(&mut self, target: Target, value: F) { + let opt_old_value = self.target_values.insert(target, value); + if let Some(old_value) = opt_old_value { + assert_eq!( + value, old_value, + "Target {:?} was set twice with different values: {} != {}", + target, old_value, value + ); + } + } +} + +impl Witness for PartialWitness { + fn try_get_target(&self, target: Target) -> Option { + self.target_values.get(&target).copied() + } +} + +/// `PartitionWitness` holds a disjoint-set forest of the targets respecting a +/// circuit's copy constraints. The value of a target is defined to be the value +/// of its root in the forest. +#[derive(Clone, Debug)] +pub struct PartitionWitness<'a, F: Field> { + pub values: Vec>, + pub representative_map: &'a [usize], + pub num_wires: usize, + pub degree: usize, +} + +impl<'a, F: Field> PartitionWitness<'a, F> { + pub fn new(num_wires: usize, degree: usize, representative_map: &'a [usize]) -> Self { + Self { + values: vec![None; representative_map.len()], + representative_map, + num_wires, + degree, + } + } + + /// Set a `Target`. On success, returns the representative index of the + /// newly-set target. If the target was already set, returns `None`. + pub fn set_target_returning_rep(&mut self, target: Target, value: F) -> Option { + let rep_index = self.representative_map[self.target_index(target)]; + let rep_value = &mut self.values[rep_index]; + if let Some(old_value) = *rep_value { + assert_eq!( + value, old_value, + "Partition containing {:?} was set twice with different values: {} != {}", + target, old_value, value + ); + None + } else { + *rep_value = Some(value); + Some(rep_index) + } + } + + pub(crate) fn target_index(&self, target: Target) -> usize { + target.index(self.num_wires, self.degree) + } + + pub fn full_witness(self) -> MatrixWitness { + let mut wire_values = vec![vec![F::ZERO; self.degree]; self.num_wires]; + for i in 0..self.degree { + for j in 0..self.num_wires { + let t = Target::Wire(Wire { row: i, column: j }); + if let Some(x) = self.try_get_target(t) { + wire_values[j][i] = x; + } + } + } + + MatrixWitness { wire_values } + } +} + +impl<'a, F: Field> WitnessWrite for PartitionWitness<'a, F> { + fn set_target(&mut self, target: Target, value: F) { + self.set_target_returning_rep(target, value); + } +} + +impl<'a, F: Field> Witness for PartitionWitness<'a, F> { + fn try_get_target(&self, target: Target) -> Option { + let rep_index = self.representative_map[self.target_index(target)]; + self.values[rep_index] + } +} diff --git a/plonky2/src/lib.rs b/plonky2/src/lib.rs new file mode 100644 index 000000000..b0b6bfb4d --- /dev/null +++ b/plonky2/src/lib.rs @@ -0,0 +1,22 @@ +#![allow(clippy::too_many_arguments)] +#![allow(clippy::needless_range_loop)] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(not(feature = "std"))] +pub extern crate alloc; + +/// Re-export of `plonky2_field`. +#[doc(inline)] +pub use plonky2_field as field; + +pub mod fri; +pub mod gadgets; +pub mod gates; +pub mod hash; +pub mod iop; +pub mod plonk; +pub mod recursion; +pub mod util; + +#[cfg(test)] +mod lookup_test; diff --git a/plonky2/src/lookup_test.rs b/plonky2/src/lookup_test.rs new file mode 100644 index 000000000..014320776 --- /dev/null +++ b/plonky2/src/lookup_test.rs @@ -0,0 +1,526 @@ +#[cfg(not(feature = "std"))] +use alloc::{sync::Arc, vec, vec::Vec}; +#[cfg(feature = "std")] +use std::sync::{Arc, Once}; + +use itertools::Itertools; +use log::Level; + +use crate::field::types::Field; +use crate::gadgets::lookup::{OTHER_TABLE, SMALLER_TABLE, TIP5_TABLE}; +use crate::gates::lookup_table::LookupTable; +use crate::gates::noop::NoopGate; +use crate::iop::witness::{PartialWitness, WitnessWrite}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::CircuitConfig; +use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; +use crate::plonk::prover::prove; +use crate::util::timing::TimingTree; + +const D: usize = 2; +type C = PoseidonGoldilocksConfig; +type F = >::F; + +const LUT_SIZE: usize = u16::MAX as usize + 1; + +#[cfg(feature = "std")] +static LOGGER_INITIALIZED: Once = Once::new(); + +#[test] +fn test_no_lookup() -> anyhow::Result<()> { + init_logger(); + + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + builder.add_gate(NoopGate, vec![]); + let pw = PartialWitness::new(); + + let data = builder.build::(); + let mut timing = TimingTree::new("prove first", Level::Debug); + let proof = prove(&data.prover_only, &data.common, pw, &mut timing)?; + timing.print(); + data.verify(proof)?; + + Ok(()) +} + +#[should_panic] +#[test] +fn test_lookup_table_not_used() { + init_logger(); + + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + + let tip5_table = TIP5_TABLE.to_vec(); + let table: LookupTable = Arc::new((0..256).zip_eq(tip5_table).collect()); + builder.add_lookup_table_from_pairs(table); + + builder.build::(); +} + +#[should_panic] +#[test] +fn test_lookup_without_table() { + init_logger(); + + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + + let dummy = builder.add_virtual_target(); + builder.add_lookup_from_index(dummy, 0); + + builder.build::(); +} + +// Tests two lookups in one lookup table. +#[test] +fn test_one_lookup() -> anyhow::Result<()> { + init_logger(); + + let tip5_table = TIP5_TABLE.to_vec(); + let table: LookupTable = Arc::new((0..256).zip_eq(tip5_table).collect()); + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + + let initial_a = builder.add_virtual_target(); + let initial_b = builder.add_virtual_target(); + + let look_val_a = 1; + let look_val_b = 2; + + let out_a = table[look_val_a].1; + let out_b = table[look_val_b].1; + let table_index = builder.add_lookup_table_from_pairs(table); + let output_a = builder.add_lookup_from_index(initial_a, table_index); + + let output_b = builder.add_lookup_from_index(initial_b, table_index); + + builder.register_public_input(initial_a); + builder.register_public_input(initial_b); + builder.register_public_input(output_a); + builder.register_public_input(output_b); + + let mut pw = PartialWitness::new(); + + pw.set_target(initial_a, F::from_canonical_usize(look_val_a)); + pw.set_target(initial_b, F::from_canonical_usize(look_val_b)); + + let data = builder.build::(); + let mut timing = TimingTree::new("prove one lookup", Level::Debug); + let proof = prove(&data.prover_only, &data.common, pw, &mut timing)?; + timing.print(); + data.verify(proof.clone())?; + + assert!( + proof.public_inputs[2] == F::from_canonical_u16(out_a), + "First lookup, at index {} in the Tip5 table gives an incorrect output.", + proof.public_inputs[0] + ); + assert!( + proof.public_inputs[3] == F::from_canonical_u16(out_b), + "Second lookup, at index {} in the Tip5 table gives an incorrect output.", + proof.public_inputs[1] + ); + + Ok(()) +} + +// Tests one lookup in two different lookup tables. +#[test] +fn test_two_luts() -> anyhow::Result<()> { + init_logger(); + + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + + let initial_a = builder.add_virtual_target(); + let initial_b = builder.add_virtual_target(); + + let look_val_a = 1; + let look_val_b = 2; + + let tip5_table = TIP5_TABLE.to_vec(); + + let first_out = tip5_table[look_val_a]; + let second_out = tip5_table[look_val_b]; + + let table: LookupTable = Arc::new((0..256).zip_eq(tip5_table).collect()); + + let other_table = OTHER_TABLE.to_vec(); + + let table_index = builder.add_lookup_table_from_pairs(table); + let output_a = builder.add_lookup_from_index(initial_a, table_index); + + let output_b = builder.add_lookup_from_index(initial_b, table_index); + let sum = builder.add(output_a, output_b); + + let s = first_out + second_out; + let final_out = other_table[s as usize]; + + let table2: LookupTable = Arc::new((0..256).zip_eq(other_table).collect()); + let table2_index = builder.add_lookup_table_from_pairs(table2); + + let output_final = builder.add_lookup_from_index(sum, table2_index); + + builder.register_public_input(initial_a); + builder.register_public_input(initial_b); + builder.register_public_input(sum); + builder.register_public_input(output_a); + builder.register_public_input(output_b); + builder.register_public_input(output_final); + + let mut pw = PartialWitness::new(); + pw.set_target(initial_a, F::from_canonical_usize(look_val_a)); + pw.set_target(initial_b, F::from_canonical_usize(look_val_b)); + let data = builder.build::(); + let mut timing = TimingTree::new("prove two_luts", Level::Debug); + let proof = prove(&data.prover_only, &data.common, pw, &mut timing)?; + data.verify(proof.clone())?; + timing.print(); + + assert!( + proof.public_inputs[3] == F::from_canonical_u16(first_out), + "First lookup, at index {} in the Tip5 table gives an incorrect output.", + proof.public_inputs[0] + ); + assert!( + proof.public_inputs[4] == F::from_canonical_u16(second_out), + "Second lookup, at index {} in the Tip5 table gives an incorrect output.", + proof.public_inputs[1] + ); + assert!( + proof.public_inputs[2] == F::from_canonical_u16(s), + "Sum between the first two LUT outputs is incorrect." + ); + assert!( + proof.public_inputs[5] == F::from_canonical_u16(final_out), + "Output of the second LUT at index {} is incorrect.", + s + ); + + Ok(()) +} + +#[test] +fn test_different_inputs() -> anyhow::Result<()> { + init_logger(); + + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + + let initial_a = builder.add_virtual_target(); + let initial_b = builder.add_virtual_target(); + + let init_a = 1; + let init_b = 2; + + let tab: Vec = SMALLER_TABLE.to_vec(); + let table: LookupTable = Arc::new((2..10).zip_eq(tab).collect()); + + let other_table = OTHER_TABLE.to_vec(); + + let table2: LookupTable = Arc::new((0..256).zip_eq(other_table).collect()); + let small_index = builder.add_lookup_table_from_pairs(table.clone()); + let output_a = builder.add_lookup_from_index(initial_a, small_index); + + let output_b = builder.add_lookup_from_index(initial_b, small_index); + let sum = builder.add(output_a, output_b); + + let other_index = builder.add_lookup_table_from_pairs(table2.clone()); + let output_final = builder.add_lookup_from_index(sum, other_index); + + builder.register_public_input(initial_a); + builder.register_public_input(initial_b); + builder.register_public_input(sum); + builder.register_public_input(output_a); + builder.register_public_input(output_b); + builder.register_public_input(output_final); + + let mut pw = PartialWitness::new(); + + let look_val_a = table[init_a].0; + let look_val_b = table[init_b].0; + pw.set_target(initial_a, F::from_canonical_u16(look_val_a)); + pw.set_target(initial_b, F::from_canonical_u16(look_val_b)); + + let data = builder.build::(); + let mut timing = TimingTree::new("prove different lookups", Level::Debug); + let proof = prove(&data.prover_only, &data.common, pw, &mut timing)?; + data.verify(proof.clone())?; + timing.print(); + + let out_a = table[init_a].1; + let out_b = table[init_b].1; + let s = out_a + out_b; + let out_final = table2[s as usize].1; + + assert!( + proof.public_inputs[3] == F::from_canonical_u16(out_a), + "First lookup, at index {} in the smaller LUT gives an incorrect output.", + proof.public_inputs[0] + ); + assert!( + proof.public_inputs[4] == F::from_canonical_u16(out_b), + "Second lookup, at index {} in the smaller LUT gives an incorrect output.", + proof.public_inputs[1] + ); + assert!( + proof.public_inputs[2] == F::from_canonical_u16(s), + "Sum between the first two LUT outputs is incorrect." + ); + assert!( + proof.public_inputs[5] == F::from_canonical_u16(out_final), + "Output of the second LUT at index {} is incorrect.", + s + ); + + Ok(()) +} + +// This test looks up over 514 values for one LookupTableGate, which means that +// several LookupGates are created. +#[test] +fn test_many_lookups() -> anyhow::Result<()> { + init_logger(); + + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + + let initial_a = builder.add_virtual_target(); + let initial_b = builder.add_virtual_target(); + + let look_val_a = 1; + let look_val_b = 2; + + let tip5_table = TIP5_TABLE.to_vec(); + let table: LookupTable = Arc::new((0..256).zip_eq(tip5_table).collect()); + + let out_a = table[look_val_a].1; + let out_b = table[look_val_b].1; + + let tip5_index = builder.add_lookup_table_from_pairs(table); + let output_a = builder.add_lookup_from_index(initial_a, tip5_index); + + let output_b = builder.add_lookup_from_index(initial_b, tip5_index); + let sum = builder.add(output_a, output_b); + + for _ in 0..514 { + builder.add_lookup_from_index(initial_a, tip5_index); + } + + let other_table = OTHER_TABLE.to_vec(); + + let table2: LookupTable = Arc::new((0..256).zip_eq(other_table).collect()); + + let s = out_a + out_b; + let out_final = table2[s as usize].1; + + let other_index = builder.add_lookup_table_from_pairs(table2); + let output_final = builder.add_lookup_from_index(sum, other_index); + + builder.register_public_input(initial_a); + builder.register_public_input(initial_b); + builder.register_public_input(sum); + builder.register_public_input(output_a); + builder.register_public_input(output_b); + builder.register_public_input(output_final); + + let mut pw = PartialWitness::new(); + + pw.set_target(initial_a, F::from_canonical_usize(look_val_a)); + pw.set_target(initial_b, F::from_canonical_usize(look_val_b)); + + let data = builder.build::(); + let mut timing = TimingTree::new("prove different lookups", Level::Debug); + let proof = prove(&data.prover_only, &data.common, pw, &mut timing)?; + + data.verify(proof.clone())?; + timing.print(); + + assert!( + proof.public_inputs[3] == F::from_canonical_u16(out_a), + "First lookup, at index {} in the Tip5 table gives an incorrect output.", + proof.public_inputs[0] + ); + assert!( + proof.public_inputs[4] == F::from_canonical_u16(out_b), + "Second lookup, at index {} in the Tip5 table gives an incorrect output.", + proof.public_inputs[1] + ); + assert!( + proof.public_inputs[2] == F::from_canonical_u16(s), + "Sum between the first two LUT outputs is incorrect." + ); + assert!( + proof.public_inputs[5] == F::from_canonical_u16(out_final), + "Output of the second LUT at index {} is incorrect.", + s + ); + + Ok(()) +} + +// Tests whether, when adding the same LUT to the circuit, the circuit only adds +// one copy, with the same index. +#[test] +fn test_same_luts() -> anyhow::Result<()> { + init_logger(); + + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + + let initial_a = builder.add_virtual_target(); + let initial_b = builder.add_virtual_target(); + + let look_val_a = 1; + let look_val_b = 2; + + let tip5_table = TIP5_TABLE.to_vec(); + let table: LookupTable = Arc::new((0..256).zip_eq(tip5_table).collect()); + + let table_index = builder.add_lookup_table_from_pairs(table.clone()); + let output_a = builder.add_lookup_from_index(initial_a, table_index); + + let output_b = builder.add_lookup_from_index(initial_b, table_index); + let sum = builder.add(output_a, output_b); + + let table2_index = builder.add_lookup_table_from_pairs(table); + + let output_final = builder.add_lookup_from_index(sum, table2_index); + + builder.register_public_input(initial_a); + builder.register_public_input(initial_b); + builder.register_public_input(sum); + builder.register_public_input(output_a); + builder.register_public_input(output_b); + builder.register_public_input(output_final); + + let luts_length = builder.get_luts_length(); + + assert!( + luts_length == 1, + "There are {} LUTs when there should be only one", + luts_length + ); + + let mut pw = PartialWitness::new(); + + pw.set_target(initial_a, F::from_canonical_usize(look_val_a)); + pw.set_target(initial_b, F::from_canonical_usize(look_val_b)); + + let data = builder.build::(); + let mut timing = TimingTree::new("prove two_luts", Level::Debug); + let proof = prove(&data.prover_only, &data.common, pw, &mut timing)?; + data.verify(proof)?; + timing.print(); + + Ok(()) +} + +#[test] +fn test_big_lut() -> anyhow::Result<()> { + init_logger(); + + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + + let inputs: [u16; LUT_SIZE] = core::array::from_fn(|i| i as u16); + let lut_fn = |inp: u16| inp / 10; + let lut_index = builder.add_lookup_table_from_fn(lut_fn, &inputs); + + let initial_a = builder.add_virtual_target(); + let initial_b = builder.add_virtual_target(); + + let look_val_a = 51; + let look_val_b = 2; + + let output_a = builder.add_lookup_from_index(initial_a, lut_index); + let output_b = builder.add_lookup_from_index(initial_b, lut_index); + + builder.register_public_input(output_a); + builder.register_public_input(output_b); + + let data = builder.build::(); + + let mut pw = PartialWitness::new(); + + pw.set_target(initial_a, F::from_canonical_u16(look_val_a)); + pw.set_target(initial_b, F::from_canonical_u16(look_val_b)); + + let proof = data.prove(pw)?; + assert_eq!( + proof.public_inputs[0], + F::from_canonical_u16(lut_fn(look_val_a)) + ); + assert_eq!( + proof.public_inputs[1], + F::from_canonical_u16(lut_fn(look_val_b)) + ); + + data.verify(proof) +} + +#[test] +fn test_many_lookups_on_big_lut() -> anyhow::Result<()> { + init_logger(); + + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + + let inputs: [u16; LUT_SIZE] = core::array::from_fn(|i| i as u16); + let lut_fn = |inp: u16| inp / 10; + let lut_index = builder.add_lookup_table_from_fn(lut_fn, &inputs); + + let inputs = (0..LUT_SIZE) + .map(|_| { + let input_target = builder.add_virtual_target(); + _ = builder.add_lookup_from_index(input_target, lut_index); + input_target + }) + .collect::>(); + + let initial_a = builder.add_virtual_target(); + let initial_b = builder.add_virtual_target(); + + let look_val_a = 51; + let look_val_b = 2; + + let output_a = builder.add_lookup_from_index(initial_a, lut_index); + let output_b = builder.add_lookup_from_index(initial_b, lut_index); + let sum = builder.add(output_a, output_b); + + builder.register_public_input(sum); + + let data = builder.build::(); + + let mut pw = PartialWitness::new(); + + inputs + .into_iter() + .enumerate() + .for_each(|(i, t)| pw.set_target(t, F::from_canonical_usize(i))); + pw.set_target(initial_a, F::from_canonical_u16(look_val_a)); + pw.set_target(initial_b, F::from_canonical_u16(look_val_b)); + + let proof = data.prove(pw)?; + assert_eq!( + proof.public_inputs[0], + F::from_canonical_u16(lut_fn(look_val_a) + lut_fn(look_val_b)) + ); + + data.verify(proof) +} + +fn init_logger() { + #[cfg(feature = "std")] + { + LOGGER_INITIALIZED.call_once(|| { + let mut builder = env_logger::Builder::from_default_env(); + builder.format_timestamp(None); + builder.filter_level(log::LevelFilter::Debug); + + builder.try_init().unwrap(); + }); + } +} diff --git a/plonky2/src/plonk/circuit_builder.rs b/plonky2/src/plonk/circuit_builder.rs new file mode 100644 index 000000000..8f908c15b --- /dev/null +++ b/plonky2/src/plonk/circuit_builder.rs @@ -0,0 +1,1336 @@ +//! Logic for building plonky2 circuits. + +#[cfg(not(feature = "std"))] +use alloc::{collections::BTreeMap, sync::Arc, vec, vec::Vec}; +use core::cmp::max; +#[cfg(feature = "std")] +use std::{collections::BTreeMap, sync::Arc, time::Instant}; + +use hashbrown::{HashMap, HashSet}; +use itertools::Itertools; +use log::{debug, info, warn, Level}; +use plonky2_util::ceil_div_usize; + +use crate::field::cosets::get_unique_coset_shifts; +use crate::field::extension::{Extendable, FieldExtension}; +use crate::field::fft::fft_root_table; +use crate::field::polynomial::PolynomialValues; +use crate::field::types::Field; +use crate::fri::oracle::PolynomialBatch; +use crate::fri::{FriConfig, FriParams}; +use crate::gadgets::arithmetic::BaseArithmeticOperation; +use crate::gadgets::arithmetic_extension::ExtensionArithmeticOperation; +use crate::gadgets::polynomial::PolynomialCoeffsExtTarget; +use crate::gates::arithmetic_base::ArithmeticGate; +use crate::gates::arithmetic_extension::ArithmeticExtensionGate; +use crate::gates::constant::ConstantGate; +use crate::gates::gate::{CurrentSlot, Gate, GateInstance, GateRef}; +use crate::gates::lookup::{Lookup, LookupGate}; +use crate::gates::lookup_table::LookupTable; +use crate::gates::noop::NoopGate; +use crate::gates::public_input::PublicInputGate; +use crate::gates::selectors::{selector_ends_lookups, selector_polynomials, selectors_lookup}; +use crate::hash::hash_types::{HashOut, HashOutTarget, MerkleCapTarget, RichField}; +use crate::hash::merkle_proofs::MerkleProofTarget; +use crate::hash::merkle_tree::MerkleCap; +use crate::iop::ext_target::ExtensionTarget; +use crate::iop::generator::{ + ConstantGenerator, CopyGenerator, RandomValueGenerator, SimpleGenerator, WitnessGeneratorRef, +}; +use crate::iop::target::{BoolTarget, Target}; +use crate::iop::wire::Wire; +use crate::plonk::circuit_data::{ + CircuitConfig, CircuitData, CommonCircuitData, MockCircuitData, ProverCircuitData, + ProverOnlyCircuitData, VerifierCircuitData, VerifierCircuitTarget, VerifierOnlyCircuitData, +}; +use crate::plonk::config::{AlgebraicHasher, GenericConfig, GenericHashOut, Hasher}; +use crate::plonk::copy_constraint::CopyConstraint; +use crate::plonk::permutation_argument::Forest; +use crate::plonk::plonk_common::PlonkOracle; +use crate::timed; +use crate::util::context_tree::ContextTree; +use crate::util::partial_products::num_partial_products; +use crate::util::timing::TimingTree; +use crate::util::{log2_ceil, log2_strict, transpose, transpose_poly_values}; + +/// Number of random coins needed for lookups (for each challenge). +/// A coin is a randomly sampled extension field element from the verifier, +/// consisting internally of `CircuitConfig::num_challenges` field elements. +pub const NUM_COINS_LOOKUP: usize = 4; + +/// Enum listing the different types of lookup challenges. +/// `ChallengeA` is used for the linear combination of input and output pairs in +/// Sum and LDC. `ChallengeB` is used for the linear combination of input and +/// output pairs in the polynomial RE. `ChallengeAlpha` is used for the running +/// sums: 1/(alpha - combo_i). `ChallengeDelta` is a challenge on which to +/// evaluate the interpolated LUT function. +pub enum LookupChallenges { + ChallengeA = 0, + ChallengeB = 1, + ChallengeAlpha = 2, + ChallengeDelta = 3, +} + +/// Structure containing, for each lookup table, the indices of the last lookup +/// row, the last lookup table row and the first lookup table row. Since the +/// rows are in reverse order in the trace, they actually correspond, +/// respectively, to: the indices of the first `LookupGate`, the first +/// `LookupTableGate` and the last `LookupTableGate`. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct LookupWire { + /// Index of the last lookup row (i.e. the first `LookupGate`). + pub last_lu_gate: usize, + /// Index of the last lookup table row (i.e. the first `LookupTableGate`). + pub last_lut_gate: usize, + /// Index of the first lookup table row (i.e. the last `LookupTableGate`). + pub first_lut_gate: usize, +} + +/// Structure used to construct a plonky2 circuit. It provides all the necessary +/// toolkit that, from an initial circuit configuration, will enable one to +/// design a circuit and its associated prover/verifier data. +/// +/// # Usage +/// +/// ```rust +/// use plonky2::plonk::circuit_data::CircuitConfig; +/// use plonky2::iop::witness::PartialWitness; +/// use plonky2::plonk::circuit_builder::CircuitBuilder; +/// use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; +/// use plonky2::field::types::Field; +/// +/// // Define parameters for this circuit +/// const D: usize = 2; +/// type C = PoseidonGoldilocksConfig; +/// type F = >::F; +/// +/// let config = CircuitConfig::standard_recursion_config(); +/// let mut builder = CircuitBuilder::::new(config); +/// +/// // Build a circuit for the statement: "I know the 100th term +/// // of the Fibonacci sequence, starting from 0 and 1". +/// let initial_a = builder.constant(F::ZERO); +/// let initial_b = builder.constant(F::ONE); +/// let mut prev_target = initial_a; +/// let mut cur_target = initial_b; +/// for _ in 0..99 { +/// // Encode an addition of the two previous terms +/// let temp = builder.add(prev_target, cur_target); +/// // Shift the two previous terms with the new value +/// prev_target = cur_target; +/// cur_target = temp; +/// } +/// +/// // The only public input is the result (which is generated). +/// builder.register_public_input(cur_target); +/// +/// // Build the circuit +/// let circuit_data = builder.build::(); +/// +/// // Now compute the witness and generate a proof +/// let mut pw = PartialWitness::new(); +/// +/// // There are no public inputs to register, as the only one +/// // will be generated while proving the statement. +/// let proof = circuit_data.prove(pw).unwrap(); +/// +/// // Verify the proof +/// assert!(circuit_data.verify(proof).is_ok()); +/// ``` +pub struct CircuitBuilder, const D: usize> { + /// Circuit configuration to be used by this [`CircuitBuilder`]. + pub config: CircuitConfig, + + /// A domain separator, which is included in the initial Fiat-Shamir seed. + /// This is generally not needed, but can be used to ensure that proofs + /// for one application are not valid for another. Defaults to the empty + /// vector. + domain_separator: Option>, + + /// The types of gates used in this circuit. + gates: HashSet>, + + /// The concrete placement of each gate. + pub(crate) gate_instances: Vec>, + + /// Targets to be made public. + public_inputs: Vec, + + /// The next available index for a `VirtualTarget`. + virtual_target_index: usize, + + copy_constraints: Vec, + + /// A tree of named scopes, used for debugging. + context_log: ContextTree, + + /// Generators used to generate the witness. + generators: Vec>, + + constants_to_targets: HashMap, + targets_to_constants: HashMap, + + /// Memoized results of `arithmetic` calls. + pub(crate) base_arithmetic_results: HashMap, Target>, + + /// Memoized results of `arithmetic_extension` calls. + pub(crate) arithmetic_results: HashMap, ExtensionTarget>, + + /// Map between gate type and the current gate of this type with available + /// slots. + current_slots: HashMap, CurrentSlot>, + + /// List of constant generators used to fill the constant wires. + constant_generators: Vec>, + + /// Rows for each LUT: [`LookupWire`] contains: first [`LookupGate`], first + /// and last + /// [LookupTableGate](crate::gates::lookup_table::LookupTableGate). + lookup_rows: Vec, + + /// For each LUT index, vector of `(looking_in, looking_out)` pairs. + lut_to_lookups: Vec, + + // Lookup tables in the form of `Vec<(input_value, output_value)>`. + luts: Vec, + + /// Optional common data. When it is `Some(goal_data)`, the `build` function + /// panics if the resulting common data doesn't equal `goal_data`. + /// This is used in cyclic recursion. + pub(crate) goal_common_data: Option>, + + /// Optional verifier data that is registered as public inputs. + /// This is used in cyclic recursion to hold the circuit's own verifier key. + pub(crate) verifier_data_public_input: Option, +} + +impl, const D: usize> CircuitBuilder { + /// Given a [`CircuitConfig`], generate a new [`CircuitBuilder`] instance. + /// It will also check that the configuration provided is consistent, i.e. + /// that the different parameters provided can achieve the targeted security + /// level. + pub fn new(config: CircuitConfig) -> Self { + let builder = CircuitBuilder { + config, + domain_separator: None, + gates: HashSet::new(), + gate_instances: Vec::new(), + public_inputs: Vec::new(), + virtual_target_index: 0, + copy_constraints: Vec::new(), + 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(), + current_slots: HashMap::new(), + constant_generators: Vec::new(), + lookup_rows: Vec::new(), + lut_to_lookups: Vec::new(), + luts: Vec::new(), + goal_common_data: None, + verifier_data_public_input: None, + }; + builder.check_config(); + builder + } + + /// Assert that the configuration used to create this `CircuitBuilder` is + /// consistent, i.e. that the different parameters meet the targeted + /// security level. + fn check_config(&self) { + let &CircuitConfig { + security_bits, + fri_config: + FriConfig { + rate_bits, + proof_of_work_bits, + num_query_rounds, + .. + }, + .. + } = &self.config; + + // Conjectured FRI security; see the ethSTARK paper. + let fri_field_bits = F::Extension::order().bits() as usize; + let fri_query_security_bits = num_query_rounds * rate_bits + proof_of_work_bits as usize; + let fri_security_bits = fri_field_bits.min(fri_query_security_bits); + assert!( + fri_security_bits >= security_bits, + "FRI params fall short of target security" + ); + } + + pub fn set_domain_separator(&mut self, separator: Vec) { + assert!(self.domain_separator.is_none()); + self.domain_separator = Some(separator); + } + + /// Outputs the number of gates in this circuit. + pub fn num_gates(&self) -> usize { + self.gate_instances.len() + } + + /// Registers the given target as a public input. + pub fn register_public_input(&mut self, target: Target) { + self.public_inputs.push(target); + } + + /// Registers the given targets as public inputs. + pub fn register_public_inputs(&mut self, targets: &[Target]) { + targets.iter().for_each(|&t| self.register_public_input(t)); + } + + /// Outputs the number of public inputs in this circuit. + pub fn num_public_inputs(&self) -> usize { + self.public_inputs.len() + } + + /// Adds lookup rows for a lookup table. + pub fn add_lookup_rows( + &mut self, + last_lu_gate: usize, + last_lut_gate: usize, + first_lut_gate: usize, + ) { + self.lookup_rows.push(LookupWire { + last_lu_gate, + last_lut_gate, + first_lut_gate, + }); + } + + /// Adds a looking (input, output) pair to the corresponding LUT. + pub fn update_lookups(&mut self, looking_in: Target, looking_out: Target, lut_index: usize) { + assert!( + lut_index < self.lut_to_lookups.len(), + "The LUT with index {} has not been created. The last LUT is at index {}", + lut_index, + self.lut_to_lookups.len() - 1 + ); + self.lut_to_lookups[lut_index].push((looking_in, looking_out)); + } + + /// Outputs the number of lookup tables in this circuit. + pub fn num_luts(&self) -> usize { + self.lut_to_lookups.len() + } + + /// Given an index, outputs the corresponding looking table in the set of + /// tables used in this circuit, as a sequence of target tuples `(input, + /// output)`. + pub fn get_lut_lookups(&self, lut_index: usize) -> &[(Target, Target)] { + &self.lut_to_lookups[lut_index] + } + + /// Adds a new "virtual" target. This is not an actual wire in the witness, + /// but just a target that help facilitate witness generation. In + /// particular, a generator can assign a values to a virtual target, + /// which can then be copied to other (virtual or concrete) targets. When we + /// generate the final witness (a grid of wire values), these virtual + /// targets will go away. + pub fn add_virtual_target(&mut self) -> Target { + let index = self.virtual_target_index; + self.virtual_target_index += 1; + Target::VirtualTarget { index } + } + + /// Adds `n` new "virtual" targets. + pub fn add_virtual_targets(&mut self, n: usize) -> Vec { + (0..n).map(|_i| self.add_virtual_target()).collect() + } + + /// Adds `N` new "virtual" targets, arranged as an array. + pub fn add_virtual_target_arr(&mut self) -> [Target; N] { + [0; N].map(|_| self.add_virtual_target()) + } + + /// Adds a new `HashOutTarget`. `NUM_HASH_OUT_ELTS` being hardcoded to 4, it + /// internally adds 4 virtual targets in a vector fashion. + pub fn add_virtual_hash(&mut self) -> HashOutTarget { + HashOutTarget::from_vec(self.add_virtual_targets(4)) + } + + /// Adds a new `MerkleCapTarget`, consisting in `1 << cap_height` + /// `HashOutTarget`. + pub fn add_virtual_cap(&mut self, cap_height: usize) -> MerkleCapTarget { + MerkleCapTarget(self.add_virtual_hashes(1 << cap_height)) + } + + /// Adds `n` new `HashOutTarget` in a vector fashion. + pub fn add_virtual_hashes(&mut self, n: usize) -> Vec { + (0..n).map(|_i| self.add_virtual_hash()).collect() + } + + pub(crate) fn add_virtual_merkle_proof(&mut self, len: usize) -> MerkleProofTarget { + MerkleProofTarget { + siblings: self.add_virtual_hashes(len), + } + } + + pub fn add_virtual_extension_target(&mut self) -> ExtensionTarget { + ExtensionTarget(self.add_virtual_targets(D).try_into().unwrap()) + } + + pub fn add_virtual_extension_targets(&mut self, n: usize) -> Vec> { + (0..n) + .map(|_i| self.add_virtual_extension_target()) + .collect() + } + + pub(crate) fn add_virtual_poly_coeff_ext( + &mut self, + num_coeffs: usize, + ) -> PolynomialCoeffsExtTarget { + let coeffs = self.add_virtual_extension_targets(num_coeffs); + PolynomialCoeffsExtTarget(coeffs) + } + + pub fn add_virtual_bool_target_unsafe(&mut self) -> BoolTarget { + BoolTarget::new_unsafe(self.add_virtual_target()) + } + + pub fn add_virtual_bool_target_safe(&mut self) -> BoolTarget { + let b = BoolTarget::new_unsafe(self.add_virtual_target()); + self.assert_bool(b); + b + } + + /// Add a virtual target and register it as a public input. + pub fn add_virtual_public_input(&mut self) -> Target { + let t = self.add_virtual_target(); + self.register_public_input(t); + t + } + + pub fn add_virtual_public_input_arr(&mut self) -> [Target; N] { + let ts = [0; N].map(|_| self.add_virtual_target()); + self.register_public_inputs(&ts); + ts + } + + pub fn add_virtual_verifier_data(&mut self, cap_height: usize) -> VerifierCircuitTarget { + VerifierCircuitTarget { + constants_sigmas_cap: self.add_virtual_cap(cap_height), + circuit_digest: self.add_virtual_hash(), + } + } + + /// Add a virtual verifier data, register it as a public input and set it to + /// `self.verifier_data_public_input`. + /// + /// **WARNING**: Do not register any public input after calling this! + // TODO: relax this + pub fn add_verifier_data_public_inputs(&mut self) -> VerifierCircuitTarget { + assert!( + self.verifier_data_public_input.is_none(), + "add_verifier_data_public_inputs only needs to be called once" + ); + + let verifier_data = self.add_virtual_verifier_data(self.config.fri_config.cap_height); + // The verifier data are public inputs. + self.register_public_inputs(&verifier_data.circuit_digest.elements); + for i in 0..self.config.fri_config.num_cap_elements() { + self.register_public_inputs(&verifier_data.constants_sigmas_cap.0[i].elements); + } + + self.verifier_data_public_input = Some(verifier_data.clone()); + verifier_data + } + + /// Adds a gate to the circuit, and returns its index. + pub fn add_gate>(&mut self, gate_type: G, mut constants: Vec) -> usize { + self.check_gate_compatibility(&gate_type); + + assert!( + constants.len() <= gate_type.num_constants(), + "Too many constants." + ); + constants.resize(gate_type.num_constants(), F::ZERO); + + let row = self.gate_instances.len(); + + self.constant_generators + .extend(gate_type.extra_constant_wires().into_iter().map( + |(constant_index, wire_index)| ConstantGenerator { + row, + constant_index, + wire_index, + constant: F::ZERO, // Placeholder; will be replaced later. + }, + )); + + // Note that we can't immediately add this gate's generators, because the list + // of constants could be modified later, i.e. in the case of + // `ConstantGate`. We will add them later in `build` instead. + + // Register this gate type if we haven't seen it before. + let gate_ref = GateRef::new(gate_type); + self.gates.insert(gate_ref.clone()); + + self.gate_instances.push(GateInstance { + gate_ref, + constants, + }); + + row + } + + fn check_gate_compatibility>(&self, gate: &G) { + assert!( + gate.num_wires() <= self.config.num_wires, + "{:?} requires {} wires, but our CircuitConfig has only {}", + gate.id(), + gate.num_wires(), + self.config.num_wires + ); + assert!( + gate.num_constants() <= self.config.num_constants, + "{:?} requires {} constants, but our CircuitConfig has only {}", + gate.id(), + gate.num_constants(), + self.config.num_constants + ); + } + + /// Adds a gate type to the set of gates to be used in this circuit. This + /// can be useful in conditional recursion to uniformize the set of + /// gates of the different circuits. + pub fn add_gate_to_gate_set(&mut self, gate: GateRef) { + self.gates.insert(gate); + } + + /// Adds a generator which will copy `src` to `dst`. + pub fn generate_copy(&mut self, src: Target, dst: Target) { + self.add_simple_generator(CopyGenerator { src, dst }); + } + + /// Uses Plonk's permutation argument to require that two elements be equal. + /// Both elements must be routable, otherwise this method will panic. + /// + /// For an example of usage, see [`CircuitBuilder::assert_one()`]. + pub fn connect(&mut self, x: Target, y: Target) { + assert!( + x.is_routable(&self.config), + "Tried to route a wire that isn't routable" + ); + assert!( + y.is_routable(&self.config), + "Tried to route a wire that isn't routable" + ); + self.copy_constraints + .push(CopyConstraint::new((x, y), self.context_log.open_stack())); + } + + /// Enforces that two [`ExtensionTarget`] underlying values are equal. + pub fn connect_extension(&mut self, src: ExtensionTarget, dst: ExtensionTarget) { + for i in 0..D { + self.connect(src.0[i], dst.0[i]); + } + } + + /// Enforces that a routable `Target` value is 0, using Plonk's permutation + /// argument. + pub fn assert_zero(&mut self, x: Target) { + let zero = self.zero(); + self.connect(x, zero); + } + + /// Enforces that a routable `Target` value is 1, using Plonk's permutation + /// argument. + /// + /// # Example + /// + /// Let say the circuit contains a target `a`, and a target `b` as public + /// input so that the prover can non-deterministically compute the + /// multiplicative inverse of `a` when generating a proof. + /// + /// One can then add the following constraint in the circuit to enforce that + /// the value provided by the prover is correct: + /// + /// ```ignore + /// let c = builder.mul(a, b); + /// builder.assert_one(c); + /// ``` + pub fn assert_one(&mut self, x: Target) { + let one = self.one(); + self.connect(x, one); + } + + pub fn add_generators(&mut self, generators: Vec>) { + self.generators.extend(generators); + } + + pub fn add_simple_generator>(&mut self, generator: G) { + self.generators + .push(WitnessGeneratorRef::new(generator.adapter())); + } + + /// Returns a routable target with a value of 0. + pub fn zero(&mut self) -> Target { + self.constant(F::ZERO) + } + + /// Returns a routable target with a value of 1. + pub fn one(&mut self) -> Target { + self.constant(F::ONE) + } + + /// Returns a routable target with a value of 2. + pub fn two(&mut self) -> Target { + self.constant(F::TWO) + } + + /// Returns a routable target with a value of `order() - 1`. + pub fn neg_one(&mut self) -> Target { + self.constant(F::NEG_ONE) + } + + /// Returns a routable boolean target set to false. + pub fn _false(&mut self) -> BoolTarget { + BoolTarget::new_unsafe(self.zero()) + } + + /// Returns a routable boolean target set to true. + pub fn _true(&mut self) -> BoolTarget { + BoolTarget::new_unsafe(self.one()) + } + + /// Returns a routable target with the given constant value. + pub fn constant(&mut self, c: F) -> Target { + if let Some(&target) = self.constants_to_targets.get(&c) { + // We already have a wire for this constant. + return target; + } + + let target = self.add_virtual_target(); + self.constants_to_targets.insert(c, target); + self.targets_to_constants.insert(target, c); + + target + } + + /// Returns a vector of routable targets with the given constant values. + pub fn constants(&mut self, constants: &[F]) -> Vec { + constants.iter().map(|&c| self.constant(c)).collect() + } + + /// Returns a routable target with the given constant boolean value. + pub fn constant_bool(&mut self, b: bool) -> BoolTarget { + if b { + self._true() + } else { + self._false() + } + } + + /// Returns a routable [`HashOutTarget`]. + pub fn constant_hash(&mut self, h: HashOut) -> HashOutTarget { + HashOutTarget { + elements: h.elements.map(|x| self.constant(x)), + } + } + + /// Returns a routable [`MerkleCapTarget`]. + pub fn constant_merkle_cap>>( + &mut self, + cap: &MerkleCap, + ) -> MerkleCapTarget { + MerkleCapTarget(cap.0.iter().map(|h| self.constant_hash(*h)).collect()) + } + + pub fn constant_verifier_data>( + &mut self, + verifier_data: &VerifierOnlyCircuitData, + ) -> VerifierCircuitTarget + where + C::Hasher: AlgebraicHasher, + { + VerifierCircuitTarget { + constants_sigmas_cap: self.constant_merkle_cap(&verifier_data.constants_sigmas_cap), + circuit_digest: self.constant_hash(verifier_data.circuit_digest), + } + } + + /// If the given target is a constant (i.e. it was created by the + /// `constant(F)` method), returns its constant value. Otherwise, + /// returns `None`. + pub fn target_as_constant(&self, target: Target) -> Option { + self.targets_to_constants.get(&target).cloned() + } + + /// If the given [`ExtensionTarget`] is a constant (i.e. it was created by + /// the `constant_extension(F)` method), returns its constant value. + /// Otherwise, returns `None`. + pub fn target_as_constant_ext(&self, target: ExtensionTarget) -> Option { + // Get a Vec of any coefficients that are constant. If we end up with exactly D + // of them, then the `ExtensionTarget` as a whole is constant. + let const_coeffs: Vec = target + .0 + .iter() + .filter_map(|&t| self.target_as_constant(t)) + .collect(); + + if let Ok(d_const_coeffs) = const_coeffs.try_into() { + Some(F::Extension::from_basefield_array(d_const_coeffs)) + } else { + None + } + } + + pub fn push_context(&mut self, level: log::Level, ctx: &str) { + self.context_log.push(ctx, level, self.num_gates()); + } + + pub fn pop_context(&mut self) { + self.context_log.pop(self.num_gates()); + } + + /// Returns the total number of LUTs. + pub fn get_luts_length(&self) -> usize { + self.luts.len() + } + + /// Gets the length of the LUT at index `idx`. + pub fn get_luts_idx_length(&self, idx: usize) -> usize { + assert!( + idx < self.luts.len(), + "index idx: {} greater than the total number of created LUTS: {}", + idx, + self.luts.len() + ); + self.luts[idx].len() + } + + /// Checks whether a LUT is already stored in `self.luts` + pub fn is_stored(&self, lut: LookupTable) -> Option { + self.luts.iter().position(|elt| *elt == lut) + } + + /// Returns the LUT at index `idx`. + pub fn get_lut(&self, idx: usize) -> LookupTable { + assert!( + idx < self.luts.len(), + "index idx: {} greater than the total number of created LUTS: {}", + idx, + self.luts.len() + ); + self.luts[idx].clone() + } + + /// Generates a LUT from a function. + pub fn get_lut_from_fn(f: fn(T) -> T, inputs: &[T]) -> Vec<(T, T)> + where + T: Copy, + { + inputs.iter().map(|&input| (input, f(input))).collect() + } + + /// Given a function `f: fn(u16) -> u16`, adds a LUT to the circuit builder. + pub fn update_luts_from_fn(&mut self, f: fn(u16) -> u16, inputs: &[u16]) -> usize { + let lut = Arc::new(Self::get_lut_from_fn::(f, inputs)); + + // If the LUT `lut` is already stored in `self.luts`, return its index. + // Otherwise, append `table` to `self.luts` and return its index. + if let Some(idx) = self.is_stored(lut.clone()) { + idx + } else { + self.luts.push(lut); + self.lut_to_lookups.push(vec![]); + assert!(self.luts.len() == self.lut_to_lookups.len()); + self.luts.len() - 1 + } + } + + /// Adds a table to the vector of LUTs in the circuit builder, given a list + /// of inputs and table values. + pub fn update_luts_from_table(&mut self, inputs: &[u16], table: &[u16]) -> usize { + assert!( + inputs.len() == table.len(), + "Inputs and table have incompatible lengths: {} and {}", + inputs.len(), + table.len() + ); + let pairs = inputs + .iter() + .copied() + .zip_eq(table.iter().copied()) + .collect(); + let lut: LookupTable = Arc::new(pairs); + + // If the LUT `lut` is already stored in `self.luts`, return its index. + // Otherwise, append `table` to `self.luts` and return its index. + if let Some(idx) = self.is_stored(lut.clone()) { + idx + } else { + self.luts.push(lut); + self.lut_to_lookups.push(vec![]); + assert!(self.luts.len() == self.lut_to_lookups.len()); + self.luts.len() - 1 + } + } + + /// Adds a table to the vector of LUTs in the circuit builder. + pub fn update_luts_from_pairs(&mut self, table: LookupTable) -> usize { + // If the LUT `table` is already stored in `self.luts`, return its index. + // Otherwise, append `table` to `self.luts` and return its index. + if let Some(idx) = self.is_stored(table.clone()) { + idx + } else { + self.luts.push(table); + self.lut_to_lookups.push(vec![]); + assert!(self.luts.len() == self.lut_to_lookups.len()); + self.luts.len() - 1 + } + } + + /// Find an available slot, of the form `(row, op)` for gate `G` using + /// parameters `params` and constants `constants`. Parameters are any + /// data used to differentiate which gate should be used for the given + /// operation. + pub fn find_slot + Clone>( + &mut self, + gate: G, + params: &[F], + constants: &[F], + ) -> (usize, usize) { + let num_gates = self.num_gates(); + let num_ops = gate.num_ops(); + let gate_ref = GateRef::new(gate.clone()); + let gate_slot = self.current_slots.entry(gate_ref.clone()).or_default(); + let slot = gate_slot.current_slot.get(params); + let (gate_idx, slot_idx) = if let Some(&s) = slot { + s + } else { + self.add_gate(gate, constants.to_vec()); + (num_gates, 0) + }; + let current_slot = &mut self.current_slots.get_mut(&gate_ref).unwrap().current_slot; + if slot_idx == num_ops - 1 { + // We've filled up the slots at this index. + current_slot.remove(params); + } else { + // Increment the slot operation index. + current_slot.insert(params.to_vec(), (gate_idx, slot_idx + 1)); + } + + (gate_idx, slot_idx) + } + + fn fri_params(&self, degree_bits: usize) -> FriParams { + self.config + .fri_config + .fri_params(degree_bits, self.config.zero_knowledge) + } + + /// The number of (base field) `arithmetic` operations that can be performed + /// in a single gate. + pub(crate) const fn num_base_arithmetic_ops_per_gate(&self) -> usize { + if self.config.use_base_arithmetic_gate { + ArithmeticGate::new_from_config(&self.config).num_ops + } else { + self.num_ext_arithmetic_ops_per_gate() + } + } + + /// The number of `arithmetic_extension` operations that can be performed in + /// a single gate. + pub(crate) const fn num_ext_arithmetic_ops_per_gate(&self) -> usize { + ArithmeticExtensionGate::::new_from_config(&self.config).num_ops + } + + /// The number of polynomial values that will be revealed per opening, both + /// for the "regular" polynomials and for the Z polynomials. Because + /// calculating these values involves a recursive dependence (the amount + /// of blinding depends on the degree, which depends on the blinding), + /// this function takes in an estimate of the degree. + fn num_blinding_gates(&self, degree_estimate: usize) -> (usize, usize) { + let degree_bits_estimate = log2_strict(degree_estimate); + let fri_queries = self.config.fri_config.num_query_rounds; + let arities: Vec = self + .fri_params(degree_bits_estimate) + .reduction_arity_bits + .iter() + .map(|x| 1 << x) + .collect(); + let total_fri_folding_points: usize = arities.iter().map(|x| x - 1).sum::(); + let final_poly_coeffs: usize = degree_estimate / arities.iter().product::(); + let fri_openings = fri_queries * (1 + D * total_fri_folding_points + D * final_poly_coeffs); + + // We add D for openings at zeta. + let regular_poly_openings = D + fri_openings; + // We add 2 * D for openings at zeta and g * zeta. + let z_openings = 2 * D + fri_openings; + + (regular_poly_openings, z_openings) + } + + /// The number of polynomial values that will be revealed per opening, both + /// for the "regular" polynomials (which are opened at only one + /// location) and for the Z polynomials (which are opened at two). + fn blinding_counts(&self) -> (usize, usize) { + let num_gates = self.gate_instances.len(); + let mut degree_estimate = 1 << log2_ceil(num_gates); + + loop { + let (regular_poly_openings, z_openings) = self.num_blinding_gates(degree_estimate); + + // For most polynomials, we add one random element to offset each opened value. + // But blinding Z is separate. For that, we add two random elements with a copy + // constraint between them. + let total_blinding_count = regular_poly_openings + 2 * z_openings; + + if num_gates + total_blinding_count <= degree_estimate { + return (regular_poly_openings, z_openings); + } + + // The blinding gates do not fit within our estimated degree; increase our + // estimate. + degree_estimate *= 2; + } + } + + fn blind_and_pad(&mut self) { + if self.config.zero_knowledge { + self.blind(); + } + + while !self.gate_instances.len().is_power_of_two() { + self.add_gate(NoopGate, vec![]); + } + } + + fn blind(&mut self) { + let (regular_poly_openings, z_openings) = self.blinding_counts(); + info!( + "Adding {} blinding terms for witness polynomials, and {}*2 for Z polynomials", + regular_poly_openings, z_openings + ); + + let num_routed_wires = self.config.num_routed_wires; + let num_wires = self.config.num_wires; + + // For each "regular" blinding factor, we simply add a no-op gate, and insert a + // random value for each wire. + for _ in 0..regular_poly_openings { + let row = self.add_gate(NoopGate, vec![]); + for w in 0..num_wires { + self.add_simple_generator(RandomValueGenerator { + target: Target::Wire(Wire { row, column: w }), + }); + } + } + + // For each z poly blinding factor, we add two new gates with the same random + // value, and enforce a copy constraint between them. + // See https://mirprotocol.org/blog/Adding-zero-knowledge-to-Plonk-Halo + for _ in 0..z_openings { + let gate_1 = self.add_gate(NoopGate, vec![]); + let gate_2 = self.add_gate(NoopGate, vec![]); + + for w in 0..num_routed_wires { + self.add_simple_generator(RandomValueGenerator { + target: Target::Wire(Wire { + row: gate_1, + column: w, + }), + }); + self.generate_copy( + Target::Wire(Wire { + row: gate_1, + column: w, + }), + Target::Wire(Wire { + row: gate_2, + column: w, + }), + ); + } + } + } + + fn constant_polys(&self) -> Vec> { + let max_constants = self + .gates + .iter() + .map(|g| g.0.num_constants()) + .max() + .unwrap(); + transpose( + &self + .gate_instances + .iter() + .map(|g| { + let mut consts = g.constants.clone(); + consts.resize(max_constants, F::ZERO); + consts + }) + .collect::>(), + ) + .into_iter() + .map(PolynomialValues::new) + .collect() + } + + fn sigma_vecs(&self, k_is: &[F], subgroup: &[F]) -> (Vec>, Forest) { + let degree = self.gate_instances.len(); + let degree_log = log2_strict(degree); + let config = &self.config; + let mut forest = Forest::new( + config.num_wires, + config.num_routed_wires, + degree, + self.virtual_target_index, + ); + + for gate in 0..degree { + for input in 0..config.num_wires { + forest.add(Target::Wire(Wire { + row: gate, + column: input, + })); + } + } + + for index in 0..self.virtual_target_index { + forest.add(Target::VirtualTarget { index }); + } + + for &CopyConstraint { pair: (a, b), .. } in &self.copy_constraints { + forest.merge(a, b); + } + + forest.compress_paths(); + + let wire_partition = forest.wire_partition(); + ( + wire_partition.get_sigma_polys(degree_log, k_is, subgroup), + forest, + ) + } + + pub fn print_gate_counts(&self, min_delta: usize) { + // Print gate counts for each context. + self.context_log + .filter(self.num_gates(), min_delta) + .print(self.num_gates()); + + // Print total count of each gate type. + debug!("Total gate counts:"); + for gate in self.gates.iter().cloned() { + let count = self + .gate_instances + .iter() + .filter(|inst| inst.gate_ref == gate) + .count(); + debug!("- {} instances of {}", count, gate.0.id()); + } + } + + /// In PLONK's permutation argument, there's a slight chance of division by + /// zero. We can mitigate this by randomizing some unused witness + /// elements, so if proving fails with division by zero, the next + /// attempt will have an (almost) independent chance of success. See . + fn randomize_unused_pi_wires(&mut self, pi_gate: usize) { + for wire in PublicInputGate::wires_public_inputs_hash().end..self.config.num_wires { + self.add_simple_generator(RandomValueGenerator { + target: Target::wire(pi_gate, wire), + }); + } + } + + /// Builds a "full circuit", with both prover and verifier data. + pub fn build_with_options>( + self, + commit_to_sigma: bool, + ) -> CircuitData { + let (circuit_data, success) = self.try_build_with_options(commit_to_sigma); + if !success { + panic!("Failed to build circuit"); + } + circuit_data + } + + pub fn try_build_with_options>( + mut self, + commit_to_sigma: bool, + ) -> (CircuitData, bool) { + let mut timing = TimingTree::new("preprocess", Level::Trace); + + #[cfg(feature = "std")] + let start = Instant::now(); + + let rate_bits = self.config.fri_config.rate_bits; + let cap_height = self.config.fri_config.cap_height; + // Total number of LUTs. + let num_luts = self.get_luts_length(); + // Hash the public inputs, and route them to a `PublicInputGate` which will + // enforce that those hash wires match the claimed public inputs. + let num_public_inputs = self.public_inputs.len(); + let public_inputs_hash = + self.hash_n_to_hash_no_pad::(self.public_inputs.clone()); + let pi_gate = self.add_gate(PublicInputGate, vec![]); + for (&hash_part, wire) in public_inputs_hash + .elements + .iter() + .zip(PublicInputGate::wires_public_inputs_hash()) + { + self.connect(hash_part, Target::wire(pi_gate, wire)) + } + self.randomize_unused_pi_wires(pi_gate); + + // Place LUT-related gates. + self.add_all_lookups(); + + // Make sure we have enough constant generators. If not, add a `ConstantGate`. + while self.constants_to_targets.len() > self.constant_generators.len() { + self.add_gate( + ConstantGate { + num_consts: self.config.num_constants, + }, + vec![], + ); + } + + // For each constant-target pair used in the circuit, use a constant generator + // to fill this target. + for ((c, t), mut const_gen) in self + .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. + self.gate_instances[const_gen.row].constants[const_gen.constant_index] = c; + // Generate a copy between the target and the routable wire. + self.connect(Target::wire(const_gen.row, const_gen.wire_index), t); + // Set the constant in the generator (it's initially set with a dummy value). + const_gen.set_constant(c); + self.add_simple_generator(const_gen); + } + + debug!( + "Degree before blinding & padding: {}", + self.gate_instances.len() + ); + self.blind_and_pad(); + let degree = self.gate_instances.len(); + debug!("Degree after blinding & padding: {}", degree); + let degree_bits = log2_strict(degree); + let fri_params = self.fri_params(degree_bits); + assert!( + fri_params.total_arities() <= degree_bits + rate_bits - cap_height, + "FRI total reduction arity is too large.", + ); + + let quotient_degree_factor = self.config.max_quotient_degree_factor; + let mut gates = self.gates.iter().cloned().collect::>(); + // Gates need to be sorted by their degrees (and ID to make the ordering + // deterministic) to compute the selector polynomials. + gates.sort_unstable_by_key(|g| (g.0.degree(), g.0.id())); + let (mut constant_vecs, selectors_info) = + selector_polynomials(&gates, &self.gate_instances, quotient_degree_factor + 1); + + // Get the lookup selectors. + let num_lookup_selectors = if num_luts != 0 { + let selector_lookups = + selectors_lookup(&gates, &self.gate_instances, &self.lookup_rows); + let selector_ends = selector_ends_lookups(&self.lookup_rows, &self.gate_instances); + let all_lookup_selectors = [selector_lookups, selector_ends].concat(); + let num_lookup_selectors = all_lookup_selectors.len(); + constant_vecs.extend(all_lookup_selectors); + num_lookup_selectors + } else { + 0 + }; + + constant_vecs.extend(self.constant_polys()); + let num_constants = constant_vecs.len(); + + let subgroup = F::two_adic_subgroup(degree_bits); + + let k_is = get_unique_coset_shifts(degree, self.config.num_routed_wires); + let (sigma_vecs, forest) = timed!( + timing, + "generate sigma polynomials", + self.sigma_vecs(&k_is, &subgroup) + ); + + // Precompute FFT roots. + let max_fft_points = 1 << (degree_bits + max(rate_bits, log2_ceil(quotient_degree_factor))); + let fft_root_table = fft_root_table(max_fft_points); + + let constants_sigmas_commitment = if commit_to_sigma { + let constants_sigmas_vecs = [constant_vecs, sigma_vecs.clone()].concat(); + PolynomialBatch::::from_values( + constants_sigmas_vecs, + rate_bits, + PlonkOracle::CONSTANTS_SIGMAS.blinding, + cap_height, + &mut timing, + Some(&fft_root_table), + ) + } else { + PolynomialBatch::::default() + }; + + // Map between gates where not all generators are used and the gate's number of + // used generators. + let incomplete_gates = self + .current_slots + .values() + .flat_map(|current_slot| current_slot.current_slot.values().copied()) + .collect::>(); + + // Add gate generators. + self.add_generators( + self.gate_instances + .iter() + .enumerate() + .flat_map(|(index, gate)| { + let mut gens = gate.gate_ref.0.generators(index, &gate.constants); + // Remove unused generators, if any. + if let Some(&op) = incomplete_gates.get(&index) { + gens.drain(op..); + } + gens + }) + .collect(), + ); + + // Index generator indices by their watched targets. + let mut generator_indices_by_watches = BTreeMap::new(); + for (i, generator) in self.generators.iter().enumerate() { + for watch in generator.0.watch_list() { + let watch_index = forest.target_index(watch); + let watch_rep_index = forest.parents[watch_index]; + generator_indices_by_watches + .entry(watch_rep_index) + .or_insert_with(Vec::new) + .push(i); + } + } + for indices in generator_indices_by_watches.values_mut() { + indices.dedup(); + indices.shrink_to_fit(); + } + + let num_gate_constraints = gates + .iter() + .map(|gate| gate.0.num_constraints()) + .max() + .expect("No gates?"); + + let num_partial_products = + num_partial_products(self.config.num_routed_wires, quotient_degree_factor); + + let lookup_degree = self.config.max_quotient_degree_factor - 1; + let num_lookup_polys = if num_luts == 0 { + 0 + } else { + // There is 1 RE polynomial and multiple Sum/LDC polynomials. + ceil_div_usize(LookupGate::num_slots(&self.config), lookup_degree) + 1 + }; + let constants_sigmas_cap = constants_sigmas_commitment.merkle_tree.cap.clone(); + let domain_separator = self.domain_separator.unwrap_or_default(); + let domain_separator_digest = C::Hasher::hash_pad(&domain_separator); + // TODO: This should also include an encoding of gate constraints. + let circuit_digest_parts = [ + constants_sigmas_cap.flatten(), + domain_separator_digest.to_vec(), + vec![ + F::from_canonical_usize(degree_bits), + /* Add other circuit data here */ + ], + ]; + let circuit_digest = C::Hasher::hash_no_pad(&circuit_digest_parts.concat()); + + let common = CommonCircuitData { + config: self.config, + fri_params, + gates, + selectors_info, + quotient_degree_factor, + num_gate_constraints, + num_constants, + num_public_inputs, + k_is, + num_partial_products, + num_lookup_polys, + num_lookup_selectors, + luts: self.luts, + }; + + let mut success = true; + + if let Some(goal_data) = self.goal_common_data { + if goal_data != common { + warn!("The expected circuit data passed to cyclic recursion method did not match the actual circuit"); + success = false; + } + } + + let prover_only = ProverOnlyCircuitData:: { + generators: self.generators, + generator_indices_by_watches, + constants_sigmas_commitment, + sigmas: transpose_poly_values(sigma_vecs), + subgroup, + public_inputs: self.public_inputs, + representative_map: forest.parents, + fft_root_table: Some(fft_root_table), + circuit_digest, + lookup_rows: self.lookup_rows.clone(), + lut_to_lookups: self.lut_to_lookups.clone(), + }; + + let verifier_only = VerifierOnlyCircuitData:: { + constants_sigmas_cap, + circuit_digest, + }; + + timing.print(); + #[cfg(feature = "std")] + debug!("Building circuit took {}s", start.elapsed().as_secs_f32()); + ( + CircuitData { + prover_only, + verifier_only, + common, + }, + success, + ) + } + + /// Builds a "full circuit", with both prover and verifier data. + pub fn build>(self) -> CircuitData { + self.build_with_options(true) + } + + pub fn mock_build>(self) -> MockCircuitData { + let circuit_data = self.build_with_options(false); + MockCircuitData { + prover_only: circuit_data.prover_only, + common: circuit_data.common, + } + } + /// Builds a "prover circuit", with data needed to generate proofs but not + /// verify them. + pub fn build_prover>(self) -> ProverCircuitData { + // TODO: Can skip parts of this. + let circuit_data = self.build::(); + circuit_data.prover_data() + } + + /// Builds a "verifier circuit", with data needed to verify proofs but not + /// generate them. + pub fn build_verifier>(self) -> VerifierCircuitData { + // TODO: Can skip parts of this. + let circuit_data = self.build::(); + circuit_data.verifier_data() + } +} diff --git a/plonky2/src/plonk/circuit_data.rs b/plonky2/src/plonk/circuit_data.rs new file mode 100644 index 000000000..0efcb1d2a --- /dev/null +++ b/plonky2/src/plonk/circuit_data.rs @@ -0,0 +1,693 @@ +//! Circuit data specific to the prover and the verifier. +//! +//! This module also defines a [`CircuitConfig`] to be customized +//! when building circuits for arbitrary statements. +//! +//! After building a circuit, one obtains an instance of [`CircuitData`]. +//! This contains both prover and verifier data, allowing to generate +//! proofs for the given circuit and verify them. +//! +//! Most of the [`CircuitData`] is actually prover-specific, and can be +//! extracted by calling [`CircuitData::prover_data`] method. +//! The verifier data can similarly be extracted by calling +//! [`CircuitData::verifier_data`]. This is useful to allow even small devices +//! to verify plonky2 proofs. + +#[cfg(not(feature = "std"))] +use alloc::{collections::BTreeMap, vec, vec::Vec}; +use core::ops::{Range, RangeFrom}; +#[cfg(feature = "std")] +use std::collections::BTreeMap; + +use anyhow::Result; +use serde::Serialize; + +use super::circuit_builder::LookupWire; +use crate::field::extension::Extendable; +use crate::field::fft::FftRootTable; +use crate::field::types::Field; +use crate::fri::oracle::PolynomialBatch; +use crate::fri::reduction_strategies::FriReductionStrategy; +use crate::fri::structure::{ + FriBatchInfo, FriBatchInfoTarget, FriInstanceInfo, FriInstanceInfoTarget, FriOracleInfo, + FriPolynomialInfo, +}; +use crate::fri::{FriConfig, FriParams}; +use crate::gates::gate::GateRef; +use crate::gates::lookup::Lookup; +use crate::gates::lookup_table::LookupTable; +use crate::gates::selectors::SelectorsInfo; +use crate::hash::hash_types::{HashOutTarget, MerkleCapTarget, RichField}; +use crate::hash::merkle_tree::MerkleCap; +use crate::iop::ext_target::ExtensionTarget; +use crate::iop::generator::{generate_partial_witness, WitnessGeneratorRef}; +use crate::iop::target::Target; +use crate::iop::witness::{PartialWitness, PartitionWitness}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::config::{GenericConfig, Hasher}; +use crate::plonk::plonk_common::PlonkOracle; +use crate::plonk::proof::{CompressedProofWithPublicInputs, ProofWithPublicInputs}; +use crate::plonk::prover::prove; +use crate::plonk::verifier::verify; +use crate::util::serialization::{ + Buffer, GateSerializer, IoResult, Read, WitnessGeneratorSerializer, Write, +}; +use crate::util::timing::TimingTree; + +/// Configuration to be used when building a circuit. This defines the shape of +/// the circuit as well as its targeted security level and sub-protocol (e.g. +/// FRI) parameters. +/// +/// It supports a [`Default`] implementation tailored for recursion with +/// Poseidon hash (of width 12) as internal hash function and FRI rate of 1/8. +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct CircuitConfig { + /// The number of wires available at each row. This corresponds to the + /// "width" of the circuit, and consists in the sum of routed wires and + /// advice wires. + pub num_wires: usize, + /// The number of routed wires, i.e. wires that will be involved in Plonk's + /// permutation argument. This allows copy constraints, i.e. enforcing + /// that two distant values in a circuit are equal. Non-routed wires are + /// called advice wires. + pub num_routed_wires: usize, + /// The number of constants that can be used per gate. If a gate requires + /// more constants than the config allows, the [`CircuitBuilder`] will + /// complain when trying to add this gate to its set of gates. + pub num_constants: usize, + /// Whether to use a dedicated gate for base field arithmetic, rather than + /// using a single gate for both base field and extension field + /// arithmetic. + pub use_base_arithmetic_gate: bool, + pub security_bits: usize, + /// The number of challenge points to generate, for IOPs that have soundness + /// errors of (roughly) `degree / |F|`. + pub num_challenges: usize, + /// A boolean to activate the zero-knowledge property. When this is set to + /// `false`, proofs *may* leak additional information. + pub zero_knowledge: bool, + /// A cap on the quotient polynomial's degree factor. The actual degree + /// factor is derived systematically, but will never exceed this value. + pub max_quotient_degree_factor: usize, + pub fri_config: FriConfig, +} + +impl Default for CircuitConfig { + fn default() -> Self { + Self::standard_recursion_config() + } +} + +impl CircuitConfig { + pub const fn num_advice_wires(&self) -> usize { + self.num_wires - self.num_routed_wires + } + + /// A typical recursion config, without zero-knowledge, targeting ~100 bit + /// security. + pub const fn standard_recursion_config() -> Self { + Self { + num_wires: 135, + num_routed_wires: 80, + num_constants: 2, + use_base_arithmetic_gate: true, + security_bits: 100, + num_challenges: 2, + zero_knowledge: false, + max_quotient_degree_factor: 8, + fri_config: FriConfig { + rate_bits: 3, + cap_height: 4, + proof_of_work_bits: 16, + reduction_strategy: FriReductionStrategy::ConstantArityBits(4, 5), + num_query_rounds: 28, + }, + } + } + + pub fn standard_ecc_config() -> Self { + Self { + num_wires: 136, + ..Self::standard_recursion_config() + } + } + + pub fn wide_ecc_config() -> Self { + Self { + num_wires: 234, + ..Self::standard_recursion_config() + } + } + + pub fn standard_recursion_zk_config() -> Self { + CircuitConfig { + zero_knowledge: true, + ..Self::standard_recursion_config() + } + } +} + +/// Mock circuit data to only do witness generation without generating a proof. +#[derive(Eq, PartialEq, Debug)] +pub struct MockCircuitData, C: GenericConfig, const D: usize> +{ + pub prover_only: ProverOnlyCircuitData, + pub common: CommonCircuitData, +} + +impl, C: GenericConfig, const D: usize> + MockCircuitData +{ + pub fn generate_witness(&self, inputs: PartialWitness) -> PartitionWitness { + generate_partial_witness::(inputs, &self.prover_only, &self.common) + } +} + +/// Circuit data required by the prover or the verifier. +#[derive(Eq, PartialEq, Debug)] +pub struct CircuitData, C: GenericConfig, const D: usize> { + pub prover_only: ProverOnlyCircuitData, + pub verifier_only: VerifierOnlyCircuitData, + pub common: CommonCircuitData, +} + +impl, C: GenericConfig, const D: usize> + CircuitData +{ + pub fn to_bytes( + &self, + gate_serializer: &dyn GateSerializer, + generator_serializer: &dyn WitnessGeneratorSerializer, + ) -> IoResult> { + let mut buffer = Vec::new(); + buffer.write_circuit_data(self, gate_serializer, generator_serializer)?; + Ok(buffer) + } + + pub fn from_bytes( + bytes: &[u8], + gate_serializer: &dyn GateSerializer, + generator_serializer: &dyn WitnessGeneratorSerializer, + ) -> IoResult { + let mut buffer = Buffer::new(bytes); + buffer.read_circuit_data(gate_serializer, generator_serializer) + } + + pub fn prove(&self, inputs: PartialWitness) -> Result> { + prove::( + &self.prover_only, + &self.common, + inputs, + &mut TimingTree::default(), + ) + } + + pub fn verify(&self, proof_with_pis: ProofWithPublicInputs) -> Result<()> { + verify::(proof_with_pis, &self.verifier_only, &self.common) + } + + pub fn verify_compressed( + &self, + compressed_proof_with_pis: CompressedProofWithPublicInputs, + ) -> Result<()> { + compressed_proof_with_pis.verify(&self.verifier_only, &self.common) + } + + pub fn compress( + &self, + proof: ProofWithPublicInputs, + ) -> Result> { + proof.compress(&self.verifier_only.circuit_digest, &self.common) + } + + pub fn decompress( + &self, + proof: CompressedProofWithPublicInputs, + ) -> Result> { + proof.decompress(&self.verifier_only.circuit_digest, &self.common) + } + + pub fn verifier_data(&self) -> VerifierCircuitData { + let CircuitData { + verifier_only, + common, + .. + } = self; + VerifierCircuitData { + verifier_only: verifier_only.clone(), + common: common.clone(), + } + } + + pub fn prover_data(self) -> ProverCircuitData { + let CircuitData { + prover_only, + common, + .. + } = self; + ProverCircuitData { + prover_only, + common, + } + } +} + +/// Circuit data required by the prover. This may be thought of as a proving +/// key, although it includes code for witness generation. +/// +/// The goal here is to make proof generation as fast as we can, rather than +/// making this prover structure as succinct as we can. Thus we include various +/// precomputed data which isn't strictly required, like LDEs of preprocessed +/// polynomials. If more succinctness was desired, we could construct a more +/// minimal prover structure and convert back and forth. +pub struct ProverCircuitData< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +> { + pub prover_only: ProverOnlyCircuitData, + pub common: CommonCircuitData, +} + +impl, C: GenericConfig, const D: usize> + ProverCircuitData +{ + pub fn to_bytes( + &self, + gate_serializer: &dyn GateSerializer, + generator_serializer: &dyn WitnessGeneratorSerializer, + ) -> IoResult> { + let mut buffer = Vec::new(); + buffer.write_prover_circuit_data(self, gate_serializer, generator_serializer)?; + Ok(buffer) + } + + pub fn from_bytes( + bytes: &[u8], + gate_serializer: &dyn GateSerializer, + generator_serializer: &dyn WitnessGeneratorSerializer, + ) -> IoResult { + let mut buffer = Buffer::new(bytes); + buffer.read_prover_circuit_data(gate_serializer, generator_serializer) + } + + pub fn prove(&self, inputs: PartialWitness) -> Result> { + prove::( + &self.prover_only, + &self.common, + inputs, + &mut TimingTree::default(), + ) + } +} + +/// Circuit data required by the prover. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct VerifierCircuitData< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +> { + pub verifier_only: VerifierOnlyCircuitData, + pub common: CommonCircuitData, +} + +impl, C: GenericConfig, const D: usize> + VerifierCircuitData +{ + pub fn to_bytes(&self, gate_serializer: &dyn GateSerializer) -> IoResult> { + let mut buffer = Vec::new(); + buffer.write_verifier_circuit_data(self, gate_serializer)?; + Ok(buffer) + } + + pub fn from_bytes( + bytes: Vec, + gate_serializer: &dyn GateSerializer, + ) -> IoResult { + let mut buffer = Buffer::new(&bytes); + buffer.read_verifier_circuit_data(gate_serializer) + } + + pub fn verify(&self, proof_with_pis: ProofWithPublicInputs) -> Result<()> { + verify::(proof_with_pis, &self.verifier_only, &self.common) + } + + pub fn verify_compressed( + &self, + compressed_proof_with_pis: CompressedProofWithPublicInputs, + ) -> Result<()> { + compressed_proof_with_pis.verify(&self.verifier_only, &self.common) + } +} + +/// Circuit data required by the prover, but not the verifier. +#[derive(Eq, PartialEq, Debug)] +pub struct ProverOnlyCircuitData< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +> { + pub generators: Vec>, + /// Generator indices (within the `Vec` above), indexed by the + /// representative of each target they watch. + pub generator_indices_by_watches: BTreeMap>, + /// Commitments to the constants polynomials and sigma polynomials. + pub constants_sigmas_commitment: PolynomialBatch, + /// The transpose of the list of sigma polynomials. + pub sigmas: Vec>, + /// Subgroup of order `degree`. + pub subgroup: Vec, + /// Targets to be made public. + pub public_inputs: Vec, + /// A map from each `Target`'s index to the index of its representative in + /// the disjoint-set forest. + pub representative_map: Vec, + /// Pre-computed roots for faster FFT. + pub fft_root_table: Option>, + /// A digest of the "circuit" (i.e. the instance, minus public inputs), + /// which can be used to seed Fiat-Shamir. + pub circuit_digest: <>::Hasher as Hasher>::Hash, + ///The concrete placement of the lookup gates for each lookup table index. + pub lookup_rows: Vec, + /// A vector of (looking_in, looking_out) pairs for for each lookup table + /// index. + pub lut_to_lookups: Vec, +} + +impl, C: GenericConfig, const D: usize> + ProverOnlyCircuitData +{ + pub fn to_bytes( + &self, + generator_serializer: &dyn WitnessGeneratorSerializer, + common_data: &CommonCircuitData, + ) -> IoResult> { + let mut buffer = Vec::new(); + buffer.write_prover_only_circuit_data(self, generator_serializer, common_data)?; + Ok(buffer) + } + + pub fn from_bytes( + bytes: &[u8], + generator_serializer: &dyn WitnessGeneratorSerializer, + common_data: &CommonCircuitData, + ) -> IoResult { + let mut buffer = Buffer::new(bytes); + buffer.read_prover_only_circuit_data(generator_serializer, common_data) + } +} + +/// Circuit data required by the verifier, but not the prover. +#[derive(Debug, Clone, Eq, PartialEq, Serialize)] +pub struct VerifierOnlyCircuitData, const D: usize> { + /// A commitment to each constant polynomial and each permutation + /// polynomial. + pub constants_sigmas_cap: MerkleCap, + /// A digest of the "circuit" (i.e. the instance, minus public inputs), + /// which can be used to seed Fiat-Shamir. + pub circuit_digest: <>::Hasher as Hasher>::Hash, +} + +impl, const D: usize> VerifierOnlyCircuitData { + pub fn to_bytes(&self) -> IoResult> { + let mut buffer = Vec::new(); + buffer.write_verifier_only_circuit_data(self)?; + Ok(buffer) + } + + pub fn from_bytes(bytes: Vec) -> IoResult { + let mut buffer = Buffer::new(&bytes); + buffer.read_verifier_only_circuit_data() + } +} + +/// Circuit data required by both the prover and the verifier. +#[derive(Debug, Clone, Eq, PartialEq, Serialize)] +pub struct CommonCircuitData, const D: usize> { + pub config: CircuitConfig, + + pub fri_params: FriParams, + + /// The types of gates used in this circuit, along with their prefixes. + pub gates: Vec>, + + /// Information on the circuit's selector polynomials. + pub selectors_info: SelectorsInfo, + + /// The degree of the PLONK quotient polynomial. + pub quotient_degree_factor: usize, + + /// The largest number of constraints imposed by any gate. + pub num_gate_constraints: usize, + + /// The number of constant wires. + pub num_constants: usize, + + pub num_public_inputs: usize, + + /// The `{k_i}` valued used in `S_ID_i` in Plonk's permutation argument. + pub k_is: Vec, + + /// The number of partial products needed to compute the `Z` polynomials. + pub num_partial_products: usize, + + /// The number of lookup polynomials. + pub num_lookup_polys: usize, + + /// The number of lookup selectors. + pub num_lookup_selectors: usize, + + /// The stored lookup tables. + pub luts: Vec, +} + +impl, const D: usize> CommonCircuitData { + pub fn to_bytes(&self, gate_serializer: &dyn GateSerializer) -> IoResult> { + let mut buffer = Vec::new(); + buffer.write_common_circuit_data(self, gate_serializer)?; + Ok(buffer) + } + + pub fn from_bytes( + bytes: Vec, + gate_serializer: &dyn GateSerializer, + ) -> IoResult { + let mut buffer = Buffer::new(&bytes); + buffer.read_common_circuit_data(gate_serializer) + } + + pub const fn degree_bits(&self) -> usize { + self.fri_params.degree_bits + } + + pub const fn degree(&self) -> usize { + 1 << self.degree_bits() + } + + pub const fn lde_size(&self) -> usize { + self.fri_params.lde_size() + } + + pub fn lde_generator(&self) -> F { + F::primitive_root_of_unity(self.degree_bits() + self.config.fri_config.rate_bits) + } + + pub fn constraint_degree(&self) -> usize { + self.gates + .iter() + .map(|g| g.0.degree()) + .max() + .expect("No gates?") + } + + pub const fn quotient_degree(&self) -> usize { + self.quotient_degree_factor * self.degree() + } + + /// Range of the constants polynomials in the `constants_sigmas_commitment`. + pub const fn constants_range(&self) -> Range { + 0..self.num_constants + } + + /// Range of the sigma polynomials in the `constants_sigmas_commitment`. + pub const fn sigmas_range(&self) -> Range { + self.num_constants..self.num_constants + self.config.num_routed_wires + } + + /// Range of the `z`s polynomials in the `zs_partial_products_commitment`. + pub const fn zs_range(&self) -> Range { + 0..self.config.num_challenges + } + + /// Range of the partial products polynomials in the + /// `zs_partial_products_lookup_commitment`. + pub const fn partial_products_range(&self) -> Range { + self.config.num_challenges..(self.num_partial_products + 1) * self.config.num_challenges + } + + /// Range of lookup polynomials in the + /// `zs_partial_products_lookup_commitment`. + pub const fn lookup_range(&self) -> RangeFrom { + self.num_zs_partial_products_polys().. + } + + /// Range of lookup polynomials needed for evaluation at `g * zeta`. + pub const fn next_lookup_range(&self, i: usize) -> Range { + self.num_zs_partial_products_polys() + i * self.num_lookup_polys + ..self.num_zs_partial_products_polys() + i * self.num_lookup_polys + 2 + } + + pub(crate) fn get_fri_instance(&self, zeta: F::Extension) -> FriInstanceInfo { + // All polynomials are opened at zeta. + let zeta_batch = FriBatchInfo { + point: zeta, + polynomials: self.fri_all_polys(), + }; + + // The Z polynomials are also opened at g * zeta. + let g = F::Extension::primitive_root_of_unity(self.degree_bits()); + let zeta_next = g * zeta; + let zeta_next_batch = FriBatchInfo { + point: zeta_next, + polynomials: self.fri_next_batch_polys(), + }; + + let openings = vec![zeta_batch, zeta_next_batch]; + FriInstanceInfo { + oracles: self.fri_oracles(), + batches: openings, + } + } + + pub(crate) fn get_fri_instance_target( + &self, + builder: &mut CircuitBuilder, + zeta: ExtensionTarget, + ) -> FriInstanceInfoTarget { + // All polynomials are opened at zeta. + let zeta_batch = FriBatchInfoTarget { + point: zeta, + polynomials: self.fri_all_polys(), + }; + + // The Z polynomials are also opened at g * zeta. + let g = F::primitive_root_of_unity(self.degree_bits()); + let zeta_next = builder.mul_const_extension(g, zeta); + let zeta_next_batch = FriBatchInfoTarget { + point: zeta_next, + polynomials: self.fri_next_batch_polys(), + }; + + let openings = vec![zeta_batch, zeta_next_batch]; + FriInstanceInfoTarget { + oracles: self.fri_oracles(), + batches: openings, + } + } + + fn fri_oracles(&self) -> Vec { + vec![ + FriOracleInfo { + num_polys: self.num_preprocessed_polys(), + blinding: PlonkOracle::CONSTANTS_SIGMAS.blinding, + }, + FriOracleInfo { + num_polys: self.config.num_wires, + blinding: PlonkOracle::WIRES.blinding, + }, + FriOracleInfo { + num_polys: self.num_zs_partial_products_polys() + self.num_all_lookup_polys(), + blinding: PlonkOracle::ZS_PARTIAL_PRODUCTS.blinding, + }, + FriOracleInfo { + num_polys: self.num_quotient_polys(), + blinding: PlonkOracle::QUOTIENT.blinding, + }, + ] + } + + fn fri_preprocessed_polys(&self) -> Vec { + FriPolynomialInfo::from_range( + PlonkOracle::CONSTANTS_SIGMAS.index, + 0..self.num_preprocessed_polys(), + ) + } + + pub(crate) const fn num_preprocessed_polys(&self) -> usize { + self.sigmas_range().end + } + + fn fri_wire_polys(&self) -> Vec { + let num_wire_polys = self.config.num_wires; + FriPolynomialInfo::from_range(PlonkOracle::WIRES.index, 0..num_wire_polys) + } + + fn fri_zs_partial_products_polys(&self) -> Vec { + FriPolynomialInfo::from_range( + PlonkOracle::ZS_PARTIAL_PRODUCTS.index, + 0..self.num_zs_partial_products_polys(), + ) + } + + pub(crate) const fn num_zs_partial_products_polys(&self) -> usize { + self.config.num_challenges * (1 + self.num_partial_products) + } + + /// Returns the total number of lookup polynomials. + pub(crate) const fn num_all_lookup_polys(&self) -> usize { + self.config.num_challenges * self.num_lookup_polys + } + fn fri_zs_polys(&self) -> Vec { + FriPolynomialInfo::from_range(PlonkOracle::ZS_PARTIAL_PRODUCTS.index, self.zs_range()) + } + + /// Returns polynomials that require evaluation at `zeta` and `g * zeta`. + fn fri_next_batch_polys(&self) -> Vec { + [self.fri_zs_polys(), self.fri_lookup_polys()].concat() + } + + fn fri_quotient_polys(&self) -> Vec { + FriPolynomialInfo::from_range(PlonkOracle::QUOTIENT.index, 0..self.num_quotient_polys()) + } + + /// Returns the information for lookup polynomials, i.e. the index within + /// the oracle and the indices of the polynomials within the commitment. + fn fri_lookup_polys(&self) -> Vec { + FriPolynomialInfo::from_range( + PlonkOracle::ZS_PARTIAL_PRODUCTS.index, + self.num_zs_partial_products_polys() + ..self.num_zs_partial_products_polys() + self.num_all_lookup_polys(), + ) + } + pub(crate) const fn num_quotient_polys(&self) -> usize { + self.config.num_challenges * self.quotient_degree_factor + } + + fn fri_all_polys(&self) -> Vec { + [ + self.fri_preprocessed_polys(), + self.fri_wire_polys(), + self.fri_zs_partial_products_polys(), + self.fri_quotient_polys(), + self.fri_lookup_polys(), + ] + .concat() + } +} + +/// The `Target` version of `VerifierCircuitData`, for use inside recursive +/// circuits. Note that this is intentionally missing certain fields, such as +/// `CircuitConfig`, because we support only a limited form of dynamic inner +/// circuits. We can't practically make things like the wire count dynamic, at +/// least not without setting a maximum wire count and paying for the worst +/// case. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct VerifierCircuitTarget { + /// A commitment to each constant polynomial and each permutation + /// polynomial. + pub constants_sigmas_cap: MerkleCapTarget, + /// A digest of the "circuit" (i.e. the instance, minus public inputs), + /// which can be used to seed Fiat-Shamir. + pub circuit_digest: HashOutTarget, +} diff --git a/plonky2/src/plonk/config.rs b/plonky2/src/plonk/config.rs new file mode 100644 index 000000000..a7801c788 --- /dev/null +++ b/plonky2/src/plonk/config.rs @@ -0,0 +1,129 @@ +//! Hashing configuration to be used when building a circuit. +//! +//! This module defines a [`Hasher`] trait as well as its recursive +//! counterpart [`AlgebraicHasher`] for in-circuit hashing. It also +//! provides concrete configurations, one fully recursive leveraging +//! the Poseidon hash function both internally and natively, and one +//! mixing Poseidon internally and truncated Keccak externally. + +#[cfg(not(feature = "std"))] +use alloc::{vec, vec::Vec}; +use core::fmt::Debug; + +use serde::de::DeserializeOwned; +use serde::Serialize; + +use crate::field::extension::quadratic::QuadraticExtension; +use crate::field::extension::{Extendable, FieldExtension}; +use crate::field::goldilocks_field::GoldilocksField; +use crate::hash::hash_types::{HashOut, RichField}; +use crate::hash::hashing::PlonkyPermutation; +use crate::hash::keccak::KeccakHash; +use crate::hash::poseidon::PoseidonHash; +use crate::iop::target::{BoolTarget, Target}; +use crate::plonk::circuit_builder::CircuitBuilder; + +pub trait GenericHashOut: + Copy + Clone + Debug + Eq + PartialEq + Send + Sync + Serialize + DeserializeOwned +{ + fn to_bytes(&self) -> Vec; + fn from_bytes(bytes: &[u8]) -> Self; + + fn to_vec(&self) -> Vec; +} + +/// Trait for hash functions. +pub trait Hasher: Sized + Copy + Debug + Eq + PartialEq { + /// Size of `Hash` in bytes. + const HASH_SIZE: usize; + + /// Hash Output + type Hash: GenericHashOut; + + /// Permutation used in the sponge construction. + type Permutation: PlonkyPermutation; + + /// Hash a message without any padding step. Note that this can enable + /// length-extension attacks. However, it is still collision-resistant + /// in cases where the input has a fixed length. + fn hash_no_pad(input: &[F]) -> Self::Hash; + + /// Pad the message using the `pad10*1` rule, then hash it. + fn hash_pad(input: &[F]) -> Self::Hash { + let mut padded_input = input.to_vec(); + padded_input.push(F::ONE); + while (padded_input.len() + 1) % Self::Permutation::RATE != 0 { + padded_input.push(F::ZERO); + } + padded_input.push(F::ONE); + Self::hash_no_pad(&padded_input) + } + + /// Hash the slice if necessary to reduce its length to ~256 bits. If it + /// already fits, this is a no-op. + fn hash_or_noop(inputs: &[F]) -> Self::Hash { + if inputs.len() * 8 <= Self::HASH_SIZE { + let mut inputs_bytes = vec![0u8; Self::HASH_SIZE]; + for i in 0..inputs.len() { + inputs_bytes[i * 8..(i + 1) * 8] + .copy_from_slice(&inputs[i].to_canonical_u64().to_le_bytes()); + } + Self::Hash::from_bytes(&inputs_bytes) + } else { + Self::hash_no_pad(inputs) + } + } + + fn two_to_one(left: Self::Hash, right: Self::Hash) -> Self::Hash; +} + +/// Trait for algebraic hash functions, built from a permutation using the +/// sponge construction. +pub trait AlgebraicHasher: Hasher> { + type AlgebraicPermutation: PlonkyPermutation; + + /// Circuit to conditionally swap two chunks of the inputs (useful in + /// verifying Merkle proofs), then apply the permutation. + fn permute_swapped( + inputs: Self::AlgebraicPermutation, + swap: BoolTarget, + builder: &mut CircuitBuilder, + ) -> Self::AlgebraicPermutation + where + F: RichField + Extendable; +} + +/// Generic configuration trait. +pub trait GenericConfig: + Debug + Clone + Sync + Sized + Send + Eq + PartialEq +{ + /// Main field. + type F: RichField + Extendable; + /// Field extension of degree D of the main field. + type FE: FieldExtension; + /// Hash function used for building Merkle trees. + type Hasher: Hasher; + /// Algebraic hash function used for the challenger and hashing public + /// inputs. + type InnerHasher: AlgebraicHasher; +} + +/// Configuration using Poseidon over the Goldilocks field. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize)] +pub struct PoseidonGoldilocksConfig; +impl GenericConfig<2> for PoseidonGoldilocksConfig { + type F = GoldilocksField; + type FE = QuadraticExtension; + type Hasher = PoseidonHash; + type InnerHasher = PoseidonHash; +} + +/// Configuration using truncated Keccak over the Goldilocks field. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct KeccakGoldilocksConfig; +impl GenericConfig<2> for KeccakGoldilocksConfig { + type F = GoldilocksField; + type FE = QuadraticExtension; + type Hasher = KeccakHash<25>; + type InnerHasher = PoseidonHash; +} diff --git a/plonky2/src/plonk/copy_constraint.rs b/plonky2/src/plonk/copy_constraint.rs new file mode 100644 index 000000000..cf7a6a19a --- /dev/null +++ b/plonky2/src/plonk/copy_constraint.rs @@ -0,0 +1,25 @@ +#[cfg(not(feature = "std"))] +use alloc::string::String; + +use crate::iop::target::Target; + +/// A named copy constraint. +pub struct CopyConstraint { + pub pair: (Target, Target), + pub name: String, +} + +impl From<(Target, Target)> for CopyConstraint { + fn from(pair: (Target, Target)) -> Self { + Self { + pair, + name: String::new(), + } + } +} + +impl CopyConstraint { + pub const fn new(pair: (Target, Target), name: String) -> Self { + Self { pair, name } + } +} diff --git a/plonky2/src/plonk/get_challenges.rs b/plonky2/src/plonk/get_challenges.rs new file mode 100644 index 000000000..a3986e57d --- /dev/null +++ b/plonky2/src/plonk/get_challenges.rs @@ -0,0 +1,371 @@ +#[cfg(not(feature = "std"))] +use alloc::{vec, vec::Vec}; + +use hashbrown::HashSet; + +use super::circuit_builder::NUM_COINS_LOOKUP; +use crate::field::extension::Extendable; +use crate::field::polynomial::PolynomialCoeffs; +use crate::fri::proof::{CompressedFriProof, FriChallenges, FriProof, FriProofTarget}; +use crate::fri::verifier::{compute_evaluation, fri_combine_initial, PrecomputedReducedOpenings}; +use crate::gadgets::polynomial::PolynomialCoeffsExtTarget; +use crate::hash::hash_types::{HashOutTarget, MerkleCapTarget, RichField}; +use crate::hash::merkle_tree::MerkleCap; +use crate::iop::challenger::{Challenger, RecursiveChallenger}; +use crate::iop::target::Target; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::CommonCircuitData; +use crate::plonk::config::{AlgebraicHasher, GenericConfig, Hasher}; +use crate::plonk::proof::{ + CompressedProof, CompressedProofWithPublicInputs, FriInferredElements, OpeningSet, + OpeningSetTarget, Proof, ProofChallenges, ProofChallengesTarget, ProofTarget, + ProofWithPublicInputs, ProofWithPublicInputsTarget, +}; +use crate::util::reverse_bits; + +fn get_challenges, C: GenericConfig, const D: usize>( + public_inputs_hash: <>::InnerHasher as Hasher>::Hash, + wires_cap: &MerkleCap, + plonk_zs_partial_products_cap: &MerkleCap, + quotient_polys_cap: &MerkleCap, + openings: &OpeningSet, + commit_phase_merkle_caps: &[MerkleCap], + final_poly: &PolynomialCoeffs, + pow_witness: F, + circuit_digest: &<>::Hasher as Hasher>::Hash, + common_data: &CommonCircuitData, +) -> anyhow::Result> { + let config = &common_data.config; + let num_challenges = config.num_challenges; + + let mut challenger = Challenger::::new(); + let has_lookup = common_data.num_lookup_polys != 0; + + // Observe the instance. + challenger.observe_hash::(*circuit_digest); + challenger.observe_hash::(public_inputs_hash); + + challenger.observe_cap::(wires_cap); + let plonk_betas = challenger.get_n_challenges(num_challenges); + let plonk_gammas = challenger.get_n_challenges(num_challenges); + + // If there are lookups in the circuit, we should get delta challenges as well. + // But we can use the already generated `plonk_betas` and `plonk_gammas` as the + // first `plonk_deltas` challenges. + let plonk_deltas = if has_lookup { + let num_lookup_challenges = NUM_COINS_LOOKUP * num_challenges; + let mut deltas = Vec::with_capacity(num_lookup_challenges); + let num_additional_challenges = num_lookup_challenges - 2 * num_challenges; + let additional = challenger.get_n_challenges(num_additional_challenges); + deltas.extend(&plonk_betas); + deltas.extend(&plonk_gammas); + deltas.extend(additional); + deltas + } else { + vec![] + }; + + // `plonk_zs_partial_products_cap` also contains the commitment to lookup + // polynomials. + challenger.observe_cap::(plonk_zs_partial_products_cap); + let plonk_alphas = challenger.get_n_challenges(num_challenges); + + challenger.observe_cap::(quotient_polys_cap); + let plonk_zeta = challenger.get_extension_challenge::(); + + challenger.observe_openings(&openings.to_fri_openings()); + + Ok(ProofChallenges { + plonk_betas, + plonk_gammas, + plonk_alphas, + plonk_deltas, + plonk_zeta, + fri_challenges: challenger.fri_challenges::( + commit_phase_merkle_caps, + final_poly, + pow_witness, + common_data.degree_bits(), + &config.fri_config, + ), + }) +} + +impl, C: GenericConfig, const D: usize> + ProofWithPublicInputs +{ + pub(crate) fn fri_query_indices( + &self, + circuit_digest: &<>::Hasher as Hasher>::Hash, + common_data: &CommonCircuitData, + ) -> anyhow::Result> { + Ok(self + .get_challenges(self.get_public_inputs_hash(), circuit_digest, common_data)? + .fri_challenges + .fri_query_indices) + } + + /// Computes all Fiat-Shamir challenges used in the Plonk proof. + pub fn get_challenges( + &self, + public_inputs_hash: <>::InnerHasher as Hasher>::Hash, + circuit_digest: &<>::Hasher as Hasher>::Hash, + common_data: &CommonCircuitData, + ) -> anyhow::Result> { + let Proof { + wires_cap, + plonk_zs_partial_products_cap, + quotient_polys_cap, + openings, + opening_proof: + FriProof { + commit_phase_merkle_caps, + final_poly, + pow_witness, + .. + }, + } = &self.proof; + + get_challenges::( + public_inputs_hash, + wires_cap, + plonk_zs_partial_products_cap, + quotient_polys_cap, + openings, + commit_phase_merkle_caps, + final_poly, + *pow_witness, + circuit_digest, + common_data, + ) + } +} + +impl, C: GenericConfig, const D: usize> + CompressedProofWithPublicInputs +{ + /// Computes all Fiat-Shamir challenges used in the Plonk proof. + pub(crate) fn get_challenges( + &self, + public_inputs_hash: <>::InnerHasher as Hasher>::Hash, + circuit_digest: &<>::Hasher as Hasher>::Hash, + common_data: &CommonCircuitData, + ) -> anyhow::Result> { + let CompressedProof { + wires_cap, + plonk_zs_partial_products_cap, + quotient_polys_cap, + openings, + opening_proof: + CompressedFriProof { + commit_phase_merkle_caps, + final_poly, + pow_witness, + .. + }, + } = &self.proof; + + get_challenges::( + public_inputs_hash, + wires_cap, + plonk_zs_partial_products_cap, + quotient_polys_cap, + openings, + commit_phase_merkle_caps, + final_poly, + *pow_witness, + circuit_digest, + common_data, + ) + } + + /// Computes all coset elements that can be inferred in the FRI reduction + /// steps. + pub(crate) fn get_inferred_elements( + &self, + challenges: &ProofChallenges, + common_data: &CommonCircuitData, + ) -> FriInferredElements { + let ProofChallenges { + plonk_zeta, + fri_challenges: + FriChallenges { + fri_alpha, + fri_betas, + fri_query_indices, + .. + }, + .. + } = challenges; + let mut fri_inferred_elements = Vec::new(); + // Holds the indices that have already been seen at each reduction depth. + let mut seen_indices_by_depth = + vec![HashSet::new(); common_data.fri_params.reduction_arity_bits.len()]; + let precomputed_reduced_evals = PrecomputedReducedOpenings::from_os_and_alpha( + &self.proof.openings.to_fri_openings(), + *fri_alpha, + ); + let log_n = common_data.degree_bits() + common_data.config.fri_config.rate_bits; + // Simulate the proof verification and collect the inferred elements. + // The content of the loop is basically the same as the + // `fri_verifier_query_round` function. + for &(mut x_index) in fri_query_indices { + let mut subgroup_x = F::MULTIPLICATIVE_GROUP_GENERATOR + * F::primitive_root_of_unity(log_n).exp_u64(reverse_bits(x_index, log_n) as u64); + let mut old_eval = fri_combine_initial::( + &common_data.get_fri_instance(*plonk_zeta), + &self + .proof + .opening_proof + .query_round_proofs + .initial_trees_proofs[&x_index], + *fri_alpha, + subgroup_x, + &precomputed_reduced_evals, + &common_data.fri_params, + ); + for (i, &arity_bits) in common_data + .fri_params + .reduction_arity_bits + .iter() + .enumerate() + { + let coset_index = x_index >> arity_bits; + if !seen_indices_by_depth[i].insert(coset_index) { + // If this index has already been seen, we can skip the rest of the reductions. + break; + } + fri_inferred_elements.push(old_eval); + let arity = 1 << arity_bits; + let mut evals = self.proof.opening_proof.query_round_proofs.steps[i][&coset_index] + .evals + .clone(); + let x_index_within_coset = x_index & (arity - 1); + evals.insert(x_index_within_coset, old_eval); + old_eval = compute_evaluation( + subgroup_x, + x_index_within_coset, + arity_bits, + &evals, + fri_betas[i], + ); + subgroup_x = subgroup_x.exp_power_of_2(arity_bits); + x_index = coset_index; + } + } + FriInferredElements(fri_inferred_elements) + } +} + +impl, const D: usize> CircuitBuilder { + fn get_challenges>( + &mut self, + public_inputs_hash: HashOutTarget, + wires_cap: &MerkleCapTarget, + plonk_zs_partial_products_cap: &MerkleCapTarget, + quotient_polys_cap: &MerkleCapTarget, + openings: &OpeningSetTarget, + commit_phase_merkle_caps: &[MerkleCapTarget], + final_poly: &PolynomialCoeffsExtTarget, + pow_witness: Target, + inner_circuit_digest: HashOutTarget, + inner_common_data: &CommonCircuitData, + ) -> ProofChallengesTarget + where + C::Hasher: AlgebraicHasher, + { + let config = &inner_common_data.config; + let num_challenges = config.num_challenges; + + let mut challenger = RecursiveChallenger::::new(self); + let has_lookup = inner_common_data.num_lookup_polys != 0; + + // Observe the instance. + challenger.observe_hash(&inner_circuit_digest); + challenger.observe_hash(&public_inputs_hash); + + challenger.observe_cap(wires_cap); + + let plonk_betas = challenger.get_n_challenges(self, num_challenges); + let plonk_gammas = challenger.get_n_challenges(self, num_challenges); + + // If there are lookups in the circuit, we should get delta challenges as well. + // But we can use the already generated `plonk_betas` and `plonk_gammas` as the + // first `plonk_deltas` challenges. + let plonk_deltas = if has_lookup { + let num_lookup_challenges = NUM_COINS_LOOKUP * num_challenges; + let mut deltas = Vec::with_capacity(num_lookup_challenges); + let num_additional_challenges = num_lookup_challenges - 2 * num_challenges; + let additional = challenger.get_n_challenges(self, num_additional_challenges); + deltas.extend(&plonk_betas); + deltas.extend(&plonk_gammas); + deltas.extend(additional); + deltas + } else { + vec![] + }; + + challenger.observe_cap(plonk_zs_partial_products_cap); + let plonk_alphas = challenger.get_n_challenges(self, num_challenges); + + challenger.observe_cap(quotient_polys_cap); + let plonk_zeta = challenger.get_extension_challenge(self); + + challenger.observe_openings(&openings.to_fri_openings()); + + ProofChallengesTarget { + plonk_betas, + plonk_gammas, + plonk_alphas, + plonk_deltas, + plonk_zeta, + fri_challenges: challenger.fri_challenges( + self, + commit_phase_merkle_caps, + final_poly, + pow_witness, + &inner_common_data.config.fri_config, + ), + } + } +} + +impl ProofWithPublicInputsTarget { + pub(crate) fn get_challenges, C: GenericConfig>( + &self, + builder: &mut CircuitBuilder, + public_inputs_hash: HashOutTarget, + inner_circuit_digest: HashOutTarget, + inner_common_data: &CommonCircuitData, + ) -> ProofChallengesTarget + where + C::Hasher: AlgebraicHasher, + { + let ProofTarget { + wires_cap, + plonk_zs_partial_products_cap, + quotient_polys_cap, + openings, + opening_proof: + FriProofTarget { + commit_phase_merkle_caps, + final_poly, + pow_witness, + .. + }, + } = &self.proof; + + builder.get_challenges::( + public_inputs_hash, + wires_cap, + plonk_zs_partial_products_cap, + quotient_polys_cap, + openings, + commit_phase_merkle_caps, + final_poly, + *pow_witness, + inner_circuit_digest, + inner_common_data, + ) + } +} diff --git a/plonky2/src/plonk/mod.rs b/plonky2/src/plonk/mod.rs new file mode 100644 index 000000000..c6d7b582c --- /dev/null +++ b/plonky2/src/plonk/mod.rs @@ -0,0 +1,19 @@ +//! plonky2 proving system. +//! +//! This module also defines the +//! [CircuitBuilder](circuit_builder::CircuitBuilder) structure, used to build +//! custom plonky2 circuits satisfying arbitrary statements. + +pub mod circuit_builder; +pub mod circuit_data; +pub mod config; +pub(crate) mod copy_constraint; +mod get_challenges; +pub(crate) mod permutation_argument; +pub mod plonk_common; +pub mod proof; +pub mod prover; +mod validate_shape; +pub(crate) mod vanishing_poly; +pub mod vars; +pub mod verifier; diff --git a/plonky2/src/plonk/permutation_argument.rs b/plonky2/src/plonk/permutation_argument.rs new file mode 100644 index 000000000..4f6170c92 --- /dev/null +++ b/plonky2/src/plonk/permutation_argument.rs @@ -0,0 +1,161 @@ +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + +use hashbrown::HashMap; +use plonky2_maybe_rayon::*; + +use crate::field::polynomial::PolynomialValues; +use crate::field::types::Field; +use crate::iop::target::Target; +use crate::iop::wire::Wire; + +/// Disjoint Set Forest data-structure following . +pub struct Forest { + /// A map of parent pointers, stored as indices. + pub(crate) parents: Vec, + + num_wires: usize, + num_routed_wires: usize, + degree: usize, +} + +impl Forest { + pub fn new( + num_wires: usize, + num_routed_wires: usize, + degree: usize, + num_virtual_targets: usize, + ) -> Self { + let capacity = num_wires * degree + num_virtual_targets; + Self { + parents: Vec::with_capacity(capacity), + num_wires, + num_routed_wires, + degree, + } + } + + pub(crate) fn target_index(&self, target: Target) -> usize { + target.index(self.num_wires, self.degree) + } + + /// Add a new partition with a single member. + pub fn add(&mut self, t: Target) { + let index = self.parents.len(); + debug_assert_eq!(self.target_index(t), index); + self.parents.push(index); + } + + /// Path compression method, see . + pub fn find(&mut self, mut x_index: usize) -> usize { + // Note: We avoid recursion here since the chains can be long, causing stack + // overflows. + + // First, find the representative of the set containing `x_index`. + let mut representative = x_index; + while self.parents[representative] != representative { + representative = self.parents[representative]; + } + + // Then, update each node in this chain to point directly to the representative. + while self.parents[x_index] != x_index { + let old_parent = self.parents[x_index]; + self.parents[x_index] = representative; + x_index = old_parent; + } + + representative + } + + /// Merge two sets. + pub fn merge(&mut self, tx: Target, ty: Target) { + let x_index = self.find(self.target_index(tx)); + let y_index = self.find(self.target_index(ty)); + + if x_index == y_index { + return; + } + + self.parents[y_index] = x_index; + } + + /// Compress all paths. After calling this, every `parent` value will point + /// to the node's representative. + pub(crate) fn compress_paths(&mut self) { + for i in 0..self.parents.len() { + self.find(i); + } + } + + /// Assumes `compress_paths` has already been called. + pub fn wire_partition(&mut self) -> WirePartition { + let mut partition = HashMap::<_, Vec<_>>::new(); + + // Here we keep just the Wire targets, filtering out everything else. + for row in 0..self.degree { + for column in 0..self.num_routed_wires { + let w = Wire { row, column }; + let t = Target::Wire(w); + let x_parent = self.parents[self.target_index(t)]; + partition.entry(x_parent).or_default().push(w); + } + } + + let partition = partition.into_values().collect(); + WirePartition { partition } + } +} + +pub struct WirePartition { + partition: Vec>, +} + +impl WirePartition { + pub(crate) fn get_sigma_polys( + &self, + degree_log: usize, + k_is: &[F], + subgroup: &[F], + ) -> Vec> { + let degree = 1 << degree_log; + let sigma = self.get_sigma_map(degree, k_is.len()); + + sigma + .chunks(degree) + .map(|chunk| { + let values = chunk + .par_iter() + .map(|&x| k_is[x / degree] * subgroup[x % degree]) + .collect::>(); + PolynomialValues::new(values) + }) + .collect() + } + + /// Generates sigma in the context of Plonk, which is a map from `[kn]` to + /// `[kn]`, where `k` is the number of routed wires and `n` is the + /// number of gates. + fn get_sigma_map(&self, degree: usize, num_routed_wires: usize) -> Vec { + // Find a wire's "neighbor" in the context of Plonk's "extended copy + // constraints" check. In other words, find the next wire in the given + // wire's partition. If the given wire is last in its partition, this + // will loop around. If the given wire has a partition all to itself, it + // is considered its own neighbor. + let mut neighbors = HashMap::with_capacity(self.partition.len()); + for subset in &self.partition { + for n in 0..subset.len() { + neighbors.insert(subset[n], subset[(n + 1) % subset.len()]); + } + } + + let mut sigma = Vec::with_capacity(num_routed_wires * degree); + for column in 0..num_routed_wires { + for row in 0..degree { + let wire = Wire { row, column }; + let neighbor = neighbors[&wire]; + sigma.push(neighbor.column * degree + neighbor.row); + } + } + sigma + } +} diff --git a/plonky2/src/plonk/plonk_common.rs b/plonky2/src/plonk/plonk_common.rs new file mode 100644 index 000000000..e1b36ade5 --- /dev/null +++ b/plonky2/src/plonk/plonk_common.rs @@ -0,0 +1,159 @@ +//! Utility methods and constants for Plonk. + +#[cfg(not(feature = "std"))] +use alloc::{vec, vec::Vec}; + +use crate::field::extension::Extendable; +use crate::field::packed::PackedField; +use crate::field::types::Field; +use crate::fri::oracle::SALT_SIZE; +use crate::gates::arithmetic_base::ArithmeticGate; +use crate::hash::hash_types::RichField; +use crate::iop::ext_target::ExtensionTarget; +use crate::iop::target::Target; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::util::reducing::ReducingFactorTarget; + +/// Holds the Merkle tree index and blinding flag of a set of polynomials used +/// in FRI. +#[derive(Debug, Copy, Clone)] +pub struct PlonkOracle { + pub(crate) index: usize, + pub(crate) blinding: bool, +} + +impl PlonkOracle { + pub const CONSTANTS_SIGMAS: PlonkOracle = PlonkOracle { + index: 0, + blinding: false, + }; + pub const WIRES: PlonkOracle = PlonkOracle { + index: 1, + blinding: true, + }; + pub const ZS_PARTIAL_PRODUCTS: PlonkOracle = PlonkOracle { + index: 2, + blinding: true, + }; + pub const QUOTIENT: PlonkOracle = PlonkOracle { + index: 3, + blinding: true, + }; +} + +pub const fn salt_size(salted: bool) -> usize { + if salted { + SALT_SIZE + } else { + 0 + } +} + +/// Evaluate the polynomial which vanishes on any multiplicative subgroup of a +/// given order `n`. +pub(crate) fn eval_zero_poly(n: usize, x: F) -> F { + // Z(x) = x^n - 1 + x.exp_u64(n as u64) - F::ONE +} + +/// 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_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_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_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_0_circuit, const D: usize>( + builder: &mut CircuitBuilder, + n: usize, + x: ExtensionTarget, + x_pow_n: ExtensionTarget, +) -> ExtensionTarget { + // 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(); + let neg_one = builder.convert_to_ext(neg_one); + let eval_zero_poly = builder.sub_extension(x_pow_n, one); + let denominator = builder.arithmetic_extension( + F::from_canonical_usize(n), + F::from_canonical_usize(n), + x, + one, + neg_one, + ); + builder.div_extension(eval_zero_poly, denominator) +} + +/// For each alpha in alphas, compute a reduction of the given terms using +/// powers of alpha. T can be any type convertible to a double-ended iterator. +pub(crate) fn reduce_with_powers_multi< + 'a, + F: Field, + I: DoubleEndedIterator, + T: IntoIterator, +>( + terms: T, + alphas: &[F], +) -> Vec { + let mut cumul = vec![F::ZERO; alphas.len()]; + for &term in terms.into_iter().rev() { + cumul + .iter_mut() + .zip(alphas) + .for_each(|(c, &alpha)| *c = term.multiply_accumulate(*c, alpha)); + } + cumul +} + +pub fn reduce_with_powers<'a, P: PackedField, T: IntoIterator>( + terms: T, + alpha: P::Scalar, +) -> P +where + T::IntoIter: DoubleEndedIterator, +{ + let mut sum = P::ZEROS; + for &term in terms.into_iter().rev() { + sum = sum * alpha + term; + } + sum +} + +pub fn reduce_with_powers_circuit, const D: usize>( + builder: &mut CircuitBuilder, + terms: &[Target], + alpha: Target, +) -> Target { + if terms.len() <= ArithmeticGate::new_from_config(&builder.config).num_ops + 1 { + terms + .iter() + .rev() + .fold(builder.zero(), |acc, &t| builder.mul_add(alpha, acc, t)) + } else { + let alpha = builder.convert_to_ext(alpha); + let mut alpha = ReducingFactorTarget::new(alpha); + alpha.reduce_base(terms, builder).0[0] + } +} + +pub fn reduce_with_powers_ext_circuit, const D: usize>( + builder: &mut CircuitBuilder, + terms: &[ExtensionTarget], + alpha: Target, +) -> ExtensionTarget { + let alpha = builder.convert_to_ext(alpha); + let mut alpha = ReducingFactorTarget::new(alpha); + alpha.reduce(terms, builder) +} diff --git a/plonky2/src/plonk/proof.rs b/plonky2/src/plonk/proof.rs new file mode 100644 index 000000000..6f7e1bdf7 --- /dev/null +++ b/plonky2/src/plonk/proof.rs @@ -0,0 +1,568 @@ +//! plonky2 proof definition. +//! +//! Proofs can be later compressed to reduce their size, into either +//! [`CompressedProof`] or [`CompressedProofWithPublicInputs`] formats. +//! The latter can be directly passed to a verifier to assert its correctness. + +#[cfg(not(feature = "std"))] +use alloc::{vec, vec::Vec}; + +use anyhow::ensure; +use plonky2_maybe_rayon::*; +use serde::{Deserialize, Serialize}; + +use crate::field::extension::Extendable; +use crate::fri::oracle::PolynomialBatch; +use crate::fri::proof::{ + CompressedFriProof, FriChallenges, FriChallengesTarget, FriProof, FriProofTarget, +}; +use crate::fri::structure::{ + FriOpeningBatch, FriOpeningBatchTarget, FriOpenings, FriOpeningsTarget, +}; +use crate::fri::FriParams; +use crate::hash::hash_types::{MerkleCapTarget, RichField}; +use crate::hash::merkle_tree::MerkleCap; +use crate::iop::ext_target::ExtensionTarget; +use crate::iop::target::Target; +use crate::plonk::circuit_data::{CommonCircuitData, VerifierOnlyCircuitData}; +use crate::plonk::config::{GenericConfig, Hasher}; +use crate::plonk::verifier::verify_with_challenges; +use crate::util::serialization::{Buffer, Read, Write}; + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(bound = "")] +pub struct Proof, C: GenericConfig, const D: usize> { + /// Merkle cap of LDEs of wire values. + pub wires_cap: MerkleCap, + /// Merkle cap of LDEs of Z, in the context of Plonk's permutation argument. + pub plonk_zs_partial_products_cap: MerkleCap, + /// Merkle cap of LDEs of the quotient polynomial components. + pub quotient_polys_cap: MerkleCap, + /// Purported values of each polynomial at the challenge point. + pub openings: OpeningSet, + /// A batch FRI argument for all openings. + pub opening_proof: FriProof, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ProofTarget { + pub wires_cap: MerkleCapTarget, + pub plonk_zs_partial_products_cap: MerkleCapTarget, + pub quotient_polys_cap: MerkleCapTarget, + pub openings: OpeningSetTarget, + pub opening_proof: FriProofTarget, +} + +impl, C: GenericConfig, const D: usize> Proof { + /// Compress the proof. + pub fn compress(self, indices: &[usize], params: &FriParams) -> CompressedProof { + let Proof { + wires_cap, + plonk_zs_partial_products_cap, + quotient_polys_cap, + openings, + opening_proof, + } = self; + + CompressedProof { + wires_cap, + plonk_zs_partial_products_cap, + quotient_polys_cap, + openings, + opening_proof: opening_proof.compress(indices, params), + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(bound = "")] +pub struct ProofWithPublicInputs< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +> { + pub proof: Proof, + pub public_inputs: Vec, +} + +impl, C: GenericConfig, const D: usize> + ProofWithPublicInputs +{ + pub fn compress( + self, + circuit_digest: &<>::Hasher as Hasher>::Hash, + common_data: &CommonCircuitData, + ) -> anyhow::Result> { + let indices = self.fri_query_indices(circuit_digest, common_data)?; + let compressed_proof = self.proof.compress(&indices, &common_data.fri_params); + Ok(CompressedProofWithPublicInputs { + public_inputs: self.public_inputs, + proof: compressed_proof, + }) + } + + pub fn get_public_inputs_hash( + &self, + ) -> <>::InnerHasher as Hasher>::Hash { + C::InnerHasher::hash_no_pad(&self.public_inputs) + } + + pub fn to_bytes(&self) -> Vec { + let mut buffer = Vec::new(); + buffer + .write_proof_with_public_inputs(self) + .expect("Writing to a byte-vector cannot fail."); + buffer + } + + pub fn from_bytes( + bytes: Vec, + common_data: &CommonCircuitData, + ) -> anyhow::Result { + let mut buffer = Buffer::new(&bytes); + let proof = buffer + .read_proof_with_public_inputs(common_data) + .map_err(anyhow::Error::msg)?; + Ok(proof) + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(bound = "")] +pub struct CompressedProof, C: GenericConfig, const D: usize> +{ + /// Merkle cap of LDEs of wire values. + pub wires_cap: MerkleCap, + /// Merkle cap of LDEs of Z, in the context of Plonk's permutation argument. + pub plonk_zs_partial_products_cap: MerkleCap, + /// Merkle cap of LDEs of the quotient polynomial components. + pub quotient_polys_cap: MerkleCap, + /// Purported values of each polynomial at the challenge point. + pub openings: OpeningSet, + /// A compressed batch FRI argument for all openings. + pub opening_proof: CompressedFriProof, +} + +impl, C: GenericConfig, const D: usize> + CompressedProof +{ + /// Decompress the proof. + pub(crate) fn decompress( + self, + challenges: &ProofChallenges, + fri_inferred_elements: FriInferredElements, + params: &FriParams, + ) -> Proof { + let CompressedProof { + wires_cap, + plonk_zs_partial_products_cap, + quotient_polys_cap, + openings, + opening_proof, + } = self; + + Proof { + wires_cap, + plonk_zs_partial_products_cap, + quotient_polys_cap, + openings, + opening_proof: opening_proof.decompress(challenges, fri_inferred_elements, params), + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(bound = "")] +pub struct CompressedProofWithPublicInputs< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +> { + pub proof: CompressedProof, + pub public_inputs: Vec, +} + +impl, C: GenericConfig, const D: usize> + CompressedProofWithPublicInputs +{ + pub fn decompress( + self, + circuit_digest: &<>::Hasher as Hasher>::Hash, + common_data: &CommonCircuitData, + ) -> anyhow::Result> { + let challenges = + self.get_challenges(self.get_public_inputs_hash(), circuit_digest, common_data)?; + let fri_inferred_elements = self.get_inferred_elements(&challenges, common_data); + let decompressed_proof = + self.proof + .decompress(&challenges, fri_inferred_elements, &common_data.fri_params); + Ok(ProofWithPublicInputs { + public_inputs: self.public_inputs, + proof: decompressed_proof, + }) + } + + pub(crate) fn verify( + self, + verifier_data: &VerifierOnlyCircuitData, + common_data: &CommonCircuitData, + ) -> anyhow::Result<()> { + ensure!( + self.public_inputs.len() == common_data.num_public_inputs, + "Number of public inputs doesn't match circuit data." + ); + let public_inputs_hash = self.get_public_inputs_hash(); + let challenges = self.get_challenges( + public_inputs_hash, + &verifier_data.circuit_digest, + common_data, + )?; + let fri_inferred_elements = self.get_inferred_elements(&challenges, common_data); + let decompressed_proof = + self.proof + .decompress(&challenges, fri_inferred_elements, &common_data.fri_params); + verify_with_challenges::( + decompressed_proof, + public_inputs_hash, + challenges, + verifier_data, + common_data, + ) + } + + pub(crate) fn get_public_inputs_hash( + &self, + ) -> <>::InnerHasher as Hasher>::Hash { + C::InnerHasher::hash_no_pad(&self.public_inputs) + } + + pub fn to_bytes(&self) -> Vec { + let mut buffer = Vec::new(); + buffer + .write_compressed_proof_with_public_inputs(self) + .expect("Writing to a byte-vector cannot fail."); + buffer + } + + pub fn from_bytes( + bytes: Vec, + common_data: &CommonCircuitData, + ) -> anyhow::Result { + let mut buffer = Buffer::new(&bytes); + let proof = buffer + .read_compressed_proof_with_public_inputs(common_data) + .map_err(anyhow::Error::msg)?; + Ok(proof) + } +} + +pub struct ProofChallenges, const D: usize> { + /// Random values used in Plonk's permutation argument. + pub plonk_betas: Vec, + + /// Random values used in Plonk's permutation argument. + pub plonk_gammas: Vec, + + /// Random values used to combine PLONK constraints. + pub plonk_alphas: Vec, + + /// Lookup challenges. + pub plonk_deltas: Vec, + + /// Point at which the PLONK polynomials are opened. + pub plonk_zeta: F::Extension, + + pub fri_challenges: FriChallenges, +} + +pub(crate) struct ProofChallengesTarget { + pub plonk_betas: Vec, + pub plonk_gammas: Vec, + pub plonk_alphas: Vec, + pub plonk_deltas: Vec, + pub plonk_zeta: ExtensionTarget, + pub fri_challenges: FriChallengesTarget, +} + +/// Coset elements that can be inferred in the FRI reduction steps. +pub(crate) struct FriInferredElements, const D: usize>( + pub Vec, +); + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ProofWithPublicInputsTarget { + pub proof: ProofTarget, + pub public_inputs: Vec, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)] +/// The purported values of each polynomial at a single point. +pub struct OpeningSet, const D: usize> { + pub constants: Vec, + pub plonk_sigmas: Vec, + pub wires: Vec, + pub plonk_zs: Vec, + pub plonk_zs_next: Vec, + pub partial_products: Vec, + pub quotient_polys: Vec, + pub lookup_zs: Vec, + pub lookup_zs_next: Vec, +} + +impl, const D: usize> OpeningSet { + pub fn new>( + zeta: F::Extension, + g: F::Extension, + constants_sigmas_commitment: &PolynomialBatch, + wires_commitment: &PolynomialBatch, + zs_partial_products_lookup_commitment: &PolynomialBatch, + quotient_polys_commitment: &PolynomialBatch, + common_data: &CommonCircuitData, + ) -> Self { + let eval_commitment = |z: F::Extension, c: &PolynomialBatch| { + c.polynomials + .par_iter() + .map(|p| p.to_extension().eval(z)) + .collect::>() + }; + let constants_sigmas_eval = eval_commitment(zeta, constants_sigmas_commitment); + + // `zs_partial_products_lookup_eval` contains the permutation argument + // polynomials as well as lookup polynomials. + let zs_partial_products_lookup_eval = + eval_commitment(zeta, zs_partial_products_lookup_commitment); + let zs_partial_products_lookup_next_eval = + eval_commitment(g * zeta, zs_partial_products_lookup_commitment); + let quotient_polys = eval_commitment(zeta, quotient_polys_commitment); + + Self { + constants: constants_sigmas_eval[common_data.constants_range()].to_vec(), + plonk_sigmas: constants_sigmas_eval[common_data.sigmas_range()].to_vec(), + wires: eval_commitment(zeta, wires_commitment), + plonk_zs: zs_partial_products_lookup_eval[common_data.zs_range()].to_vec(), + plonk_zs_next: zs_partial_products_lookup_next_eval[common_data.zs_range()].to_vec(), + partial_products: zs_partial_products_lookup_eval[common_data.partial_products_range()] + .to_vec(), + quotient_polys, + lookup_zs: zs_partial_products_lookup_eval[common_data.lookup_range()].to_vec(), + lookup_zs_next: zs_partial_products_lookup_next_eval[common_data.lookup_range()] + .to_vec(), + } + } + pub(crate) fn to_fri_openings(&self) -> FriOpenings { + let has_lookup = !self.lookup_zs.is_empty(); + let zeta_batch = if has_lookup { + FriOpeningBatch { + values: [ + self.constants.as_slice(), + self.plonk_sigmas.as_slice(), + self.wires.as_slice(), + self.plonk_zs.as_slice(), + self.partial_products.as_slice(), + self.quotient_polys.as_slice(), + self.lookup_zs.as_slice(), + ] + .concat(), + } + } else { + FriOpeningBatch { + values: [ + self.constants.as_slice(), + self.plonk_sigmas.as_slice(), + self.wires.as_slice(), + self.plonk_zs.as_slice(), + self.partial_products.as_slice(), + self.quotient_polys.as_slice(), + ] + .concat(), + } + }; + let zeta_next_batch = if has_lookup { + FriOpeningBatch { + values: [self.plonk_zs_next.clone(), self.lookup_zs_next.clone()].concat(), + } + } else { + FriOpeningBatch { + values: self.plonk_zs_next.clone(), + } + }; + FriOpenings { + batches: vec![zeta_batch, zeta_next_batch], + } + } +} + +/// The purported values of each polynomial at a single point. +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct OpeningSetTarget { + pub constants: Vec>, + pub plonk_sigmas: Vec>, + pub wires: Vec>, + pub plonk_zs: Vec>, + pub plonk_zs_next: Vec>, + pub lookup_zs: Vec>, + pub next_lookup_zs: Vec>, + pub partial_products: Vec>, + pub quotient_polys: Vec>, +} + +impl OpeningSetTarget { + pub(crate) fn to_fri_openings(&self) -> FriOpeningsTarget { + let has_lookup = !self.lookup_zs.is_empty(); + let zeta_batch = if has_lookup { + FriOpeningBatchTarget { + values: [ + self.constants.as_slice(), + self.plonk_sigmas.as_slice(), + self.wires.as_slice(), + self.plonk_zs.as_slice(), + self.partial_products.as_slice(), + self.quotient_polys.as_slice(), + self.lookup_zs.as_slice(), + ] + .concat(), + } + } else { + FriOpeningBatchTarget { + values: [ + self.constants.as_slice(), + self.plonk_sigmas.as_slice(), + self.wires.as_slice(), + self.plonk_zs.as_slice(), + self.partial_products.as_slice(), + self.quotient_polys.as_slice(), + ] + .concat(), + } + }; + let zeta_next_batch = if has_lookup { + FriOpeningBatchTarget { + values: [self.plonk_zs_next.clone(), self.next_lookup_zs.clone()].concat(), + } + } else { + FriOpeningBatchTarget { + values: self.plonk_zs_next.clone(), + } + }; + FriOpeningsTarget { + batches: vec![zeta_batch, zeta_next_batch], + } + } +} + +#[cfg(test)] +mod tests { + #[cfg(not(feature = "std"))] + use alloc::sync::Arc; + #[cfg(feature = "std")] + use std::sync::Arc; + + use anyhow::Result; + use itertools::Itertools; + use plonky2_field::types::Sample; + + use super::*; + use crate::fri::reduction_strategies::FriReductionStrategy; + use crate::gates::lookup_table::LookupTable; + use crate::gates::noop::NoopGate; + use crate::iop::witness::PartialWitness; + use crate::plonk::circuit_builder::CircuitBuilder; + use crate::plonk::circuit_data::CircuitConfig; + use crate::plonk::config::PoseidonGoldilocksConfig; + use crate::plonk::verifier::verify; + + #[test] + fn test_proof_compression() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + + let mut config = CircuitConfig::standard_recursion_config(); + config.fri_config.reduction_strategy = FriReductionStrategy::Fixed(vec![1, 1]); + config.fri_config.num_query_rounds = 50; + + let pw = PartialWitness::new(); + let mut builder = CircuitBuilder::::new(config); + + // Build dummy circuit to get a valid proof. + let x = F::rand(); + let y = F::rand(); + let z = x * y; + let xt = builder.constant(x); + let yt = builder.constant(y); + let zt = builder.constant(z); + let comp_zt = builder.mul(xt, yt); + builder.connect(zt, comp_zt); + for _ in 0..100 { + builder.add_gate(NoopGate, vec![]); + } + let data = builder.build::(); + let proof = data.prove(pw)?; + verify(proof.clone(), &data.verifier_only, &data.common)?; + + // Verify that `decompress ∘ compress = identity`. + let compressed_proof = data.compress(proof.clone())?; + let decompressed_compressed_proof = data.decompress(compressed_proof.clone())?; + assert_eq!(proof, decompressed_compressed_proof); + + verify(proof, &data.verifier_only, &data.common)?; + data.verify_compressed(compressed_proof) + } + + #[test] + fn test_proof_compression_lookup() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + use plonky2_field::types::Field; + type F = >::F; + + let mut config = CircuitConfig::standard_recursion_config(); + config.fri_config.reduction_strategy = FriReductionStrategy::Fixed(vec![1, 1]); + config.fri_config.num_query_rounds = 50; + + let pw = PartialWitness::new(); + let tip5_table = vec![ + 0, 7, 26, 63, 124, 215, 85, 254, 214, 228, 45, 185, 140, 173, 33, 240, 29, 177, 176, + 32, 8, 110, 87, 202, 204, 99, 150, 106, 230, 14, 235, 128, 213, 239, 212, 138, 23, 130, + 208, 6, 44, 71, 93, 116, 146, 189, 251, 81, 199, 97, 38, 28, 73, 179, 95, 84, 152, 48, + 35, 119, 49, 88, 242, 3, 148, 169, 72, 120, 62, 161, 166, 83, 175, 191, 137, 19, 100, + 129, 112, 55, 221, 102, 218, 61, 151, 237, 68, 164, 17, 147, 46, 234, 203, 216, 22, + 141, 65, 57, 123, 12, 244, 54, 219, 231, 96, 77, 180, 154, 5, 253, 133, 165, 98, 195, + 205, 134, 245, 30, 9, 188, 59, 142, 186, 197, 181, 144, 92, 31, 224, 163, 111, 74, 58, + 69, 113, 196, 67, 246, 225, 10, 121, 50, 60, 157, 90, 122, 2, 250, 101, 75, 178, 159, + 24, 36, 201, 11, 243, 132, 198, 190, 114, 233, 39, 52, 21, 209, 108, 238, 91, 187, 18, + 104, 194, 37, 153, 34, 200, 143, 126, 155, 236, 118, 64, 80, 172, 89, 94, 193, 135, + 183, 86, 107, 252, 13, 167, 206, 136, 220, 207, 103, 171, 160, 76, 182, 227, 217, 158, + 56, 174, 4, 66, 109, 139, 162, 184, 211, 249, 47, 125, 232, 117, 43, 16, 42, 127, 20, + 241, 25, 149, 105, 156, 51, 53, 168, 145, 247, 223, 79, 78, 226, 15, 222, 82, 115, 70, + 210, 27, 41, 1, 170, 40, 131, 192, 229, 248, 255, + ]; + let table: LookupTable = Arc::new((0..256).zip_eq(tip5_table).collect()); + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + let lut_index = builder.add_lookup_table_from_pairs(table); + + // Build dummy circuit with a lookup to get a valid proof. + let x = F::TWO; + let out = builder.constant(F::from_canonical_usize(26)); + + let xt = builder.constant(x); + let look_out = builder.add_lookup_from_index(xt, lut_index); + builder.connect(look_out, out); + for _ in 0..100 { + builder.add_gate(NoopGate, vec![]); + } + let data = builder.build::(); + + let proof = data.prove(pw)?; + verify(proof.clone(), &data.verifier_only, &data.common)?; + + // Verify that `decompress ∘ compress = identity`. + let compressed_proof = data.compress(proof.clone())?; + let decompressed_compressed_proof = data.decompress(compressed_proof.clone())?; + assert_eq!(proof, decompressed_compressed_proof); + + verify(proof, &data.verifier_only, &data.common)?; + data.verify_compressed(compressed_proof) + } +} diff --git a/plonky2/src/plonk/prover.rs b/plonky2/src/plonk/prover.rs new file mode 100644 index 000000000..5814b5109 --- /dev/null +++ b/plonky2/src/plonk/prover.rs @@ -0,0 +1,820 @@ +//! plonky2 prover implementation. + +#[cfg(not(feature = "std"))] +use alloc::{format, vec, vec::Vec}; +use core::cmp::min; +use core::mem::swap; + +use anyhow::{ensure, Result}; +use hashbrown::HashMap; +use plonky2_maybe_rayon::*; + +use super::circuit_builder::{LookupChallenges, LookupWire}; +use crate::field::extension::Extendable; +use crate::field::polynomial::{PolynomialCoeffs, PolynomialValues}; +use crate::field::types::Field; +use crate::field::zero_poly_coset::ZeroPolyOnCoset; +use crate::fri::oracle::PolynomialBatch; +use crate::gates::lookup::LookupGate; +use crate::gates::lookup_table::LookupTableGate; +use crate::gates::selectors::LookupSelectors; +use crate::hash::hash_types::RichField; +use crate::iop::challenger::Challenger; +use crate::iop::generator::generate_partial_witness; +use crate::iop::target::Target; +use crate::iop::witness::{MatrixWitness, PartialWitness, PartitionWitness, Witness, WitnessWrite}; +use crate::plonk::circuit_builder::NUM_COINS_LOOKUP; +use crate::plonk::circuit_data::{CommonCircuitData, ProverOnlyCircuitData}; +use crate::plonk::config::{GenericConfig, Hasher}; +use crate::plonk::plonk_common::PlonkOracle; +use crate::plonk::proof::{OpeningSet, Proof, ProofWithPublicInputs}; +use crate::plonk::vanishing_poly::{eval_vanishing_poly_base_batch, get_lut_poly}; +use crate::plonk::vars::EvaluationVarsBaseBatch; +use crate::timed; +use crate::util::partial_products::{partial_products_and_z_gx, quotient_chunk_products}; +use crate::util::timing::TimingTree; +use crate::util::{ceil_div_usize, log2_ceil, transpose}; + +/// Set all the lookup gate wires (including multiplicities) and pad unused LU +/// slots. Warning: rows are in descending order: the first gate to appear is +/// the last LU gate, and the last gate to appear is the first LUT gate. +pub fn set_lookup_wires< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +>( + prover_data: &ProverOnlyCircuitData, + common_data: &CommonCircuitData, + pw: &mut PartitionWitness, +) { + for ( + lut_index, + &LookupWire { + last_lu_gate: _, + last_lut_gate, + first_lut_gate, + }, + ) in prover_data.lookup_rows.iter().enumerate() + { + let lut_len = common_data.luts[lut_index].len(); + let num_entries = LookupGate::num_slots(&common_data.config); + let num_lut_entries = LookupTableGate::num_slots(&common_data.config); + + // Compute multiplicities. + let mut multiplicities = vec![0; lut_len]; + + let table_value_to_idx: HashMap = common_data.luts[lut_index] + .iter() + .enumerate() + .map(|(i, (inp_target, _))| (*inp_target, i)) + .collect(); + + for (inp_target, _) in prover_data.lut_to_lookups[lut_index].iter() { + let inp_value = pw.get_target(*inp_target); + let idx = table_value_to_idx + .get(&u16::try_from(inp_value.to_canonical_u64()).unwrap()) + .unwrap(); + + multiplicities[*idx] += 1; + } + + // Pad the last `LookupGate` with the first entry from the LUT. + let remaining_slots = (num_entries + - (prover_data.lut_to_lookups[lut_index].len() % num_entries)) + % num_entries; + let (first_inp_value, first_out_value) = common_data.luts[lut_index][0]; + for slot in (num_entries - remaining_slots)..num_entries { + let inp_target = + Target::wire(last_lut_gate - 1, LookupGate::wire_ith_looking_inp(slot)); + let out_target = + Target::wire(last_lut_gate - 1, LookupGate::wire_ith_looking_out(slot)); + pw.set_target(inp_target, F::from_canonical_u16(first_inp_value)); + pw.set_target(out_target, F::from_canonical_u16(first_out_value)); + + multiplicities[0] += 1; + } + + // We don't need to pad the last `LookupTableGate`; extra wires are set to 0 by + // default, which satisfies the constraints. + for lut_entry in 0..lut_len { + let row = first_lut_gate - lut_entry / num_lut_entries; + let col = lut_entry % num_lut_entries; + + let mul_target = Target::wire(row, LookupTableGate::wire_ith_multiplicity(col)); + + pw.set_target( + mul_target, + F::from_canonical_usize(multiplicities[lut_entry]), + ); + } + } +} + +pub fn prove, C: GenericConfig, const D: usize>( + prover_data: &ProverOnlyCircuitData, + common_data: &CommonCircuitData, + inputs: PartialWitness, + timing: &mut TimingTree, +) -> Result> +where + C::Hasher: Hasher, + C::InnerHasher: Hasher, +{ + let partition_witness = timed!( + timing, + &format!("run {} generators", prover_data.generators.len()), + generate_partial_witness(inputs, prover_data, common_data) + ); + + prove_with_partition_witness(prover_data, common_data, partition_witness, timing) +} + +pub fn prove_with_partition_witness< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +>( + prover_data: &ProverOnlyCircuitData, + common_data: &CommonCircuitData, + mut partition_witness: PartitionWitness, + timing: &mut TimingTree, +) -> Result> +where + C::Hasher: Hasher, + C::InnerHasher: Hasher, +{ + let has_lookup = !common_data.luts.is_empty(); + let config = &common_data.config; + let num_challenges = config.num_challenges; + let quotient_degree = common_data.quotient_degree(); + let degree = common_data.degree(); + + set_lookup_wires(prover_data, common_data, &mut partition_witness); + + let public_inputs = partition_witness.get_targets(&prover_data.public_inputs); + let public_inputs_hash = C::InnerHasher::hash_no_pad(&public_inputs); + + let witness = timed!( + timing, + "compute full witness", + partition_witness.full_witness() + ); + + let wires_values: Vec> = timed!( + timing, + "compute wire polynomials", + witness + .wire_values + .par_iter() + .map(|column| PolynomialValues::new(column.clone())) + .collect() + ); + + let wires_commitment = timed!( + timing, + "compute wires commitment", + PolynomialBatch::::from_values( + wires_values, + config.fri_config.rate_bits, + config.zero_knowledge && PlonkOracle::WIRES.blinding, + config.fri_config.cap_height, + timing, + prover_data.fft_root_table.as_ref(), + ) + ); + + let mut challenger = Challenger::::new(); + + // Observe the instance. + challenger.observe_hash::(prover_data.circuit_digest); + challenger.observe_hash::(public_inputs_hash); + + challenger.observe_cap::(&wires_commitment.merkle_tree.cap); + + // We need 4 values per challenge: 2 for the combos, 1 for (X-combo) in the + // accumulators and 1 to prove that the lookup table was computed correctly. + // We can reuse betas and gammas for two of them. + let num_lookup_challenges = NUM_COINS_LOOKUP * num_challenges; + + let betas = challenger.get_n_challenges(num_challenges); + let gammas = challenger.get_n_challenges(num_challenges); + + let deltas = if has_lookup { + let mut delts = Vec::with_capacity(2 * num_challenges); + let num_additional_challenges = num_lookup_challenges - 2 * num_challenges; + let additional = challenger.get_n_challenges(num_additional_challenges); + delts.extend(&betas); + delts.extend(&gammas); + delts.extend(additional); + delts + } else { + vec![] + }; + + assert!( + common_data.quotient_degree_factor < common_data.config.num_routed_wires, + "When the number of routed wires is smaller that the degree, we should change the logic to avoid computing partial products." + ); + let mut partial_products_and_zs = timed!( + timing, + "compute partial products", + all_wires_permutation_partial_products(&witness, &betas, &gammas, prover_data, common_data) + ); + + // Z is expected at the front of our batch; see `zs_range` and + // `partial_products_range`. + let plonk_z_vecs = partial_products_and_zs + .iter_mut() + .map(|partial_products_and_z| partial_products_and_z.pop().unwrap()) + .collect(); + let zs_partial_products = [plonk_z_vecs, partial_products_and_zs.concat()].concat(); + + // All lookup polys: RE and partial SLDCs. + let lookup_polys = + compute_all_lookup_polys(&witness, &deltas, prover_data, common_data, has_lookup); + + let zs_partial_products_lookups = if has_lookup { + [zs_partial_products, lookup_polys].concat() + } else { + zs_partial_products + }; + + let partial_products_zs_and_lookup_commitment = timed!( + timing, + "commit to partial products, Z's and, if any, lookup polynomials", + PolynomialBatch::from_values( + zs_partial_products_lookups, + config.fri_config.rate_bits, + config.zero_knowledge && PlonkOracle::ZS_PARTIAL_PRODUCTS.blinding, + config.fri_config.cap_height, + timing, + prover_data.fft_root_table.as_ref(), + ) + ); + + challenger.observe_cap::(&partial_products_zs_and_lookup_commitment.merkle_tree.cap); + + let alphas = challenger.get_n_challenges(num_challenges); + + let quotient_polys = timed!( + timing, + "compute quotient polys", + compute_quotient_polys::( + common_data, + prover_data, + &public_inputs_hash, + &wires_commitment, + &partial_products_zs_and_lookup_commitment, + &betas, + &gammas, + &deltas, + &alphas, + ) + ); + + let all_quotient_poly_chunks: Vec> = timed!( + timing, + "split up quotient polys", + quotient_polys + .into_par_iter() + .flat_map(|mut quotient_poly| { + quotient_poly.trim_to_len(quotient_degree).expect( + "Quotient has failed, the vanishing polynomial is not divisible by Z_H", + ); + // Split quotient into degree-n chunks. + quotient_poly.chunks(degree) + }) + .collect() + ); + + let quotient_polys_commitment = timed!( + timing, + "commit to quotient polys", + PolynomialBatch::::from_coeffs( + all_quotient_poly_chunks, + config.fri_config.rate_bits, + config.zero_knowledge && PlonkOracle::QUOTIENT.blinding, + config.fri_config.cap_height, + timing, + prover_data.fft_root_table.as_ref(), + ) + ); + + challenger.observe_cap::("ient_polys_commitment.merkle_tree.cap); + + let zeta = challenger.get_extension_challenge::(); + // To avoid leaking witness data, we want to ensure that our opening locations, + // `zeta` and `g * zeta`, are not in our subgroup `H`. It suffices to check + // `zeta` only, since `(g * zeta)^n = zeta^n`, where `n` is the order of + // `g`. + let g = F::Extension::primitive_root_of_unity(common_data.degree_bits()); + ensure!( + zeta.exp_power_of_2(common_data.degree_bits()) != F::Extension::ONE, + "Opening point is in the subgroup." + ); + + let openings = timed!( + timing, + "construct the opening set, including lookups", + OpeningSet::new( + zeta, + g, + &prover_data.constants_sigmas_commitment, + &wires_commitment, + &partial_products_zs_and_lookup_commitment, + "ient_polys_commitment, + common_data + ) + ); + challenger.observe_openings(&openings.to_fri_openings()); + let instance = common_data.get_fri_instance(zeta); + + let opening_proof = timed!( + timing, + "compute opening proofs", + PolynomialBatch::::prove_openings( + &instance, + &[ + &prover_data.constants_sigmas_commitment, + &wires_commitment, + &partial_products_zs_and_lookup_commitment, + "ient_polys_commitment, + ], + &mut challenger, + &common_data.fri_params, + timing, + ) + ); + + let proof = Proof:: { + wires_cap: wires_commitment.merkle_tree.cap, + plonk_zs_partial_products_cap: partial_products_zs_and_lookup_commitment.merkle_tree.cap, + quotient_polys_cap: quotient_polys_commitment.merkle_tree.cap, + openings, + opening_proof, + }; + Ok(ProofWithPublicInputs:: { + proof, + public_inputs, + }) +} + +/// Compute the partial products used in the `Z` polynomials. +fn all_wires_permutation_partial_products< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +>( + witness: &MatrixWitness, + betas: &[F], + gammas: &[F], + prover_data: &ProverOnlyCircuitData, + common_data: &CommonCircuitData, +) -> Vec>> { + (0..common_data.config.num_challenges) + .map(|i| { + wires_permutation_partial_products_and_zs( + witness, + betas[i], + gammas[i], + prover_data, + common_data, + ) + }) + .collect() +} + +/// Compute the partial products used in the `Z` polynomial. +/// Returns the polynomials interpolating `partial_products(f / g)` +/// where `f, g` are the products in the definition of `Z`: `Z(g^i) = f / g`. +fn wires_permutation_partial_products_and_zs< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +>( + witness: &MatrixWitness, + beta: F, + gamma: F, + prover_data: &ProverOnlyCircuitData, + common_data: &CommonCircuitData, +) -> Vec> { + let degree = common_data.quotient_degree_factor; + let subgroup = &prover_data.subgroup; + let k_is = &common_data.k_is; + let num_prods = common_data.num_partial_products; + let all_quotient_chunk_products = subgroup + .par_iter() + .enumerate() + .map(|(i, &x)| { + let s_sigmas = &prover_data.sigmas[i]; + let numerators = (0..common_data.config.num_routed_wires).map(|j| { + let wire_value = witness.get_wire(i, j); + let k_i = k_is[j]; + let s_id = k_i * x; + wire_value + beta * s_id + gamma + }); + let denominators = (0..common_data.config.num_routed_wires) + .map(|j| { + let wire_value = witness.get_wire(i, j); + let s_sigma = s_sigmas[j]; + wire_value + beta * s_sigma + gamma + }) + .collect::>(); + let denominator_invs = F::batch_multiplicative_inverse(&denominators); + let quotient_values = numerators + .zip(denominator_invs) + .map(|(num, den_inv)| num * den_inv) + .collect::>(); + + quotient_chunk_products("ient_values, degree) + }) + .collect::>(); + + let mut z_x = F::ONE; + let mut all_partial_products_and_zs = Vec::with_capacity(all_quotient_chunk_products.len()); + for quotient_chunk_products in all_quotient_chunk_products { + let mut partial_products_and_z_gx = + partial_products_and_z_gx(z_x, "ient_chunk_products); + // The last term is Z(gx), but we replace it with Z(x), otherwise Z would end up + // shifted. + swap(&mut z_x, &mut partial_products_and_z_gx[num_prods]); + all_partial_products_and_zs.push(partial_products_and_z_gx); + } + + transpose(&all_partial_products_and_zs) + .into_par_iter() + .map(PolynomialValues::new) + .collect() +} + +/// Computes lookup polynomials for a given challenge. +/// The polynomials hold the value of RE, Sum and Ldc of the Tip5 paper (). To reduce their +/// numbers, we batch multiple slots in a single polynomial. Since RE only +/// involves degree one constraints, we can batch all the slots of a row. For +/// Sum and Ldc, batching increases the constraint degree, so we bound the +/// number of partial polynomials according to `max_quotient_degree_factor`. +/// As another optimization, Sum and LDC polynomials are shared (in so called +/// partial SLDC polynomials), and the last value of the last partial polynomial +/// is Sum(end) - LDC(end). If the lookup argument is valid, then it must be +/// equal to 0. +fn compute_lookup_polys< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +>( + witness: &MatrixWitness, + deltas: &[F; 4], + prover_data: &ProverOnlyCircuitData, + common_data: &CommonCircuitData, +) -> Vec> { + let degree = common_data.degree(); + let num_lu_slots = LookupGate::num_slots(&common_data.config); + let max_lookup_degree = common_data.config.max_quotient_degree_factor - 1; + let num_partial_lookups = ceil_div_usize(num_lu_slots, max_lookup_degree); + let num_lut_slots = LookupTableGate::num_slots(&common_data.config); + let max_lookup_table_degree = ceil_div_usize(num_lut_slots, num_partial_lookups); + + // First poly is RE, the rest are partial SLDCs. + let mut final_poly_vecs = Vec::with_capacity(num_partial_lookups + 1); + for _ in 0..num_partial_lookups + 1 { + final_poly_vecs.push(PolynomialValues::::new(vec![F::ZERO; degree])); + } + + for LookupWire { + last_lu_gate: last_lu_row, + last_lut_gate: last_lut_row, + first_lut_gate: first_lut_row, + } in prover_data.lookup_rows.clone() + { + // Set values for partial Sums and RE. + for row in (last_lut_row..(first_lut_row + 1)).rev() { + // Get combos for Sum. + let looked_combos: Vec = (0..num_lut_slots) + .map(|s| { + let looked_inp = witness.get_wire(row, LookupTableGate::wire_ith_looked_inp(s)); + let looked_out = witness.get_wire(row, LookupTableGate::wire_ith_looked_out(s)); + + looked_inp + deltas[LookupChallenges::ChallengeA as usize] * looked_out + }) + .collect(); + // Get (alpha - combo). + let minus_looked_combos: Vec = (0..num_lut_slots) + .map(|s| deltas[LookupChallenges::ChallengeAlpha as usize] - looked_combos[s]) + .collect(); + // Get 1/(alpha - combo). + let looked_combo_inverses = F::batch_multiplicative_inverse(&minus_looked_combos); + + // Get lookup combos, used to check the well formation of the LUT. + let lookup_combos: Vec = (0..num_lut_slots) + .map(|s| { + let looked_inp = witness.get_wire(row, LookupTableGate::wire_ith_looked_inp(s)); + let looked_out = witness.get_wire(row, LookupTableGate::wire_ith_looked_out(s)); + + looked_inp + deltas[LookupChallenges::ChallengeB as usize] * looked_out + }) + .collect(); + + // Compute next row's first value of RE. + // If `row == first_lut_row`, then `final_poly_vecs[0].values[row + 1] == 0`. + let mut new_re = final_poly_vecs[0].values[row + 1]; + for elt in &lookup_combos { + new_re = new_re * deltas[LookupChallenges::ChallengeDelta as usize] + *elt + } + final_poly_vecs[0].values[row] = new_re; + + for slot in 0..num_partial_lookups { + let prev = if slot != 0 { + final_poly_vecs[slot].values[row] + } else { + // If `row == first_lut_row`, then + // `final_poly_vecs[num_partial_lookups].values[row + 1] == 0`. + final_poly_vecs[num_partial_lookups].values[row + 1] + }; + let sum = (slot * max_lookup_table_degree + ..min((slot + 1) * max_lookup_table_degree, num_lut_slots)) + .fold(prev, |acc, s| { + acc + witness.get_wire(row, LookupTableGate::wire_ith_multiplicity(s)) + * looked_combo_inverses[s] + }); + final_poly_vecs[slot + 1].values[row] = sum; + } + } + + // Set values for partial LDCs. + for row in (last_lu_row..last_lut_row).rev() { + // Get looking combos. + let looking_combos: Vec = (0..num_lu_slots) + .map(|s| { + let looking_in = witness.get_wire(row, LookupGate::wire_ith_looking_inp(s)); + let looking_out = witness.get_wire(row, LookupGate::wire_ith_looking_out(s)); + + looking_in + deltas[LookupChallenges::ChallengeA as usize] * looking_out + }) + .collect(); + // Get (alpha - combo). + let minus_looking_combos: Vec = (0..num_lu_slots) + .map(|s| deltas[LookupChallenges::ChallengeAlpha as usize] - looking_combos[s]) + .collect(); + // Get 1 / (alpha - combo). + let looking_combo_inverses = F::batch_multiplicative_inverse(&minus_looking_combos); + + for slot in 0..num_partial_lookups { + let prev = if slot == 0 { + // Valid at _any_ row, even `first_lu_row`. + final_poly_vecs[num_partial_lookups].values[row + 1] + } else { + final_poly_vecs[slot].values[row] + }; + let sum = (slot * max_lookup_degree + ..min((slot + 1) * max_lookup_degree, num_lu_slots)) + .fold(F::ZERO, |acc, s| acc + looking_combo_inverses[s]); + final_poly_vecs[slot + 1].values[row] = prev - sum; + } + } + } + + final_poly_vecs +} + +/// Computes lookup polynomials for all challenges. +fn compute_all_lookup_polys< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +>( + witness: &MatrixWitness, + deltas: &[F], + prover_data: &ProverOnlyCircuitData, + common_data: &CommonCircuitData, + lookup: bool, +) -> Vec> { + if lookup { + let polys: Vec>> = (0..common_data.config.num_challenges) + .map(|c| { + compute_lookup_polys( + witness, + &deltas[c * NUM_COINS_LOOKUP..(c + 1) * NUM_COINS_LOOKUP] + .try_into() + .unwrap(), + prover_data, + common_data, + ) + }) + .collect(); + polys.concat() + } else { + vec![] + } +} + +const BATCH_SIZE: usize = 32; + +fn compute_quotient_polys< + 'a, + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +>( + common_data: &CommonCircuitData, + prover_data: &'a ProverOnlyCircuitData, + public_inputs_hash: &<>::InnerHasher as Hasher>::Hash, + wires_commitment: &'a PolynomialBatch, + zs_partial_products_and_lookup_commitment: &'a PolynomialBatch, + betas: &[F], + gammas: &[F], + deltas: &[F], + alphas: &[F], +) -> Vec> { + let num_challenges = common_data.config.num_challenges; + + let has_lookup = common_data.num_lookup_polys != 0; + + let quotient_degree_bits = log2_ceil(common_data.quotient_degree_factor); + assert!( + quotient_degree_bits <= common_data.config.fri_config.rate_bits, + "Having constraints of degree higher than the rate is not supported yet. \ + If we need this in the future, we can precompute the larger LDE before computing the `PolynomialBatch`s." + ); + + // We reuse the LDE computed in `PolynomialBatch` and extract every `step` + // points to get an LDE matching `max_filtered_constraint_degree`. + let step = 1 << (common_data.config.fri_config.rate_bits - quotient_degree_bits); + // When opening the `Z`s polys at the "next" point in Plonk, need to look at the + // point `next_step` steps away since we work on an LDE of degree + // `max_filtered_constraint_degree`. + let next_step = 1 << quotient_degree_bits; + + let points = F::two_adic_subgroup(common_data.degree_bits() + quotient_degree_bits); + let lde_size = points.len(); + + let z_h_on_coset = ZeroPolyOnCoset::new(common_data.degree_bits(), quotient_degree_bits); + + // Precompute the lookup table evals on the challenges in delta + // These values are used to produce the final RE constraints for each lut, + // and are the same each time in check_lookup_constraints_batched. + // lut_poly_evals[i][j] gives the eval for the i'th challenge and the j'th + // lookup table + let lut_re_poly_evals: Vec> = if has_lookup { + let num_lut_slots = LookupTableGate::num_slots(&common_data.config); + (0..num_challenges) + .map(move |i| { + let cur_deltas = &deltas[NUM_COINS_LOOKUP * i..NUM_COINS_LOOKUP * (i + 1)]; + let cur_challenge_delta = cur_deltas[LookupChallenges::ChallengeDelta as usize]; + + (LookupSelectors::StartEnd as usize..common_data.num_lookup_selectors) + .map(|r| { + let lut_row_number = ceil_div_usize( + common_data.luts[r - LookupSelectors::StartEnd as usize].len(), + num_lut_slots, + ); + + get_lut_poly( + common_data, + r - LookupSelectors::StartEnd as usize, + cur_deltas, + num_lut_slots * lut_row_number, + ) + .eval(cur_challenge_delta) + }) + .collect() + }) + .collect() + } else { + vec![] + }; + + let lut_re_poly_evals_refs: Vec<&[F]> = + lut_re_poly_evals.iter().map(|v| v.as_slice()).collect(); + + let points_batches = points.par_chunks(BATCH_SIZE); + let num_batches = ceil_div_usize(points.len(), BATCH_SIZE); + + let quotient_values: Vec> = points_batches + .enumerate() + .flat_map(|(batch_i, xs_batch)| { + // Each batch must be the same size, except the last one, which may be smaller. + debug_assert!( + xs_batch.len() == BATCH_SIZE + || (batch_i == num_batches - 1 && xs_batch.len() <= BATCH_SIZE) + ); + + let indices_batch: Vec = + (BATCH_SIZE * batch_i..BATCH_SIZE * batch_i + xs_batch.len()).collect(); + + let mut shifted_xs_batch = Vec::with_capacity(xs_batch.len()); + let mut local_zs_batch = Vec::with_capacity(xs_batch.len()); + let mut next_zs_batch = Vec::with_capacity(xs_batch.len()); + + let mut local_lookup_batch = Vec::with_capacity(xs_batch.len()); + let mut next_lookup_batch = Vec::with_capacity(xs_batch.len()); + + let mut partial_products_batch = Vec::with_capacity(xs_batch.len()); + let mut s_sigmas_batch = Vec::with_capacity(xs_batch.len()); + + let mut local_constants_batch_refs = Vec::with_capacity(xs_batch.len()); + let mut local_wires_batch_refs = Vec::with_capacity(xs_batch.len()); + + for (&i, &x) in indices_batch.iter().zip(xs_batch) { + let shifted_x = F::coset_shift() * x; + let i_next = (i + next_step) % lde_size; + let local_constants_sigmas = prover_data + .constants_sigmas_commitment + .get_lde_values(i, step); + let local_constants = &local_constants_sigmas[common_data.constants_range()]; + let s_sigmas = &local_constants_sigmas[common_data.sigmas_range()]; + let local_wires = wires_commitment.get_lde_values(i, step); + let local_zs_partial_and_lookup = + zs_partial_products_and_lookup_commitment.get_lde_values(i, step); + let next_zs_partial_and_lookup = + zs_partial_products_and_lookup_commitment.get_lde_values(i_next, step); + + let local_zs = &local_zs_partial_and_lookup[common_data.zs_range()]; + + let next_zs = &next_zs_partial_and_lookup[common_data.zs_range()]; + + let partial_products = + &local_zs_partial_and_lookup[common_data.partial_products_range()]; + + if has_lookup { + let local_lookup_zs = &local_zs_partial_and_lookup[common_data.lookup_range()]; + + let next_lookup_zs = &next_zs_partial_and_lookup[common_data.lookup_range()]; + debug_assert_eq!(local_lookup_zs.len(), common_data.num_all_lookup_polys()); + + local_lookup_batch.push(local_lookup_zs); + next_lookup_batch.push(next_lookup_zs); + } + + debug_assert_eq!(local_wires.len(), common_data.config.num_wires); + debug_assert_eq!(local_zs.len(), num_challenges); + + local_constants_batch_refs.push(local_constants); + local_wires_batch_refs.push(local_wires); + + shifted_xs_batch.push(shifted_x); + local_zs_batch.push(local_zs); + next_zs_batch.push(next_zs); + partial_products_batch.push(partial_products); + s_sigmas_batch.push(s_sigmas); + } + + // NB (JN): I'm not sure how (in)efficient the below is. It needs measuring. + let mut local_constants_batch = + vec![F::ZERO; xs_batch.len() * local_constants_batch_refs[0].len()]; + for i in 0..local_constants_batch_refs[0].len() { + for (j, constants) in local_constants_batch_refs.iter().enumerate() { + local_constants_batch[i * xs_batch.len() + j] = constants[i]; + } + } + + let mut local_wires_batch = + vec![F::ZERO; xs_batch.len() * local_wires_batch_refs[0].len()]; + for i in 0..local_wires_batch_refs[0].len() { + for (j, wires) in local_wires_batch_refs.iter().enumerate() { + local_wires_batch[i * xs_batch.len() + j] = wires[i]; + } + } + + let vars_batch = EvaluationVarsBaseBatch::new( + xs_batch.len(), + &local_constants_batch, + &local_wires_batch, + public_inputs_hash, + ); + + let mut quotient_values_batch = eval_vanishing_poly_base_batch::( + common_data, + &indices_batch, + &shifted_xs_batch, + vars_batch, + &local_zs_batch, + &next_zs_batch, + &local_lookup_batch, + &next_lookup_batch, + &partial_products_batch, + &s_sigmas_batch, + betas, + gammas, + deltas, + alphas, + &z_h_on_coset, + &lut_re_poly_evals_refs, + ); + + for (&i, quotient_values) in indices_batch.iter().zip(quotient_values_batch.iter_mut()) + { + let denominator_inv = z_h_on_coset.eval_inverse(i); + quotient_values + .iter_mut() + .for_each(|v| *v *= denominator_inv); + } + quotient_values_batch + }) + .collect(); + + transpose("ient_values) + .into_par_iter() + .map(PolynomialValues::new) + .map(|values| values.coset_ifft(F::coset_shift())) + .collect() +} diff --git a/plonky2/src/plonk/validate_shape.rs b/plonky2/src/plonk/validate_shape.rs new file mode 100644 index 000000000..304aa04a2 --- /dev/null +++ b/plonky2/src/plonk/validate_shape.rs @@ -0,0 +1,72 @@ +use anyhow::ensure; + +use crate::field::extension::Extendable; +use crate::hash::hash_types::RichField; +use crate::plonk::circuit_data::CommonCircuitData; +use crate::plonk::config::GenericConfig; +use crate::plonk::proof::{OpeningSet, Proof, ProofWithPublicInputs}; + +pub(crate) fn validate_proof_with_pis_shape( + proof_with_pis: &ProofWithPublicInputs, + common_data: &CommonCircuitData, +) -> anyhow::Result<()> +where + F: RichField + Extendable, + C: GenericConfig, +{ + let ProofWithPublicInputs { + proof, + public_inputs, + } = proof_with_pis; + validate_proof_shape(proof, common_data)?; + ensure!( + public_inputs.len() == common_data.num_public_inputs, + "Number of public inputs doesn't match circuit data." + ); + Ok(()) +} + +fn validate_proof_shape( + proof: &Proof, + common_data: &CommonCircuitData, +) -> anyhow::Result<()> +where + F: RichField + Extendable, + C: GenericConfig, +{ + let config = &common_data.config; + let Proof { + wires_cap, + plonk_zs_partial_products_cap, + quotient_polys_cap, + openings, + // The shape of the opening proof will be checked in the FRI verifier (see + // validate_fri_proof_shape), so we ignore it here. + opening_proof: _, + } = proof; + let OpeningSet { + constants, + plonk_sigmas, + wires, + plonk_zs, + plonk_zs_next, + partial_products, + quotient_polys, + lookup_zs, + lookup_zs_next, + } = openings; + let cap_height = common_data.fri_params.config.cap_height; + ensure!(wires_cap.height() == cap_height); + ensure!(plonk_zs_partial_products_cap.height() == cap_height); + ensure!(quotient_polys_cap.height() == cap_height); + ensure!(constants.len() == common_data.num_constants); + ensure!(plonk_sigmas.len() == config.num_routed_wires); + ensure!(wires.len() == config.num_wires); + ensure!(plonk_zs.len() == config.num_challenges); + ensure!(plonk_zs_next.len() == config.num_challenges); + ensure!(partial_products.len() == config.num_challenges * common_data.num_partial_products); + ensure!(quotient_polys.len() == common_data.num_quotient_polys()); + ensure!(lookup_zs.len() == common_data.num_all_lookup_polys()); + ensure!(lookup_zs_next.len() == common_data.num_all_lookup_polys()); + Ok(()) +} diff --git a/plonky2/src/plonk/vanishing_poly.rs b/plonky2/src/plonk/vanishing_poly.rs new file mode 100644 index 000000000..869cb98a0 --- /dev/null +++ b/plonky2/src/plonk/vanishing_poly.rs @@ -0,0 +1,1153 @@ +#[cfg(not(feature = "std"))] +use alloc::{format, vec, vec::Vec}; +use core::cmp::min; + +use plonky2_field::polynomial::PolynomialCoeffs; +use plonky2_util::ceil_div_usize; + +use super::circuit_builder::{LookupChallenges, NUM_COINS_LOOKUP}; +use super::vars::EvaluationVarsBase; +use crate::field::batch_util::batch_add_inplace; +use crate::field::extension::{Extendable, FieldExtension}; +use crate::field::types::Field; +use crate::field::zero_poly_coset::ZeroPolyOnCoset; +use crate::gates::lookup::LookupGate; +use crate::gates::lookup_table::LookupTableGate; +use crate::gates::selectors::LookupSelectors; +use crate::hash::hash_types::RichField; +use crate::iop::ext_target::ExtensionTarget; +use crate::iop::target::Target; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::CommonCircuitData; +use crate::plonk::plonk_common; +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; +use crate::util::strided_view::PackedStridedView; +use crate::with_context; + +/// Get the polynomial associated to a lookup table with current challenges. +pub(crate) fn get_lut_poly, const D: usize>( + common_data: &CommonCircuitData, + lut_index: usize, + deltas: &[F], + degree: usize, +) -> PolynomialCoeffs { + let b = deltas[LookupChallenges::ChallengeB as usize]; + let mut coeffs = Vec::with_capacity(common_data.luts[lut_index].len()); + let n = common_data.luts[lut_index].len(); + for (input, output) in common_data.luts[lut_index].iter() { + coeffs.push(F::from_canonical_u16(*input) + b * F::from_canonical_u16(*output)); + } + coeffs.append(&mut vec![F::ZERO; degree - n]); + coeffs.reverse(); + PolynomialCoeffs::new(coeffs) +} + +/// Evaluate the vanishing polynomial at `x`. In this context, the vanishing +/// polynomial is a random linear combination of gate constraints, plus some +/// other terms relating to the permutation argument. All such terms should +/// vanish on `H`. +pub(crate) fn eval_vanishing_poly, const D: usize>( + common_data: &CommonCircuitData, + x: F::Extension, + vars: EvaluationVars, + local_zs: &[F::Extension], + next_zs: &[F::Extension], + local_lookup_zs: &[F::Extension], + next_lookup_zs: &[F::Extension], + partial_products: &[F::Extension], + s_sigmas: &[F::Extension], + betas: &[F], + gammas: &[F], + alphas: &[F], + deltas: &[F], +) -> Vec { + let has_lookup = common_data.num_lookup_polys != 0; + let max_degree = common_data.quotient_degree_factor; + let num_prods = common_data.num_partial_products; + + let constraint_terms = evaluate_gate_constraints::(common_data, vars); + + let lookup_selectors = &vars.local_constants[common_data.selectors_info.num_selectors() + ..common_data.selectors_info.num_selectors() + common_data.num_lookup_selectors]; + + // The L_0(x) (Z(x) - 1) vanishing terms. + let mut vanishing_z_1_terms = Vec::new(); + + // The terms checking the lookup constraints, if any. + let mut vanishing_all_lookup_terms = if has_lookup { + let num_sldc_polys = common_data.num_lookup_polys - 1; + Vec::with_capacity( + common_data.config.num_challenges * (4 + common_data.luts.len() + 2 * num_sldc_polys), + ) + } else { + Vec::new() + }; + + // The terms checking the partial products. + let mut vanishing_partial_products_terms = Vec::new(); + + 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(l_0_x * (z_x - F::Extension::ONE)); + + if has_lookup { + let cur_local_lookup_zs = &local_lookup_zs + [common_data.num_lookup_polys * i..common_data.num_lookup_polys * (i + 1)]; + let cur_next_lookup_zs = &next_lookup_zs + [common_data.num_lookup_polys * i..common_data.num_lookup_polys * (i + 1)]; + + let cur_deltas = &deltas[NUM_COINS_LOOKUP * i..NUM_COINS_LOOKUP * (i + 1)]; + + let lookup_constraints = check_lookup_constraints( + common_data, + vars, + cur_local_lookup_zs, + cur_next_lookup_zs, + lookup_selectors, + cur_deltas.try_into().unwrap(), + ); + + vanishing_all_lookup_terms.extend(lookup_constraints); + } + + let numerator_values = (0..common_data.config.num_routed_wires) + .map(|j| { + let wire_value = vars.local_wires[j]; + let k_i = common_data.k_is[j]; + let s_id = x.scalar_mul(k_i); + wire_value + s_id.scalar_mul(betas[i]) + gammas[i].into() + }) + .collect::>(); + let denominator_values = (0..common_data.config.num_routed_wires) + .map(|j| { + let wire_value = vars.local_wires[j]; + let s_sigma = s_sigmas[j]; + wire_value + s_sigma.scalar_mul(betas[i]) + gammas[i].into() + }) + .collect::>(); + + // The partial products considered for this iteration of `i`. + let current_partial_products = &partial_products[i * num_prods..(i + 1) * num_prods]; + // Check the quotient partial products. + let partial_product_checks = check_partial_products( + &numerator_values, + &denominator_values, + current_partial_products, + z_x, + z_gx, + max_degree, + ); + vanishing_partial_products_terms.extend(partial_product_checks); + } + + let vanishing_terms = [ + vanishing_z_1_terms, + vanishing_partial_products_terms, + vanishing_all_lookup_terms, + constraint_terms, + ] + .concat(); + + let alphas = &alphas.iter().map(|&a| a.into()).collect::>(); + plonk_common::reduce_with_powers_multi(&vanishing_terms, alphas) +} + +/// Like `eval_vanishing_poly`, but specialized for base field points. Batched. +pub(crate) fn eval_vanishing_poly_base_batch, const D: usize>( + common_data: &CommonCircuitData, + indices_batch: &[usize], + xs_batch: &[F], + vars_batch: EvaluationVarsBaseBatch, + local_zs_batch: &[&[F]], + next_zs_batch: &[&[F]], + local_lookup_zs_batch: &[&[F]], + next_lookup_zs_batch: &[&[F]], + partial_products_batch: &[&[F]], + s_sigmas_batch: &[&[F]], + betas: &[F], + gammas: &[F], + deltas: &[F], + alphas: &[F], + z_h_on_coset: &ZeroPolyOnCoset, + lut_re_poly_evals: &[&[F]], +) -> Vec> { + let has_lookup = common_data.num_lookup_polys != 0; + + let n = indices_batch.len(); + assert_eq!(xs_batch.len(), n); + assert_eq!(vars_batch.len(), n); + assert_eq!(local_zs_batch.len(), n); + assert_eq!(next_zs_batch.len(), n); + if has_lookup { + assert_eq!(local_lookup_zs_batch.len(), n); + assert_eq!(next_lookup_zs_batch.len(), n); + } else { + assert_eq!(local_lookup_zs_batch.len(), 0); + assert_eq!(next_lookup_zs_batch.len(), 0); + } + assert_eq!(partial_products_batch.len(), n); + assert_eq!(s_sigmas_batch.len(), n); + + let max_degree = common_data.quotient_degree_factor; + let num_prods = common_data.num_partial_products; + + let num_gate_constraints = common_data.num_gate_constraints; + + let constraint_terms_batch = + evaluate_gate_constraints_base_batch::(common_data, vars_batch); + debug_assert!(constraint_terms_batch.len() == n * num_gate_constraints); + + let num_challenges = common_data.config.num_challenges; + let num_routed_wires = common_data.config.num_routed_wires; + + let mut numerator_values = Vec::with_capacity(num_routed_wires); + let mut denominator_values = Vec::with_capacity(num_routed_wires); + + // 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(); + + // The terms checking the lookup constraints. + let mut vanishing_all_lookup_terms = if has_lookup { + let num_sldc_polys = common_data.num_lookup_polys - 1; + Vec::with_capacity( + common_data.config.num_challenges * (4 + common_data.luts.len() + 2 * num_sldc_polys), + ) + } else { + Vec::new() + }; + + let mut res_batch: Vec> = Vec::with_capacity(n); + for k in 0..n { + let index = indices_batch[k]; + let x = xs_batch[k]; + let vars = vars_batch.view(k); + + let lookup_selectors: Vec = (0..common_data.num_lookup_selectors) + .map(|i| vars.local_constants[common_data.selectors_info.num_selectors() + i]) + .collect(); + + let local_zs = local_zs_batch[k]; + let next_zs = next_zs_batch[k]; + let local_lookup_zs = if has_lookup { + local_lookup_zs_batch[k] + } else { + &[] + }; + + let next_lookup_zs = if has_lookup { + next_lookup_zs_batch[k] + } else { + &[] + }; + + let partial_products = partial_products_batch[k]; + let s_sigmas = s_sigmas_batch[k]; + + let constraint_terms = PackedStridedView::new(&constraint_terms_batch, n, k); + + 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(l_0_x * z_x.sub_one()); + + // If there are lookups in the circuit, then we add the lookup constraints. + if has_lookup { + let cur_deltas = &deltas[NUM_COINS_LOOKUP * i..NUM_COINS_LOOKUP * (i + 1)]; + + let cur_local_lookup_zs = &local_lookup_zs + [common_data.num_lookup_polys * i..common_data.num_lookup_polys * (i + 1)]; + let cur_next_lookup_zs = &next_lookup_zs + [common_data.num_lookup_polys * i..common_data.num_lookup_polys * (i + 1)]; + + let lookup_constraints = check_lookup_constraints_batch( + common_data, + vars, + cur_local_lookup_zs, + cur_next_lookup_zs, + &lookup_selectors, + cur_deltas.try_into().unwrap(), + lut_re_poly_evals[i], + ); + vanishing_all_lookup_terms.extend(lookup_constraints); + } + + numerator_values.extend((0..num_routed_wires).map(|j| { + let wire_value = vars.local_wires[j]; + let k_i = common_data.k_is[j]; + let s_id = k_i * x; + wire_value + betas[i] * s_id + gammas[i] + })); + denominator_values.extend((0..num_routed_wires).map(|j| { + let wire_value = vars.local_wires[j]; + let s_sigma = s_sigmas[j]; + wire_value + betas[i] * s_sigma + gammas[i] + })); + + // The partial products considered for this iteration of `i`. + let current_partial_products = &partial_products[i * num_prods..(i + 1) * num_prods]; + // Check the numerator partial products. + let partial_product_checks = check_partial_products( + &numerator_values, + &denominator_values, + current_partial_products, + z_x, + z_gx, + max_degree, + ); + vanishing_partial_products_terms.extend(partial_product_checks); + + numerator_values.clear(); + denominator_values.clear(); + } + + let vanishing_terms = vanishing_z_1_terms + .iter() + .chain(vanishing_partial_products_terms.iter()) + .chain(vanishing_all_lookup_terms.iter()) + .chain(constraint_terms); + let res = plonk_common::reduce_with_powers_multi(vanishing_terms, alphas); + res_batch.push(res); + + vanishing_z_1_terms.clear(); + vanishing_partial_products_terms.clear(); + vanishing_all_lookup_terms.clear(); + } + res_batch +} + +/// Evaluates all lookup constraints, based on the logarithmic derivatives paper (), +/// following the Tip5 paper's implementation (). +/// +/// There are three polynomials to check: +/// - RE ensures the well formation of lookup tables; +/// - Sum is a running sum of m_i/(X - (input_i + a * output_i)) where (input_i, +/// output_i) are input pairs in the lookup table (LUT); +/// - LDC is a running sum of 1/(X - (input_i + a * output_i)) where (input_i, +/// output_i) are input pairs that look in the LUT. +/// Sum and LDC are broken down in partial polynomials to lower the constraint +/// degree, similarly to the permutation argument. They also share the same +/// partial SLDC polynomials, so that the last SLDC value is Sum(end) - +/// LDC(end). The final constraint Sum(end) = LDC(end) becomes simply SLDC(end) +/// = 0, and we can remove the LDC initial constraint. +pub fn check_lookup_constraints, const D: usize>( + common_data: &CommonCircuitData, + vars: EvaluationVars, + local_lookup_zs: &[F::Extension], + next_lookup_zs: &[F::Extension], + lookup_selectors: &[F::Extension], + deltas: &[F; 4], +) -> Vec { + let num_lu_slots = LookupGate::num_slots(&common_data.config); + let num_lut_slots = LookupTableGate::num_slots(&common_data.config); + let lu_degree = common_data.quotient_degree_factor - 1; + let num_sldc_polys = local_lookup_zs.len() - 1; + let lut_degree = ceil_div_usize(num_lut_slots, num_sldc_polys); + + let mut constraints = Vec::with_capacity(4 + common_data.luts.len() + 2 * num_sldc_polys); + + // RE is the first polynomial stored. + let z_re = local_lookup_zs[0]; + let next_z_re = next_lookup_zs[0]; + + // Partial Sums and LDCs are both stored in the remaining SLDC polynomials. + let z_x_lookup_sldcs = &local_lookup_zs[1..num_sldc_polys + 1]; + let z_gx_lookup_sldcs = &next_lookup_zs[1..num_sldc_polys + 1]; + + let delta_challenge_a = F::Extension::from(deltas[LookupChallenges::ChallengeA as usize]); + let delta_challenge_b = F::Extension::from(deltas[LookupChallenges::ChallengeB as usize]); + + // Compute all current looked and looking combos, i.e. the combos we need for + // the SLDC polynomials. + let current_looked_combos: Vec = (0..num_lut_slots) + .map(|s| { + let input_wire = vars.local_wires[LookupTableGate::wire_ith_looked_inp(s)]; + let output_wire = vars.local_wires[LookupTableGate::wire_ith_looked_out(s)]; + input_wire + delta_challenge_a * output_wire + }) + .collect(); + + let current_looking_combos: Vec = (0..num_lu_slots) + .map(|s| { + let input_wire = vars.local_wires[LookupGate::wire_ith_looking_inp(s)]; + let output_wire = vars.local_wires[LookupGate::wire_ith_looking_out(s)]; + input_wire + delta_challenge_a * output_wire + }) + .collect(); + + // Compute all current lookup combos, i.e. the combos used to check that the LUT + // is correct. + let current_lookup_combos: Vec = (0..num_lut_slots) + .map(|s| { + let input_wire = vars.local_wires[LookupTableGate::wire_ith_looked_inp(s)]; + let output_wire = vars.local_wires[LookupTableGate::wire_ith_looked_out(s)]; + input_wire + delta_challenge_b * output_wire + }) + .collect(); + + // Check last LDC constraint. + constraints.push( + lookup_selectors[LookupSelectors::LastLdc as usize] * z_x_lookup_sldcs[num_sldc_polys - 1], + ); + + // Check initial Sum constraint. + constraints.push(lookup_selectors[LookupSelectors::InitSre as usize] * z_x_lookup_sldcs[0]); + + // Check initial RE constraint. + constraints.push(lookup_selectors[LookupSelectors::InitSre as usize] * z_re); + + let current_delta = deltas[LookupChallenges::ChallengeDelta as usize]; + + // Check final RE constraints for each different LUT. + for r in LookupSelectors::StartEnd as usize..common_data.num_lookup_selectors { + let cur_ends_selector = lookup_selectors[r]; + let lut_row_number = ceil_div_usize( + common_data.luts[r - LookupSelectors::StartEnd as usize].len(), + num_lut_slots, + ); + let cur_function_eval = get_lut_poly( + common_data, + r - LookupSelectors::StartEnd as usize, + deltas, + num_lut_slots * lut_row_number, + ) + .eval(current_delta); + + constraints.push(cur_ends_selector * (z_re - cur_function_eval.into())) + } + + // Check RE row transition constraint. + let mut cur_sum = next_z_re; + for elt in ¤t_lookup_combos { + cur_sum = + cur_sum * F::Extension::from(deltas[LookupChallenges::ChallengeDelta as usize]) + *elt; + } + let unfiltered_re_line = z_re - cur_sum; + + constraints.push(lookup_selectors[LookupSelectors::TransSre as usize] * unfiltered_re_line); + + for poly in 0..num_sldc_polys { + // Compute prod(alpha - combo) for the current slot for Sum. + let lut_prod: F::Extension = (poly * lut_degree + ..min((poly + 1) * lut_degree, num_lut_slots)) + .map(|i| { + F::Extension::from(deltas[LookupChallenges::ChallengeAlpha as usize]) + - current_looked_combos[i] + }) + .product(); + + // Compute prod(alpha - combo) for the current slot for LDC. + let lu_prod: F::Extension = (poly * lu_degree..min((poly + 1) * lu_degree, num_lu_slots)) + .map(|i| { + F::Extension::from(deltas[LookupChallenges::ChallengeAlpha as usize]) + - current_looking_combos[i] + }) + .product(); + + // Function which computes, given index i: prod_{j!=i}(alpha - combo_j) for Sum. + let lut_prod_i = |i| { + (poly * lut_degree..min((poly + 1) * lut_degree, num_lut_slots)) + .map(|j| { + if j != i { + F::Extension::from(deltas[LookupChallenges::ChallengeAlpha as usize]) + - current_looked_combos[j] + } else { + F::Extension::ONE + } + }) + .product() + }; + + // Function which computes, given index i: prod_{j!=i}(alpha - combo_j) for LDC. + let lu_prod_i = |i| { + (poly * lu_degree..min((poly + 1) * lu_degree, num_lu_slots)) + .map(|j| { + if j != i { + F::Extension::from(deltas[LookupChallenges::ChallengeAlpha as usize]) + - current_looking_combos[j] + } else { + F::Extension::ONE + } + }) + .product() + }; + // Compute sum_i(prod_{j!=i}(alpha - combo_j)) for LDC. + let lu_sum_prods = (poly * lu_degree..min((poly + 1) * lu_degree, num_lu_slots)) + .fold(F::Extension::ZERO, |acc, i| acc + lu_prod_i(i)); + + // Compute sum_i(mul_i.prod_{j!=i}(alpha - combo_j)) for Sum. + let lut_sum_prods_with_mul = (poly * lut_degree + ..min((poly + 1) * lut_degree, num_lut_slots)) + .fold(F::Extension::ZERO, |acc, i| { + acc + vars.local_wires[LookupTableGate::wire_ith_multiplicity(i)] * lut_prod_i(i) + }); + + // The previous element is the previous poly of the current row or the last poly + // of the next row. + let prev = if poly == 0 { + z_gx_lookup_sldcs[num_sldc_polys - 1] + } else { + z_x_lookup_sldcs[poly - 1] + }; + + // Check Sum row and col transitions. It's the same constraint, with a row + // transition happening for slot == 0. + let unfiltered_sum_transition = + lut_prod * (z_x_lookup_sldcs[poly] - prev) - lut_sum_prods_with_mul; + constraints + .push(lookup_selectors[LookupSelectors::TransSre as usize] * unfiltered_sum_transition); + + // Check LDC row and col transitions. It's the same constraint, with a row + // transition happening for slot == 0. + let unfiltered_ldc_transition = lu_prod * (z_x_lookup_sldcs[poly] - prev) + lu_sum_prods; + constraints + .push(lookup_selectors[LookupSelectors::TransLdc as usize] * unfiltered_ldc_transition); + } + + constraints +} + +/// Same as `check_lookup_constraints`, but for the base field case. +pub fn check_lookup_constraints_batch, const D: usize>( + common_data: &CommonCircuitData, + vars: EvaluationVarsBase, + local_lookup_zs: &[F], + next_lookup_zs: &[F], + lookup_selectors: &[F], + deltas: &[F; 4], + lut_re_poly_evals: &[F], +) -> Vec { + let num_lu_slots = LookupGate::num_slots(&common_data.config); + let num_lut_slots = LookupTableGate::num_slots(&common_data.config); + let lu_degree = common_data.quotient_degree_factor - 1; + let num_sldc_polys = local_lookup_zs.len() - 1; + let lut_degree = ceil_div_usize(num_lut_slots, num_sldc_polys); + + let mut constraints = Vec::with_capacity(4 + common_data.luts.len() + 2 * num_sldc_polys); + + // RE is the first polynomial stored. + let z_re = local_lookup_zs[0]; + let next_z_re = next_lookup_zs[0]; + + // Partial Sums and LDCs are both stored in the remaining polynomials. + let z_x_lookup_sldcs = &local_lookup_zs[1..num_sldc_polys + 1]; + let z_gx_lookup_sldcs = &next_lookup_zs[1..num_sldc_polys + 1]; + + // Compute all current looked and looking combos, i.e. the combos we need for + // the SLDC polynomials. + let current_looked_combos: Vec = (0..num_lut_slots) + .map(|s| { + let input_wire = vars.local_wires[LookupTableGate::wire_ith_looked_inp(s)]; + let output_wire = vars.local_wires[LookupTableGate::wire_ith_looked_out(s)]; + input_wire + deltas[LookupChallenges::ChallengeA as usize] * output_wire + }) + .collect(); + + let current_looking_combos: Vec = (0..num_lu_slots) + .map(|s| { + let input_wire = vars.local_wires[LookupGate::wire_ith_looking_inp(s)]; + let output_wire = vars.local_wires[LookupGate::wire_ith_looking_out(s)]; + input_wire + deltas[LookupChallenges::ChallengeA as usize] * output_wire + }) + .collect(); + + // Compute all current lookup combos, i.e. the combos used to check that the LUT + // is correct. + let current_lookup_combos: Vec = (0..num_lut_slots) + .map(|s| { + let input_wire = vars.local_wires[LookupTableGate::wire_ith_looked_inp(s)]; + let output_wire = vars.local_wires[LookupTableGate::wire_ith_looked_out(s)]; + input_wire + deltas[LookupChallenges::ChallengeB as usize] * output_wire + }) + .collect(); + + // Check last LDC constraint. + constraints.push( + lookup_selectors[LookupSelectors::LastLdc as usize] * z_x_lookup_sldcs[num_sldc_polys - 1], + ); + + // Check initial Sum constraint. + constraints.push(lookup_selectors[LookupSelectors::InitSre as usize] * z_x_lookup_sldcs[0]); + + // Check initial RE constraint. + constraints.push(lookup_selectors[LookupSelectors::InitSre as usize] * z_re); + + // Check final RE constraints for each different LUT. + for r in LookupSelectors::StartEnd as usize..common_data.num_lookup_selectors { + let cur_ends_selector = lookup_selectors[r]; + + // Use the precomputed value for the lut poly evaluation + let re_poly_eval = lut_re_poly_evals[r - LookupSelectors::StartEnd as usize]; + + constraints.push(cur_ends_selector * (z_re - re_poly_eval)) + } + + // Check RE row transition constraint. + let mut cur_sum = next_z_re; + for elt in ¤t_lookup_combos { + cur_sum = cur_sum * deltas[LookupChallenges::ChallengeDelta as usize] + *elt; + } + let unfiltered_re_line = z_re - cur_sum; + + constraints.push(lookup_selectors[LookupSelectors::TransSre as usize] * unfiltered_re_line); + + for poly in 0..num_sldc_polys { + // Compute prod(alpha - combo) for the current slot for Sum. + let lut_prod: F = (poly * lut_degree..min((poly + 1) * lut_degree, num_lut_slots)) + .map(|i| deltas[LookupChallenges::ChallengeAlpha as usize] - current_looked_combos[i]) + .product(); + + // Compute prod(alpha - combo) for the current slot for LDC. + let lu_prod: F = (poly * lu_degree..min((poly + 1) * lu_degree, num_lu_slots)) + .map(|i| deltas[LookupChallenges::ChallengeAlpha as usize] - current_looking_combos[i]) + .product(); + + // Function which computes, given index i: prod_{j!=i}(alpha - combo_j) for Sum. + let lut_prod_i = |i| { + (poly * lut_degree..min((poly + 1) * lut_degree, num_lut_slots)) + .map(|j| { + if j != i { + deltas[LookupChallenges::ChallengeAlpha as usize] - current_looked_combos[j] + } else { + F::ONE + } + }) + .product() + }; + + // Function which computes, given index i: prod_{j!=i}(alpha - combo_j) for LDC. + let lu_prod_i = |i| { + (poly * lu_degree..min((poly + 1) * lu_degree, num_lu_slots)) + .map(|j| { + if j != i { + deltas[LookupChallenges::ChallengeAlpha as usize] + - current_looking_combos[j] + } else { + F::ONE + } + }) + .product() + }; + + // Compute sum_i(prod_{j!=i}(alpha - combo_j)) for LDC. + let lu_sum_prods = (poly * lu_degree..min((poly + 1) * lu_degree, num_lu_slots)) + .fold(F::ZERO, |acc, i| acc + lu_prod_i(i)); + + // Compute sum_i(mul_i.prod_{j!=i}(alpha - combo_j)) for Sum. + let lut_sum_prods_with_mul = (poly * lut_degree + ..min((poly + 1) * lut_degree, num_lut_slots)) + .fold(F::ZERO, |acc, i| { + acc + vars.local_wires[LookupTableGate::wire_ith_multiplicity(i)] * lut_prod_i(i) + }); + + // The previous element is the previous poly of the current row or the last poly + // of the next row. + let prev = if poly == 0 { + z_gx_lookup_sldcs[num_sldc_polys - 1] + } else { + z_x_lookup_sldcs[poly - 1] + }; + + // Check Sum row and col transitions. It's the same constraint, with a row + // transition happening for slot == 0. + let unfiltered_sum_transition = + lut_prod * (z_x_lookup_sldcs[poly] - prev) - lut_sum_prods_with_mul; + constraints + .push(lookup_selectors[LookupSelectors::TransSre as usize] * unfiltered_sum_transition); + + // Check LDC row and col transitions. It's the same constraint, with a row + // transition happening for slot == 0. + let unfiltered_ldc_transition = lu_prod * (z_x_lookup_sldcs[poly] - prev) + lu_sum_prods; + constraints + .push(lookup_selectors[LookupSelectors::TransLdc as usize] * unfiltered_ldc_transition); + } + constraints +} + +/// Evaluates all gate constraints. +/// +/// `num_gate_constraints` is the largest number of constraints imposed by any +/// gate. It is not strictly necessary, but it helps performance by ensuring +/// that we allocate a vector with exactly the capacity that we need. +pub fn evaluate_gate_constraints, const D: usize>( + common_data: &CommonCircuitData, + vars: EvaluationVars, +) -> Vec { + let mut constraints = vec![F::Extension::ZERO; common_data.num_gate_constraints]; + for (i, gate) in common_data.gates.iter().enumerate() { + let selector_index = common_data.selectors_info.selector_indices[i]; + let gate_constraints = gate.0.eval_filtered( + vars, + i, + selector_index, + common_data.selectors_info.groups[selector_index].clone(), + common_data.selectors_info.num_selectors(), + common_data.num_lookup_selectors, + ); + for (i, c) in gate_constraints.into_iter().enumerate() { + debug_assert!( + i < common_data.num_gate_constraints, + "num_constraints() gave too low of a number" + ); + constraints[i] += c; + } + } + constraints +} + +/// Evaluate all gate constraints in the base field. +/// +/// Returns a vector of `num_gate_constraints * vars_batch.len()` field +/// elements. The constraints corresponding to `vars_batch[i]` are found in +/// `result[i], result[vars_batch.len() + i], result[2 * vars_batch.len() + i], +/// ...`. +pub fn evaluate_gate_constraints_base_batch, const D: usize>( + common_data: &CommonCircuitData, + vars_batch: EvaluationVarsBaseBatch, +) -> Vec { + let mut constraints_batch = vec![F::ZERO; common_data.num_gate_constraints * vars_batch.len()]; + for (i, gate) in common_data.gates.iter().enumerate() { + let selector_index = common_data.selectors_info.selector_indices[i]; + let gate_constraints_batch = gate.0.eval_filtered_base_batch( + vars_batch, + i, + selector_index, + common_data.selectors_info.groups[selector_index].clone(), + common_data.selectors_info.num_selectors(), + common_data.num_lookup_selectors, + ); + debug_assert!( + gate_constraints_batch.len() <= constraints_batch.len(), + "num_constraints() gave too low of a number" + ); + // below adds all constraints for all points + batch_add_inplace( + &mut constraints_batch[..gate_constraints_batch.len()], + &gate_constraints_batch, + ); + } + constraints_batch +} + +pub fn evaluate_gate_constraints_circuit, const D: usize>( + builder: &mut CircuitBuilder, + common_data: &CommonCircuitData, + vars: EvaluationTargets, +) -> Vec> { + let mut all_gate_constraints = vec![builder.zero_extension(); common_data.num_gate_constraints]; + for (i, gate) in common_data.gates.iter().enumerate() { + let selector_index = common_data.selectors_info.selector_indices[i]; + with_context!( + builder, + &format!("evaluate {} constraints", gate.0.id()), + gate.0.eval_filtered_circuit( + builder, + vars, + i, + selector_index, + common_data.selectors_info.groups[selector_index].clone(), + common_data.selectors_info.num_selectors(), + common_data.num_lookup_selectors, + &mut all_gate_constraints, + ) + ); + } + all_gate_constraints +} + +pub(crate) fn get_lut_poly_circuit, const D: usize>( + builder: &mut CircuitBuilder, + common_data: &CommonCircuitData, + lut_index: usize, + deltas: &[Target], + degree: usize, +) -> Target { + let b = deltas[LookupChallenges::ChallengeB as usize]; + let delta = deltas[LookupChallenges::ChallengeDelta as usize]; + let n = common_data.luts[lut_index].len(); + let mut coeffs: Vec = common_data.luts[lut_index] + .iter() + .map(|(input, output)| { + let temp = builder.mul_const(F::from_canonical_u16(*output), b); + builder.add_const(temp, F::from_canonical_u16(*input)) + }) + .collect(); + for _ in n..degree { + coeffs.push(builder.zero()); + } + coeffs.reverse(); + coeffs + .iter() + .rev() + .fold(builder.constant(F::ZERO), |acc, &c| { + let temp = builder.mul(acc, delta); + builder.add(temp, c) + }) +} + +/// Evaluate the vanishing polynomial at `x`. In this context, the vanishing +/// polynomial is a random linear combination of gate constraints, plus some +/// other terms relating to the permutation argument. All such terms should +/// vanish on `H`. +/// +/// Assumes `x != 1`; if `x` could be 1 then this is unsound. This is fine if +/// `x` is a random variable drawn from a sufficiently large domain. +pub(crate) fn eval_vanishing_poly_circuit, const D: usize>( + builder: &mut CircuitBuilder, + common_data: &CommonCircuitData, + x: ExtensionTarget, + x_pow_deg: ExtensionTarget, + vars: EvaluationTargets, + local_zs: &[ExtensionTarget], + next_zs: &[ExtensionTarget], + local_lookup_zs: &[ExtensionTarget], + next_lookup_zs: &[ExtensionTarget], + partial_products: &[ExtensionTarget], + s_sigmas: &[ExtensionTarget], + betas: &[Target], + gammas: &[Target], + alphas: &[Target], + deltas: &[Target], +) -> Vec> { + let has_lookup = common_data.num_lookup_polys != 0; + let max_degree = common_data.quotient_degree_factor; + let num_prods = common_data.num_partial_products; + + let constraint_terms = with_context!( + builder, + "evaluate gate constraints", + evaluate_gate_constraints_circuit::(builder, common_data, vars,) + ); + + let lookup_selectors = &vars.local_constants[common_data.selectors_info.num_selectors() + ..common_data.selectors_info.num_selectors() + common_data.num_lookup_selectors]; + + // The L_0(x) (Z(x) - 1) vanishing terms. + let mut vanishing_z_1_terms = Vec::new(); + + // The terms checking lookup constraints. + let mut vanishing_all_lookup_terms = if has_lookup { + let num_sldc_polys = common_data.num_lookup_polys - 1; + Vec::with_capacity( + common_data.config.num_challenges * (4 + common_data.luts.len() + 2 * num_sldc_polys), + ) + } else { + Vec::new() + }; + + // The terms checking the partial products. + let mut vanishing_partial_products_terms = Vec::new(); + + 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::with_capacity(common_data.config.num_routed_wires); + for j in 0..common_data.config.num_routed_wires { + let k = builder.constant(common_data.k_is[j]); + s_ids.push(builder.scalar_mul_ext(k, x)); + } + + for i in 0..common_data.config.num_challenges { + let z_x = local_zs[i]; + let z_gx = next_zs[i]; + + // L_0(x) (Z(x) - 1) = 0. + vanishing_z_1_terms.push(builder.mul_sub_extension(l_0_x, z_x, l_0_x)); + + // If there are lookups in the circuit, then we add the lookup constraints + if has_lookup { + let cur_local_lookup_zs = &local_lookup_zs + [common_data.num_lookup_polys * i..common_data.num_lookup_polys * (i + 1)]; + let cur_next_lookup_zs = &next_lookup_zs + [common_data.num_lookup_polys * i..common_data.num_lookup_polys * (i + 1)]; + + let cur_deltas = &deltas[NUM_COINS_LOOKUP * i..NUM_COINS_LOOKUP * (i + 1)]; + + let lookup_constraints = check_lookup_constraints_circuit( + builder, + common_data, + vars, + cur_local_lookup_zs, + cur_next_lookup_zs, + lookup_selectors, + cur_deltas, + ); + vanishing_all_lookup_terms.extend(lookup_constraints); + } + + let mut numerator_values = Vec::with_capacity(common_data.config.num_routed_wires); + let mut denominator_values = Vec::with_capacity(common_data.config.num_routed_wires); + + for j in 0..common_data.config.num_routed_wires { + let wire_value = vars.local_wires[j]; + let beta_ext = builder.convert_to_ext(betas[i]); + let gamma_ext = builder.convert_to_ext(gammas[i]); + + // The numerator is `beta * s_id + wire_value + gamma`, and the denominator is + // `beta * s_sigma + wire_value + gamma`. + let wire_value_plus_gamma = builder.add_extension(wire_value, gamma_ext); + let numerator = builder.mul_add_extension(beta_ext, s_ids[j], wire_value_plus_gamma); + let denominator = + builder.mul_add_extension(beta_ext, s_sigmas[j], wire_value_plus_gamma); + numerator_values.push(numerator); + denominator_values.push(denominator); + } + + // The partial products considered for this iteration of `i`. + let current_partial_products = &partial_products[i * num_prods..(i + 1) * num_prods]; + // Check the quotient partial products. + let partial_product_checks = check_partial_products_circuit( + builder, + &numerator_values, + &denominator_values, + current_partial_products, + z_x, + z_gx, + max_degree, + ); + vanishing_partial_products_terms.extend(partial_product_checks); + } + + let vanishing_terms = [ + vanishing_z_1_terms, + vanishing_partial_products_terms, + vanishing_all_lookup_terms, + constraint_terms, + ] + .concat(); + + alphas + .iter() + .map(|&alpha| { + let alpha = builder.convert_to_ext(alpha); + let mut alpha = ReducingFactorTarget::new(alpha); + alpha.reduce(&vanishing_terms, builder) + }) + .collect() +} + +/// Same as `check_lookup_constraints`, but for the recursive case. +pub fn check_lookup_constraints_circuit, const D: usize>( + builder: &mut CircuitBuilder, + common_data: &CommonCircuitData, + vars: EvaluationTargets, + local_lookup_zs: &[ExtensionTarget], + next_lookup_zs: &[ExtensionTarget], + lookup_selectors: &[ExtensionTarget], + deltas: &[Target], +) -> Vec> { + let num_lu_slots = LookupGate::num_slots(&common_data.config); + let num_lut_slots = LookupTableGate::num_slots(&common_data.config); + let lu_degree = common_data.quotient_degree_factor - 1; + let num_sldc_polys = local_lookup_zs.len() - 1; + let lut_degree = ceil_div_usize(num_lut_slots, num_sldc_polys); + + let mut constraints = Vec::with_capacity(4 + common_data.luts.len() + 2 * num_sldc_polys); + + // RE is the first polynomial stored. + let z_re = local_lookup_zs[0]; + let next_z_re = next_lookup_zs[0]; + + // Partial Sums and LDCs (i.e. the SLDC polynomials) are stored in the remaining + // polynomials. + let z_x_lookup_sldcs = &local_lookup_zs[1..num_sldc_polys + 1]; + let z_gx_lookup_sldcs = &next_lookup_zs[1..num_sldc_polys + 1]; + + // Convert deltas to ExtensionTargets. + let ext_deltas = deltas + .iter() + .map(|d| builder.convert_to_ext(*d)) + .collect::>(); + + // Computing all current looked and looking combos, i.e. the combos we need for + // the SLDC polynomials. + let current_looked_combos = (0..num_lut_slots) + .map(|s| { + let input_wire = vars.local_wires[LookupTableGate::wire_ith_looked_inp(s)]; + let output_wire = vars.local_wires[LookupTableGate::wire_ith_looked_out(s)]; + builder.mul_add_extension( + ext_deltas[LookupChallenges::ChallengeA as usize], + output_wire, + input_wire, + ) + }) + .collect::>(); + let current_looking_combos = (0..num_lu_slots) + .map(|s| { + let input_wire = vars.local_wires[LookupGate::wire_ith_looking_inp(s)]; + let output_wire = vars.local_wires[LookupGate::wire_ith_looking_out(s)]; + builder.mul_add_extension( + ext_deltas[LookupChallenges::ChallengeA as usize], + output_wire, + input_wire, + ) + }) + .collect::>(); + + let current_lut_subs = (0..num_lut_slots) + .map(|s| { + builder.sub_extension( + ext_deltas[LookupChallenges::ChallengeAlpha as usize], + current_looked_combos[s], + ) + }) + .collect::>(); + + let current_lu_subs = (0..num_lu_slots) + .map(|s| { + builder.sub_extension( + ext_deltas[LookupChallenges::ChallengeAlpha as usize], + current_looking_combos[s], + ) + }) + .collect::>(); + + // Computing all current lookup combos, i.e. the combos used to check that the + // LUT is correct. + let current_lookup_combos = (0..num_lut_slots) + .map(|s| { + let input_wire = vars.local_wires[LookupTableGate::wire_ith_looked_inp(s)]; + let output_wire = vars.local_wires[LookupTableGate::wire_ith_looked_out(s)]; + builder.mul_add_extension( + ext_deltas[LookupChallenges::ChallengeB as usize], + output_wire, + input_wire, + ) + }) + .collect::>(); + + // Check last LDC constraint. + constraints.push(builder.mul_extension( + lookup_selectors[LookupSelectors::LastLdc as usize], + z_x_lookup_sldcs[num_sldc_polys - 1], + )); + + // Check initial Sum constraint. + constraints.push(builder.mul_extension( + lookup_selectors[LookupSelectors::InitSre as usize], + z_x_lookup_sldcs[0], + )); + + // Check initial RE constraint. + constraints + .push(builder.mul_extension(lookup_selectors[LookupSelectors::InitSre as usize], z_re)); + + // Check final RE constraints for each different LUT. + for r in LookupSelectors::StartEnd as usize..common_data.num_lookup_selectors { + let cur_ends_selectors = lookup_selectors[r]; + let lut_row_number = ceil_div_usize( + common_data.luts[r - LookupSelectors::StartEnd as usize].len(), + num_lut_slots, + ); + let cur_function_eval = get_lut_poly_circuit( + builder, + common_data, + r - LookupSelectors::StartEnd as usize, + deltas, + num_lut_slots * lut_row_number, + ); + let cur_function_eval_ext = builder.convert_to_ext(cur_function_eval); + + let cur_re = builder.sub_extension(z_re, cur_function_eval_ext); + constraints.push(builder.mul_extension(cur_ends_selectors, cur_re)); + } + + // Check RE row transition constraint. + let mut cur_sum = next_z_re; + for elt in ¤t_lookup_combos { + cur_sum = builder.mul_add_extension( + cur_sum, + ext_deltas[LookupChallenges::ChallengeDelta as usize], + *elt, + ); + } + let unfiltered_re_line = builder.sub_extension(z_re, cur_sum); + + constraints.push(builder.mul_extension( + lookup_selectors[LookupSelectors::TransSre as usize], + unfiltered_re_line, + )); + + for poly in 0..num_sldc_polys { + // Compute prod(alpha - combo) for the current slot for Sum. + let mut lut_prod = builder.one_extension(); + for i in poly * lut_degree..min((poly + 1) * lut_degree, num_lut_slots) { + lut_prod = builder.mul_extension(lut_prod, current_lut_subs[i]); + } + + // Compute prod(alpha - combo) for the current slot for LDC. + let mut lu_prod = builder.one_extension(); + for i in poly * lu_degree..min((poly + 1) * lu_degree, num_lu_slots) { + lu_prod = builder.mul_extension(lu_prod, current_lu_subs[i]); + } + + let one = builder.one_extension(); + let zero = builder.zero_extension(); + + // Compute sum_i(prod_{j!=i}(alpha - combo_j)) for LDC. + let lu_sum_prods = + (poly * lu_degree..min((poly + 1) * lu_degree, num_lu_slots)).fold(zero, |acc, i| { + let mut prod_i = one; + + for j in poly * lu_degree..min((poly + 1) * lu_degree, num_lu_slots) { + if j != i { + prod_i = builder.mul_extension(prod_i, current_lu_subs[j]); + } + } + builder.add_extension(acc, prod_i) + }); + + // Compute sum_i(mul_i.prod_{j!=i}(alpha - combo_j)) for Sum. + let lut_sum_prods_mul = (poly * lut_degree..min((poly + 1) * lut_degree, num_lut_slots)) + .fold(zero, |acc, i| { + let mut prod_i = one; + + for j in poly * lut_degree..min((poly + 1) * lut_degree, num_lut_slots) { + if j != i { + prod_i = builder.mul_extension(prod_i, current_lut_subs[j]); + } + } + builder.mul_add_extension( + prod_i, + vars.local_wires[LookupTableGate::wire_ith_multiplicity(i)], + acc, + ) + }); + + // The previous element is the previous poly of the current row or the last poly + // of the next row. + let prev = if poly == 0 { + z_gx_lookup_sldcs[num_sldc_polys - 1] + } else { + z_x_lookup_sldcs[poly - 1] + }; + + let cur_sub = builder.sub_extension(z_x_lookup_sldcs[poly], prev); + + // Check sum row and col transitions. It's the same constraint, with a row + // transition happening for slot == 0. + let unfiltered_sum_transition = + builder.mul_sub_extension(lut_prod, cur_sub, lut_sum_prods_mul); + constraints.push(builder.mul_extension( + lookup_selectors[LookupSelectors::TransSre as usize], + unfiltered_sum_transition, + )); + + // Check ldc row and col transitions. It's the same constraint, with a row + // transition happening for slot == 0. + let unfiltered_ldc_transition = builder.mul_add_extension(lu_prod, cur_sub, lu_sum_prods); + constraints.push(builder.mul_extension( + lookup_selectors[LookupSelectors::TransLdc as usize], + unfiltered_ldc_transition, + )); + } + constraints +} diff --git a/plonky2/src/plonk/vars.rs b/plonky2/src/plonk/vars.rs new file mode 100644 index 000000000..a17495ce8 --- /dev/null +++ b/plonky2/src/plonk/vars.rs @@ -0,0 +1,238 @@ +//! Logic for evaluating constraints. + +use core::ops::Range; + +use crate::field::extension::algebra::ExtensionAlgebra; +use crate::field::extension::{Extendable, FieldExtension}; +use crate::field::packed::PackedField; +use crate::field::types::Field; +use crate::hash::hash_types::{HashOut, HashOutTarget, RichField}; +use crate::iop::ext_target::{ExtensionAlgebraTarget, ExtensionTarget}; +use crate::util::strided_view::PackedStridedView; + +#[derive(Debug, Copy, Clone)] +pub struct EvaluationVars<'a, F: RichField + Extendable, const D: usize> { + pub local_constants: &'a [F::Extension], + pub local_wires: &'a [F::Extension], + pub public_inputs_hash: &'a HashOut, +} + +/// A batch of evaluation vars, in the base field. +/// Wires and constants are stored in an evaluation point-major order (that is, +/// wire 0 for all evaluation points, then wire 1 for all points, and so on). +#[derive(Debug, Copy, Clone)] +pub struct EvaluationVarsBaseBatch<'a, F: Field> { + batch_size: usize, + pub local_constants: &'a [F], + pub local_wires: &'a [F], + pub public_inputs_hash: &'a HashOut, +} + +/// A view into `EvaluationVarsBaseBatch` for a particular evaluation point. +/// Does not copy the data. +#[derive(Debug, Copy, Clone)] +pub struct EvaluationVarsBase<'a, F: Field> { + pub local_constants: PackedStridedView<'a, F>, + pub local_wires: PackedStridedView<'a, F>, + pub public_inputs_hash: &'a HashOut, +} + +/// Like `EvaluationVarsBase`, but packed. +// It's a separate struct because `EvaluationVarsBase` implements `get_local_ext` and we do not yet +// have packed extension fields. +#[derive(Debug, Copy, Clone)] +pub struct EvaluationVarsBasePacked<'a, P: PackedField> { + pub local_constants: PackedStridedView<'a, P>, + pub local_wires: PackedStridedView<'a, P>, + pub public_inputs_hash: &'a HashOut, +} + +impl<'a, F: RichField + Extendable, const D: usize> EvaluationVars<'a, F, D> { + pub fn get_local_ext_algebra( + &self, + wire_range: Range, + ) -> ExtensionAlgebra { + debug_assert_eq!(wire_range.len(), D); + let arr = self.local_wires[wire_range].try_into().unwrap(); + ExtensionAlgebra::from_basefield_array(arr) + } + + pub fn remove_prefix(&mut self, num_selectors: usize) { + self.local_constants = &self.local_constants[num_selectors..]; + } +} + +impl<'a, F: Field> EvaluationVarsBaseBatch<'a, F> { + pub fn new( + batch_size: usize, + local_constants: &'a [F], + local_wires: &'a [F], + public_inputs_hash: &'a HashOut, + ) -> Self { + assert_eq!(local_constants.len() % batch_size, 0); + assert_eq!(local_wires.len() % batch_size, 0); + Self { + batch_size, + local_constants, + local_wires, + public_inputs_hash, + } + } + + pub fn remove_prefix(&mut self, num_selectors: usize) { + self.local_constants = &self.local_constants[num_selectors * self.len()..]; + } + + pub const fn len(&self) -> usize { + self.batch_size + } + + pub const fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn view(&self, index: usize) -> EvaluationVarsBase<'a, F> { + // We cannot implement `Index` as `EvaluationVarsBase` is a struct, not a + // reference. + assert!(index < self.len()); + let local_constants = PackedStridedView::new(self.local_constants, self.len(), index); + let local_wires = PackedStridedView::new(self.local_wires, self.len(), index); + EvaluationVarsBase { + local_constants, + local_wires, + public_inputs_hash: self.public_inputs_hash, + } + } + + pub const fn iter(&self) -> EvaluationVarsBaseBatchIter<'a, F> { + EvaluationVarsBaseBatchIter::new(*self) + } + + pub fn pack>( + &self, + ) -> ( + EvaluationVarsBaseBatchIterPacked<'a, P>, + EvaluationVarsBaseBatchIterPacked<'a, F>, + ) { + let n_leftovers = self.len() % P::WIDTH; + ( + EvaluationVarsBaseBatchIterPacked::new_with_start(*self, 0), + EvaluationVarsBaseBatchIterPacked::new_with_start(*self, self.len() - n_leftovers), + ) + } +} + +impl<'a, F: Field> EvaluationVarsBase<'a, F> { + pub fn get_local_ext(&self, wire_range: Range) -> F::Extension + where + F: RichField + Extendable, + { + debug_assert_eq!(wire_range.len(), D); + let arr = self.local_wires.view(wire_range).try_into().unwrap(); + F::Extension::from_basefield_array(arr) + } +} + +/// Iterator of views (`EvaluationVarsBase`) into a `EvaluationVarsBaseBatch`. +pub struct EvaluationVarsBaseBatchIter<'a, F: Field> { + i: usize, + vars_batch: EvaluationVarsBaseBatch<'a, F>, +} + +impl<'a, F: Field> EvaluationVarsBaseBatchIter<'a, F> { + pub const fn new(vars_batch: EvaluationVarsBaseBatch<'a, F>) -> Self { + EvaluationVarsBaseBatchIter { i: 0, vars_batch } + } +} + +impl<'a, F: Field> Iterator for EvaluationVarsBaseBatchIter<'a, F> { + type Item = EvaluationVarsBase<'a, F>; + fn next(&mut self) -> Option { + if self.i < self.vars_batch.len() { + let res = self.vars_batch.view(self.i); + self.i += 1; + Some(res) + } else { + None + } + } +} + +/// Iterator of packed views (`EvaluationVarsBasePacked`) into a +/// `EvaluationVarsBaseBatch`. Note: if the length of `EvaluationVarsBaseBatch` +/// is not a multiple of `P::WIDTH`, then the leftovers at the end are ignored. +pub struct EvaluationVarsBaseBatchIterPacked<'a, P: PackedField> { + /// Index to yield next, in units of `P::Scalar`. E.g. if `P::WIDTH == 4`, + /// then we will yield the vars for points `i`, `i + 1`, `i + 2`, and `i + /// + 3`, packed. + i: usize, + vars_batch: EvaluationVarsBaseBatch<'a, P::Scalar>, +} + +impl<'a, P: PackedField> EvaluationVarsBaseBatchIterPacked<'a, P> { + pub fn new_with_start( + vars_batch: EvaluationVarsBaseBatch<'a, P::Scalar>, + start: usize, + ) -> Self { + assert!(start <= vars_batch.len()); + EvaluationVarsBaseBatchIterPacked { + i: start, + vars_batch, + } + } +} + +impl<'a, P: PackedField> Iterator for EvaluationVarsBaseBatchIterPacked<'a, P> { + type Item = EvaluationVarsBasePacked<'a, P>; + fn next(&mut self) -> Option { + if self.i + P::WIDTH <= self.vars_batch.len() { + let local_constants = PackedStridedView::new( + self.vars_batch.local_constants, + self.vars_batch.len(), + self.i, + ); + let local_wires = + PackedStridedView::new(self.vars_batch.local_wires, self.vars_batch.len(), self.i); + let res = EvaluationVarsBasePacked { + local_constants, + local_wires, + public_inputs_hash: self.vars_batch.public_inputs_hash, + }; + self.i += P::WIDTH; + Some(res) + } else { + None + } + } + fn size_hint(&self) -> (usize, Option) { + let len = self.len(); + (len, Some(len)) + } +} + +impl<'a, P: PackedField> ExactSizeIterator for EvaluationVarsBaseBatchIterPacked<'a, P> { + fn len(&self) -> usize { + (self.vars_batch.len() - self.i) / P::WIDTH + } +} + +impl<'a, const D: usize> EvaluationTargets<'a, D> { + pub fn remove_prefix(&mut self, num_selectors: usize) { + self.local_constants = &self.local_constants[num_selectors..]; + } +} + +#[derive(Copy, Clone)] +pub struct EvaluationTargets<'a, const D: usize> { + pub local_constants: &'a [ExtensionTarget], + pub local_wires: &'a [ExtensionTarget], + pub public_inputs_hash: &'a HashOutTarget, +} + +impl<'a, const D: usize> EvaluationTargets<'a, D> { + pub fn get_local_ext_algebra(&self, wire_range: Range) -> ExtensionAlgebraTarget { + debug_assert_eq!(wire_range.len(), D); + let arr = self.local_wires[wire_range].try_into().unwrap(); + ExtensionAlgebraTarget(arr) + } +} diff --git a/plonky2/src/plonk/verifier.rs b/plonky2/src/plonk/verifier.rs new file mode 100644 index 000000000..a3c1de50b --- /dev/null +++ b/plonky2/src/plonk/verifier.rs @@ -0,0 +1,122 @@ +//! plonky2 verifier implementation. + +use anyhow::{ensure, Result}; + +use crate::field::extension::Extendable; +use crate::field::types::Field; +use crate::fri::verifier::verify_fri_proof; +use crate::hash::hash_types::RichField; +use crate::plonk::circuit_data::{CommonCircuitData, VerifierOnlyCircuitData}; +use crate::plonk::config::{GenericConfig, Hasher}; +use crate::plonk::plonk_common::reduce_with_powers; +use crate::plonk::proof::{Proof, ProofChallenges, ProofWithPublicInputs}; +use crate::plonk::validate_shape::validate_proof_with_pis_shape; +use crate::plonk::vanishing_poly::eval_vanishing_poly; +use crate::plonk::vars::EvaluationVars; + +pub(crate) fn verify, C: GenericConfig, const D: usize>( + proof_with_pis: ProofWithPublicInputs, + verifier_data: &VerifierOnlyCircuitData, + common_data: &CommonCircuitData, +) -> Result<()> { + validate_proof_with_pis_shape(&proof_with_pis, common_data)?; + + let public_inputs_hash = proof_with_pis.get_public_inputs_hash(); + let challenges = proof_with_pis.get_challenges( + public_inputs_hash, + &verifier_data.circuit_digest, + common_data, + )?; + + verify_with_challenges::( + proof_with_pis.proof, + public_inputs_hash, + challenges, + verifier_data, + common_data, + ) +} + +pub(crate) fn verify_with_challenges< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +>( + proof: Proof, + public_inputs_hash: <>::InnerHasher as Hasher>::Hash, + challenges: ProofChallenges, + verifier_data: &VerifierOnlyCircuitData, + common_data: &CommonCircuitData, +) -> Result<()> { + let local_constants = &proof.openings.constants; + let local_wires = &proof.openings.wires; + let vars = EvaluationVars { + local_constants, + local_wires, + public_inputs_hash: &public_inputs_hash, + }; + let local_zs = &proof.openings.plonk_zs; + let next_zs = &proof.openings.plonk_zs_next; + let local_lookup_zs = &proof.openings.lookup_zs; + let next_lookup_zs = &proof.openings.lookup_zs_next; + let s_sigmas = &proof.openings.plonk_sigmas; + let partial_products = &proof.openings.partial_products; + + // Evaluate the vanishing polynomial at our challenge point, zeta. + let vanishing_polys_zeta = eval_vanishing_poly::( + common_data, + challenges.plonk_zeta, + vars, + local_zs, + next_zs, + local_lookup_zs, + next_lookup_zs, + partial_products, + s_sigmas, + &challenges.plonk_betas, + &challenges.plonk_gammas, + &challenges.plonk_alphas, + &challenges.plonk_deltas, + ); + + // Check each polynomial identity, of the form `vanishing(x) = Z_H(x) + // quotient(x)`, at zeta. + let quotient_polys_zeta = &proof.openings.quotient_polys; + let zeta_pow_deg = challenges + .plonk_zeta + .exp_power_of_2(common_data.degree_bits()); + let z_h_zeta = zeta_pow_deg - F::Extension::ONE; + // `quotient_polys_zeta` holds `num_challenges * quotient_degree_factor` + // evaluations. Each chunk of `quotient_degree_factor` holds the evaluations + // of `t_0(zeta),...,t_{quotient_degree_factor-1}(zeta)` where the "real" + // quotient polynomial is `t(X) = t_0(X) + t_1(X)*X^n + t_2(X)*X^{2n} + ...`. + // So to reconstruct `t(zeta)` we can compute `reduce_with_powers(chunk, + // zeta^n)` for each `quotient_degree_factor`-sized chunk of the original + // evaluations. + for (i, chunk) in quotient_polys_zeta + .chunks(common_data.quotient_degree_factor) + .enumerate() + { + ensure!(vanishing_polys_zeta[i] == z_h_zeta * reduce_with_powers(chunk, zeta_pow_deg)); + } + + let merkle_caps = &[ + verifier_data.constants_sigmas_cap.clone(), + proof.wires_cap, + // In the lookup case, `plonk_zs_partial_products_cap` should also include the lookup + // commitment. + proof.plonk_zs_partial_products_cap, + proof.quotient_polys_cap, + ]; + + verify_fri_proof::( + &common_data.get_fri_instance(challenges.plonk_zeta), + &proof.openings.to_fri_openings(), + &challenges.fri_challenges, + merkle_caps, + &proof.opening_proof, + &common_data.fri_params, + )?; + + Ok(()) +} diff --git a/plonky2/src/recursion/conditional_recursive_verifier.rs b/plonky2/src/recursion/conditional_recursive_verifier.rs new file mode 100644 index 000000000..43bef5892 --- /dev/null +++ b/plonky2/src/recursion/conditional_recursive_verifier.rs @@ -0,0 +1,412 @@ +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + +use itertools::Itertools; + +use crate::field::extension::Extendable; +use crate::fri::proof::{ + FriInitialTreeProofTarget, FriProofTarget, FriQueryRoundTarget, FriQueryStepTarget, +}; +use crate::gadgets::polynomial::PolynomialCoeffsExtTarget; +use crate::hash::hash_types::{HashOutTarget, MerkleCapTarget, RichField}; +use crate::hash::merkle_proofs::MerkleProofTarget; +use crate::iop::ext_target::ExtensionTarget; +use crate::iop::target::{BoolTarget, Target}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::{CommonCircuitData, VerifierCircuitTarget}; +use crate::plonk::config::{AlgebraicHasher, GenericConfig}; +use crate::plonk::proof::{OpeningSetTarget, ProofTarget, ProofWithPublicInputsTarget}; +use crate::with_context; + +impl, const D: usize> CircuitBuilder { + /// Verify `proof0` if `condition` else verify `proof1`. + /// `proof0` and `proof1` are assumed to use the same `CommonCircuitData`. + pub fn conditionally_verify_proof>( + &mut self, + condition: BoolTarget, + proof_with_pis0: &ProofWithPublicInputsTarget, + inner_verifier_data0: &VerifierCircuitTarget, + proof_with_pis1: &ProofWithPublicInputsTarget, + inner_verifier_data1: &VerifierCircuitTarget, + inner_common_data: &CommonCircuitData, + ) where + C::Hasher: AlgebraicHasher, + { + let selected_proof = + self.select_proof_with_pis(condition, proof_with_pis0, proof_with_pis1); + let selected_verifier_data = VerifierCircuitTarget { + constants_sigmas_cap: self.select_cap( + condition, + &inner_verifier_data0.constants_sigmas_cap, + &inner_verifier_data1.constants_sigmas_cap, + ), + circuit_digest: self.select_hash( + condition, + inner_verifier_data0.circuit_digest, + inner_verifier_data1.circuit_digest, + ), + }; + + self.verify_proof::(&selected_proof, &selected_verifier_data, inner_common_data); + } + + /// Conditionally verify a proof with a new generated dummy proof. + pub fn conditionally_verify_proof_or_dummy + 'static>( + &mut self, + condition: BoolTarget, + proof_with_pis: &ProofWithPublicInputsTarget, + inner_verifier_data: &VerifierCircuitTarget, + inner_common_data: &CommonCircuitData, + ) -> anyhow::Result<()> + where + C::Hasher: AlgebraicHasher, + { + let (dummy_proof_with_pis_target, dummy_verifier_data_target) = + self.dummy_proof_and_vk::(inner_common_data)?; + self.conditionally_verify_proof::( + condition, + proof_with_pis, + inner_verifier_data, + &dummy_proof_with_pis_target, + &dummy_verifier_data_target, + inner_common_data, + ); + Ok(()) + } + + /// Computes `if b { proof_with_pis0 } else { proof_with_pis1 }`. + fn select_proof_with_pis( + &mut self, + b: BoolTarget, + proof_with_pis0: &ProofWithPublicInputsTarget, + proof_with_pis1: &ProofWithPublicInputsTarget, + ) -> ProofWithPublicInputsTarget { + let ProofWithPublicInputsTarget { + proof: + ProofTarget { + wires_cap: wires_cap0, + plonk_zs_partial_products_cap: plonk_zs_partial_products_cap0, + quotient_polys_cap: quotient_polys_cap0, + openings: openings0, + opening_proof: opening_proof0, + }, + public_inputs: public_inputs0, + } = proof_with_pis0; + let ProofWithPublicInputsTarget { + proof: + ProofTarget { + wires_cap: wires_cap1, + plonk_zs_partial_products_cap: plonk_zs_partial_products_cap1, + quotient_polys_cap: quotient_polys_cap1, + openings: openings1, + opening_proof: opening_proof1, + }, + public_inputs: public_inputs1, + } = proof_with_pis1; + with_context!(self, "select proof", { + let selected_wires_cap = self.select_cap(b, wires_cap0, wires_cap1); + let selected_plonk_zs_partial_products_cap = self.select_cap( + b, + plonk_zs_partial_products_cap0, + plonk_zs_partial_products_cap1, + ); + let selected_quotient_polys_cap = + self.select_cap(b, quotient_polys_cap0, quotient_polys_cap1); + let selected_openings = self.select_opening_set(b, openings0, openings1); + let selected_opening_proof = + self.select_opening_proof(b, opening_proof0, opening_proof1); + let selected_public_inputs = self.select_vec(b, public_inputs0, public_inputs1); + ProofWithPublicInputsTarget { + proof: ProofTarget { + wires_cap: selected_wires_cap, + plonk_zs_partial_products_cap: selected_plonk_zs_partial_products_cap, + quotient_polys_cap: selected_quotient_polys_cap, + openings: selected_openings, + opening_proof: selected_opening_proof, + }, + public_inputs: selected_public_inputs, + } + }) + } + + /// Computes `if b { v0 } else { v1 }`. + fn select_vec(&mut self, b: BoolTarget, v0: &[Target], v1: &[Target]) -> Vec { + v0.iter() + .zip_eq(v1) + .map(|(t0, t1)| self.select(b, *t0, *t1)) + .collect() + } + + /// Computes `if b { h0 } else { h1 }`. + pub(crate) fn select_hash( + &mut self, + b: BoolTarget, + h0: HashOutTarget, + h1: HashOutTarget, + ) -> HashOutTarget { + HashOutTarget { + elements: core::array::from_fn(|i| self.select(b, h0.elements[i], h1.elements[i])), + } + } + + /// Computes `if b { cap0 } else { cap1 }`. + fn select_cap( + &mut self, + b: BoolTarget, + cap0: &MerkleCapTarget, + cap1: &MerkleCapTarget, + ) -> MerkleCapTarget { + assert_eq!(cap0.0.len(), cap1.0.len()); + MerkleCapTarget( + cap0.0 + .iter() + .zip_eq(&cap1.0) + .map(|(h0, h1)| self.select_hash(b, *h0, *h1)) + .collect(), + ) + } + + /// Computes `if b { v0 } else { v1 }`. + fn select_vec_cap( + &mut self, + b: BoolTarget, + v0: &[MerkleCapTarget], + v1: &[MerkleCapTarget], + ) -> Vec { + v0.iter() + .zip_eq(v1) + .map(|(c0, c1)| self.select_cap(b, c0, c1)) + .collect() + } + + /// Computes `if b { os0 } else { os1 }`. + fn select_opening_set( + &mut self, + b: BoolTarget, + os0: &OpeningSetTarget, + os1: &OpeningSetTarget, + ) -> OpeningSetTarget { + OpeningSetTarget { + constants: self.select_vec_ext(b, &os0.constants, &os1.constants), + plonk_sigmas: self.select_vec_ext(b, &os0.plonk_sigmas, &os1.plonk_sigmas), + wires: self.select_vec_ext(b, &os0.wires, &os1.wires), + plonk_zs: self.select_vec_ext(b, &os0.plonk_zs, &os1.plonk_zs), + plonk_zs_next: self.select_vec_ext(b, &os0.plonk_zs_next, &os1.plonk_zs_next), + lookup_zs: self.select_vec_ext(b, &os0.lookup_zs, &os1.lookup_zs), + next_lookup_zs: self.select_vec_ext(b, &os0.next_lookup_zs, &os1.next_lookup_zs), + partial_products: self.select_vec_ext(b, &os0.partial_products, &os1.partial_products), + quotient_polys: self.select_vec_ext(b, &os0.quotient_polys, &os1.quotient_polys), + } + } + + /// Computes `if b { v0 } else { v1 }`. + fn select_vec_ext( + &mut self, + b: BoolTarget, + v0: &[ExtensionTarget], + v1: &[ExtensionTarget], + ) -> Vec> { + v0.iter() + .zip_eq(v1) + .map(|(e0, e1)| self.select_ext(b, *e0, *e1)) + .collect() + } + + /// Computes `if b { proof0 } else { proof1 }`. + fn select_opening_proof( + &mut self, + b: BoolTarget, + proof0: &FriProofTarget, + proof1: &FriProofTarget, + ) -> FriProofTarget { + FriProofTarget { + commit_phase_merkle_caps: self.select_vec_cap( + b, + &proof0.commit_phase_merkle_caps, + &proof1.commit_phase_merkle_caps, + ), + query_round_proofs: self.select_vec_query_round( + b, + &proof0.query_round_proofs, + &proof1.query_round_proofs, + ), + final_poly: PolynomialCoeffsExtTarget(self.select_vec_ext( + b, + &proof0.final_poly.0, + &proof1.final_poly.0, + )), + pow_witness: self.select(b, proof0.pow_witness, proof1.pow_witness), + } + } + + /// Computes `if b { qr0 } else { qr1 }`. + fn select_query_round( + &mut self, + b: BoolTarget, + qr0: &FriQueryRoundTarget, + qr1: &FriQueryRoundTarget, + ) -> FriQueryRoundTarget { + FriQueryRoundTarget { + initial_trees_proof: self.select_initial_tree_proof( + b, + &qr0.initial_trees_proof, + &qr1.initial_trees_proof, + ), + steps: self.select_vec_query_step(b, &qr0.steps, &qr1.steps), + } + } + + /// Computes `if b { v0 } else { v1 }`. + fn select_vec_query_round( + &mut self, + b: BoolTarget, + v0: &[FriQueryRoundTarget], + v1: &[FriQueryRoundTarget], + ) -> Vec> { + v0.iter() + .zip_eq(v1) + .map(|(qr0, qr1)| self.select_query_round(b, qr0, qr1)) + .collect() + } + + /// Computes `if b { proof0 } else { proof1 }`. + fn select_initial_tree_proof( + &mut self, + b: BoolTarget, + proof0: &FriInitialTreeProofTarget, + proof1: &FriInitialTreeProofTarget, + ) -> FriInitialTreeProofTarget { + FriInitialTreeProofTarget { + evals_proofs: proof0 + .evals_proofs + .iter() + .zip_eq(&proof1.evals_proofs) + .map(|((v0, p0), (v1, p1))| { + ( + self.select_vec(b, v0, v1), + self.select_merkle_proof(b, p0, p1), + ) + }) + .collect(), + } + } + + /// Computes `if b { proof0 } else { proof1 }`. + fn select_merkle_proof( + &mut self, + b: BoolTarget, + proof0: &MerkleProofTarget, + proof1: &MerkleProofTarget, + ) -> MerkleProofTarget { + MerkleProofTarget { + siblings: proof0 + .siblings + .iter() + .zip_eq(&proof1.siblings) + .map(|(h0, h1)| self.select_hash(b, *h0, *h1)) + .collect(), + } + } + + /// Computes `if b { qs0 } else { qs01 }`. + fn select_query_step( + &mut self, + b: BoolTarget, + qs0: &FriQueryStepTarget, + qs1: &FriQueryStepTarget, + ) -> FriQueryStepTarget { + FriQueryStepTarget { + evals: self.select_vec_ext(b, &qs0.evals, &qs1.evals), + merkle_proof: self.select_merkle_proof(b, &qs0.merkle_proof, &qs1.merkle_proof), + } + } + + /// Computes `if b { v0 } else { v1 }`. + fn select_vec_query_step( + &mut self, + b: BoolTarget, + v0: &[FriQueryStepTarget], + v1: &[FriQueryStepTarget], + ) -> Vec> { + v0.iter() + .zip_eq(v1) + .map(|(qs0, qs1)| self.select_query_step(b, qs0, qs1)) + .collect() + } +} + +#[cfg(test)] +mod tests { + #[cfg(not(feature = "std"))] + use alloc::vec; + + use anyhow::Result; + use hashbrown::HashMap; + + use super::*; + use crate::field::types::Sample; + use crate::gates::noop::NoopGate; + use crate::iop::witness::{PartialWitness, WitnessWrite}; + use crate::plonk::circuit_data::CircuitConfig; + use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + use crate::recursion::dummy_circuit::{dummy_circuit, dummy_proof}; + + #[test] + fn test_conditional_recursive_verifier() -> Result<()> { + init_logger(); + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + let config = CircuitConfig::standard_recursion_config(); + + // Generate proof. + let mut builder = CircuitBuilder::::new(config.clone()); + let mut pw = PartialWitness::new(); + let t = builder.add_virtual_target(); + pw.set_target(t, F::rand()); + builder.register_public_input(t); + let _t2 = builder.square(t); + for _ in 0..64 { + builder.add_gate(NoopGate, vec![]); + } + let data = builder.build::(); + let proof = data.prove(pw)?; + data.verify(proof.clone())?; + + // Generate dummy proof with the same `CommonCircuitData`. + let dummy_data = dummy_circuit(&data.common); + let dummy_proof = dummy_proof(&dummy_data, HashMap::new())?; + + // Conditionally verify the two proofs. + let mut builder = CircuitBuilder::::new(config); + let mut pw = PartialWitness::new(); + let pt = builder.add_virtual_proof_with_pis(&data.common); + pw.set_proof_with_pis_target(&pt, &proof); + let dummy_pt = builder.add_virtual_proof_with_pis(&data.common); + pw.set_proof_with_pis_target::(&dummy_pt, &dummy_proof); + let inner_data = + builder.add_virtual_verifier_data(data.common.config.fri_config.cap_height); + pw.set_verifier_data_target(&inner_data, &data.verifier_only); + let dummy_inner_data = + builder.add_virtual_verifier_data(data.common.config.fri_config.cap_height); + pw.set_verifier_data_target(&dummy_inner_data, &dummy_data.verifier_only); + let b = builder.constant_bool(F::rand().0 % 2 == 0); + builder.conditionally_verify_proof::( + b, + &pt, + &inner_data, + &dummy_pt, + &dummy_inner_data, + &data.common, + ); + + builder.print_gate_counts(100); + let data = builder.build::(); + let proof = data.prove(pw)?; + data.verify(proof) + } + + fn init_logger() { + let _ = env_logger::builder().format_timestamp(None).try_init(); + } +} diff --git a/plonky2/src/recursion/cyclic_recursion.rs b/plonky2/src/recursion/cyclic_recursion.rs new file mode 100644 index 000000000..801c4818b --- /dev/null +++ b/plonky2/src/recursion/cyclic_recursion.rs @@ -0,0 +1,386 @@ +#![allow(clippy::int_plus_one)] // Makes more sense for some inequalities below. + +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + +use anyhow::{ensure, Result}; + +use crate::field::extension::Extendable; +use crate::hash::hash_types::{HashOut, HashOutTarget, MerkleCapTarget, RichField}; +use crate::hash::merkle_tree::MerkleCap; +use crate::iop::target::{BoolTarget, Target}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::{ + CommonCircuitData, VerifierCircuitTarget, VerifierOnlyCircuitData, +}; +use crate::plonk::config::{AlgebraicHasher, GenericConfig}; +use crate::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget}; +use crate::util::serialization::{Buffer, IoResult, Read, Write}; + +impl, const D: usize> VerifierOnlyCircuitData { + fn from_slice(slice: &[C::F], common_data: &CommonCircuitData) -> Result + where + C::Hasher: AlgebraicHasher, + { + // The structure of the public inputs is `[..., circuit_digest, + // constants_sigmas_cap]`. + let cap_len = common_data.config.fri_config.num_cap_elements(); + let len = slice.len(); + ensure!(len >= 4 + 4 * cap_len, "Not enough public inputs"); + let constants_sigmas_cap = MerkleCap( + (0..cap_len) + .map(|i| HashOut { + elements: core::array::from_fn(|j| slice[len - 4 * (cap_len - i) + j]), + }) + .collect(), + ); + let circuit_digest = + HashOut::from_partial(&slice[len - 4 - 4 * cap_len..len - 4 * cap_len]); + + Ok(Self { + circuit_digest, + constants_sigmas_cap, + }) + } +} + +impl VerifierCircuitTarget { + pub fn to_bytes(&self) -> IoResult> { + let mut buffer = Vec::new(); + buffer.write_target_merkle_cap(&self.constants_sigmas_cap)?; + buffer.write_target_hash(&self.circuit_digest)?; + Ok(buffer) + } + + pub fn from_bytes(bytes: Vec) -> IoResult { + let mut buffer = Buffer::new(&bytes); + let constants_sigmas_cap = buffer.read_target_merkle_cap()?; + let circuit_digest = buffer.read_target_hash()?; + Ok(Self { + constants_sigmas_cap, + circuit_digest, + }) + } + + fn from_slice, const D: usize>( + slice: &[Target], + common_data: &CommonCircuitData, + ) -> Result { + let cap_len = common_data.config.fri_config.num_cap_elements(); + let len = slice.len(); + ensure!(len >= 4 + 4 * cap_len, "Not enough public inputs"); + let constants_sigmas_cap = MerkleCapTarget( + (0..cap_len) + .map(|i| HashOutTarget { + elements: core::array::from_fn(|j| slice[len - 4 * (cap_len - i) + j]), + }) + .collect(), + ); + let circuit_digest = HashOutTarget { + elements: core::array::from_fn(|i| slice[len - 4 - 4 * cap_len + i]), + }; + + Ok(Self { + circuit_digest, + constants_sigmas_cap, + }) + } +} + +impl, const D: usize> CircuitBuilder { + /// If `condition` is true, recursively verify a proof for the same circuit + /// as the one we're currently building. Otherwise, verify + /// `other_proof_with_pis`. + /// + /// For a typical IVC use case, `condition` will be false for the very first + /// proof in a chain, i.e. the base case. + /// + /// Note that this does not enforce that the inner circuit uses the correct + /// verification key. This is not possible to check in this recursive + /// circuit, since we do not know the verification key until after we + /// build it. Verifiers must separately call + /// `check_cyclic_proof_verifier_data`, in addition to verifying a recursive + /// proof, to check that the verification key matches. + /// + /// WARNING: Do not register any public input after calling this! TODO: + /// relax this + pub fn conditionally_verify_cyclic_proof>( + &mut self, + condition: BoolTarget, + cyclic_proof_with_pis: &ProofWithPublicInputsTarget, + other_proof_with_pis: &ProofWithPublicInputsTarget, + other_verifier_data: &VerifierCircuitTarget, + common_data: &CommonCircuitData, + ) -> Result<()> + where + C::Hasher: AlgebraicHasher, + { + let verifier_data = self + .verifier_data_public_input + .clone() + .expect("Must call add_verifier_data_public_inputs before cyclic recursion"); + + if let Some(existing_common_data) = self.goal_common_data.as_ref() { + assert_eq!(existing_common_data, common_data); + } else { + self.goal_common_data = Some(common_data.clone()); + } + + let inner_cyclic_pis = VerifierCircuitTarget::from_slice::( + &cyclic_proof_with_pis.public_inputs, + common_data, + )?; + // Connect previous verifier data to current one. This guarantees that every + // proof in the cycle uses the same verifier data. + self.connect_hashes( + inner_cyclic_pis.circuit_digest, + verifier_data.circuit_digest, + ); + self.connect_merkle_caps( + &inner_cyclic_pis.constants_sigmas_cap, + &verifier_data.constants_sigmas_cap, + ); + + // Verify the cyclic proof if `condition` is set to true, otherwise verify the + // other proof. + self.conditionally_verify_proof::( + condition, + cyclic_proof_with_pis, + &verifier_data, + other_proof_with_pis, + other_verifier_data, + common_data, + ); + + // Make sure we have every gate to match `common_data`. + for g in &common_data.gates { + self.add_gate_to_gate_set(g.clone()); + } + + Ok(()) + } + + pub fn conditionally_verify_cyclic_proof_or_dummy + 'static>( + &mut self, + condition: BoolTarget, + cyclic_proof_with_pis: &ProofWithPublicInputsTarget, + common_data: &CommonCircuitData, + ) -> Result<()> + where + C::Hasher: AlgebraicHasher, + { + let (dummy_proof_with_pis_target, dummy_verifier_data_target) = + self.dummy_proof_and_vk::(common_data)?; + self.conditionally_verify_cyclic_proof::( + condition, + cyclic_proof_with_pis, + &dummy_proof_with_pis_target, + &dummy_verifier_data_target, + common_data, + )?; + Ok(()) + } +} + +/// Additional checks to be performed on a cyclic recursive proof in addition to +/// verifying the proof. Checks that the purported verifier data in the public +/// inputs match the real verifier data. +pub fn check_cyclic_proof_verifier_data< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +>( + proof: &ProofWithPublicInputs, + verifier_data: &VerifierOnlyCircuitData, + common_data: &CommonCircuitData, +) -> Result<()> +where + C::Hasher: AlgebraicHasher, +{ + let pis = VerifierOnlyCircuitData::::from_slice(&proof.public_inputs, common_data)?; + ensure!(verifier_data.constants_sigmas_cap == pis.constants_sigmas_cap); + ensure!(verifier_data.circuit_digest == pis.circuit_digest); + + Ok(()) +} + +#[cfg(test)] +mod tests { + #[cfg(not(feature = "std"))] + use alloc::vec; + + use anyhow::Result; + + use crate::field::extension::Extendable; + use crate::field::types::{Field, PrimeField64}; + use crate::gates::noop::NoopGate; + use crate::hash::hash_types::{HashOutTarget, RichField}; + use crate::hash::hashing::hash_n_to_hash_no_pad; + use crate::hash::poseidon::{PoseidonHash, PoseidonPermutation}; + use crate::iop::witness::{PartialWitness, WitnessWrite}; + use crate::plonk::circuit_builder::CircuitBuilder; + use crate::plonk::circuit_data::{CircuitConfig, CommonCircuitData}; + use crate::plonk::config::{AlgebraicHasher, GenericConfig, PoseidonGoldilocksConfig}; + use crate::recursion::cyclic_recursion::check_cyclic_proof_verifier_data; + use crate::recursion::dummy_circuit::cyclic_base_proof; + + // Generates `CommonCircuitData` usable for recursion. + fn common_data_for_recursion< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, + >() -> CommonCircuitData + where + C::Hasher: AlgebraicHasher, + { + let config = CircuitConfig::standard_recursion_config(); + let builder = CircuitBuilder::::new(config); + let data = builder.build::(); + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + let proof = builder.add_virtual_proof_with_pis(&data.common); + let verifier_data = + builder.add_virtual_verifier_data(data.common.config.fri_config.cap_height); + builder.verify_proof::(&proof, &verifier_data, &data.common); + let data = builder.build::(); + + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + let proof = builder.add_virtual_proof_with_pis(&data.common); + let verifier_data = + builder.add_virtual_verifier_data(data.common.config.fri_config.cap_height); + builder.verify_proof::(&proof, &verifier_data, &data.common); + while builder.num_gates() < 1 << 12 { + builder.add_gate(NoopGate, vec![]); + } + builder.build::().common + } + + /// Uses cyclic recursion to build a hash chain. + /// The circuit has the following public input structure: + /// - Initial hash (4) + /// - Output for the tip of the hash chain (4) + /// - Chain length, i.e. the number of times the hash has been applied (1) + /// - VK for cyclic recursion (?) + #[test] + fn test_cyclic_recursion() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + let one = builder.one(); + + // Circuit that computes a repeated hash. + let initial_hash_target = builder.add_virtual_hash(); + builder.register_public_inputs(&initial_hash_target.elements); + let current_hash_in = builder.add_virtual_hash(); + let current_hash_out = + builder.hash_n_to_hash_no_pad::(current_hash_in.elements.to_vec()); + builder.register_public_inputs(¤t_hash_out.elements); + let counter = builder.add_virtual_public_input(); + + let mut common_data = common_data_for_recursion::(); + let verifier_data_target = builder.add_verifier_data_public_inputs(); + common_data.num_public_inputs = builder.num_public_inputs(); + + let condition = builder.add_virtual_bool_target_safe(); + + // Unpack inner proof's public inputs. + let inner_cyclic_proof_with_pis = builder.add_virtual_proof_with_pis(&common_data); + let inner_cyclic_pis = &inner_cyclic_proof_with_pis.public_inputs; + let inner_cyclic_initial_hash = HashOutTarget::try_from(&inner_cyclic_pis[0..4]).unwrap(); + let inner_cyclic_latest_hash = HashOutTarget::try_from(&inner_cyclic_pis[4..8]).unwrap(); + let inner_cyclic_counter = inner_cyclic_pis[8]; + + // Connect our initial hash to that of our inner proof. (If there is no inner + // proof, the initial hash will be unconstrained, which is intentional.) + builder.connect_hashes(initial_hash_target, inner_cyclic_initial_hash); + + // The input hash is the previous hash output if we have an inner proof, or the + // initial hash if this is the base case. + let actual_hash_in = + builder.select_hash(condition, inner_cyclic_latest_hash, initial_hash_target); + builder.connect_hashes(current_hash_in, actual_hash_in); + + // Our chain length will be inner_counter + 1 if we have an inner proof, or 1 if + // not. + let new_counter = builder.mul_add(condition.target, inner_cyclic_counter, one); + builder.connect(counter, new_counter); + + builder.conditionally_verify_cyclic_proof_or_dummy::( + condition, + &inner_cyclic_proof_with_pis, + &common_data, + )?; + + let cyclic_circuit_data = builder.build::(); + + let mut pw = PartialWitness::new(); + let initial_hash = [F::ZERO, F::ONE, F::TWO, F::from_canonical_usize(3)]; + let initial_hash_pis = initial_hash.into_iter().enumerate().collect(); + pw.set_bool_target(condition, false); + pw.set_proof_with_pis_target::( + &inner_cyclic_proof_with_pis, + &cyclic_base_proof( + &common_data, + &cyclic_circuit_data.verifier_only, + initial_hash_pis, + ), + ); + pw.set_verifier_data_target(&verifier_data_target, &cyclic_circuit_data.verifier_only); + let proof = cyclic_circuit_data.prove(pw)?; + check_cyclic_proof_verifier_data( + &proof, + &cyclic_circuit_data.verifier_only, + &cyclic_circuit_data.common, + )?; + cyclic_circuit_data.verify(proof.clone())?; + + // 1st recursive layer. + let mut pw = PartialWitness::new(); + pw.set_bool_target(condition, true); + pw.set_proof_with_pis_target(&inner_cyclic_proof_with_pis, &proof); + pw.set_verifier_data_target(&verifier_data_target, &cyclic_circuit_data.verifier_only); + let proof = cyclic_circuit_data.prove(pw)?; + check_cyclic_proof_verifier_data( + &proof, + &cyclic_circuit_data.verifier_only, + &cyclic_circuit_data.common, + )?; + cyclic_circuit_data.verify(proof.clone())?; + + // 2nd recursive layer. + let mut pw = PartialWitness::new(); + pw.set_bool_target(condition, true); + pw.set_proof_with_pis_target(&inner_cyclic_proof_with_pis, &proof); + pw.set_verifier_data_target(&verifier_data_target, &cyclic_circuit_data.verifier_only); + let proof = cyclic_circuit_data.prove(pw)?; + check_cyclic_proof_verifier_data( + &proof, + &cyclic_circuit_data.verifier_only, + &cyclic_circuit_data.common, + )?; + + // Verify that the proof correctly computes a repeated hash. + let initial_hash = &proof.public_inputs[..4]; + let hash = &proof.public_inputs[4..8]; + let counter = proof.public_inputs[8]; + let expected_hash: [F; 4] = iterate_poseidon( + initial_hash.try_into().unwrap(), + counter.to_canonical_u64() as usize, + ); + assert_eq!(hash, expected_hash); + + cyclic_circuit_data.verify(proof) + } + + fn iterate_poseidon(initial_state: [F; 4], n: usize) -> [F; 4] { + let mut current = initial_state; + for _ in 0..n { + current = hash_n_to_hash_no_pad::>(¤t).elements; + } + current + } +} diff --git a/plonky2/src/recursion/dummy_circuit.rs b/plonky2/src/recursion/dummy_circuit.rs new file mode 100644 index 000000000..b6d8f1ff6 --- /dev/null +++ b/plonky2/src/recursion/dummy_circuit.rs @@ -0,0 +1,262 @@ +#[cfg(not(feature = "std"))] +use alloc::{ + string::{String, ToString}, + vec, + vec::Vec, +}; + +use hashbrown::HashMap; +use plonky2_field::extension::Extendable; +use plonky2_field::polynomial::PolynomialCoeffs; +use plonky2_util::ceil_div_usize; + +use crate::fri::proof::{FriProof, FriProofTarget}; +use crate::gadgets::polynomial::PolynomialCoeffsExtTarget; +use crate::gates::noop::NoopGate; +use crate::hash::hash_types::{HashOutTarget, MerkleCapTarget, RichField}; +use crate::hash::merkle_tree::MerkleCap; +use crate::iop::generator::{GeneratedValues, SimpleGenerator}; +use crate::iop::target::Target; +use crate::iop::witness::{PartialWitness, PartitionWitness, WitnessWrite}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::{ + CircuitData, CommonCircuitData, VerifierCircuitTarget, VerifierOnlyCircuitData, +}; +use crate::plonk::config::{AlgebraicHasher, GenericConfig, GenericHashOut, Hasher}; +use crate::plonk::proof::{ + OpeningSet, OpeningSetTarget, Proof, ProofTarget, ProofWithPublicInputs, + ProofWithPublicInputsTarget, +}; +use crate::util::serialization::{Buffer, IoResult, Read, Write}; + +/// Creates a dummy proof which is suitable for use as a base proof in a cyclic +/// recursion tree. Such a base proof will not actually be verified, so most of +/// its data is arbitrary. However, its public inputs which encode the cyclic +/// verification key must be set properly, and this method takes care of that. +/// It also allows the user to specify any other public inputs which should be +/// set in this base proof. +pub fn cyclic_base_proof( + common_data: &CommonCircuitData, + verifier_data: &VerifierOnlyCircuitData, + mut nonzero_public_inputs: HashMap, +) -> ProofWithPublicInputs +where + F: RichField + Extendable, + C: GenericConfig, + C::Hasher: AlgebraicHasher, +{ + let pis_len = common_data.num_public_inputs; + let cap_elements = common_data.config.fri_config.num_cap_elements(); + let start_vk_pis = pis_len - 4 - 4 * cap_elements; + + // Add the cyclic verifier data public inputs. + nonzero_public_inputs.extend((start_vk_pis..).zip(verifier_data.circuit_digest.elements)); + for i in 0..cap_elements { + let start = start_vk_pis + 4 + 4 * i; + nonzero_public_inputs + .extend((start..).zip(verifier_data.constants_sigmas_cap.0[i].elements)); + } + + // TODO: A bit wasteful to build a dummy circuit here. We could potentially use + // a proof that just consists of zeros, apart from public inputs. + dummy_proof::( + &dummy_circuit::(common_data), + nonzero_public_inputs, + ) + .unwrap() +} + +/// Generate a proof for a dummy circuit. The `public_inputs` parameter let the +/// caller specify certain public inputs (identified by their indices) which +/// should be given specific values. The rest will default to zero. +pub(crate) fn dummy_proof< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +>( + circuit: &CircuitData, + nonzero_public_inputs: HashMap, +) -> anyhow::Result> +where +{ + let mut pw = PartialWitness::new(); + for i in 0..circuit.common.num_public_inputs { + let pi = nonzero_public_inputs.get(&i).copied().unwrap_or_default(); + pw.set_target(circuit.prover_only.public_inputs[i], pi); + } + circuit.prove(pw) +} + +/// Generate a circuit matching a given `CommonCircuitData`. +pub(crate) fn dummy_circuit< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +>( + common_data: &CommonCircuitData, +) -> CircuitData { + let config = common_data.config.clone(); + assert!( + !common_data.config.zero_knowledge, + "Degree calculation can be off if zero-knowledge is on." + ); + + // Number of `NoopGate`s to add to get a circuit of size `degree` in the end. + // Need to account for public input hashing, a `PublicInputGate` and a + // `ConstantGate`. + let degree = common_data.degree(); + let num_noop_gate = degree - ceil_div_usize(common_data.num_public_inputs, 8) - 2; + + let mut builder = CircuitBuilder::::new(config); + for _ in 0..num_noop_gate { + builder.add_gate(NoopGate, vec![]); + } + for gate in &common_data.gates { + builder.add_gate_to_gate_set(gate.clone()); + } + for _ in 0..common_data.num_public_inputs { + builder.add_virtual_public_input(); + } + + let circuit = builder.build::(); + assert_eq!(&circuit.common, common_data); + circuit +} + +impl, const D: usize> CircuitBuilder { + pub(crate) fn dummy_proof_and_vk + 'static>( + &mut self, + common_data: &CommonCircuitData, + ) -> anyhow::Result<(ProofWithPublicInputsTarget, VerifierCircuitTarget)> + where + C::Hasher: AlgebraicHasher, + { + let dummy_circuit = dummy_circuit::(common_data); + let dummy_proof_with_pis = dummy_proof::(&dummy_circuit, HashMap::new())?; + let dummy_proof_with_pis_target = self.add_virtual_proof_with_pis(common_data); + let dummy_verifier_data_target = + self.add_virtual_verifier_data(self.config.fri_config.cap_height); + + self.add_simple_generator(DummyProofGenerator { + proof_with_pis_target: dummy_proof_with_pis_target.clone(), + proof_with_pis: dummy_proof_with_pis, + verifier_data_target: dummy_verifier_data_target.clone(), + verifier_data: dummy_circuit.verifier_only, + }); + + Ok((dummy_proof_with_pis_target, dummy_verifier_data_target)) + } +} + +#[derive(Debug)] +pub struct DummyProofGenerator +where + F: RichField + Extendable, + C: GenericConfig, +{ + pub(crate) proof_with_pis_target: ProofWithPublicInputsTarget, + pub(crate) proof_with_pis: ProofWithPublicInputs, + pub(crate) verifier_data_target: VerifierCircuitTarget, + pub(crate) verifier_data: VerifierOnlyCircuitData, +} + +impl Default for DummyProofGenerator +where + F: RichField + Extendable, + C: GenericConfig, +{ + fn default() -> Self { + let proof_with_pis_target = ProofWithPublicInputsTarget { + proof: ProofTarget { + wires_cap: MerkleCapTarget(vec![]), + plonk_zs_partial_products_cap: MerkleCapTarget(vec![]), + quotient_polys_cap: MerkleCapTarget(vec![]), + openings: OpeningSetTarget::default(), + opening_proof: FriProofTarget { + commit_phase_merkle_caps: vec![], + query_round_proofs: vec![], + final_poly: PolynomialCoeffsExtTarget(vec![]), + pow_witness: Target::default(), + }, + }, + public_inputs: vec![], + }; + + let proof_with_pis = ProofWithPublicInputs { + proof: Proof { + wires_cap: MerkleCap(vec![]), + plonk_zs_partial_products_cap: MerkleCap(vec![]), + quotient_polys_cap: MerkleCap(vec![]), + openings: OpeningSet::default(), + opening_proof: FriProof { + commit_phase_merkle_caps: vec![], + query_round_proofs: vec![], + final_poly: PolynomialCoeffs { coeffs: vec![] }, + pow_witness: F::ZERO, + }, + }, + public_inputs: vec![], + }; + + let verifier_data_target = VerifierCircuitTarget { + constants_sigmas_cap: MerkleCapTarget(vec![]), + circuit_digest: HashOutTarget { + elements: [Target::default(); 4], + }, + }; + + let verifier_data = VerifierOnlyCircuitData { + constants_sigmas_cap: MerkleCap(vec![]), + circuit_digest: <>::Hasher as Hasher>::Hash::from_bytes( + &vec![0; <>::Hasher as Hasher>::HASH_SIZE], + ), + }; + + Self { + proof_with_pis_target, + proof_with_pis, + verifier_data_target, + verifier_data, + } + } +} + +impl SimpleGenerator for DummyProofGenerator +where + F: RichField + Extendable, + C: GenericConfig + 'static, + C::Hasher: AlgebraicHasher, +{ + fn id(&self) -> String { + "DummyProofGenerator".to_string() + } + + fn dependencies(&self) -> Vec { + vec![] + } + + fn run_once(&self, _witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + out_buffer.set_proof_with_pis_target(&self.proof_with_pis_target, &self.proof_with_pis); + out_buffer.set_verifier_data_target(&self.verifier_data_target, &self.verifier_data); + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_target_proof_with_public_inputs(&self.proof_with_pis_target)?; + dst.write_proof_with_public_inputs(&self.proof_with_pis)?; + dst.write_target_verifier_circuit(&self.verifier_data_target)?; + dst.write_verifier_only_circuit_data(&self.verifier_data) + } + + fn deserialize(src: &mut Buffer, common_data: &CommonCircuitData) -> IoResult { + let proof_with_pis_target = src.read_target_proof_with_public_inputs()?; + let proof_with_pis = src.read_proof_with_public_inputs(common_data)?; + let verifier_data_target = src.read_target_verifier_circuit()?; + let verifier_data = src.read_verifier_only_circuit_data()?; + Ok(Self { + proof_with_pis_target, + proof_with_pis, + verifier_data_target, + verifier_data, + }) + } +} diff --git a/plonky2/src/recursion/mod.rs b/plonky2/src/recursion/mod.rs new file mode 100644 index 000000000..438f60076 --- /dev/null +++ b/plonky2/src/recursion/mod.rs @@ -0,0 +1,10 @@ +//! Recursion logic for verifying recursively plonky2 circuits. +//! +//! This module also provides ways to perform conditional recursive verification +//! (between two different circuits, depending on a condition), and cyclic +//! recursion where a circuit implements its own verification logic. + +pub mod conditional_recursive_verifier; +pub mod cyclic_recursion; +pub mod dummy_circuit; +pub mod recursive_verifier; diff --git a/plonky2/src/recursion/recursive_verifier.rs b/plonky2/src/recursion/recursive_verifier.rs new file mode 100644 index 000000000..36e7cf6c8 --- /dev/null +++ b/plonky2/src/recursion/recursive_verifier.rs @@ -0,0 +1,727 @@ +use crate::field::extension::Extendable; +use crate::hash::hash_types::{HashOutTarget, RichField}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::{CommonCircuitData, VerifierCircuitTarget}; +use crate::plonk::config::{AlgebraicHasher, GenericConfig}; +use crate::plonk::plonk_common::salt_size; +use crate::plonk::proof::{ + OpeningSetTarget, ProofChallengesTarget, ProofTarget, ProofWithPublicInputsTarget, +}; +use crate::plonk::vanishing_poly::eval_vanishing_poly_circuit; +use crate::plonk::vars::EvaluationTargets; +use crate::util::reducing::ReducingFactorTarget; +use crate::with_context; + +impl, const D: usize> CircuitBuilder { + /// Recursively verifies an inner proof. + pub fn verify_proof>( + &mut self, + proof_with_pis: &ProofWithPublicInputsTarget, + inner_verifier_data: &VerifierCircuitTarget, + inner_common_data: &CommonCircuitData, + ) where + C::Hasher: AlgebraicHasher, + { + assert_eq!( + proof_with_pis.public_inputs.len(), + inner_common_data.num_public_inputs + ); + let public_inputs_hash = + self.hash_n_to_hash_no_pad::(proof_with_pis.public_inputs.clone()); + let challenges = proof_with_pis.get_challenges::( + self, + public_inputs_hash, + inner_verifier_data.circuit_digest, + inner_common_data, + ); + + self.verify_proof_with_challenges::( + &proof_with_pis.proof, + public_inputs_hash, + challenges, + inner_verifier_data, + inner_common_data, + ); + } + + /// Recursively verifies an inner proof. + fn verify_proof_with_challenges>( + &mut self, + proof: &ProofTarget, + public_inputs_hash: HashOutTarget, + challenges: ProofChallengesTarget, + inner_verifier_data: &VerifierCircuitTarget, + inner_common_data: &CommonCircuitData, + ) where + C::Hasher: AlgebraicHasher, + { + let one = self.one_extension(); + + let local_constants = &proof.openings.constants; + let local_wires = &proof.openings.wires; + let vars = EvaluationTargets { + local_constants, + local_wires, + public_inputs_hash: &public_inputs_hash, + }; + let local_zs = &proof.openings.plonk_zs; + let next_zs = &proof.openings.plonk_zs_next; + let local_lookup_zs = &proof.openings.lookup_zs; + let next_lookup_zs = &proof.openings.next_lookup_zs; + let s_sigmas = &proof.openings.plonk_sigmas; + let partial_products = &proof.openings.partial_products; + + let zeta_pow_deg = + self.exp_power_of_2_extension(challenges.plonk_zeta, inner_common_data.degree_bits()); + let vanishing_polys_zeta = with_context!( + self, + "evaluate the vanishing polynomial at our challenge point, zeta.", + eval_vanishing_poly_circuit::( + self, + inner_common_data, + challenges.plonk_zeta, + zeta_pow_deg, + vars, + local_zs, + next_zs, + local_lookup_zs, + next_lookup_zs, + partial_products, + s_sigmas, + &challenges.plonk_betas, + &challenges.plonk_gammas, + &challenges.plonk_alphas, + &challenges.plonk_deltas, + ) + ); + + with_context!(self, "check vanishing and quotient polynomials.", { + let quotient_polys_zeta = &proof.openings.quotient_polys; + let mut scale = ReducingFactorTarget::new(zeta_pow_deg); + let z_h_zeta = self.sub_extension(zeta_pow_deg, one); + for (i, chunk) in quotient_polys_zeta + .chunks(inner_common_data.quotient_degree_factor) + .enumerate() + { + let recombined_quotient = scale.reduce(chunk, self); + let computed_vanishing_poly = self.mul_extension(z_h_zeta, recombined_quotient); + self.connect_extension(vanishing_polys_zeta[i], computed_vanishing_poly); + } + }); + + let merkle_caps = &[ + inner_verifier_data.constants_sigmas_cap.clone(), + proof.wires_cap.clone(), + proof.plonk_zs_partial_products_cap.clone(), + proof.quotient_polys_cap.clone(), + ]; + + let fri_instance = inner_common_data.get_fri_instance_target(self, challenges.plonk_zeta); + with_context!( + self, + "verify FRI proof", + self.verify_fri_proof::( + &fri_instance, + &proof.openings.to_fri_openings(), + &challenges.fri_challenges, + merkle_caps, + &proof.opening_proof, + &inner_common_data.fri_params, + ) + ); + } + + pub fn add_virtual_proof_with_pis( + &mut self, + common_data: &CommonCircuitData, + ) -> ProofWithPublicInputsTarget { + let proof = self.add_virtual_proof(common_data); + let public_inputs = self.add_virtual_targets(common_data.num_public_inputs); + ProofWithPublicInputsTarget { + proof, + public_inputs, + } + } + + fn add_virtual_proof(&mut self, common_data: &CommonCircuitData) -> ProofTarget { + let config = &common_data.config; + let fri_params = &common_data.fri_params; + let cap_height = fri_params.config.cap_height; + + let salt = salt_size(common_data.fri_params.hiding); + let num_leaves_per_oracle = &[ + common_data.num_preprocessed_polys(), + config.num_wires + salt, + common_data.num_zs_partial_products_polys() + common_data.num_all_lookup_polys() + salt, + common_data.num_quotient_polys() + salt, + ]; + + ProofTarget { + wires_cap: self.add_virtual_cap(cap_height), + plonk_zs_partial_products_cap: self.add_virtual_cap(cap_height), + quotient_polys_cap: self.add_virtual_cap(cap_height), + openings: self.add_opening_set(common_data), + opening_proof: self.add_virtual_fri_proof(num_leaves_per_oracle, fri_params), + } + } + + fn add_opening_set(&mut self, common_data: &CommonCircuitData) -> OpeningSetTarget { + let config = &common_data.config; + let num_challenges = config.num_challenges; + let total_partial_products = num_challenges * common_data.num_partial_products; + let has_lookup = common_data.num_lookup_polys != 0; + let num_lookups = if has_lookup { + common_data.num_all_lookup_polys() + } else { + 0 + }; + OpeningSetTarget { + constants: self.add_virtual_extension_targets(common_data.num_constants), + plonk_sigmas: self.add_virtual_extension_targets(config.num_routed_wires), + wires: self.add_virtual_extension_targets(config.num_wires), + plonk_zs: self.add_virtual_extension_targets(num_challenges), + plonk_zs_next: self.add_virtual_extension_targets(num_challenges), + lookup_zs: self.add_virtual_extension_targets(num_lookups), + next_lookup_zs: self.add_virtual_extension_targets(num_lookups), + partial_products: self.add_virtual_extension_targets(total_partial_products), + quotient_polys: self.add_virtual_extension_targets(common_data.num_quotient_polys()), + } + } +} + +#[cfg(test)] +mod tests { + #[cfg(not(feature = "std"))] + use alloc::{sync::Arc, vec}; + #[cfg(feature = "std")] + use std::sync::Arc; + + use anyhow::Result; + use itertools::Itertools; + use log::{info, Level}; + + use super::*; + use crate::fri::reduction_strategies::FriReductionStrategy; + use crate::fri::FriConfig; + use crate::gadgets::lookup::{OTHER_TABLE, TIP5_TABLE}; + use crate::gates::lookup_table::LookupTable; + use crate::gates::noop::NoopGate; + use crate::iop::witness::{PartialWitness, WitnessWrite}; + use crate::plonk::circuit_data::{CircuitConfig, VerifierOnlyCircuitData}; + use crate::plonk::config::{GenericConfig, KeccakGoldilocksConfig, PoseidonGoldilocksConfig}; + use crate::plonk::proof::{CompressedProofWithPublicInputs, ProofWithPublicInputs}; + use crate::plonk::prover::prove; + use crate::util::timing::TimingTree; + + #[test] + fn test_recursive_verifier() -> Result<()> { + init_logger(); + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + let config = CircuitConfig::standard_recursion_zk_config(); + + let (proof, vd, common_data) = dummy_proof::(&config, 4_000)?; + let (proof, vd, common_data) = + recursive_proof::(proof, vd, common_data, &config, None, true, true)?; + test_serialization(&proof, &vd, &common_data)?; + + Ok(()) + } + + #[test] + fn test_recursive_verifier_one_lookup() -> Result<()> { + init_logger(); + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + let config = CircuitConfig::standard_recursion_zk_config(); + + let (proof, vd, common_data) = dummy_lookup_proof::(&config, 10)?; + let (proof, vd, common_data) = + recursive_proof::(proof, vd, common_data, &config, None, true, true)?; + test_serialization(&proof, &vd, &common_data)?; + + Ok(()) + } + + #[test] + fn test_recursive_verifier_two_luts() -> Result<()> { + init_logger(); + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + let config = CircuitConfig::standard_recursion_config(); + + let (proof, vd, common_data) = dummy_two_luts_proof::(&config)?; + let (proof, vd, common_data) = + recursive_proof::(proof, vd, common_data, &config, None, true, true)?; + test_serialization(&proof, &vd, &common_data)?; + + Ok(()) + } + + #[test] + fn test_recursive_verifier_too_many_rows() -> Result<()> { + init_logger(); + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + let config = CircuitConfig::standard_recursion_config(); + + let (proof, vd, common_data) = dummy_too_many_rows_proof::(&config)?; + let (proof, vd, common_data) = + recursive_proof::(proof, vd, common_data, &config, None, true, true)?; + test_serialization(&proof, &vd, &common_data)?; + + Ok(()) + } + + #[test] + fn test_recursive_recursive_verifier() -> Result<()> { + init_logger(); + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + + let config = CircuitConfig::standard_recursion_config(); + + // Start with a degree 2^14 proof + let (proof, vd, common_data) = dummy_proof::(&config, 16_000)?; + assert_eq!(common_data.degree_bits(), 14); + + // Shrink it to 2^13. + let (proof, vd, common_data) = + recursive_proof::(proof, vd, common_data, &config, Some(13), false, false)?; + assert_eq!(common_data.degree_bits(), 13); + + // Shrink it to 2^12. + let (proof, vd, common_data) = + recursive_proof::(proof, vd, common_data, &config, None, true, true)?; + assert_eq!(common_data.degree_bits(), 12); + + test_serialization(&proof, &vd, &common_data)?; + + Ok(()) + } + + /// Creates a chain of recursive proofs where the last proof is made as + /// small as reasonably possible, using a high rate, high PoW bits, etc. + #[test] + #[ignore] + fn test_size_optimized_recursion() -> Result<()> { + init_logger(); + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type KC = KeccakGoldilocksConfig; + type F = >::F; + + let standard_config = CircuitConfig::standard_recursion_config(); + + // An initial dummy proof. + let (proof, vd, common_data) = dummy_proof::(&standard_config, 4_000)?; + assert_eq!(common_data.degree_bits(), 12); + + // A standard recursive proof. + let (proof, vd, common_data) = recursive_proof::( + proof, + vd, + common_data, + &standard_config, + None, + false, + false, + )?; + assert_eq!(common_data.degree_bits(), 12); + + // A high-rate recursive proof, designed to be verifiable with fewer routed + // wires. + let high_rate_config = CircuitConfig { + fri_config: FriConfig { + rate_bits: 7, + proof_of_work_bits: 16, + num_query_rounds: 12, + ..standard_config.fri_config.clone() + }, + ..standard_config + }; + let (proof, vd, common_data) = recursive_proof::( + proof, + vd, + common_data, + &high_rate_config, + None, + true, + true, + )?; + assert_eq!(common_data.degree_bits(), 12); + + // A final proof, optimized for size. + let final_config = CircuitConfig { + num_routed_wires: 37, + fri_config: FriConfig { + rate_bits: 8, + cap_height: 0, + proof_of_work_bits: 20, + reduction_strategy: FriReductionStrategy::MinSize(None), + num_query_rounds: 10, + }, + ..high_rate_config + }; + let (proof, vd, common_data) = recursive_proof::( + proof, + vd, + common_data, + &final_config, + None, + true, + true, + )?; + assert_eq!(common_data.degree_bits(), 12, "final proof too large"); + + test_serialization(&proof, &vd, &common_data)?; + + Ok(()) + } + + #[test] + fn test_recursive_verifier_multi_hash() -> Result<()> { + init_logger(); + const D: usize = 2; + type PC = PoseidonGoldilocksConfig; + type KC = KeccakGoldilocksConfig; + type F = >::F; + + let config = CircuitConfig::standard_recursion_config(); + let (proof, vd, common_data) = dummy_proof::(&config, 4_000)?; + + let (proof, vd, common_data) = + recursive_proof::(proof, vd, common_data, &config, None, false, false)?; + test_serialization(&proof, &vd, &common_data)?; + + let (proof, vd, common_data) = + recursive_proof::(proof, vd, common_data, &config, None, false, false)?; + test_serialization(&proof, &vd, &common_data)?; + + Ok(()) + } + + type Proof = ( + ProofWithPublicInputs, + VerifierOnlyCircuitData, + CommonCircuitData, + ); + + /// Creates a dummy proof which should have roughly `num_dummy_gates` gates. + fn dummy_proof, C: GenericConfig, const D: usize>( + config: &CircuitConfig, + num_dummy_gates: u64, + ) -> Result> { + let mut builder = CircuitBuilder::::new(config.clone()); + for _ in 0..num_dummy_gates { + builder.add_gate(NoopGate, vec![]); + } + + let data = builder.build::(); + let inputs = PartialWitness::new(); + let proof = data.prove(inputs)?; + data.verify(proof.clone())?; + + Ok((proof, data.verifier_only, data.common)) + } + + /// Creates a dummy lookup proof which does one lookup to one LUT. + fn dummy_lookup_proof< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, + >( + config: &CircuitConfig, + num_dummy_gates: u64, + ) -> Result> { + let mut builder = CircuitBuilder::::new(config.clone()); + let initial_a = builder.add_virtual_target(); + let initial_b = builder.add_virtual_target(); + + let look_val_a = 1; + let look_val_b = 2; + + let tip5_table = TIP5_TABLE.to_vec(); + let table: LookupTable = Arc::new((0..256).zip_eq(tip5_table).collect()); + + let out_a = table[look_val_a].1; + let out_b = table[look_val_b].1; + + let tip5_index = builder.add_lookup_table_from_pairs(table); + + let output_a = builder.add_lookup_from_index(initial_a, tip5_index); + let output_b = builder.add_lookup_from_index(initial_b, tip5_index); + + for _ in 0..num_dummy_gates + 1 { + builder.add_gate(NoopGate, vec![]); + } + + builder.register_public_input(initial_a); + builder.register_public_input(initial_b); + builder.register_public_input(output_a); + builder.register_public_input(output_b); + + let data = builder.build::(); + let mut inputs = PartialWitness::new(); + inputs.set_target(initial_a, F::from_canonical_usize(look_val_a)); + inputs.set_target(initial_b, F::from_canonical_usize(look_val_b)); + + let proof = data.prove(inputs)?; + data.verify(proof.clone())?; + + assert!( + proof.public_inputs[2] == F::from_canonical_u16(out_a), + "First lookup, at index {} in the Tip5 table gives an incorrect output.", + proof.public_inputs[0] + ); + assert!( + proof.public_inputs[3] == F::from_canonical_u16(out_b), + "Second lookup, at index {} in the Tip5 table gives an incorrect output.", + proof.public_inputs[1] + ); + + Ok((proof, data.verifier_only, data.common)) + } + + /// Creates a dummy lookup proof which does one lookup to two different + /// LUTs. + fn dummy_two_luts_proof< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, + >( + config: &CircuitConfig, + ) -> Result> { + let mut builder = CircuitBuilder::::new(config.clone()); + let initial_a = builder.add_virtual_target(); + let initial_b = builder.add_virtual_target(); + + let look_val_a = 1; + let look_val_b = 2; + + let tip5_table = TIP5_TABLE.to_vec(); + + let first_out = tip5_table[look_val_a]; + let second_out = tip5_table[look_val_b]; + + let table: LookupTable = Arc::new((0..256).zip_eq(tip5_table).collect()); + + let other_table = OTHER_TABLE.to_vec(); + + let tip5_index = builder.add_lookup_table_from_pairs(table); + let output_a = builder.add_lookup_from_index(initial_a, tip5_index); + + let output_b = builder.add_lookup_from_index(initial_b, tip5_index); + let sum = builder.add(output_a, output_b); + + let s = first_out + second_out; + let final_out = other_table[s as usize]; + + let table2: LookupTable = Arc::new((0..256).zip_eq(other_table).collect()); + + let other_index = builder.add_lookup_table_from_pairs(table2); + let output_final = builder.add_lookup_from_index(sum, other_index); + + builder.register_public_input(initial_a); + builder.register_public_input(initial_b); + + builder.register_public_input(sum); + builder.register_public_input(output_a); + builder.register_public_input(output_b); + builder.register_public_input(output_final); + + let mut pw = PartialWitness::new(); + pw.set_target(initial_a, F::ONE); + pw.set_target(initial_b, F::TWO); + + let data = builder.build::(); + let proof = data.prove(pw)?; + data.verify(proof.clone())?; + + assert!( + proof.public_inputs[3] == F::from_canonical_u16(first_out), + "First lookup, at index {} in the Tip5 table gives an incorrect output.", + proof.public_inputs[0] + ); + assert!( + proof.public_inputs[4] == F::from_canonical_u16(second_out), + "Second lookup, at index {} in the Tip5 table gives an incorrect output.", + proof.public_inputs[1] + ); + assert!( + proof.public_inputs[2] == F::from_canonical_u16(s), + "Sum between the first two LUT outputs is incorrect." + ); + assert!( + proof.public_inputs[5] == F::from_canonical_u16(final_out), + "Output of the second LUT at index {} is incorrect.", + s + ); + + Ok((proof, data.verifier_only, data.common)) + } + + /// Creates a dummy proof which has more than 256 lookups to one LUT. + fn dummy_too_many_rows_proof< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, + >( + config: &CircuitConfig, + ) -> Result> { + let mut builder = CircuitBuilder::::new(config.clone()); + + let initial_a = builder.add_virtual_target(); + let initial_b = builder.add_virtual_target(); + + let look_val_a = 1; + let look_val_b = 2; + + let tip5_table = TIP5_TABLE.to_vec(); + let table: LookupTable = Arc::new((0..256).zip_eq(tip5_table).collect()); + + let out_a = table[look_val_a].1; + let out_b = table[look_val_b].1; + + let tip5_index = builder.add_lookup_table_from_pairs(table); + let output_b = builder.add_lookup_from_index(initial_b, tip5_index); + let mut output = builder.add_lookup_from_index(initial_a, tip5_index); + for _ in 0..514 { + output = builder.add_lookup_from_index(initial_a, tip5_index); + } + + builder.register_public_input(initial_a); + builder.register_public_input(initial_b); + builder.register_public_input(output_b); + builder.register_public_input(output); + + let mut pw = PartialWitness::new(); + + pw.set_target(initial_a, F::from_canonical_usize(look_val_a)); + pw.set_target(initial_b, F::from_canonical_usize(look_val_b)); + + let data = builder.build::(); + let proof = data.prove(pw)?; + assert!( + proof.public_inputs[2] == F::from_canonical_u16(out_b), + "First lookup, at index {} in the Tip5 table gives an incorrect output.", + proof.public_inputs[1] + ); + assert!( + proof.public_inputs[3] == F::from_canonical_u16(out_a), + "Lookups at index {} in the Tip5 table gives an incorrect output.", + proof.public_inputs[0] + ); + data.verify(proof.clone())?; + + Ok((proof, data.verifier_only, data.common)) + } + + fn recursive_proof< + F: RichField + Extendable, + C: GenericConfig, + InnerC: GenericConfig, + const D: usize, + >( + inner_proof: ProofWithPublicInputs, + inner_vd: VerifierOnlyCircuitData, + inner_cd: CommonCircuitData, + config: &CircuitConfig, + min_degree_bits: Option, + print_gate_counts: bool, + print_timing: bool, + ) -> Result> + where + InnerC::Hasher: AlgebraicHasher, + { + let mut builder = CircuitBuilder::::new(config.clone()); + let mut pw = PartialWitness::new(); + let pt = builder.add_virtual_proof_with_pis(&inner_cd); + pw.set_proof_with_pis_target(&pt, &inner_proof); + + let inner_data = builder.add_virtual_verifier_data(inner_cd.config.fri_config.cap_height); + pw.set_cap_target( + &inner_data.constants_sigmas_cap, + &inner_vd.constants_sigmas_cap, + ); + pw.set_hash_target(inner_data.circuit_digest, inner_vd.circuit_digest); + + builder.verify_proof::(&pt, &inner_data, &inner_cd); + + if print_gate_counts { + builder.print_gate_counts(0); + } + + if let Some(min_degree_bits) = min_degree_bits { + // We don't want to pad all the way up to 2^min_degree_bits, as the builder will + // add a few special gates afterward. So just pad to + // 2^(min_degree_bits - 1) + 1. Then the builder will pad to the + // next power of two, 2^min_degree_bits. + let min_gates = (1 << (min_degree_bits - 1)) + 1; + for _ in builder.num_gates()..min_gates { + builder.add_gate(NoopGate, vec![]); + } + } + + let data = builder.build::(); + + let mut timing = TimingTree::new("prove", Level::Debug); + let proof = prove(&data.prover_only, &data.common, pw, &mut timing)?; + if print_timing { + timing.print(); + } + + data.verify(proof.clone())?; + + Ok((proof, data.verifier_only, data.common)) + } + + /// Test serialization and print some size info. + fn test_serialization< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, + >( + proof: &ProofWithPublicInputs, + vd: &VerifierOnlyCircuitData, + common_data: &CommonCircuitData, + ) -> Result<()> { + let proof_bytes = proof.to_bytes(); + info!("Proof length: {} bytes", proof_bytes.len()); + let proof_from_bytes = ProofWithPublicInputs::from_bytes(proof_bytes, common_data)?; + assert_eq!(proof, &proof_from_bytes); + + #[cfg(feature = "std")] + let now = std::time::Instant::now(); + + let compressed_proof = proof.clone().compress(&vd.circuit_digest, common_data)?; + let decompressed_compressed_proof = compressed_proof + .clone() + .decompress(&vd.circuit_digest, common_data)?; + + #[cfg(feature = "std")] + info!("{:.4}s to compress proof", now.elapsed().as_secs_f64()); + + assert_eq!(proof, &decompressed_compressed_proof); + + let compressed_proof_bytes = compressed_proof.to_bytes(); + info!( + "Compressed proof length: {} bytes", + compressed_proof_bytes.len() + ); + let compressed_proof_from_bytes = + CompressedProofWithPublicInputs::from_bytes(compressed_proof_bytes, common_data)?; + assert_eq!(compressed_proof, compressed_proof_from_bytes); + + Ok(()) + } + + fn init_logger() { + let _ = env_logger::builder().format_timestamp(None).try_init(); + } +} diff --git a/plonky2/src/util/context_tree.rs b/plonky2/src/util/context_tree.rs new file mode 100644 index 000000000..b55b63084 --- /dev/null +++ b/plonky2/src/util/context_tree.rs @@ -0,0 +1,149 @@ +#[cfg(not(feature = "std"))] +use alloc::{ + string::{String, ToString}, + vec, + vec::Vec, +}; + +use log::{log, Level}; + +/// The hierarchy of contexts, and the gate count contributed by each one. +/// Useful for debugging. +pub(crate) struct ContextTree { + /// The name of this scope. + name: String, + /// The level at which to log this scope and its children. + level: log::Level, + /// The gate count when this scope was created. + enter_gate_count: usize, + /// The gate count when this scope was destroyed, or None if it has not yet + /// been destroyed. + exit_gate_count: Option, + /// Any child contexts. + children: Vec, +} + +impl ContextTree { + pub fn new() -> Self { + Self { + name: "root".to_string(), + level: Level::Debug, + enter_gate_count: 0, + exit_gate_count: None, + children: vec![], + } + } + + /// Whether this context is still in scope. + const fn is_open(&self) -> bool { + self.exit_gate_count.is_none() + } + + /// A description of the stack of currently-open scopes. + pub fn open_stack(&self) -> String { + let mut stack = Vec::new(); + self.open_stack_helper(&mut stack); + stack.join(" > ") + } + + fn open_stack_helper(&self, stack: &mut Vec) { + if self.is_open() { + stack.push(self.name.clone()); + if let Some(last_child) = self.children.last() { + last_child.open_stack_helper(stack); + } + } + } + + pub fn push(&mut self, ctx: &str, mut level: log::Level, current_gate_count: usize) { + assert!(self.is_open()); + + // We don't want a scope's log level to be stronger than that of its parent. + level = level.max(self.level); + + if let Some(last_child) = self.children.last_mut() { + if last_child.is_open() { + last_child.push(ctx, level, current_gate_count); + return; + } + } + + self.children.push(ContextTree { + name: ctx.to_string(), + level, + enter_gate_count: current_gate_count, + exit_gate_count: None, + children: vec![], + }) + } + + /// Close the deepest open context from this tree. + pub fn pop(&mut self, current_gate_count: usize) { + assert!(self.is_open()); + + if let Some(last_child) = self.children.last_mut() { + if last_child.is_open() { + last_child.pop(current_gate_count); + return; + } + } + + self.exit_gate_count = Some(current_gate_count); + } + + fn gate_count_delta(&self, current_gate_count: usize) -> usize { + self.exit_gate_count.unwrap_or(current_gate_count) - self.enter_gate_count + } + + /// Filter out children with a low gate count. + pub fn filter(&self, current_gate_count: usize, min_delta: usize) -> Self { + Self { + name: self.name.clone(), + level: self.level, + enter_gate_count: self.enter_gate_count, + exit_gate_count: self.exit_gate_count, + children: self + .children + .iter() + .filter(|c| c.gate_count_delta(current_gate_count) >= min_delta) + .map(|c| c.filter(current_gate_count, min_delta)) + .collect(), + } + } + + pub fn print(&self, current_gate_count: usize) { + self.print_helper(current_gate_count, 0); + } + + fn print_helper(&self, current_gate_count: usize, depth: usize) { + let prefix = "| ".repeat(depth); + log!( + self.level, + "{}{} gates to {}", + prefix, + self.gate_count_delta(current_gate_count), + self.name + ); + for child in &self.children { + child.print_helper(current_gate_count, depth + 1); + } + } +} + +/// Creates a named scope; useful for debugging. +#[macro_export] +macro_rules! with_context { + ($builder:expr, $level:expr, $ctx:expr, $exp:expr) => {{ + $builder.push_context($level, $ctx); + let res = $exp; + $builder.pop_context(); + res + }}; + // If no context is specified, default to Debug. + ($builder:expr, $ctx:expr, $exp:expr) => {{ + $builder.push_context(log::Level::Debug, $ctx); + let res = $exp; + $builder.pop_context(); + res + }}; +} diff --git a/plonky2/src/util/mod.rs b/plonky2/src/util/mod.rs new file mode 100644 index 000000000..8f9960034 --- /dev/null +++ b/plonky2/src/util/mod.rs @@ -0,0 +1,149 @@ +//! Utility module for helper methods and plonky2 serialization logic. + +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + +use plonky2_maybe_rayon::*; +#[doc(inline)] +pub use plonky2_util::*; + +use crate::field::polynomial::PolynomialValues; +use crate::field::types::Field; + +pub(crate) mod context_tree; +pub(crate) mod partial_products; +pub mod reducing; +pub mod serialization; +pub mod strided_view; +pub mod timing; + +pub(crate) fn transpose_poly_values(polys: Vec>) -> Vec> { + let poly_values = polys.into_iter().map(|p| p.values).collect::>(); + transpose(&poly_values) +} + +pub fn transpose(matrix: &[Vec]) -> Vec> { + let len = matrix[0].len(); + (0..len) + .into_par_iter() + .map(|i| matrix.iter().map(|row| row[i]).collect()) + .collect() +} + +pub(crate) const fn reverse_bits(n: usize, num_bits: usize) -> usize { + // NB: The only reason we need overflowing_shr() here as opposed + // to plain '>>' is to accommodate the case n == num_bits == 0, + // which would become `0 >> 64`. Rust thinks that any shift of 64 + // bits causes overflow, even when the argument is zero. + n.reverse_bits() + .overflowing_shr(usize::BITS - num_bits as u32) + .0 +} + +#[cfg(test)] +mod tests { + + #[cfg(not(feature = "std"))] + use alloc::vec; + + use super::*; + + #[test] + fn test_reverse_bits() { + assert_eq!(reverse_bits(0b0000000000, 10), 0b0000000000); + assert_eq!(reverse_bits(0b0000000001, 10), 0b1000000000); + assert_eq!(reverse_bits(0b1000000000, 10), 0b0000000001); + assert_eq!(reverse_bits(0b00000, 5), 0b00000); + assert_eq!(reverse_bits(0b01011, 5), 0b11010); + } + + #[test] + fn test_reverse_index_bits() { + assert_eq!(reverse_index_bits(&[10, 20, 30, 40]), vec![10, 30, 20, 40]); + + let input256: Vec = (0..256).collect(); + #[rustfmt::skip] + let output256: Vec = vec![ + 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, + 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, + 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, + 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, + 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, + 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, + 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, + 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, + 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, + 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, + 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, + 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, + 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, + 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, + 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, + 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff, + ]; + assert_eq!(reverse_index_bits(&input256[..]), output256); + } + + #[test] + fn test_reverse_index_bits_in_place_trivial() { + let mut arr1: Vec = vec![10]; + reverse_index_bits_in_place(&mut arr1); + assert_eq!(arr1, vec![10]); + + let mut arr2: Vec = vec![10, 20]; + reverse_index_bits_in_place(&mut arr2); + assert_eq!(arr2, vec![10, 20]); + } + + #[test] + fn test_reverse_index_bits_in_place_small() { + let mut arr4: Vec = vec![10, 20, 30, 40]; + reverse_index_bits_in_place(&mut arr4); + assert_eq!(arr4, vec![10, 30, 20, 40]); + + let mut arr256: Vec = (0..256).collect(); + #[rustfmt::skip] + let output256: Vec = vec![ + 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, + 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, + 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, + 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, + 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, + 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, + 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, + 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, + 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, + 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, + 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, + 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, + 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, + 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, + 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, + 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff, + ]; + reverse_index_bits_in_place(&mut arr256); + assert_eq!(arr256, output256); + } + + #[test] + fn test_reverse_index_bits_in_place_big_even() { + let mut arr: Vec = (0..1 << 16).collect(); + let target = reverse_index_bits(&arr); + reverse_index_bits_in_place(&mut arr); + assert_eq!(arr, target); + reverse_index_bits_in_place(&mut arr); + let range: Vec = (0..1 << 16).collect(); + assert_eq!(arr, range); + } + + #[test] + fn test_reverse_index_bits_in_place_big_odd() { + let mut arr: Vec = (0..1 << 17).collect(); + let target = reverse_index_bits(&arr); + reverse_index_bits_in_place(&mut arr); + assert_eq!(arr, target); + reverse_index_bits_in_place(&mut arr); + let range: Vec = (0..1 << 17).collect(); + assert_eq!(arr, range); + } +} diff --git a/plonky2/src/util/partial_products.rs b/plonky2/src/util/partial_products.rs new file mode 100644 index 000000000..57db7b563 --- /dev/null +++ b/plonky2/src/util/partial_products.rs @@ -0,0 +1,156 @@ +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; +use core::iter; + +use itertools::Itertools; + +use crate::field::extension::Extendable; +use crate::field::types::Field; +use crate::hash::hash_types::RichField; +use crate::iop::ext_target::ExtensionTarget; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::util::ceil_div_usize; + +pub(crate) fn quotient_chunk_products( + quotient_values: &[F], + max_degree: usize, +) -> Vec { + debug_assert!(max_degree > 1); + assert!(!quotient_values.is_empty()); + let chunk_size = max_degree; + quotient_values + .chunks(chunk_size) + .map(|chunk| chunk.iter().copied().product()) + .collect() +} + +/// Compute partial products of the original vector `v` such that all products +/// consist of `max_degree` or less elements. This is done until we've computed +/// the product `P` of all elements in the vector. +pub(crate) fn partial_products_and_z_gx(z_x: F, quotient_chunk_products: &[F]) -> Vec { + assert!(!quotient_chunk_products.is_empty()); + let mut res = Vec::with_capacity(quotient_chunk_products.len()); + let mut acc = z_x; + for "ient_chunk_product in quotient_chunk_products { + acc *= quotient_chunk_product; + res.push(acc); + } + res +} + +/// Returns the length of the output of `partial_products()` on a vector of +/// length `n`. +pub(crate) fn num_partial_products(n: usize, max_degree: usize) -> usize { + debug_assert!(max_degree > 1); + let chunk_size = max_degree; + // We'll split the product into `ceil_div_usize(n, chunk_size)` chunks, but the + // last chunk will be associated with Z(gx) itself. Thus we subtract one to + // get the chunks associated with partial products. + ceil_div_usize(n, chunk_size) - 1 +} + +/// Checks the relationship between each pair of partial product accumulators. +/// In particular, this sequence of accumulators starts with `Z(x)`, then +/// contains each partial product polynomials `p_i(x)`, and finally `Z(g x)`. +/// See the partial products section of the Plonky2 paper. +pub(crate) fn check_partial_products( + numerators: &[F], + denominators: &[F], + partials: &[F], + z_x: F, + z_gx: F, + max_degree: usize, +) -> Vec { + debug_assert!(max_degree > 1); + let product_accs = iter::once(&z_x) + .chain(partials.iter()) + .chain(iter::once(&z_gx)); + let chunk_size = max_degree; + numerators + .chunks(chunk_size) + .zip_eq(denominators.chunks(chunk_size)) + .zip_eq(product_accs.tuple_windows()) + .map(|((nume_chunk, deno_chunk), (&prev_acc, &next_acc))| { + let num_chunk_product = nume_chunk.iter().copied().product(); + let den_chunk_product = deno_chunk.iter().copied().product(); + // Assert that next_acc * deno_product = prev_acc * nume_product. + prev_acc * num_chunk_product - next_acc * den_chunk_product + }) + .collect() +} + +/// Checks the relationship between each pair of partial product accumulators. +/// In particular, this sequence of accumulators starts with `Z(x)`, then +/// contains each partial product polynomials `p_i(x)`, and finally `Z(g x)`. +/// See the partial products section of the Plonky2 paper. +pub(crate) fn check_partial_products_circuit, const D: usize>( + builder: &mut CircuitBuilder, + numerators: &[ExtensionTarget], + denominators: &[ExtensionTarget], + partials: &[ExtensionTarget], + z_x: ExtensionTarget, + z_gx: ExtensionTarget, + max_degree: usize, +) -> Vec> { + debug_assert!(max_degree > 1); + let product_accs = iter::once(&z_x) + .chain(partials.iter()) + .chain(iter::once(&z_gx)); + let chunk_size = max_degree; + numerators + .chunks(chunk_size) + .zip_eq(denominators.chunks(chunk_size)) + .zip_eq(product_accs.tuple_windows()) + .map(|((nume_chunk, deno_chunk), (&prev_acc, &next_acc))| { + let nume_product = builder.mul_many_extension(nume_chunk); + let deno_product = builder.mul_many_extension(deno_chunk); + let next_acc_deno = builder.mul_extension(next_acc, deno_product); + // Assert that next_acc * deno_product = prev_acc * nume_product. + builder.mul_sub_extension(prev_acc, nume_product, next_acc_deno) + }) + .collect() +} + +#[cfg(test)] +mod tests { + #[cfg(not(feature = "std"))] + use alloc::vec; + + use super::*; + use crate::field::goldilocks_field::GoldilocksField; + + #[test] + fn test_partial_products() { + type F = GoldilocksField; + let denominators = vec![F::ONE; 6]; + let z_x = F::ONE; + let v = field_vec(&[1, 2, 3, 4, 5, 6]); + let z_gx = F::from_canonical_u64(720); + let quotient_chunks_prods = quotient_chunk_products(&v, 2); + assert_eq!(quotient_chunks_prods, field_vec(&[2, 12, 30])); + let pps_and_z_gx = partial_products_and_z_gx(z_x, "ient_chunks_prods); + let pps = &pps_and_z_gx[..pps_and_z_gx.len() - 1]; + assert_eq!(pps_and_z_gx, field_vec(&[2, 24, 720])); + + let nums = num_partial_products(v.len(), 2); + assert_eq!(pps.len(), nums); + assert!(check_partial_products(&v, &denominators, pps, z_x, z_gx, 2) + .iter() + .all(|x| x.is_zero())); + + let quotient_chunks_prods = quotient_chunk_products(&v, 3); + assert_eq!(quotient_chunks_prods, field_vec(&[6, 120])); + let pps_and_z_gx = partial_products_and_z_gx(z_x, "ient_chunks_prods); + let pps = &pps_and_z_gx[..pps_and_z_gx.len() - 1]; + assert_eq!(pps_and_z_gx, field_vec(&[6, 720])); + let nums = num_partial_products(v.len(), 3); + assert_eq!(pps.len(), nums); + assert!(check_partial_products(&v, &denominators, pps, z_x, z_gx, 3) + .iter() + .all(|x| x.is_zero())); + } + + fn field_vec(xs: &[usize]) -> Vec { + xs.iter().map(|&x| F::from_canonical_usize(x)).collect() + } +} diff --git a/plonky2/src/util/reducing.rs b/plonky2/src/util/reducing.rs new file mode 100644 index 000000000..7ae49a6ea --- /dev/null +++ b/plonky2/src/util/reducing.rs @@ -0,0 +1,368 @@ +#[cfg(not(feature = "std"))] +use alloc::{vec, vec::Vec}; +use core::borrow::Borrow; + +use crate::field::extension::{Extendable, FieldExtension}; +use crate::field::packed::PackedField; +use crate::field::polynomial::PolynomialCoeffs; +use crate::field::types::Field; +use crate::gates::arithmetic_extension::ArithmeticExtensionGate; +use crate::gates::reducing::ReducingGate; +use crate::gates::reducing_extension::ReducingExtensionGate; +use crate::hash::hash_types::RichField; +use crate::iop::ext_target::ExtensionTarget; +use crate::iop::target::Target; +use crate::plonk::circuit_builder::CircuitBuilder; + +/// When verifying the composition polynomial in FRI we have to compute sums of +/// the form `(sum_0^k a^i * x_i)/d_0 + (sum_k^r a^i * y_i)/d_1` +/// The most efficient way to do this is to compute both quotient separately +/// using Horner's method, scale the second one by `a^(r-1-k)`, and add them up. +/// This struct abstract away these operations by implementing Horner's method +/// and keeping track of the number of multiplications by `a` to compute the +/// scaling factor. See for more details and discussions. +#[derive(Debug, Clone)] +pub struct ReducingFactor { + base: F, + count: u64, +} + +impl ReducingFactor { + pub const fn new(base: F) -> Self { + Self { base, count: 0 } + } + + fn mul(&mut self, x: F) -> F { + self.count += 1; + self.base * x + } + + fn mul_ext(&mut self, x: P) -> P + where + FE: FieldExtension, + P: PackedField, + { + self.count += 1; + // TODO: Would like to use `FE::scalar_mul`, but it doesn't work with Packed + // currently. + x * FE::from_basefield(self.base) + } + + fn mul_poly(&mut self, p: &mut PolynomialCoeffs) { + self.count += 1; + *p *= self.base; + } + + pub fn reduce(&mut self, iter: impl DoubleEndedIterator>) -> F { + iter.rev() + .fold(F::ZERO, |acc, x| self.mul(acc) + *x.borrow()) + } + + pub fn reduce_ext( + &mut self, + iter: impl DoubleEndedIterator>, + ) -> P + where + FE: FieldExtension, + P: PackedField, + { + iter.rev() + .fold(P::ZEROS, |acc, x| self.mul_ext(acc) + *x.borrow()) + } + + pub fn reduce_polys( + &mut self, + polys: impl DoubleEndedIterator>>, + ) -> PolynomialCoeffs { + polys.rev().fold(PolynomialCoeffs::empty(), |mut acc, x| { + self.mul_poly(&mut acc); + acc += x.borrow(); + acc + }) + } + + pub fn reduce_polys_base, const D: usize>( + &mut self, + polys: impl IntoIterator>>, + ) -> PolynomialCoeffs { + self.base + .powers() + .zip(polys) + .map(|(base_power, poly)| { + self.count += 1; + poly.borrow().mul_extension(base_power) + }) + .sum() + } + + pub fn shift(&mut self, x: F) -> F { + let tmp = self.base.exp_u64(self.count) * x; + self.count = 0; + tmp + } + + pub fn shift_poly(&mut self, p: &mut PolynomialCoeffs) { + *p *= self.base.exp_u64(self.count); + self.count = 0; + } + + pub fn reset(&mut self) { + self.count = 0; + } +} + +#[derive(Debug, Clone)] +pub struct ReducingFactorTarget { + base: ExtensionTarget, + count: u64, +} + +impl ReducingFactorTarget { + pub const fn new(base: ExtensionTarget) -> Self { + Self { base, count: 0 } + } + + /// Reduces a vector of `Target`s using `ReducingGate`s. + pub fn reduce_base( + &mut self, + terms: &[Target], + builder: &mut CircuitBuilder, + ) -> ExtensionTarget + where + F: RichField + Extendable, + { + let l = terms.len(); + + // For small reductions, use an arithmetic gate. + if l <= ArithmeticExtensionGate::::new_from_config(&builder.config).num_ops + 1 { + let terms_ext = terms + .iter() + .map(|&t| builder.convert_to_ext(t)) + .collect::>(); + return self.reduce_arithmetic(&terms_ext, builder); + } + + let max_coeffs_len = ReducingGate::::max_coeffs_len( + builder.config.num_wires, + builder.config.num_routed_wires, + ); + self.count += l as u64; + let zero = builder.zero(); + let zero_ext = builder.zero_extension(); + let mut acc = zero_ext; + let mut reversed_terms = terms.to_vec(); + while reversed_terms.len() % max_coeffs_len != 0 { + reversed_terms.push(zero); + } + reversed_terms.reverse(); + for chunk in reversed_terms.chunks_exact(max_coeffs_len) { + let gate = ReducingGate::new(max_coeffs_len); + let row = builder.add_gate(gate.clone(), vec![]); + + builder.connect_extension( + self.base, + ExtensionTarget::from_range(row, ReducingGate::::wires_alpha()), + ); + builder.connect_extension( + acc, + ExtensionTarget::from_range(row, ReducingGate::::wires_old_acc()), + ); + for (&t, c) in chunk.iter().zip(gate.wires_coeffs()) { + builder.connect(t, Target::wire(row, c)); + } + + acc = ExtensionTarget::from_range(row, ReducingGate::::wires_output()); + } + + acc + } + + /// Reduces a vector of `ExtensionTarget`s using `ReducingExtensionGate`s. + pub fn reduce( + &mut self, + terms: &[ExtensionTarget], // Could probably work with a `DoubleEndedIterator` too. + builder: &mut CircuitBuilder, + ) -> ExtensionTarget + where + F: RichField + Extendable, + { + let l = terms.len(); + + // For small reductions, use an arithmetic gate. + if l <= ArithmeticExtensionGate::::new_from_config(&builder.config).num_ops + 1 { + return self.reduce_arithmetic(terms, builder); + } + + let max_coeffs_len = ReducingExtensionGate::::max_coeffs_len( + builder.config.num_wires, + builder.config.num_routed_wires, + ); + self.count += l as u64; + let zero_ext = builder.zero_extension(); + let mut acc = zero_ext; + let mut reversed_terms = terms.to_vec(); + while reversed_terms.len() % max_coeffs_len != 0 { + reversed_terms.push(zero_ext); + } + reversed_terms.reverse(); + for chunk in reversed_terms.chunks_exact(max_coeffs_len) { + let gate = ReducingExtensionGate::new(max_coeffs_len); + let row = builder.add_gate(gate.clone(), vec![]); + + builder.connect_extension( + self.base, + ExtensionTarget::from_range(row, ReducingExtensionGate::::wires_alpha()), + ); + builder.connect_extension( + acc, + ExtensionTarget::from_range(row, ReducingExtensionGate::::wires_old_acc()), + ); + for (i, &t) in chunk.iter().enumerate() { + builder.connect_extension( + t, + ExtensionTarget::from_range(row, ReducingExtensionGate::::wires_coeff(i)), + ); + } + + acc = ExtensionTarget::from_range(row, ReducingExtensionGate::::wires_output()); + } + + acc + } + + /// Reduces a vector of `ExtensionTarget`s using `ArithmeticGate`s. + fn reduce_arithmetic( + &mut self, + terms: &[ExtensionTarget], + builder: &mut CircuitBuilder, + ) -> ExtensionTarget + where + F: RichField + Extendable, + { + self.count += terms.len() as u64; + terms + .iter() + .rev() + .fold(builder.zero_extension(), |acc, &et| { + builder.mul_add_extension(self.base, acc, et) + }) + } + + pub fn shift( + &mut self, + x: ExtensionTarget, + builder: &mut CircuitBuilder, + ) -> ExtensionTarget + where + F: RichField + Extendable, + { + let zero_ext = builder.zero_extension(); + let exp = if x == zero_ext { + // The result will get zeroed out, so don't actually compute the exponentiation. + zero_ext + } else { + builder.exp_u64_extension(self.base, self.count) + }; + + self.count = 0; + builder.mul_extension(exp, x) + } + + pub fn reset(&mut self) { + self.count = 0; + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + + use super::*; + use crate::field::types::Sample; + use crate::iop::witness::{PartialWitness, WitnessWrite}; + use crate::plonk::circuit_data::CircuitConfig; + use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + use crate::plonk::verifier::verify; + + fn test_reduce_gadget_base(n: usize) -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type FF = >::FE; + + let config = CircuitConfig::standard_recursion_config(); + + let mut pw = PartialWitness::new(); + let mut builder = CircuitBuilder::::new(config); + + let alpha = FF::rand(); + let vs = F::rand_vec(n); + + let manual_reduce = ReducingFactor::new(alpha).reduce(vs.iter().map(|&v| FF::from(v))); + let manual_reduce = builder.constant_extension(manual_reduce); + + let mut alpha_t = ReducingFactorTarget::new(builder.constant_extension(alpha)); + let vs_t = builder.add_virtual_targets(vs.len()); + for (&v, &v_t) in vs.iter().zip(&vs_t) { + pw.set_target(v_t, v); + } + let circuit_reduce = alpha_t.reduce_base(&vs_t, &mut builder); + + builder.connect_extension(manual_reduce, circuit_reduce); + + let data = builder.build::(); + let proof = data.prove(pw)?; + + verify(proof, &data.verifier_only, &data.common) + } + + fn test_reduce_gadget(n: usize) -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type FF = >::FE; + + let config = CircuitConfig::standard_recursion_config(); + + let mut pw = PartialWitness::new(); + let mut builder = CircuitBuilder::::new(config); + + let alpha = FF::rand(); + let vs = (0..n).map(FF::from_canonical_usize).collect::>(); + + let manual_reduce = ReducingFactor::new(alpha).reduce(vs.iter()); + let manual_reduce = builder.constant_extension(manual_reduce); + + let mut alpha_t = ReducingFactorTarget::new(builder.constant_extension(alpha)); + let vs_t = builder.add_virtual_extension_targets(vs.len()); + pw.set_extension_targets(&vs_t, &vs); + let circuit_reduce = alpha_t.reduce(&vs_t, &mut builder); + + builder.connect_extension(manual_reduce, circuit_reduce); + + let data = builder.build::(); + let proof = data.prove(pw)?; + + verify(proof, &data.verifier_only, &data.common) + } + + #[test] + fn test_reduce_gadget_even() -> Result<()> { + test_reduce_gadget(10) + } + + #[test] + fn test_reduce_gadget_odd() -> Result<()> { + test_reduce_gadget(11) + } + + #[test] + fn test_reduce_gadget_base_100() -> Result<()> { + test_reduce_gadget_base(100) + } + + #[test] + fn test_reduce_gadget_100() -> Result<()> { + test_reduce_gadget(100) + } +} diff --git a/plonky2/src/util/serialization/gate_serialization.rs b/plonky2/src/util/serialization/gate_serialization.rs new file mode 100644 index 000000000..8b10da07b --- /dev/null +++ b/plonky2/src/util/serialization/gate_serialization.rs @@ -0,0 +1,139 @@ +//! A module to help with GateRef serialization + +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; +#[cfg(feature = "std")] +use std::vec::Vec; // For macros below + +use plonky2_field::extension::Extendable; + +use crate::gates::gate::GateRef; +use crate::hash::hash_types::RichField; +use crate::plonk::circuit_data::CommonCircuitData; +use crate::util::serialization::{Buffer, IoResult}; + +pub trait GateSerializer, const D: usize> { + fn read_gate( + &self, + buf: &mut Buffer, + common_data: &CommonCircuitData, + ) -> IoResult>; + fn write_gate( + &self, + buf: &mut Vec, + gate: &GateRef, + common_data: &CommonCircuitData, + ) -> IoResult<()>; +} + +#[macro_export] +macro_rules! read_gate_impl { + ($buf:expr, $tag:expr, $common:expr, $($gate_types:ty),+) => {{ + let tag = $tag; + let buf = $buf; + let mut i = 0..; + $(if tag == i.next().unwrap() { + let gate = <$gate_types as $crate::gates::gate::Gate>::deserialize(buf, $common)?; + Ok($crate::gates::gate::GateRef::::new(gate)) + } else)* + { + Err($crate::util::serialization::IoError) + } + }} +} + +#[macro_export] +macro_rules! get_gate_tag_impl { + ($gate:expr, $($gate_types:ty),+) => {{ + let gate_any = $gate.0.as_any(); + let mut i = 0..; + $(if let (tag, true) = (i.next().unwrap(), gate_any.is::<$gate_types>()) { + Ok(tag) + } else)* + { + log::log!( + log::Level::Error, + "attempted to serialize gate with id `{}` which is unsupported by this gate serializer", + $gate.0.id() + ); + Err($crate::util::serialization::IoError) + } + }}; +} + +#[macro_export] +/// Macro implementing the [`GateSerializer`] trait. +/// To serialize a list of gates used for a circuit, +/// this macro should be called with a struct on which to implement +/// this as first argument, followed by all the targeted gates. +macro_rules! impl_gate_serializer { + ($target:ty, $($gate_types:ty),+) => { + fn read_gate( + &self, + buf: &mut $crate::util::serialization::Buffer, + common: &$crate::plonk::circuit_data::CommonCircuitData, + ) -> $crate::util::serialization::IoResult<$crate::gates::gate::GateRef> { + let tag = $crate::util::serialization::Read::read_u32(buf)?; + read_gate_impl!(buf, tag, common, $($gate_types),+) + } + + fn write_gate( + &self, + buf: &mut $crate::util::serialization::gate_serialization::Vec, + gate: &$crate::gates::gate::GateRef, + common: &$crate::plonk::circuit_data::CommonCircuitData, + ) -> $crate::util::serialization::IoResult<()> { + let tag = get_gate_tag_impl!(gate, $($gate_types),+)?; + + $crate::util::serialization::Write::write_u32(buf, tag)?; + gate.0.serialize(buf, common)?; + Ok(()) + } + }; +} + +pub mod default { + use plonky2_field::extension::Extendable; + + use crate::gates::arithmetic_base::ArithmeticGate; + use crate::gates::arithmetic_extension::ArithmeticExtensionGate; + use crate::gates::base_sum::BaseSumGate; + use crate::gates::constant::ConstantGate; + use crate::gates::coset_interpolation::CosetInterpolationGate; + use crate::gates::exponentiation::ExponentiationGate; + use crate::gates::lookup::LookupGate; + use crate::gates::lookup_table::LookupTableGate; + use crate::gates::multiplication_extension::MulExtensionGate; + use crate::gates::noop::NoopGate; + use crate::gates::poseidon::PoseidonGate; + use crate::gates::poseidon_mds::PoseidonMdsGate; + use crate::gates::public_input::PublicInputGate; + use crate::gates::random_access::RandomAccessGate; + use crate::gates::reducing::ReducingGate; + use crate::gates::reducing_extension::ReducingExtensionGate; + use crate::hash::hash_types::RichField; + use crate::util::serialization::GateSerializer; + + pub struct DefaultGateSerializer; + impl, const D: usize> GateSerializer for DefaultGateSerializer { + impl_gate_serializer! { + DefaultGateSerializer, + ArithmeticGate, + ArithmeticExtensionGate, + BaseSumGate<2>, + ConstantGate, + CosetInterpolationGate, + ExponentiationGate, + LookupGate, + LookupTableGate, + MulExtensionGate, + NoopGate, + PoseidonMdsGate, + PoseidonGate, + PublicInputGate, + RandomAccessGate, + ReducingExtensionGate, + ReducingGate + } + } +} diff --git a/plonky2/src/util/serialization/generator_serialization.rs b/plonky2/src/util/serialization/generator_serialization.rs new file mode 100644 index 000000000..6ede00200 --- /dev/null +++ b/plonky2/src/util/serialization/generator_serialization.rs @@ -0,0 +1,168 @@ +//! A module to help with WitnessGeneratorRef serialization + +#[cfg(not(feature = "std"))] +pub use alloc::vec::Vec; +#[cfg(feature = "std")] +pub use std::vec::Vec; // For macros below + +use plonky2_field::extension::Extendable; + +use crate::hash::hash_types::RichField; +use crate::iop::generator::WitnessGeneratorRef; +use crate::plonk::circuit_data::CommonCircuitData; +use crate::util::serialization::{Buffer, IoResult}; + +pub trait WitnessGeneratorSerializer, const D: usize> { + fn read_generator( + &self, + buf: &mut Buffer, + common_data: &CommonCircuitData, + ) -> IoResult>; + + fn write_generator( + &self, + buf: &mut Vec, + generator: &WitnessGeneratorRef, + common_data: &CommonCircuitData, + ) -> IoResult<()>; +} + +#[macro_export] +macro_rules! read_generator_impl { + ($buf:expr, $tag:expr, $common:expr, $($generator_types:ty),+) => {{ + let tag = $tag; + let buf = $buf; + let mut i = 0..; + + $(if tag == i.next().unwrap() { + let generator = + <$generator_types as $crate::iop::generator::SimpleGenerator>::deserialize(buf, $common)?; + Ok($crate::iop::generator::WitnessGeneratorRef::::new( + $crate::iop::generator::SimpleGenerator::::adapter(generator), + )) + } else)* + { + Err($crate::util::serialization::IoError) + } + }}; +} + +#[macro_export] +macro_rules! get_generator_tag_impl { + ($generator:expr, $($generator_types:ty),+) => {{ + let mut i = 0..; + $(if let (tag, true) = (i.next().unwrap(), $generator.0.id() == $crate::iop::generator::SimpleGenerator::::id(&<$generator_types>::default())) { + Ok(tag) + } else)* + { + log::log!( + log::Level::Error, + "attempted to serialize generator with id {} which is unsupported by this generator serializer", + $generator.0.id() + ); + Err($crate::util::serialization::IoError) + } + }}; +} + +#[macro_export] +/// Macro implementing the [`WitnessGeneratorSerializer`] trait. +/// To serialize a list of generators used for a circuit, +/// this macro should be called with a struct on which to implement +/// this as first argument, followed by all the targeted generators. +macro_rules! impl_generator_serializer { + ($target:ty, $($generator_types:ty),+) => { + fn read_generator( + &self, + buf: &mut $crate::util::serialization::Buffer, + common: &$crate::plonk::circuit_data::CommonCircuitData, + ) -> $crate::util::serialization::IoResult<$crate::iop::generator::WitnessGeneratorRef> { + let tag = $crate::util::serialization::Read::read_u32(buf)?; + read_generator_impl!(buf, tag, common, $($generator_types),+) + } + + fn write_generator( + &self, + buf: &mut $crate::util::serialization::generator_serialization::Vec, + generator: &$crate::iop::generator::WitnessGeneratorRef, + common: &$crate::plonk::circuit_data::CommonCircuitData, + ) -> $crate::util::serialization::IoResult<()> { + let tag = get_generator_tag_impl!(generator, $($generator_types),+)?; + + $crate::util::serialization::Write::write_u32(buf, tag)?; + generator.0.serialize(buf, common)?; + Ok(()) + } + }; +} + +pub mod default { + use core::marker::PhantomData; + + use plonky2_field::extension::Extendable; + + use crate::gadgets::arithmetic::EqualityGenerator; + use crate::gadgets::arithmetic_extension::QuotientGeneratorExtension; + use crate::gadgets::range_check::LowHighGenerator; + use crate::gadgets::split_base::BaseSumGenerator; + use crate::gadgets::split_join::{SplitGenerator, WireSplitGenerator}; + use crate::gates::arithmetic_base::ArithmeticBaseGenerator; + use crate::gates::arithmetic_extension::ArithmeticExtensionGenerator; + use crate::gates::base_sum::BaseSplitGenerator; + use crate::gates::coset_interpolation::InterpolationGenerator; + use crate::gates::exponentiation::ExponentiationGenerator; + use crate::gates::lookup::LookupGenerator; + use crate::gates::lookup_table::LookupTableGenerator; + use crate::gates::multiplication_extension::MulExtensionGenerator; + use crate::gates::poseidon::PoseidonGenerator; + use crate::gates::poseidon_mds::PoseidonMdsGenerator; + use crate::gates::random_access::RandomAccessGenerator; + use crate::gates::reducing::ReducingGenerator; + use crate::gates::reducing_extension::ReducingGenerator as ReducingExtensionGenerator; + use crate::hash::hash_types::RichField; + use crate::iop::generator::{ + ConstantGenerator, CopyGenerator, NonzeroTestGenerator, RandomValueGenerator, + }; + use crate::plonk::config::{AlgebraicHasher, GenericConfig}; + use crate::recursion::dummy_circuit::DummyProofGenerator; + use crate::util::serialization::WitnessGeneratorSerializer; + + pub struct DefaultGeneratorSerializer, const D: usize> { + pub _phantom: PhantomData, + } + + impl WitnessGeneratorSerializer for DefaultGeneratorSerializer + where + F: RichField + Extendable, + C: GenericConfig + 'static, + C::Hasher: AlgebraicHasher, + { + impl_generator_serializer! { + DefaultGeneratorSerializer, + ArithmeticBaseGenerator, + ArithmeticExtensionGenerator, + BaseSplitGenerator<2>, + BaseSumGenerator<2>, + ConstantGenerator, + CopyGenerator, + DummyProofGenerator, + EqualityGenerator, + ExponentiationGenerator, + InterpolationGenerator, + LookupGenerator, + LookupTableGenerator, + LowHighGenerator, + MulExtensionGenerator, + NonzeroTestGenerator, + PoseidonGenerator, + PoseidonMdsGenerator, + QuotientGeneratorExtension, + RandomAccessGenerator, + RandomValueGenerator, + ReducingGenerator, + ReducingExtensionGenerator, + SplitGenerator, + WireSplitGenerator + } + } +} diff --git a/plonky2/src/util/serialization/mod.rs b/plonky2/src/util/serialization/mod.rs new file mode 100644 index 000000000..c76642a32 --- /dev/null +++ b/plonky2/src/util/serialization/mod.rs @@ -0,0 +1,2247 @@ +#[macro_use] +pub mod generator_serialization; + +#[macro_use] +pub mod gate_serialization; + +#[cfg(not(feature = "std"))] +use alloc::{collections::BTreeMap, sync::Arc, vec, vec::Vec}; +use core::convert::Infallible; +use core::fmt::{Debug, Display, Formatter}; +use core::mem::size_of; +use core::ops::Range; +#[cfg(feature = "std")] +use std::{collections::BTreeMap, sync::Arc}; + +pub use gate_serialization::default::DefaultGateSerializer; +pub use gate_serialization::GateSerializer; +pub use generator_serialization::default::DefaultGeneratorSerializer; +pub use generator_serialization::WitnessGeneratorSerializer; +use hashbrown::HashMap; + +use crate::field::extension::{Extendable, FieldExtension}; +use crate::field::polynomial::PolynomialCoeffs; +use crate::field::types::{Field64, PrimeField64}; +use crate::fri::oracle::PolynomialBatch; +use crate::fri::proof::{ + CompressedFriProof, CompressedFriQueryRounds, FriInitialTreeProof, FriInitialTreeProofTarget, + FriProof, FriProofTarget, FriQueryRound, FriQueryRoundTarget, FriQueryStep, FriQueryStepTarget, +}; +use crate::fri::reduction_strategies::FriReductionStrategy; +use crate::fri::{FriConfig, FriParams}; +use crate::gadgets::polynomial::PolynomialCoeffsExtTarget; +use crate::gates::gate::GateRef; +use crate::gates::lookup::Lookup; +use crate::gates::selectors::SelectorsInfo; +use crate::hash::hash_types::{HashOutTarget, MerkleCapTarget, RichField}; +use crate::hash::merkle_proofs::{MerkleProof, MerkleProofTarget}; +use crate::hash::merkle_tree::{MerkleCap, MerkleTree}; +use crate::iop::ext_target::ExtensionTarget; +use crate::iop::generator::WitnessGeneratorRef; +use crate::iop::target::{BoolTarget, Target}; +use crate::iop::wire::Wire; +use crate::plonk::circuit_builder::LookupWire; +use crate::plonk::circuit_data::{ + CircuitConfig, CircuitData, CommonCircuitData, ProverCircuitData, ProverOnlyCircuitData, + VerifierCircuitData, VerifierCircuitTarget, VerifierOnlyCircuitData, +}; +use crate::plonk::config::{GenericConfig, GenericHashOut, Hasher}; +use crate::plonk::plonk_common::salt_size; +use crate::plonk::proof::{ + CompressedProof, CompressedProofWithPublicInputs, OpeningSet, OpeningSetTarget, Proof, + ProofTarget, ProofWithPublicInputs, ProofWithPublicInputsTarget, +}; + +/// A no_std compatible variant of `std::io::Error` +#[derive(Debug)] +pub struct IoError; + +impl Display for IoError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + Debug::fmt(self, f) + } +} + +/// A no_std compatible variant of `std::io::Result` +pub type IoResult = Result; + +/// A `Read` which is able to report how many bytes are remaining. +pub trait Remaining: Read { + /// Returns the number of bytes remaining in the buffer. + fn remaining(&self) -> usize; + + /// Returns whether zero bytes are remaining. + fn is_empty(&self) -> bool { + self.remaining() == 0 + } +} + +/// Similar to `std::io::Read`, but works with no_std. +pub trait Read { + /// Reads exactly the length of `bytes` from `self` and writes it to + /// `bytes`. + fn read_exact(&mut self, bytes: &mut [u8]) -> IoResult<()>; + + /// Reads a `bool` value from `self`. + #[inline] + fn read_bool(&mut self) -> IoResult { + let i = self.read_u8()?; + match i { + 0 => Ok(false), + 1 => Ok(true), + _ => Err(IoError), + } + } + + /// Reads a `BoolTarget` value from `self`. + #[inline] + fn read_target_bool(&mut self) -> IoResult { + Ok(BoolTarget::new_unsafe(self.read_target()?)) + } + + /// Reads a vector of `BoolTarget` from `self`. + #[inline] + fn read_target_bool_vec(&mut self) -> IoResult> { + let length = self.read_usize()?; + (0..length) + .map(|_| self.read_target_bool()) + .collect::, _>>() + } + + /// Reads a `u8` value from `self`. + #[inline] + fn read_u8(&mut self) -> IoResult { + let mut buf = [0; size_of::()]; + self.read_exact(&mut buf)?; + Ok(buf[0]) + } + + /// Reads a `u16` value from `self`. + #[inline] + fn read_u16(&mut self) -> IoResult { + let mut buf = [0; size_of::()]; + self.read_exact(&mut buf)?; + Ok(u16::from_le_bytes(buf)) + } + + /// Reads a `u32` value from `self`. + #[inline] + fn read_u32(&mut self) -> IoResult { + let mut buf = [0; size_of::()]; + self.read_exact(&mut buf)?; + Ok(u32::from_le_bytes(buf)) + } + + /// Reads a `usize` value from `self`. + #[inline] + fn read_usize(&mut self) -> IoResult { + let mut buf = [0; core::mem::size_of::()]; + self.read_exact(&mut buf)?; + Ok(u64::from_le_bytes(buf) as usize) + } + + /// Reads a vector of `usize` value from `self`. + #[inline] + fn read_usize_vec(&mut self) -> IoResult> { + let len = self.read_usize()?; + let mut res = Vec::with_capacity(len); + for _ in 0..len { + res.push(self.read_usize()?); + } + + Ok(res) + } + + /// Reads a element from the field `F` with size less than `2^64` from + /// `self.` + #[inline] + fn read_field(&mut self) -> IoResult + where + F: Field64, + { + let mut buf = [0; size_of::()]; + self.read_exact(&mut buf)?; + Ok(F::from_canonical_u64(u64::from_le_bytes(buf))) + } + + /// Reads a vector of elements from the field `F` from `self`. + #[inline] + fn read_field_vec(&mut self, length: usize) -> IoResult> + where + F: Field64, + { + (0..length) + .map(|_| self.read_field()) + .collect::, _>>() + } + + /// Reads an element from the field extension of `F` from `self.` + #[inline] + fn read_field_ext(&mut self) -> IoResult + where + F: Field64 + Extendable, + { + let mut arr = [F::ZERO; D]; + for a in arr.iter_mut() { + *a = self.read_field()?; + } + Ok(>::from_basefield_array( + arr, + )) + } + + /// Reads a vector of elements from the field extension of `F` from `self`. + #[inline] + fn read_field_ext_vec( + &mut self, + length: usize, + ) -> IoResult> + where + F: RichField + Extendable, + { + (0..length).map(|_| self.read_field_ext::()).collect() + } + + /// Reads a Target from `self.` + #[inline] + fn read_target(&mut self) -> IoResult { + let is_wire = self.read_bool()?; + if is_wire { + let row = self.read_usize()?; + let column = self.read_usize()?; + Ok(Target::wire(row, column)) + } else { + let index = self.read_usize()?; + Ok(Target::VirtualTarget { index }) + } + } + + /// Reads an ExtensionTarget from `self`. + #[inline] + fn read_target_ext(&mut self) -> IoResult> { + let mut res = [Target::wire(0, 0); D]; + for r in res.iter_mut() { + *r = self.read_target()?; + } + + Ok(ExtensionTarget(res)) + } + + /// Reads an array of Target from `self`. + #[inline] + fn read_target_array(&mut self) -> IoResult<[Target; N]> { + (0..N) + .map(|_| self.read_target()) + .collect::, _>>() + .map(|v| v.try_into().unwrap()) + } + + /// Reads a vector of Target from `self`. + #[inline] + fn read_target_vec(&mut self) -> IoResult> { + let length = self.read_usize()?; + (0..length) + .map(|_| self.read_target()) + .collect::, _>>() + } + + /// Reads a vector of ExtensionTarget from `self`. + #[inline] + fn read_target_ext_vec(&mut self) -> IoResult>> { + let length = self.read_usize()?; + (0..length) + .map(|_| self.read_target_ext::()) + .collect::, _>>() + } + + /// Reads a hash value from `self`. + #[inline] + fn read_hash(&mut self) -> IoResult + where + F: RichField, + H: Hasher, + { + let mut buf = vec![0; H::HASH_SIZE]; + self.read_exact(&mut buf)?; + Ok(H::Hash::from_bytes(&buf)) + } + + /// Reads a HashOutTarget value from `self`. + #[inline] + fn read_target_hash(&mut self) -> IoResult { + let mut elements = [Target::wire(0, 0); 4]; + for e in elements.iter_mut() { + *e = self.read_target()?; + } + + Ok(HashOutTarget { elements }) + } + + /// Reads a vector of Hash from `self`. + #[inline] + fn read_hash_vec(&mut self, length: usize) -> IoResult> + where + F: RichField, + H: Hasher, + { + (0..length) + .map(|_| self.read_hash::()) + .collect::, _>>() + } + + /// Reads a value of type [`MerkleCap`] from `self` with the given + /// `cap_height`. + #[inline] + fn read_merkle_cap(&mut self, cap_height: usize) -> IoResult> + where + F: RichField, + H: Hasher, + { + let cap_length = 1 << cap_height; + Ok(MerkleCap( + (0..cap_length) + .map(|_| self.read_hash::()) + .collect::, _>>()?, + )) + } + + /// Reads a value of type [`MerkleCapTarget`] from `self`. + #[inline] + fn read_target_merkle_cap(&mut self) -> IoResult { + let length = self.read_usize()?; + Ok(MerkleCapTarget( + (0..length) + .map(|_| self.read_target_hash()) + .collect::, _>>()?, + )) + } + + /// Reads a value of type [`MerkleTree`] from `self`. + #[inline] + fn read_merkle_tree(&mut self) -> IoResult> + where + F: RichField, + H: Hasher, + { + let leaves_len = self.read_usize()?; + let mut leaves = Vec::with_capacity(leaves_len); + for _ in 0..leaves_len { + let leaf_len = self.read_usize()?; + leaves.push(self.read_field_vec(leaf_len)?); + } + + let digests_len = self.read_usize()?; + let digests = self.read_hash_vec::(digests_len)?; + let cap_height = self.read_usize()?; + let cap = self.read_merkle_cap::(cap_height)?; + Ok(MerkleTree { + leaves, + digests, + cap, + }) + } + + /// Reads a value of type [`OpeningSet`] from `self` with the given + /// `common_data`. + #[inline] + fn read_opening_set( + &mut self, + common_data: &CommonCircuitData, + ) -> IoResult> + where + F: RichField + Extendable, + C: GenericConfig, + { + let config = &common_data.config; + let constants = self.read_field_ext_vec::(common_data.num_constants)?; + let plonk_sigmas = self.read_field_ext_vec::(config.num_routed_wires)?; + let wires = self.read_field_ext_vec::(config.num_wires)?; + let plonk_zs = self.read_field_ext_vec::(config.num_challenges)?; + let plonk_zs_next = self.read_field_ext_vec::(config.num_challenges)?; + let lookup_zs = self.read_field_ext_vec::(common_data.num_all_lookup_polys())?; + let lookup_zs_next = self.read_field_ext_vec::(common_data.num_all_lookup_polys())?; + let partial_products = self + .read_field_ext_vec::(common_data.num_partial_products * config.num_challenges)?; + let quotient_polys = self.read_field_ext_vec::( + common_data.quotient_degree_factor * config.num_challenges, + )?; + Ok(OpeningSet { + constants, + plonk_sigmas, + wires, + plonk_zs, + plonk_zs_next, + partial_products, + quotient_polys, + lookup_zs, + lookup_zs_next, + }) + } + + /// Reads a value of type [`OpeningSetTarget`] from `self`. + #[inline] + fn read_target_opening_set(&mut self) -> IoResult> { + let constants = self.read_target_ext_vec::()?; + let plonk_sigmas = self.read_target_ext_vec::()?; + let wires = self.read_target_ext_vec::()?; + let plonk_zs = self.read_target_ext_vec::()?; + let plonk_zs_next = self.read_target_ext_vec::()?; + let lookup_zs = self.read_target_ext_vec::()?; + let next_lookup_zs = self.read_target_ext_vec::()?; + let partial_products = self.read_target_ext_vec::()?; + let quotient_polys = self.read_target_ext_vec::()?; + + Ok(OpeningSetTarget { + constants, + plonk_sigmas, + wires, + plonk_zs, + plonk_zs_next, + lookup_zs, + next_lookup_zs, + partial_products, + quotient_polys, + }) + } + + /// Reads a value of type [`MerkleProof`] from `self`. + #[inline] + fn read_merkle_proof(&mut self) -> IoResult> + where + F: RichField, + H: Hasher, + { + let length = self.read_u8()?; + Ok(MerkleProof { + siblings: (0..length) + .map(|_| self.read_hash::()) + .collect::>()?, + }) + } + + /// Reads a value of type [`MerkleProofTarget`] from `self`. + #[inline] + fn read_target_merkle_proof(&mut self) -> IoResult { + let length = self.read_u8()?; + Ok(MerkleProofTarget { + siblings: (0..length) + .map(|_| self.read_target_hash()) + .collect::>()?, + }) + } + + /// Reads a value of type [`FriInitialTreeProof`] from `self` with the given + /// `common_data`. + #[inline] + fn read_fri_initial_proof( + &mut self, + common_data: &CommonCircuitData, + ) -> IoResult> + where + F: RichField + Extendable, + C: GenericConfig, + { + let config = &common_data.config; + let salt = salt_size(common_data.fri_params.hiding); + let mut evals_proofs = Vec::with_capacity(4); + + let constants_sigmas_v = + self.read_field_vec(common_data.num_constants + config.num_routed_wires)?; + let constants_sigmas_p = self.read_merkle_proof()?; + evals_proofs.push((constants_sigmas_v, constants_sigmas_p)); + + let wires_v = self.read_field_vec(config.num_wires + salt)?; + let wires_p = self.read_merkle_proof()?; + evals_proofs.push((wires_v, wires_p)); + + let zs_partial_v = self.read_field_vec( + config.num_challenges + * (1 + common_data.num_partial_products + common_data.num_lookup_polys) + + salt, + )?; + let zs_partial_p = self.read_merkle_proof()?; + evals_proofs.push((zs_partial_v, zs_partial_p)); + + let quotient_v = + self.read_field_vec(config.num_challenges * common_data.quotient_degree_factor + salt)?; + let quotient_p = self.read_merkle_proof()?; + evals_proofs.push((quotient_v, quotient_p)); + + Ok(FriInitialTreeProof { evals_proofs }) + } + + /// Reads a value of type [`FriInitialTreeProofTarget`] from `self`. + #[inline] + fn read_target_fri_initial_proof(&mut self) -> IoResult { + let len = self.read_usize()?; + let mut evals_proofs = Vec::with_capacity(len); + + for _ in 0..len { + evals_proofs.push((self.read_target_vec()?, self.read_target_merkle_proof()?)); + } + + Ok(FriInitialTreeProofTarget { evals_proofs }) + } + + /// Reads a value of type [`FriQueryStep`] from `self` with the given + /// `arity` and `compressed` flag. + #[inline] + fn read_fri_query_step( + &mut self, + arity: usize, + compressed: bool, + ) -> IoResult> + where + F: RichField + Extendable, + C: GenericConfig, + { + let evals = self.read_field_ext_vec::(arity - usize::from(compressed))?; + let merkle_proof = self.read_merkle_proof()?; + Ok(FriQueryStep { + evals, + merkle_proof, + }) + } + + /// Reads a value of type [`FriQueryStepTarget`] from `self`. + #[inline] + fn read_target_fri_query_step(&mut self) -> IoResult> { + let evals = self.read_target_ext_vec::()?; + let merkle_proof = self.read_target_merkle_proof()?; + Ok(FriQueryStepTarget { + evals, + merkle_proof, + }) + } + + /// Reads a vector of [`FriQueryRound`]s from `self` with `common_data`. + #[inline] + #[allow(clippy::type_complexity)] + fn read_fri_query_rounds( + &mut self, + common_data: &CommonCircuitData, + ) -> IoResult>> + where + F: RichField + Extendable, + C: GenericConfig, + { + let config = &common_data.config; + let mut fqrs = Vec::with_capacity(config.fri_config.num_query_rounds); + for _ in 0..config.fri_config.num_query_rounds { + let initial_trees_proof = self.read_fri_initial_proof::(common_data)?; + let steps = common_data + .fri_params + .reduction_arity_bits + .iter() + .map(|&ar| self.read_fri_query_step::(1 << ar, false)) + .collect::>()?; + fqrs.push(FriQueryRound { + initial_trees_proof, + steps, + }) + } + Ok(fqrs) + } + + /// Reads a vector of [`FriQueryRoundTarget`]s from `self`. + #[inline] + fn read_target_fri_query_rounds( + &mut self, + ) -> IoResult>> { + let num_query_rounds = self.read_usize()?; + let mut fqrs = Vec::with_capacity(num_query_rounds); + for _ in 0..num_query_rounds { + let initial_trees_proof = self.read_target_fri_initial_proof()?; + let num_steps = self.read_usize()?; + let steps = (0..num_steps) + .map(|_| self.read_target_fri_query_step::()) + .collect::, _>>()?; + fqrs.push(FriQueryRoundTarget { + initial_trees_proof, + steps, + }) + } + Ok(fqrs) + } + + /// Reads a value of type [`FriProof`] from `self` with `common_data`. + #[inline] + fn read_fri_proof( + &mut self, + common_data: &CommonCircuitData, + ) -> IoResult> + where + F: RichField + Extendable, + C: GenericConfig, + { + let config = &common_data.config; + let commit_phase_merkle_caps = (0..common_data.fri_params.reduction_arity_bits.len()) + .map(|_| self.read_merkle_cap(config.fri_config.cap_height)) + .collect::, _>>()?; + let query_round_proofs = self.read_fri_query_rounds::(common_data)?; + let final_poly = PolynomialCoeffs::new( + self.read_field_ext_vec::(common_data.fri_params.final_poly_len())?, + ); + let pow_witness = self.read_field()?; + Ok(FriProof { + commit_phase_merkle_caps, + query_round_proofs, + final_poly, + pow_witness, + }) + } + + /// Reads a value of type [`FriProofTarget`] from `self`. + #[inline] + fn read_target_fri_proof(&mut self) -> IoResult> { + let length = self.read_usize()?; + let commit_phase_merkle_caps = (0..length) + .map(|_| self.read_target_merkle_cap()) + .collect::, _>>()?; + let query_round_proofs = self.read_target_fri_query_rounds::()?; + let final_poly = PolynomialCoeffsExtTarget(self.read_target_ext_vec::()?); + let pow_witness = self.read_target()?; + + Ok(FriProofTarget { + commit_phase_merkle_caps, + query_round_proofs, + final_poly, + pow_witness, + }) + } + + fn read_fri_reduction_strategy(&mut self) -> IoResult { + let variant = self.read_u8()?; + match variant { + 0 => { + let arities = self.read_usize_vec()?; + Ok(FriReductionStrategy::Fixed(arities)) + } + 1 => { + let arity_bits = self.read_usize()?; + let final_poly_bits = self.read_usize()?; + + Ok(FriReductionStrategy::ConstantArityBits( + arity_bits, + final_poly_bits, + )) + } + 2 => { + let is_some = self.read_u8()?; + match is_some { + 0 => Ok(FriReductionStrategy::MinSize(None)), + 1 => { + let max = self.read_usize()?; + Ok(FriReductionStrategy::MinSize(Some(max))) + } + _ => Err(IoError), + } + } + _ => Err(IoError), + } + } + + fn read_fri_config(&mut self) -> IoResult { + let rate_bits = self.read_usize()?; + let cap_height = self.read_usize()?; + let num_query_rounds = self.read_usize()?; + let proof_of_work_bits = self.read_u32()?; + let reduction_strategy = self.read_fri_reduction_strategy()?; + + Ok(FriConfig { + rate_bits, + cap_height, + num_query_rounds, + proof_of_work_bits, + reduction_strategy, + }) + } + + fn read_circuit_config(&mut self) -> IoResult { + let num_wires = self.read_usize()?; + let num_routed_wires = self.read_usize()?; + let num_constants = self.read_usize()?; + let security_bits = self.read_usize()?; + let num_challenges = self.read_usize()?; + let max_quotient_degree_factor = self.read_usize()?; + let use_base_arithmetic_gate = self.read_bool()?; + let zero_knowledge = self.read_bool()?; + let fri_config = self.read_fri_config()?; + + Ok(CircuitConfig { + num_wires, + num_routed_wires, + num_constants, + security_bits, + num_challenges, + max_quotient_degree_factor, + use_base_arithmetic_gate, + zero_knowledge, + fri_config, + }) + } + + fn read_fri_params(&mut self) -> IoResult { + let config = self.read_fri_config()?; + let reduction_arity_bits = self.read_usize_vec()?; + let degree_bits = self.read_usize()?; + let hiding = self.read_bool()?; + + Ok(FriParams { + config, + reduction_arity_bits, + degree_bits, + hiding, + }) + } + + fn read_gate, const D: usize>( + &mut self, + gate_serializer: &dyn GateSerializer, + common_data: &CommonCircuitData, + ) -> IoResult>; + + fn read_generator, const D: usize>( + &mut self, + generator_serializer: &dyn WitnessGeneratorSerializer, + common_data: &CommonCircuitData, + ) -> IoResult>; + + fn read_selectors_info(&mut self) -> IoResult { + let selector_indices = self.read_usize_vec()?; + let groups_len = self.read_usize()?; + let mut groups = Vec::with_capacity(groups_len); + for _ in 0..groups_len { + let start = self.read_usize()?; + let end = self.read_usize()?; + groups.push(Range { start, end }); + } + + Ok(SelectorsInfo { + selector_indices, + groups, + }) + } + + fn read_polynomial_batch< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, + >( + &mut self, + ) -> IoResult> { + let poly_len = self.read_usize()?; + let mut polynomials = Vec::with_capacity(poly_len); + for _ in 0..poly_len { + let plen = self.read_usize()?; + polynomials.push(PolynomialCoeffs::new(self.read_field_vec(plen)?)); + } + + let merkle_tree = self.read_merkle_tree()?; + let degree_log = self.read_usize()?; + let rate_bits = self.read_usize()?; + let blinding = self.read_bool()?; + + Ok(PolynomialBatch { + polynomials, + merkle_tree, + degree_log, + rate_bits, + blinding, + }) + } + + fn read_common_circuit_data, const D: usize>( + &mut self, + gate_serializer: &dyn GateSerializer, + ) -> IoResult> { + let config = self.read_circuit_config()?; + let fri_params = self.read_fri_params()?; + + let selectors_info = self.read_selectors_info()?; + let quotient_degree_factor = self.read_usize()?; + let num_gate_constraints = self.read_usize()?; + let num_constants = self.read_usize()?; + let num_public_inputs = self.read_usize()?; + + let k_is_len = self.read_usize()?; + let k_is = self.read_field_vec(k_is_len)?; + + let num_partial_products = self.read_usize()?; + + let num_lookup_polys = self.read_usize()?; + let num_lookup_selectors = self.read_usize()?; + let length = self.read_usize()?; + let mut luts = Vec::with_capacity(length); + + for _ in 0..length { + luts.push(Arc::new(self.read_lut()?)); + } + + let gates_len = self.read_usize()?; + let mut gates = Vec::with_capacity(gates_len); + + // We construct the common data without gates first, + // to pass it as argument when reading the gates. + let mut common_data = CommonCircuitData { + config, + fri_params, + gates: vec![], + selectors_info, + quotient_degree_factor, + num_gate_constraints, + num_constants, + num_public_inputs, + k_is, + num_partial_products, + num_lookup_polys, + num_lookup_selectors, + luts, + }; + + for _ in 0..gates_len { + let gate = self.read_gate::(gate_serializer, &common_data)?; + gates.push(gate); + } + + common_data.gates = gates; + + Ok(common_data) + } + + fn read_circuit_data< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, + >( + &mut self, + gate_serializer: &dyn GateSerializer, + generator_serializer: &dyn WitnessGeneratorSerializer, + ) -> IoResult> { + let common = self.read_common_circuit_data(gate_serializer)?; + let prover_only = self.read_prover_only_circuit_data(generator_serializer, &common)?; + let verifier_only = self.read_verifier_only_circuit_data()?; + Ok(CircuitData { + prover_only, + verifier_only, + common, + }) + } + + fn read_prover_only_circuit_data< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, + >( + &mut self, + generator_serializer: &dyn WitnessGeneratorSerializer, + common_data: &CommonCircuitData, + ) -> IoResult> { + let gen_len = self.read_usize()?; + let mut generators = Vec::with_capacity(gen_len); + for _ in 0..gen_len { + generators.push(self.read_generator(generator_serializer, common_data)?); + } + let map_len = self.read_usize()?; + let mut generator_indices_by_watches = BTreeMap::new(); + for _ in 0..map_len { + let k = self.read_usize()?; + generator_indices_by_watches.insert(k, self.read_usize_vec()?); + } + + let constants_sigmas_commitment = self.read_polynomial_batch()?; + let sigmas_len = self.read_usize()?; + let mut sigmas = Vec::with_capacity(sigmas_len); + for _ in 0..sigmas_len { + let sigma_len = self.read_usize()?; + sigmas.push(self.read_field_vec(sigma_len)?); + } + + let subgroup_len = self.read_usize()?; + let subgroup = self.read_field_vec(subgroup_len)?; + + let public_inputs = self.read_target_vec()?; + + let representative_map = self.read_usize_vec()?; + + let is_some = self.read_bool()?; + let fft_root_table = match is_some { + true => { + let table_len = self.read_usize()?; + let mut table = Vec::with_capacity(table_len); + for _ in 0..table_len { + let len = self.read_usize()?; + table.push(self.read_field_vec(len)?); + } + Some(table) + } + false => None, + }; + + let circuit_digest = self.read_hash::>::Hasher>()?; + + let length = self.read_usize()?; + let mut lookup_rows = Vec::with_capacity(length); + for _ in 0..length { + lookup_rows.push(LookupWire { + last_lu_gate: self.read_usize()?, + last_lut_gate: self.read_usize()?, + first_lut_gate: self.read_usize()?, + }); + } + + let length = self.read_usize()?; + let mut lut_to_lookups = Vec::with_capacity(length); + for _ in 0..length { + lut_to_lookups.push(self.read_target_lut()?); + } + + Ok(ProverOnlyCircuitData { + generators, + generator_indices_by_watches, + constants_sigmas_commitment, + sigmas, + subgroup, + public_inputs, + representative_map, + fft_root_table, + circuit_digest, + lookup_rows, + lut_to_lookups, + }) + } + + fn read_prover_circuit_data< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, + >( + &mut self, + gate_serializer: &dyn GateSerializer, + generator_serializer: &dyn WitnessGeneratorSerializer, + ) -> IoResult> { + let common = self.read_common_circuit_data(gate_serializer)?; + let prover_only = self.read_prover_only_circuit_data(generator_serializer, &common)?; + Ok(ProverCircuitData { + prover_only, + common, + }) + } + + fn read_verifier_only_circuit_data< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, + >( + &mut self, + ) -> IoResult> { + let height = self.read_usize()?; + let constants_sigmas_cap = self.read_merkle_cap(height)?; + let circuit_digest = self.read_hash::>::Hasher>()?; + Ok(VerifierOnlyCircuitData { + constants_sigmas_cap, + circuit_digest, + }) + } + + fn read_verifier_circuit_data< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, + >( + &mut self, + gate_serializer: &dyn GateSerializer, + ) -> IoResult> { + let verifier_only = self.read_verifier_only_circuit_data()?; + let common = self.read_common_circuit_data(gate_serializer)?; + Ok(VerifierCircuitData { + verifier_only, + common, + }) + } + + fn read_target_verifier_circuit(&mut self) -> IoResult { + let constants_sigmas_cap = self.read_target_merkle_cap()?; + let circuit_digest = self.read_target_hash()?; + Ok(VerifierCircuitTarget { + constants_sigmas_cap, + circuit_digest, + }) + } + + /// Reads a value of type [`Proof`] from `self` with `common_data`. + #[inline] + fn read_proof( + &mut self, + common_data: &CommonCircuitData, + ) -> IoResult> + where + F: RichField + Extendable, + C: GenericConfig, + { + let config = &common_data.config; + let wires_cap = self.read_merkle_cap(config.fri_config.cap_height)?; + let plonk_zs_partial_products_cap = self.read_merkle_cap(config.fri_config.cap_height)?; + let quotient_polys_cap = self.read_merkle_cap(config.fri_config.cap_height)?; + let openings = self.read_opening_set::(common_data)?; + let opening_proof = self.read_fri_proof::(common_data)?; + Ok(Proof { + wires_cap, + plonk_zs_partial_products_cap, + quotient_polys_cap, + openings, + opening_proof, + }) + } + + /// Reads a value of type [`ProofTarget`] from `self`. + #[inline] + fn read_target_proof(&mut self) -> IoResult> { + let wires_cap = self.read_target_merkle_cap()?; + let plonk_zs_partial_products_cap = self.read_target_merkle_cap()?; + let quotient_polys_cap = self.read_target_merkle_cap()?; + let openings = self.read_target_opening_set::()?; + let opening_proof = self.read_target_fri_proof::()?; + Ok(ProofTarget { + wires_cap, + plonk_zs_partial_products_cap, + quotient_polys_cap, + openings, + opening_proof, + }) + } + + /// Reads a value of type [`ProofWithPublicInputs`] from `self` with + /// `common_data`. + #[inline] + fn read_proof_with_public_inputs( + &mut self, + common_data: &CommonCircuitData, + ) -> IoResult> + where + Self: Remaining, + F: RichField + Extendable, + C: GenericConfig, + { + let proof = self.read_proof(common_data)?; + let pi_len = self.read_usize()?; + let public_inputs = self.read_field_vec(pi_len)?; + Ok(ProofWithPublicInputs { + proof, + public_inputs, + }) + } + + /// Reads a value of type [`ProofWithPublicInputsTarget`] from `self`. + #[inline] + fn read_target_proof_with_public_inputs( + &mut self, + ) -> IoResult> { + let proof = self.read_target_proof()?; + let public_inputs = self.read_target_vec()?; + Ok(ProofWithPublicInputsTarget { + proof, + public_inputs, + }) + } + + /// Reads a value of type [`CompressedFriQueryRounds`] from `self` with + /// `common_data`. + #[inline] + fn read_compressed_fri_query_rounds( + &mut self, + common_data: &CommonCircuitData, + ) -> IoResult> + where + F: RichField + Extendable, + C: GenericConfig, + { + let config = &common_data.config; + let original_indices = (0..config.fri_config.num_query_rounds) + .map(|_| self.read_u32().map(|i| i as usize)) + .collect::, _>>()?; + let mut indices = original_indices.clone(); + indices.sort_unstable(); + indices.dedup(); + let mut pairs = Vec::new(); + for &i in &indices { + pairs.push((i, self.read_fri_initial_proof::(common_data)?)); + } + let initial_trees_proofs = HashMap::from_iter(pairs); + + let mut steps = Vec::with_capacity(common_data.fri_params.reduction_arity_bits.len()); + for &a in &common_data.fri_params.reduction_arity_bits { + indices.iter_mut().for_each(|x| { + *x >>= a; + }); + indices.dedup(); + let query_steps = (0..indices.len()) + .map(|_| self.read_fri_query_step::(1 << a, true)) + .collect::, _>>()?; + steps.push( + indices + .iter() + .copied() + .zip(query_steps) + .collect::>(), + ); + } + + Ok(CompressedFriQueryRounds { + indices: original_indices, + initial_trees_proofs, + steps, + }) + } + + /// Reads a value of type [`CompressedFriProof`] from `self` with + /// `common_data`. + #[inline] + fn read_compressed_fri_proof( + &mut self, + common_data: &CommonCircuitData, + ) -> IoResult> + where + F: RichField + Extendable, + C: GenericConfig, + { + let config = &common_data.config; + let commit_phase_merkle_caps = (0..common_data.fri_params.reduction_arity_bits.len()) + .map(|_| self.read_merkle_cap(config.fri_config.cap_height)) + .collect::, _>>()?; + let query_round_proofs = self.read_compressed_fri_query_rounds::(common_data)?; + let final_poly = PolynomialCoeffs::new( + self.read_field_ext_vec::(common_data.fri_params.final_poly_len())?, + ); + let pow_witness = self.read_field()?; + Ok(CompressedFriProof { + commit_phase_merkle_caps, + query_round_proofs, + final_poly, + pow_witness, + }) + } + + /// Reads a value of type [`CompressedProof`] from `self` with + /// `common_data`. + #[inline] + fn read_compressed_proof( + &mut self, + common_data: &CommonCircuitData, + ) -> IoResult> + where + F: RichField + Extendable, + C: GenericConfig, + { + let config = &common_data.config; + let wires_cap = self.read_merkle_cap(config.fri_config.cap_height)?; + let plonk_zs_partial_products_cap = self.read_merkle_cap(config.fri_config.cap_height)?; + let quotient_polys_cap = self.read_merkle_cap(config.fri_config.cap_height)?; + let openings = self.read_opening_set::(common_data)?; + let opening_proof = self.read_compressed_fri_proof::(common_data)?; + Ok(CompressedProof { + wires_cap, + plonk_zs_partial_products_cap, + quotient_polys_cap, + openings, + opening_proof, + }) + } + + /// Reads a value of type [`CompressedProofWithPublicInputs`] from `self` + /// with `common_data`. + #[inline] + fn read_compressed_proof_with_public_inputs( + &mut self, + common_data: &CommonCircuitData, + ) -> IoResult> + where + Self: Remaining, + F: RichField + Extendable, + C: GenericConfig, + { + let proof = self.read_compressed_proof(common_data)?; + let public_inputs = self.read_field_vec(self.remaining() / size_of::())?; + Ok(CompressedProofWithPublicInputs { + proof, + public_inputs, + }) + } + + /// Reads a lookup table stored as `Vec<(u16, u16)>` from `self`. + #[inline] + fn read_lut(&mut self) -> IoResult> { + let length = self.read_usize()?; + let mut lut = Vec::with_capacity(length); + for _ in 0..length { + lut.push((self.read_u16()?, self.read_u16()?)); + } + + Ok(lut) + } + + /// Reads a target lookup table stored as `Lookup` from `self`. + #[inline] + fn read_target_lut(&mut self) -> IoResult { + let length = self.read_usize()?; + let mut lut = Vec::with_capacity(length); + for _ in 0..length { + lut.push((self.read_target()?, self.read_target()?)); + } + + Ok(lut) + } +} + +/// Writing +pub trait Write { + /// Error Type + type Error; + + /// Writes all `bytes` to `self`. + fn write_all(&mut self, bytes: &[u8]) -> IoResult<()>; + + /// Writes a bool `x` to `self`. + #[inline] + fn write_bool(&mut self, x: bool) -> IoResult<()> { + self.write_u8(u8::from(x)) + } + + /// Writes a target bool `x` to `self`. + #[inline] + fn write_target_bool(&mut self, x: BoolTarget) -> IoResult<()> { + self.write_target(x.target) + } + + /// Writes a vector of BoolTarget `v` to `self.` + #[inline] + fn write_target_bool_vec(&mut self, v: &[BoolTarget]) -> IoResult<()> { + self.write_usize(v.len())?; + for &elem in v.iter() { + self.write_target_bool(elem)?; + } + + Ok(()) + } + + /// Writes a byte `x` to `self`. + #[inline] + fn write_u8(&mut self, x: u8) -> IoResult<()> { + self.write_all(&[x]) + } + + /// Writes a word `x` to `self`. + #[inline] + fn write_u16(&mut self, x: u16) -> IoResult<()> { + self.write_all(&x.to_le_bytes()) + } + + /// Writes a word `x` to `self.` + #[inline] + fn write_u32(&mut self, x: u32) -> IoResult<()> { + self.write_all(&x.to_le_bytes()) + } + + /// Writes a word `x` to `self.` + #[inline] + fn write_usize(&mut self, x: usize) -> IoResult<()> { + self.write_all(&(x as u64).to_le_bytes()) + } + + /// Writes a vector of words `v` to `self.` + #[inline] + fn write_usize_vec(&mut self, v: &[usize]) -> IoResult<()> { + self.write_usize(v.len())?; + for &elem in v.iter() { + self.write_usize(elem)?; + } + + Ok(()) + } + + /// Writes an element `x` from the field `F` to `self`. + #[inline] + fn write_field(&mut self, x: F) -> IoResult<()> + where + F: PrimeField64, + { + self.write_all(&x.to_canonical_u64().to_le_bytes()) + } + + /// Writes a vector `v` of elements from the field `F` to `self`. + #[inline] + fn write_field_vec(&mut self, v: &[F]) -> IoResult<()> + where + F: PrimeField64, + { + for &a in v { + self.write_field(a)?; + } + Ok(()) + } + + /// Writes an element `x` from the field extension of `F` to `self`. + #[inline] + fn write_field_ext(&mut self, x: F::Extension) -> IoResult<()> + where + F: RichField + Extendable, + { + for &a in &x.to_basefield_array() { + self.write_field(a)?; + } + Ok(()) + } + + /// Writes a vector `v` of elements from the field extension of `F` to + /// `self`. + #[inline] + fn write_field_ext_vec(&mut self, v: &[F::Extension]) -> IoResult<()> + where + F: RichField + Extendable, + { + for &a in v { + self.write_field_ext::(a)?; + } + Ok(()) + } + + /// Writes a Target `x` to `self.` + #[inline] + fn write_target(&mut self, x: Target) -> IoResult<()> { + match x { + Target::Wire(Wire { row, column }) => { + self.write_bool(true)?; + self.write_usize(row)?; + self.write_usize(column)?; + } + Target::VirtualTarget { index } => { + self.write_bool(false)?; + self.write_usize(index)?; + } + }; + + Ok(()) + } + + /// Writes an ExtensionTarget `x` to `self.` + #[inline] + fn write_target_ext(&mut self, x: ExtensionTarget) -> IoResult<()> { + for &elem in x.0.iter() { + self.write_target(elem)?; + } + + Ok(()) + } + + /// Writes a vector of Target `v` to `self.` + #[inline] + fn write_target_array(&mut self, v: &[Target; N]) -> IoResult<()> { + for &elem in v.iter() { + self.write_target(elem)?; + } + + Ok(()) + } + + /// Writes a vector of Target `v` to `self.` + #[inline] + fn write_target_vec(&mut self, v: &[Target]) -> IoResult<()> { + self.write_usize(v.len())?; + for &elem in v.iter() { + self.write_target(elem)?; + } + + Ok(()) + } + + /// Writes a vector of ExtensionTarget `v` to `self.` + #[inline] + fn write_target_ext_vec(&mut self, v: &[ExtensionTarget]) -> IoResult<()> { + self.write_usize(v.len())?; + for &elem in v.iter() { + self.write_target_ext(elem)?; + } + + Ok(()) + } + + /// Writes a hash `h` to `self`. + #[inline] + fn write_hash(&mut self, h: H::Hash) -> IoResult<()> + where + F: RichField, + H: Hasher, + { + self.write_all(&h.to_bytes()) + } + + /// Writes a HashOutTarget `h` to `self`. + #[inline] + fn write_target_hash(&mut self, h: &HashOutTarget) -> IoResult<()> { + for r in h.elements.iter() { + self.write_target(*r)?; + } + + Ok(()) + } + + /// Writes a vector of Hash `v` to `self.` + #[inline] + fn write_hash_vec(&mut self, v: &[H::Hash]) -> IoResult<()> + where + F: RichField, + H: Hasher, + { + self.write_usize(v.len())?; + for &elem in v.iter() { + self.write_hash::(elem)?; + } + + Ok(()) + } + + /// Writes `cap`, a value of type [`MerkleCap`], to `self`. + #[inline] + fn write_merkle_cap(&mut self, cap: &MerkleCap) -> IoResult<()> + where + F: RichField, + H: Hasher, + { + for &a in &cap.0 { + self.write_hash::(a)?; + } + Ok(()) + } + + /// Writes `cap`, a value of type [`MerkleCapTarget`], to `self`. + #[inline] + fn write_target_merkle_cap(&mut self, cap: &MerkleCapTarget) -> IoResult<()> { + self.write_usize(cap.0.len())?; + for a in &cap.0 { + self.write_target_hash(a)?; + } + Ok(()) + } + + /// Writes `tree`, a value of type [`MerkleTree`], to `self`. + #[inline] + fn write_merkle_tree(&mut self, tree: &MerkleTree) -> IoResult<()> + where + F: RichField, + H: Hasher, + { + self.write_usize(tree.leaves.len())?; + for i in 0..tree.leaves.len() { + self.write_usize(tree.leaves[i].len())?; + self.write_field_vec(&tree.leaves[i])?; + } + self.write_hash_vec::(&tree.digests)?; + self.write_usize(tree.cap.height())?; + self.write_merkle_cap(&tree.cap)?; + + Ok(()) + } + + /// Writes a value `os` of type [`OpeningSet`] to `self.` + #[inline] + fn write_opening_set(&mut self, os: &OpeningSet) -> IoResult<()> + where + F: RichField + Extendable, + { + self.write_field_ext_vec::(&os.constants)?; + self.write_field_ext_vec::(&os.plonk_sigmas)?; + self.write_field_ext_vec::(&os.wires)?; + self.write_field_ext_vec::(&os.plonk_zs)?; + self.write_field_ext_vec::(&os.plonk_zs_next)?; + self.write_field_ext_vec::(&os.lookup_zs)?; + self.write_field_ext_vec::(&os.lookup_zs_next)?; + self.write_field_ext_vec::(&os.partial_products)?; + self.write_field_ext_vec::(&os.quotient_polys) + } + + /// Writes a value `os` of type [`OpeningSet`] to `self.` + #[inline] + fn write_target_opening_set( + &mut self, + os: &OpeningSetTarget, + ) -> IoResult<()> { + self.write_target_ext_vec::(&os.constants)?; + self.write_target_ext_vec::(&os.plonk_sigmas)?; + self.write_target_ext_vec::(&os.wires)?; + self.write_target_ext_vec::(&os.plonk_zs)?; + self.write_target_ext_vec::(&os.plonk_zs_next)?; + self.write_target_ext_vec::(&os.lookup_zs)?; + self.write_target_ext_vec::(&os.next_lookup_zs)?; + self.write_target_ext_vec::(&os.partial_products)?; + self.write_target_ext_vec::(&os.quotient_polys) + } + + /// Writes a value `p` of type [`MerkleProof`] to `self.` + #[inline] + fn write_merkle_proof(&mut self, p: &MerkleProof) -> IoResult<()> + where + F: RichField, + H: Hasher, + { + let length = p.siblings.len(); + self.write_u8( + length + .try_into() + .expect("Merkle proof length must fit in u8."), + )?; + for &h in &p.siblings { + self.write_hash::(h)?; + } + Ok(()) + } + + /// Writes a value `pt` of type [`MerkleProofTarget`] to `self.` + #[inline] + fn write_target_merkle_proof(&mut self, pt: &MerkleProofTarget) -> IoResult<()> { + let length = pt.siblings.len(); + self.write_u8( + length + .try_into() + .expect("Merkle proof length must fit in u8."), + )?; + for h in &pt.siblings { + self.write_target_hash(h)?; + } + Ok(()) + } + + /// Writes a value `fitp` of type [`FriInitialTreeProof`] to `self.` + #[inline] + fn write_fri_initial_proof( + &mut self, + fitp: &FriInitialTreeProof, + ) -> IoResult<()> + where + F: RichField + Extendable, + C: GenericConfig, + { + for (v, p) in &fitp.evals_proofs { + self.write_field_vec(v)?; + self.write_merkle_proof(p)?; + } + Ok(()) + } + + /// Writes a value `fitpt` of type [`FriInitialTreeProofTarget`] to `self.` + #[inline] + fn write_target_fri_initial_proof( + &mut self, + fitpt: &FriInitialTreeProofTarget, + ) -> IoResult<()> { + self.write_usize(fitpt.evals_proofs.len())?; + for (v, p) in &fitpt.evals_proofs { + self.write_target_vec(v)?; + self.write_target_merkle_proof(p)?; + } + Ok(()) + } + + /// Writes a value `fqs` of type [`FriQueryStep`] to `self.` + #[inline] + fn write_fri_query_step( + &mut self, + fqs: &FriQueryStep, + ) -> IoResult<()> + where + F: RichField + Extendable, + C: GenericConfig, + { + self.write_field_ext_vec::(&fqs.evals)?; + self.write_merkle_proof(&fqs.merkle_proof) + } + + /// Writes a value `fqst` of type [`FriQueryStepTarget`] to `self.` + #[inline] + fn write_target_fri_query_step( + &mut self, + fqst: &FriQueryStepTarget, + ) -> IoResult<()> { + self.write_target_ext_vec(&fqst.evals)?; + self.write_target_merkle_proof(&fqst.merkle_proof) + } + + /// Writes a value `fqrs` of type [`FriQueryRound`] to `self.` + #[inline] + fn write_fri_query_rounds( + &mut self, + fqrs: &[FriQueryRound], + ) -> IoResult<()> + where + F: RichField + Extendable, + C: GenericConfig, + { + for fqr in fqrs { + self.write_fri_initial_proof::(&fqr.initial_trees_proof)?; + for fqs in &fqr.steps { + self.write_fri_query_step::(fqs)?; + } + } + Ok(()) + } + + /// Writes a value `fqrst` of type [`FriQueryRoundTarget`] to `self.` + #[inline] + fn write_target_fri_query_rounds( + &mut self, + fqrst: &[FriQueryRoundTarget], + ) -> IoResult<()> { + self.write_usize(fqrst.len())?; + for fqr in fqrst { + self.write_target_fri_initial_proof(&fqr.initial_trees_proof)?; + self.write_usize(fqr.steps.len())?; + for fqs in &fqr.steps { + self.write_target_fri_query_step::(fqs)?; + } + } + Ok(()) + } + + /// Writes a value `fp` of type [`FriProof`] to `self.` + #[inline] + fn write_fri_proof( + &mut self, + fp: &FriProof, + ) -> IoResult<()> + where + F: RichField + Extendable, + C: GenericConfig, + { + for cap in &fp.commit_phase_merkle_caps { + self.write_merkle_cap(cap)?; + } + self.write_fri_query_rounds::(&fp.query_round_proofs)?; + self.write_field_ext_vec::(&fp.final_poly.coeffs)?; + self.write_field(fp.pow_witness) + } + + /// Writes a value `fpt` of type [`FriProofTarget`] to `self.` + #[inline] + fn write_target_fri_proof(&mut self, fpt: &FriProofTarget) -> IoResult<()> { + self.write_usize(fpt.commit_phase_merkle_caps.len())?; + for cap in &fpt.commit_phase_merkle_caps { + self.write_target_merkle_cap(cap)?; + } + self.write_target_fri_query_rounds::(&fpt.query_round_proofs)?; + self.write_target_ext_vec::(&fpt.final_poly.0)?; + self.write_target(fpt.pow_witness) + } + + fn write_fri_reduction_strategy( + &mut self, + reduction_strategy: &FriReductionStrategy, + ) -> IoResult<()> { + match reduction_strategy { + FriReductionStrategy::Fixed(seq) => { + self.write_u8(0)?; + self.write_usize_vec(seq.as_slice())?; + + Ok(()) + } + FriReductionStrategy::ConstantArityBits(arity_bits, final_poly_bits) => { + self.write_u8(1)?; + self.write_usize(*arity_bits)?; + self.write_usize(*final_poly_bits)?; + + Ok(()) + } + FriReductionStrategy::MinSize(max) => { + self.write_u8(2)?; + if let Some(max) = max { + self.write_u8(1)?; + self.write_usize(*max)?; + } else { + self.write_u8(0)?; + } + + Ok(()) + } + } + } + + fn write_fri_config(&mut self, config: &FriConfig) -> IoResult<()> { + let FriConfig { + rate_bits, + cap_height, + num_query_rounds, + proof_of_work_bits, + reduction_strategy, + } = &config; + + self.write_usize(*rate_bits)?; + self.write_usize(*cap_height)?; + self.write_usize(*num_query_rounds)?; + self.write_u32(*proof_of_work_bits)?; + self.write_fri_reduction_strategy(reduction_strategy)?; + + Ok(()) + } + + fn write_fri_params(&mut self, fri_params: &FriParams) -> IoResult<()> { + let FriParams { + config, + reduction_arity_bits, + degree_bits, + hiding, + } = fri_params; + + self.write_fri_config(config)?; + self.write_usize_vec(reduction_arity_bits.as_slice())?; + self.write_usize(*degree_bits)?; + self.write_bool(*hiding)?; + + Ok(()) + } + + fn write_circuit_config(&mut self, config: &CircuitConfig) -> IoResult<()> { + let CircuitConfig { + num_wires, + num_routed_wires, + num_constants, + security_bits, + num_challenges, + max_quotient_degree_factor, + use_base_arithmetic_gate, + zero_knowledge, + fri_config, + } = config; + + self.write_usize(*num_wires)?; + self.write_usize(*num_routed_wires)?; + self.write_usize(*num_constants)?; + self.write_usize(*security_bits)?; + self.write_usize(*num_challenges)?; + self.write_usize(*max_quotient_degree_factor)?; + self.write_bool(*use_base_arithmetic_gate)?; + self.write_bool(*zero_knowledge)?; + self.write_fri_config(fri_config)?; + + Ok(()) + } + + fn write_gate, const D: usize>( + &mut self, + gate: &GateRef, + gate_serializer: &dyn GateSerializer, + common_data: &CommonCircuitData, + ) -> IoResult<()>; + + fn write_generator, const D: usize>( + &mut self, + generator: &WitnessGeneratorRef, + generator_serializer: &dyn WitnessGeneratorSerializer, + common_data: &CommonCircuitData, + ) -> IoResult<()>; + + fn write_selectors_info(&mut self, selectors_info: &SelectorsInfo) -> IoResult<()> { + let SelectorsInfo { + selector_indices, + groups, + } = selectors_info; + + self.write_usize_vec(selector_indices.as_slice())?; + self.write_usize(groups.len())?; + for group in groups.iter() { + self.write_usize(group.start)?; + self.write_usize(group.end)?; + } + Ok(()) + } + + fn write_polynomial_batch< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, + >( + &mut self, + poly_batch: &PolynomialBatch, + ) -> IoResult<()> { + self.write_usize(poly_batch.polynomials.len())?; + for i in 0..poly_batch.polynomials.len() { + self.write_usize(poly_batch.polynomials[i].coeffs.len())?; + self.write_field_vec(&poly_batch.polynomials[i].coeffs)?; + } + self.write_merkle_tree(&poly_batch.merkle_tree)?; + self.write_usize(poly_batch.degree_log)?; + self.write_usize(poly_batch.rate_bits)?; + self.write_bool(poly_batch.blinding)?; + + Ok(()) + } + + fn write_common_circuit_data, const D: usize>( + &mut self, + common_data: &CommonCircuitData, + gate_serializer: &dyn GateSerializer, + ) -> IoResult<()> { + let CommonCircuitData { + config, + fri_params, + gates, + selectors_info, + quotient_degree_factor, + num_gate_constraints, + num_constants, + num_public_inputs, + k_is, + num_partial_products, + num_lookup_polys, + num_lookup_selectors, + luts, + } = common_data; + + self.write_circuit_config(config)?; + self.write_fri_params(fri_params)?; + + self.write_selectors_info(selectors_info)?; + self.write_usize(*quotient_degree_factor)?; + self.write_usize(*num_gate_constraints)?; + self.write_usize(*num_constants)?; + self.write_usize(*num_public_inputs)?; + + self.write_usize(k_is.len())?; + self.write_field_vec(k_is.as_slice())?; + + self.write_usize(*num_partial_products)?; + + self.write_usize(*num_lookup_polys)?; + self.write_usize(*num_lookup_selectors)?; + self.write_usize(luts.len())?; + for lut in luts.iter() { + self.write_lut(lut)?; + } + + self.write_usize(gates.len())?; + for gate in gates.iter() { + self.write_gate::(gate, gate_serializer, common_data)?; + } + + Ok(()) + } + + fn write_circuit_data< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, + >( + &mut self, + circuit_data: &CircuitData, + gate_serializer: &dyn GateSerializer, + generator_serializer: &dyn WitnessGeneratorSerializer, + ) -> IoResult<()> { + self.write_common_circuit_data(&circuit_data.common, gate_serializer)?; + self.write_prover_only_circuit_data( + &circuit_data.prover_only, + generator_serializer, + &circuit_data.common, + )?; + self.write_verifier_only_circuit_data(&circuit_data.verifier_only) + } + + fn write_prover_only_circuit_data< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, + >( + &mut self, + prover_only_circuit_data: &ProverOnlyCircuitData, + generator_serializer: &dyn WitnessGeneratorSerializer, + common_data: &CommonCircuitData, + ) -> IoResult<()> { + let ProverOnlyCircuitData { + generators, + generator_indices_by_watches, + constants_sigmas_commitment, + sigmas, + subgroup, + public_inputs, + representative_map, + fft_root_table, + circuit_digest, + lookup_rows, + lut_to_lookups, + } = prover_only_circuit_data; + + self.write_usize(generators.len())?; + for generator in generators.iter() { + self.write_generator::(generator, generator_serializer, common_data)?; + } + + self.write_usize(generator_indices_by_watches.len())?; + for (k, v) in generator_indices_by_watches { + self.write_usize(*k)?; + self.write_usize_vec(v)?; + } + + self.write_polynomial_batch(constants_sigmas_commitment)?; + self.write_usize(sigmas.len())?; + for i in 0..sigmas.len() { + self.write_usize(sigmas[i].len())?; + self.write_field_vec(&sigmas[i])?; + } + self.write_usize(subgroup.len())?; + self.write_field_vec(subgroup)?; + self.write_target_vec(public_inputs)?; + self.write_usize_vec(representative_map)?; + + match fft_root_table { + Some(table) => { + self.write_bool(true)?; + self.write_usize(table.len())?; + for i in 0..table.len() { + self.write_usize(table[i].len())?; + self.write_field_vec(&table[i])?; + } + } + None => self.write_bool(false)?, + } + + self.write_hash::>::Hasher>(*circuit_digest)?; + + self.write_usize(lookup_rows.len())?; + for wire in lookup_rows.iter() { + self.write_usize(wire.last_lu_gate)?; + self.write_usize(wire.last_lut_gate)?; + self.write_usize(wire.first_lut_gate)?; + } + + self.write_usize(lut_to_lookups.len())?; + for tlut in lut_to_lookups.iter() { + self.write_target_lut(tlut)?; + } + + Ok(()) + } + + fn write_prover_circuit_data< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, + >( + &mut self, + prover_circuit_data: &ProverCircuitData, + gate_serializer: &dyn GateSerializer, + generator_serializer: &dyn WitnessGeneratorSerializer, + ) -> IoResult<()> { + self.write_common_circuit_data(&prover_circuit_data.common, gate_serializer)?; + self.write_prover_only_circuit_data( + &prover_circuit_data.prover_only, + generator_serializer, + &prover_circuit_data.common, + ) + } + + fn write_verifier_only_circuit_data< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, + >( + &mut self, + verifier_only_circuit_data: &VerifierOnlyCircuitData, + ) -> IoResult<()> { + let VerifierOnlyCircuitData { + constants_sigmas_cap, + circuit_digest, + } = verifier_only_circuit_data; + + self.write_usize(constants_sigmas_cap.height())?; + self.write_merkle_cap(constants_sigmas_cap)?; + self.write_hash::>::Hasher>(*circuit_digest)?; + + Ok(()) + } + + fn write_verifier_circuit_data< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, + >( + &mut self, + verifier_circuit_data: &VerifierCircuitData, + gate_serializer: &dyn GateSerializer, + ) -> IoResult<()> { + self.write_verifier_only_circuit_data(&verifier_circuit_data.verifier_only)?; + self.write_common_circuit_data(&verifier_circuit_data.common, gate_serializer) + } + + fn write_target_verifier_circuit( + &mut self, + verifier_circuit: &VerifierCircuitTarget, + ) -> IoResult<()> { + let VerifierCircuitTarget { + constants_sigmas_cap, + circuit_digest, + } = verifier_circuit; + + self.write_target_merkle_cap(constants_sigmas_cap)?; + self.write_target_hash(circuit_digest)?; + + Ok(()) + } + + /// Writes a value `proof` of type [`Proof`] to `self.` + #[inline] + fn write_proof(&mut self, proof: &Proof) -> IoResult<()> + where + F: RichField + Extendable, + C: GenericConfig, + { + self.write_merkle_cap(&proof.wires_cap)?; + self.write_merkle_cap(&proof.plonk_zs_partial_products_cap)?; + self.write_merkle_cap(&proof.quotient_polys_cap)?; + self.write_opening_set(&proof.openings)?; + self.write_fri_proof::(&proof.opening_proof) + } + + /// Writes a value `proof` of type [`Proof`] to `self.` + #[inline] + fn write_target_proof(&mut self, proof: &ProofTarget) -> IoResult<()> { + self.write_target_merkle_cap(&proof.wires_cap)?; + self.write_target_merkle_cap(&proof.plonk_zs_partial_products_cap)?; + self.write_target_merkle_cap(&proof.quotient_polys_cap)?; + self.write_target_opening_set(&proof.openings)?; + self.write_target_fri_proof::(&proof.opening_proof) + } + + /// Writes a value `proof_with_pis` of type [`ProofWithPublicInputs`] to + /// `self.` + #[inline] + fn write_proof_with_public_inputs( + &mut self, + proof_with_pis: &ProofWithPublicInputs, + ) -> IoResult<()> + where + F: RichField + Extendable, + C: GenericConfig, + { + let ProofWithPublicInputs { + proof, + public_inputs, + } = proof_with_pis; + self.write_proof(proof)?; + self.write_usize(public_inputs.len())?; + self.write_field_vec(public_inputs) + } + + /// Writes a value `proof_with_pis` of type [`ProofWithPublicInputsTarget`] + /// to `self.` + #[inline] + fn write_target_proof_with_public_inputs( + &mut self, + proof_with_pis: &ProofWithPublicInputsTarget, + ) -> IoResult<()> { + let ProofWithPublicInputsTarget { + proof, + public_inputs, + } = proof_with_pis; + self.write_target_proof(proof)?; + self.write_target_vec(public_inputs) + } + + /// Writes a value `cfqrs` of type [`CompressedFriQueryRounds`] to `self.` + #[inline] + fn write_compressed_fri_query_rounds( + &mut self, + cfqrs: &CompressedFriQueryRounds, + ) -> IoResult<()> + where + F: RichField + Extendable, + C: GenericConfig, + { + for &i in &cfqrs.indices { + self.write_u32(i as u32)?; + } + let mut initial_trees_proofs = cfqrs.initial_trees_proofs.iter().collect::>(); + initial_trees_proofs.sort_by_key(|&x| x.0); + for (_, itp) in initial_trees_proofs { + self.write_fri_initial_proof::(itp)?; + } + for h in &cfqrs.steps { + let mut fri_query_steps = h.iter().collect::>(); + fri_query_steps.sort_by_key(|&x| x.0); + for (_, fqs) in fri_query_steps { + self.write_fri_query_step::(fqs)?; + } + } + Ok(()) + } + + /// Writes a value `fq` of type [`CompressedFriProof`] to `self.` + #[inline] + fn write_compressed_fri_proof( + &mut self, + fp: &CompressedFriProof, + ) -> IoResult<()> + where + F: RichField + Extendable, + C: GenericConfig, + { + for cap in &fp.commit_phase_merkle_caps { + self.write_merkle_cap(cap)?; + } + self.write_compressed_fri_query_rounds::(&fp.query_round_proofs)?; + self.write_field_ext_vec::(&fp.final_poly.coeffs)?; + self.write_field(fp.pow_witness) + } + + /// Writes a value `proof` of type [`CompressedProof`] to `self.` + #[inline] + fn write_compressed_proof( + &mut self, + proof: &CompressedProof, + ) -> IoResult<()> + where + F: RichField + Extendable, + C: GenericConfig, + { + self.write_merkle_cap(&proof.wires_cap)?; + self.write_merkle_cap(&proof.plonk_zs_partial_products_cap)?; + self.write_merkle_cap(&proof.quotient_polys_cap)?; + self.write_opening_set(&proof.openings)?; + self.write_compressed_fri_proof::(&proof.opening_proof) + } + + /// Writes a value `proof_with_pis` of type + /// [`CompressedProofWithPublicInputs`] to `self.` + #[inline] + fn write_compressed_proof_with_public_inputs( + &mut self, + proof_with_pis: &CompressedProofWithPublicInputs, + ) -> IoResult<()> + where + F: RichField + Extendable, + C: GenericConfig, + { + let CompressedProofWithPublicInputs { + proof, + public_inputs, + } = proof_with_pis; + self.write_compressed_proof(proof)?; + self.write_field_vec(public_inputs) + } + + /// Writes a lookup table to `self`. + #[inline] + fn write_lut(&mut self, lut: &[(u16, u16)]) -> IoResult<()> { + self.write_usize(lut.len())?; + for (a, b) in lut.iter() { + self.write_u16(*a)?; + self.write_u16(*b)?; + } + + Ok(()) + } + + /// Writes a target lookup table to `self`. + #[inline] + fn write_target_lut(&mut self, lut: &[(Target, Target)]) -> IoResult<()> { + self.write_usize(lut.len())?; + for (a, b) in lut.iter() { + self.write_target(*a)?; + self.write_target(*b)?; + } + + Ok(()) + } +} + +impl Write for Vec { + type Error = Infallible; + + #[inline] + fn write_all(&mut self, bytes: &[u8]) -> IoResult<()> { + self.extend_from_slice(bytes); + Ok(()) + } + + fn write_gate, const D: usize>( + &mut self, + gate: &GateRef, + gate_serializer: &dyn GateSerializer, + common_data: &CommonCircuitData, + ) -> IoResult<()> { + gate_serializer.write_gate(self, gate, common_data) + } + + fn write_generator, const D: usize>( + &mut self, + generator: &WitnessGeneratorRef, + generator_serializer: &dyn WitnessGeneratorSerializer, + common_data: &CommonCircuitData, + ) -> IoResult<()> { + generator_serializer.write_generator(self, generator, common_data) + } +} + +/// Buffer +#[derive(Debug)] +pub struct Buffer<'a> { + bytes: &'a [u8], + pos: usize, +} + +impl<'a> Buffer<'a> { + /// Builds a new [`Buffer`] over `buffer`. + #[inline] + pub const fn new(bytes: &'a [u8]) -> Self { + Self { bytes, pos: 0 } + } + + /// Returns the inner position. + #[inline] + pub const fn pos(&self) -> usize { + self.pos + } + + /// Returns the inner buffer. + #[inline] + pub const fn bytes(&self) -> &'a [u8] { + self.bytes + } + + /// Returns the inner unread buffer. + #[inline] + pub fn unread_bytes(&self) -> &'a [u8] { + &self.bytes()[self.pos()..] + } +} + +impl<'a> Remaining for Buffer<'a> { + fn remaining(&self) -> usize { + self.bytes.len() - self.pos() + } +} + +impl<'a> Read for Buffer<'a> { + #[inline] + fn read_exact(&mut self, bytes: &mut [u8]) -> IoResult<()> { + let n = bytes.len(); + if self.remaining() < n { + Err(IoError) + } else { + bytes.copy_from_slice(&self.bytes[self.pos..][..n]); + self.pos += n; + Ok(()) + } + } + + fn read_gate, const D: usize>( + &mut self, + gate_serializer: &dyn GateSerializer, + common_data: &CommonCircuitData, + ) -> IoResult> { + gate_serializer.read_gate(self, common_data) + } + + fn read_generator, const D: usize>( + &mut self, + generator_serializer: &dyn WitnessGeneratorSerializer, + common_data: &CommonCircuitData, + ) -> IoResult> { + generator_serializer.read_generator(self, common_data) + } +} diff --git a/plonky2/src/util/strided_view.rs b/plonky2/src/util/strided_view.rs new file mode 100644 index 000000000..ca9ced4ba --- /dev/null +++ b/plonky2/src/util/strided_view.rs @@ -0,0 +1,327 @@ +use core::marker::PhantomData; +use core::mem::size_of; +use core::ops::{Index, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}; + +use crate::field::packed::PackedField; + +/// Imagine a slice, but with a stride (a la a NumPy array). +/// +/// For example, if the stride is 3, +/// `packed_strided_view[0]` is `data[0]`, +/// `packed_strided_view[1]` is `data[3]`, +/// `packed_strided_view[2]` is `data[6]`, +/// and so on. An offset may be specified. With an offset of 1, we get +/// `packed_strided_view[0]` is `data[1]`, +/// `packed_strided_view[1]` is `data[4]`, +/// `packed_strided_view[2]` is `data[7]`, +/// and so on. +/// +/// Additionally, this view is *packed*, which means that it may yield a packing +/// of the underlying field slice. With a packing of width 4 and a stride of 5, +/// the accesses are `packed_strided_view[0]` is `data[0..4]`, transmuted to +/// the packing, `packed_strided_view[1]` is `data[5..9]`, transmuted to the +/// packing, `packed_strided_view[2]` is `data[10..14]`, transmuted to the +/// packing, and so on. +#[derive(Debug, Copy, Clone)] +pub struct PackedStridedView<'a, P: PackedField> { + // This type has to be a struct, which means that it is not itself a reference (in the sense + // that a slice is a reference so we can return it from e.g. `Index::index`). + + // Raw pointers rarely appear in good Rust code, but I think this is the most elegant way to + // implement this. The alternative would be to replace `start_ptr` and `length` with one slice + // (`&[P::Scalar]`). Unfortunately, with a slice, an empty view becomes an edge case that + // necessitates separate handling. It _could_ be done but it would also be uglier. + start_ptr: *const P::Scalar, + /// This is the total length of elements accessible through the view. In + /// other words, valid indices are in `0..length`. + length: usize, + /// This stride is in units of `P::Scalar` (NOT in bytes and NOT in `P`). + stride: usize, + _phantom: PhantomData<&'a [P::Scalar]>, +} + +impl<'a, P: PackedField> PackedStridedView<'a, P> { + // `wrapping_add` is needed throughout to avoid undefined behavior. Plain `add` + // causes UB if '[either] the starting [or] resulting pointer [is neither] + // in bounds or one byte past the end of the same allocated object'; the UB + // results even if the pointer is not dereferenced. + + #[inline] + pub fn new(data: &'a [P::Scalar], stride: usize, offset: usize) -> Self { + assert!( + stride >= P::WIDTH, + "stride (got {}) must be at least P::WIDTH ({})", + stride, + P::WIDTH + ); + assert_eq!( + data.len() % stride, + 0, + "data.len() ({}) must be a multiple of stride (got {})", + data.len(), + stride + ); + + // This requirement means that stride divides data into slices of `data.len() / + // stride` elements. Every access must fit entirely within one of those + // slices. + assert!( + offset + P::WIDTH <= stride, + "offset (got {}) + P::WIDTH ({}) cannot be greater than stride (got {})", + offset, + P::WIDTH, + stride + ); + + // See comment above. `start_ptr` will be more than one byte past the buffer if + // `data` has length 0 and `offset` is not 0. + let start_ptr = data.as_ptr().wrapping_add(offset); + + Self { + start_ptr, + length: data.len() / stride, + stride, + _phantom: PhantomData, + } + } + + #[inline] + pub const fn get(&self, index: usize) -> Option<&'a P> { + if index < self.length { + // Cast scalar pointer to vector pointer. + let res_ptr = unsafe { self.start_ptr.add(index * self.stride) }.cast(); + // This transmutation is safe by the spec in `PackedField`. + Some(unsafe { &*res_ptr }) + } else { + None + } + } + + /// Take a range of `PackedStridedView` indices, as `PackedStridedView`. + #[inline] + pub fn view(&self, index: I) -> Self + where + Self: Viewable, + { + // We cannot implement `Index` as `PackedStridedView` is a struct, not a + // reference. + + // The `Viewable` trait is needed for overloading. + // Re-export `Viewable::view` so users don't have to import `Viewable`. + >::view(self, index) + } + + #[inline] + pub const fn iter(&self) -> PackedStridedViewIter<'a, P> { + PackedStridedViewIter::new( + self.start_ptr, + // See comment at the top of the `impl`. Below will point more than one byte past the + // end of the buffer (unless `offset` is 0) so `wrapping_add` is needed. + self.start_ptr.wrapping_add(self.length * self.stride), + self.stride, + ) + } + + #[inline] + pub const fn len(&self) -> usize { + self.length + } + + #[inline] + pub const fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl<'a, P: PackedField> Index for PackedStridedView<'a, P> { + type Output = P; + #[inline] + fn index(&self, index: usize) -> &Self::Output { + self.get(index) + .expect("invalid memory access in PackedStridedView") + } +} + +impl<'a, P: PackedField> IntoIterator for PackedStridedView<'a, P> { + type Item = &'a P; + type IntoIter = PackedStridedViewIter<'a, P>; + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +#[derive(Clone, Copy, Debug)] +pub struct TryFromPackedStridedViewError; + +impl TryInto<[P; N]> for PackedStridedView<'_, P> { + type Error = TryFromPackedStridedViewError; + fn try_into(self) -> Result<[P; N], Self::Error> { + if N == self.len() { + let mut res = [P::default(); N]; + for i in 0..N { + res[i] = *self.get(i).unwrap(); + } + Ok(res) + } else { + Err(TryFromPackedStridedViewError) + } + } +} + +// Not deriving `Copy`. An implicit copy of an iterator is likely a bug. +#[derive(Clone, Debug)] +pub struct PackedStridedViewIter<'a, P: PackedField> { + // Again, a pair of pointers is a neater solution than a slice. `start` and `end` are always + // separated by a multiple of stride elements. To advance the iterator from the front, we + // advance `start` by `stride` elements. To advance it from the end, we subtract `stride` + // elements. Iteration is done when they meet. + // A slice cannot recreate the same pattern. The end pointer may point past the underlying + // buffer (this is okay as we do not dereference it in that case); it becomes valid as soon as + // it is decreased by `stride`. On the other hand, a slice that ends on invalid memory is + // instant undefined behavior. + start: *const P::Scalar, + end: *const P::Scalar, + stride: usize, + _phantom: PhantomData<&'a [P::Scalar]>, +} + +impl<'a, P: PackedField> PackedStridedViewIter<'a, P> { + pub(self) const fn new(start: *const P::Scalar, end: *const P::Scalar, stride: usize) -> Self { + Self { + start, + end, + stride, + _phantom: PhantomData, + } + } +} + +impl<'a, P: PackedField> Iterator for PackedStridedViewIter<'a, P> { + type Item = &'a P; + fn next(&mut self) -> Option { + debug_assert_eq!( + (self.end as usize).wrapping_sub(self.start as usize) + % (self.stride * size_of::()), + 0, + "start and end pointers should be separated by a multiple of stride" + ); + + if self.start != self.end { + let res = unsafe { &*self.start.cast() }; + // See comment in `PackedStridedView`. Below will point more than one byte past + // the end of the buffer if the offset is not 0 and we've reached + // the end. + self.start = self.start.wrapping_add(self.stride); + Some(res) + } else { + None + } + } +} + +impl<'a, P: PackedField> DoubleEndedIterator for PackedStridedViewIter<'a, P> { + fn next_back(&mut self) -> Option { + debug_assert_eq!( + (self.end as usize).wrapping_sub(self.start as usize) + % (self.stride * size_of::()), + 0, + "start and end pointers should be separated by a multiple of stride" + ); + + if self.start != self.end { + // See comment in `PackedStridedView`. `self.end` starts off pointing more than + // one byte past the end of the buffer unless `offset` is 0. + self.end = self.end.wrapping_sub(self.stride); + Some(unsafe { &*self.end.cast() }) + } else { + None + } + } +} + +pub trait Viewable { + // We cannot implement `Index` as `PackedStridedView` is a struct, not a + // reference. + type View; + fn view(&self, index: F) -> Self::View; +} + +impl<'a, P: PackedField> Viewable> for PackedStridedView<'a, P> { + type View = Self; + fn view(&self, range: Range) -> Self::View { + assert!(range.start <= self.len(), "Invalid access"); + assert!(range.end <= self.len(), "Invalid access"); + Self { + // See comment in `PackedStridedView`. `self.start_ptr` will point more than one byte + // past the end of the buffer if the offset is not 0 and the buffer has length 0. + start_ptr: self.start_ptr.wrapping_add(self.stride * range.start), + length: range.end - range.start, + stride: self.stride, + _phantom: PhantomData, + } + } +} + +impl<'a, P: PackedField> Viewable> for PackedStridedView<'a, P> { + type View = Self; + fn view(&self, range: RangeFrom) -> Self::View { + assert!(range.start <= self.len(), "Invalid access"); + Self { + // See comment in `PackedStridedView`. `self.start_ptr` will point more than one byte + // past the end of the buffer if the offset is not 0 and the buffer has length 0. + start_ptr: self.start_ptr.wrapping_add(self.stride * range.start), + length: self.len() - range.start, + stride: self.stride, + _phantom: PhantomData, + } + } +} + +impl<'a, P: PackedField> Viewable for PackedStridedView<'a, P> { + type View = Self; + fn view(&self, _range: RangeFull) -> Self::View { + *self + } +} + +impl<'a, P: PackedField> Viewable> for PackedStridedView<'a, P> { + type View = Self; + fn view(&self, range: RangeInclusive) -> Self::View { + assert!(*range.start() <= self.len(), "Invalid access"); + assert!(*range.end() < self.len(), "Invalid access"); + Self { + // See comment in `PackedStridedView`. `self.start_ptr` will point more than one byte + // past the end of the buffer if the offset is not 0 and the buffer has length 0. + start_ptr: self.start_ptr.wrapping_add(self.stride * range.start()), + length: range.end() - range.start() + 1, + stride: self.stride, + _phantom: PhantomData, + } + } +} + +impl<'a, P: PackedField> Viewable> for PackedStridedView<'a, P> { + type View = Self; + fn view(&self, range: RangeTo) -> Self::View { + assert!(range.end <= self.len(), "Invalid access"); + Self { + start_ptr: self.start_ptr, + length: range.end, + stride: self.stride, + _phantom: PhantomData, + } + } +} + +impl<'a, P: PackedField> Viewable> for PackedStridedView<'a, P> { + type View = Self; + fn view(&self, range: RangeToInclusive) -> Self::View { + assert!(range.end < self.len(), "Invalid access"); + Self { + start_ptr: self.start_ptr, + length: range.end + 1, + stride: self.stride, + _phantom: PhantomData, + } + } +} diff --git a/plonky2/src/util/timing.rs b/plonky2/src/util/timing.rs new file mode 100644 index 000000000..3874677b9 --- /dev/null +++ b/plonky2/src/util/timing.rs @@ -0,0 +1,193 @@ +use log::{log, Level}; +#[cfg(feature = "timing")] +use web_time::{Duration, Instant}; + +/// The hierarchy of scopes, and the time consumed by each one. Useful for +/// profiling. +#[cfg(feature = "timing")] +pub struct TimingTree { + /// The name of this scope. + name: String, + /// The level at which to log this scope and its children. + level: log::Level, + /// The time when this scope was created. + enter_time: Instant, + /// The time when this scope was destroyed, or None if it has not yet been + /// destroyed. + exit_time: Option, + /// Any child scopes. + children: Vec, +} + +#[cfg(not(feature = "timing"))] +pub struct TimingTree(Level); + +#[cfg(feature = "timing")] +impl Default for TimingTree { + fn default() -> Self { + TimingTree::new("root", Level::Debug) + } +} + +#[cfg(not(feature = "timing"))] +impl Default for TimingTree { + fn default() -> Self { + TimingTree::new("", Level::Debug) + } +} + +impl TimingTree { + #[cfg(feature = "timing")] + pub fn new(root_name: &str, level: Level) -> Self { + Self { + name: root_name.to_string(), + level, + enter_time: Instant::now(), + exit_time: None, + children: vec![], + } + } + + #[cfg(not(feature = "timing"))] + pub fn new(_root_name: &str, level: Level) -> Self { + Self(level) + } + + /// Whether this scope is still in scope. + #[cfg(feature = "timing")] + const fn is_open(&self) -> bool { + self.exit_time.is_none() + } + + /// A description of the stack of currently-open scopes. + #[cfg(feature = "timing")] + pub fn open_stack(&self) -> String { + let mut stack = Vec::new(); + self.open_stack_helper(&mut stack); + stack.join(" > ") + } + + #[cfg(feature = "timing")] + fn open_stack_helper(&self, stack: &mut Vec) { + if self.is_open() { + stack.push(self.name.clone()); + if let Some(last_child) = self.children.last() { + last_child.open_stack_helper(stack); + } + } + } + + #[cfg(feature = "timing")] + pub fn push(&mut self, ctx: &str, mut level: log::Level) { + assert!(self.is_open()); + + // We don't want a scope's log level to be stronger than that of its parent. + level = level.max(self.level); + + if let Some(last_child) = self.children.last_mut() { + if last_child.is_open() { + last_child.push(ctx, level); + return; + } + } + + self.children.push(TimingTree { + name: ctx.to_string(), + level, + enter_time: Instant::now(), + exit_time: None, + children: vec![], + }) + } + + #[cfg(not(feature = "timing"))] + pub fn push(&mut self, _ctx: &str, _level: log::Level) {} + + /// Close the deepest open scope from this tree. + #[cfg(feature = "timing")] + pub fn pop(&mut self) { + assert!(self.is_open()); + + if let Some(last_child) = self.children.last_mut() { + if last_child.is_open() { + last_child.pop(); + return; + } + } + + self.exit_time = Some(Instant::now()); + } + + #[cfg(not(feature = "timing"))] + pub fn pop(&mut self) {} + + #[cfg(feature = "timing")] + fn duration(&self) -> Duration { + self.exit_time + .unwrap_or_else(Instant::now) + .duration_since(self.enter_time) + } + + /// Filter out children with a low duration. + #[cfg(feature = "timing")] + pub fn filter(&self, min_delta: Duration) -> Self { + Self { + name: self.name.clone(), + level: self.level, + enter_time: self.enter_time, + exit_time: self.exit_time, + children: self + .children + .iter() + .filter(|c| c.duration() >= min_delta) + .map(|c| c.filter(min_delta)) + .collect(), + } + } + + #[cfg(feature = "timing")] + pub fn print(&self) { + self.print_helper(0); + } + + #[cfg(not(feature = "timing"))] + pub fn print(&self) { + log!( + self.0, + "TimingTree is not supported without the 'timing' feature enabled" + ); + } + + #[cfg(feature = "timing")] + fn print_helper(&self, depth: usize) { + let prefix = "| ".repeat(depth); + log!( + self.level, + "{}{:.4}s to {}", + prefix, + self.duration().as_secs_f64(), + self.name + ); + for child in &self.children { + child.print_helper(depth + 1); + } + } +} + +/// Creates a named scope; useful for debugging. +#[macro_export] +macro_rules! timed { + ($timing_tree:expr, $level:expr, $ctx:expr, $exp:expr) => {{ + $timing_tree.push($ctx, $level); + let res = $exp; + $timing_tree.pop(); + res + }}; + // If no context is specified, default to Debug. + ($timing_tree:expr, $ctx:expr, $exp:expr) => {{ + $timing_tree.push($ctx, log::Level::Debug); + let res = $exp; + $timing_tree.pop(); + res + }}; +} diff --git a/projects/cache-friendly-fft/__init__.py b/projects/cache-friendly-fft/__init__.py new file mode 100644 index 000000000..08f1acac4 --- /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 000000000..ea20bf6bd --- /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 000000000..50118827c --- /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 diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 000000000..07ade694b --- /dev/null +++ b/rust-toolchain @@ -0,0 +1 @@ +nightly \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 000000000..50411b985 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,3 @@ +group_imports = "StdExternalCrate" +imports_granularity = "Module" +unstable_features = true \ No newline at end of file diff --git a/src/cpu/kernel/asm/journal/account_created.asm b/src/cpu/kernel/asm/journal/account_created.asm deleted file mode 100644 index 4748d5cbc..000000000 --- a/src/cpu/kernel/asm/journal/account_created.asm +++ /dev/null @@ -1,13 +0,0 @@ -// struct AccountCreated { address } - -%macro journal_add_account_created - %journal_add_1(@JOURNAL_ENTRY_ACCOUNT_CREATED) -%endmacro - -global revert_account_created: - // stack: entry_type, ptr, retdest - POP - %journal_load_1 - // stack: address, retdest - %delete_account - JUMP diff --git a/src/keccak_sponge/mod.rs b/src/keccak_sponge/mod.rs deleted file mode 100644 index 92b7f0c15..000000000 --- a/src/keccak_sponge/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! 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/starky/.cargo/katex-header.html b/starky/.cargo/katex-header.html new file mode 100644 index 000000000..20723b5d2 --- /dev/null +++ b/starky/.cargo/katex-header.html @@ -0,0 +1 @@ +../../.cargo/katex-header.html \ No newline at end of file diff --git a/starky/Cargo.toml b/starky/Cargo.toml new file mode 100644 index 000000000..fe64413f9 --- /dev/null +++ b/starky/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "starky" +description = "Implementation of STARKs" +version = "0.1.2" +license = "MIT OR Apache-2.0" +authors = ["Daniel Lubarov ", "William Borgeaud "] +readme = "README.md" +repository = "https://github.com/0xPolygonZero/plonky2" +keywords = ["cryptography", "STARK", "FRI"] +categories = ["cryptography"] +edition = "2021" + +[features] +default = ["parallel", "std", "timing"] +parallel = ["plonky2/parallel", "plonky2_maybe_rayon/parallel"] +std = ["anyhow/std", "plonky2/std"] +timing = ["plonky2/timing"] + +[dependencies] +ahash = { version = "0.8.3", default-features = false, features = ["compile-time-rng"] } # NOTE: Be sure to keep this version the same as the dependency in `hashbrown`. +anyhow = { version = "1.0.40", default-features = false } +hashbrown = { version = "0.14.0", default-features = false, features = ["ahash", "serde"] } # NOTE: When upgrading, see `ahash` dependency. +itertools = { version = "0.11.0", default-features = false } +log = { version = "0.4.14", default-features = false } +num-bigint = { version = "0.4.3", default-features = false } +plonky2_maybe_rayon = { path = "../maybe_rayon", default-features = false } +plonky2 = { path = "../plonky2", default-features = false } +plonky2_util = { path = "../util", default-features = false } + +[dev-dependencies] +env_logger = { version = "0.9.0", default-features = false } + +# Display math equations properly in documentation +[package.metadata.docs.rs] +rustdoc-args = ["--html-in-header", ".cargo/katex-header.html"] diff --git a/starky/LICENSE-APACHE b/starky/LICENSE-APACHE new file mode 100644 index 000000000..1e5006dc1 --- /dev/null +++ b/starky/LICENSE-APACHE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + diff --git a/starky/LICENSE-MIT b/starky/LICENSE-MIT new file mode 100644 index 000000000..86d690b22 --- /dev/null +++ b/starky/LICENSE-MIT @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2022 The Plonky2 Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/starky/README.md b/starky/README.md new file mode 100644 index 000000000..bb4e2d8a9 --- /dev/null +++ b/starky/README.md @@ -0,0 +1,13 @@ +## License + +Licensed under either of + +* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/starky/src/config.rs b/starky/src/config.rs new file mode 100644 index 000000000..199320c8f --- /dev/null +++ b/starky/src/config.rs @@ -0,0 +1,151 @@ +//! A [`StarkConfig`] defines all the parameters to be used when proving a +//! [`Stark`][crate::stark::Stark]. +//! +//! The default configuration is aimed for speed, yielding fast but large +//! proofs, with a targeted security level of 100 bits. + +#[cfg(not(feature = "std"))] +use alloc::format; + +use anyhow::{anyhow, Result}; +use plonky2::field::extension::Extendable; +use plonky2::field::types::Field; +use plonky2::fri::reduction_strategies::FriReductionStrategy; +use plonky2::fri::{FriConfig, FriParams}; +use plonky2::hash::hash_types::RichField; + +/// A configuration containing the different parameters used by the STARK +/// prover. +#[derive(Clone, Debug)] +pub struct StarkConfig { + /// The targeted security level for the proofs generated with this + /// configuration. + pub security_bits: usize, + + /// The number of challenge points to generate, for IOPs that have soundness + /// errors of (roughly) `degree / |F|`. + pub num_challenges: usize, + + /// The configuration of the FRI sub-protocol. + pub fri_config: FriConfig, +} + +impl Default for StarkConfig { + fn default() -> Self { + Self::standard_fast_config() + } +} + +impl StarkConfig { + /// Returns a custom STARK configuration. + pub const fn new(security_bits: usize, num_challenges: usize, fri_config: FriConfig) -> Self { + Self { + security_bits, + num_challenges, + fri_config, + } + } + + /// A typical configuration with a rate of 2, resulting in fast but large + /// proofs. Targets ~100 bit conjectured security. + pub const fn standard_fast_config() -> Self { + Self { + security_bits: 100, + num_challenges: 2, + fri_config: FriConfig { + rate_bits: 1, + cap_height: 4, + proof_of_work_bits: 16, + reduction_strategy: FriReductionStrategy::ConstantArityBits(4, 5), + num_query_rounds: 84, + }, + } + } + + /// Outputs the [`FriParams`] used during the FRI sub-protocol by this + /// [`StarkConfig`]. + pub fn fri_params(&self, degree_bits: usize) -> FriParams { + self.fri_config.fri_params(degree_bits, false) + } + + /// Checks that this STARK configuration is consistent, i.e. that the + /// different parameters meet the targeted security level. + pub fn check_config, const D: usize>(&self) -> Result<()> { + let StarkConfig { + security_bits, + fri_config: + FriConfig { + rate_bits, + proof_of_work_bits, + num_query_rounds, + .. + }, + .. + } = &self; + + // Conjectured FRI security; see the ethSTARK paper. + let fri_field_bits = F::Extension::order().bits() as usize; + let fri_query_security_bits = num_query_rounds * rate_bits + *proof_of_work_bits as usize; + let fri_security_bits = fri_field_bits.min(fri_query_security_bits); + + if fri_security_bits < *security_bits { + Err(anyhow!(format!( + "FRI params fall short of target security {}, reaching only {}", + security_bits, fri_security_bits + ))) + } else { + Ok(()) + } + } +} + +#[cfg(test)] +mod tests { + use plonky2::field::goldilocks_field::GoldilocksField; + + use super::*; + + #[test] + fn test_valid_config() { + type F = GoldilocksField; + const D: usize = 2; + + let config = StarkConfig::standard_fast_config(); + assert!(config.check_config::().is_ok()); + + let high_rate_config = StarkConfig::new( + 100, + 2, + FriConfig { + rate_bits: 3, + cap_height: 4, + proof_of_work_bits: 16, + reduction_strategy: FriReductionStrategy::ConstantArityBits(4, 5), + num_query_rounds: 28, + }, + ); + assert!(high_rate_config.check_config::().is_ok()); + } + + #[test] + fn test_invalid_config() { + type F = GoldilocksField; + const D: usize = 2; + + let too_few_queries_config = StarkConfig::new( + 100, + 2, + FriConfig { + rate_bits: 1, + cap_height: 4, + proof_of_work_bits: 16, + reduction_strategy: FriReductionStrategy::ConstantArityBits(4, 5), + num_query_rounds: 50, + }, + ); + // The conjectured security yields `rate_bits` * `num_query_rounds` + + // `proof_of_work_bits` = 66 bits of security for FRI, which falls short + // of the 100 bits of security target. + assert!(too_few_queries_config.check_config::().is_err()); + } +} diff --git a/starky/src/constraint_consumer.rs b/starky/src/constraint_consumer.rs new file mode 100644 index 000000000..3fda2e7a3 --- /dev/null +++ b/starky/src/constraint_consumer.rs @@ -0,0 +1,184 @@ +//! Implementation of the constraint consumer. +//! +//! The [`ConstraintConsumer`], and its circuit counterpart, allow a +//! prover to evaluate all polynomials of a [`Stark`][crate::stark::Stark]. + +#[cfg(not(feature = "std"))] +use alloc::{vec, vec::Vec}; +use core::marker::PhantomData; + +use plonky2::field::extension::Extendable; +use plonky2::field::packed::PackedField; +use plonky2::hash::hash_types::RichField; +use plonky2::iop::ext_target::ExtensionTarget; +use plonky2::iop::target::Target; +use plonky2::plonk::circuit_builder::CircuitBuilder; + +/// A [`ConstraintConsumer`] evaluates all constraint, permutation and +/// cross-table lookup polynomials of a [`Stark`][crate::stark::Stark]. +#[derive(Debug)] +pub struct ConstraintConsumer { + /// Random values used to combine multiple constraints into one. + alphas: Vec, + + /// Running sums of constraints that have been emitted so far, scaled by + /// powers of alpha. + constraint_accs: Vec

, + + /// The evaluation of `X - g^(n-1)`. + z_last: P, + + /// The evaluation of the Lagrange basis polynomial which is nonzero at the + /// point associated with the first trace row, and zero at other points + /// in the subgroup. + lagrange_basis_first: P, + + /// The evaluation of the Lagrange basis polynomial which is nonzero at the + /// point associated with the last trace row, and zero at other points + /// in the subgroup. + lagrange_basis_last: P, +} + +impl ConstraintConsumer

{ + /// Creates a new instance of [`ConstraintConsumer`]. + pub fn new( + alphas: Vec, + z_last: P, + lagrange_basis_first: P, + lagrange_basis_last: P, + ) -> Self { + Self { + constraint_accs: vec![P::ZEROS; alphas.len()], + alphas, + z_last, + lagrange_basis_first, + lagrange_basis_last, + } + } + + /// Consumes this [`ConstraintConsumer`] and outputs its sum of accumulated + /// constraints scaled by powers of `alpha`. + pub fn accumulators(self) -> Vec

{ + self.constraint_accs + } + + /// Add one constraint valid on all rows except the last. + pub fn constraint_transition(&mut self, constraint: P) { + self.constraint(constraint * self.z_last); + } + + /// Add one constraint on all rows. + pub fn constraint(&mut self, constraint: P) { + for (&alpha, acc) in self.alphas.iter().zip(&mut self.constraint_accs) { + *acc *= alpha; + *acc += constraint; + } + } + + /// Add one constraint, but first multiply it by a filter such that it will + /// only apply to the first row of the trace. + pub fn constraint_first_row(&mut self, constraint: P) { + self.constraint(constraint * self.lagrange_basis_first); + } + + /// Add one constraint, but first multiply it by a filter such that it will + /// only apply to the last row of the trace. + pub fn constraint_last_row(&mut self, constraint: P) { + self.constraint(constraint * self.lagrange_basis_last); + } +} + +/// Circuit version of [`ConstraintConsumer`]. +#[derive(Debug)] +pub struct RecursiveConstraintConsumer, const D: usize> { + /// A random value used to combine multiple constraints into one. + alphas: Vec, + + /// A running sum of constraints that have been emitted so far, scaled by + /// powers of alpha. + constraint_accs: Vec>, + + /// The evaluation of `X - g^(n-1)`. + z_last: ExtensionTarget, + + /// The evaluation of the Lagrange basis polynomial which is nonzero at the + /// point associated with the first trace row, and zero at other points + /// in the subgroup. + lagrange_basis_first: ExtensionTarget, + + /// The evaluation of the Lagrange basis polynomial which is nonzero at the + /// point associated with the last trace row, and zero at other points + /// in the subgroup. + lagrange_basis_last: ExtensionTarget, + + _phantom: PhantomData, +} + +impl, const D: usize> RecursiveConstraintConsumer { + /// Creates a new instance of [`RecursiveConstraintConsumer`]. + pub fn new( + zero: ExtensionTarget, + alphas: Vec, + z_last: ExtensionTarget, + lagrange_basis_first: ExtensionTarget, + lagrange_basis_last: ExtensionTarget, + ) -> Self { + Self { + constraint_accs: vec![zero; alphas.len()], + alphas, + z_last, + lagrange_basis_first, + lagrange_basis_last, + _phantom: Default::default(), + } + } + + /// Consumes this [`RecursiveConstraintConsumer`] and outputs its sum of + /// accumulated `Target` constraints scaled by powers of `alpha`. + pub fn accumulators(self) -> Vec> { + self.constraint_accs + } + + /// Add one constraint valid on all rows except the last. + pub fn constraint_transition( + &mut self, + builder: &mut CircuitBuilder, + constraint: ExtensionTarget, + ) { + let filtered_constraint = builder.mul_extension(constraint, self.z_last); + self.constraint(builder, filtered_constraint); + } + + /// Add one constraint valid on all rows. + pub fn constraint( + &mut self, + builder: &mut CircuitBuilder, + constraint: ExtensionTarget, + ) { + for (&alpha, acc) in self.alphas.iter().zip(&mut self.constraint_accs) { + *acc = builder.scalar_mul_add_extension(alpha, *acc, constraint); + } + } + + /// Add one constraint, but first multiply it by a filter such that it will + /// only apply to the first row of the trace. + pub fn constraint_first_row( + &mut self, + builder: &mut CircuitBuilder, + constraint: ExtensionTarget, + ) { + let filtered_constraint = builder.mul_extension(constraint, self.lagrange_basis_first); + self.constraint(builder, filtered_constraint); + } + + /// Add one constraint, but first multiply it by a filter such that it will + /// only apply to the last row of the trace. + pub fn constraint_last_row( + &mut self, + builder: &mut CircuitBuilder, + constraint: ExtensionTarget, + ) { + let filtered_constraint = builder.mul_extension(constraint, self.lagrange_basis_last); + self.constraint(builder, filtered_constraint); + } +} diff --git a/starky/src/cross_table_lookup.rs b/starky/src/cross_table_lookup.rs new file mode 100644 index 000000000..124489a71 --- /dev/null +++ b/starky/src/cross_table_lookup.rs @@ -0,0 +1,1201 @@ +//! This crate provides support for cross-table lookups. +//! +//! If a STARK S_1 calls an operation that is carried out by another STARK S_2, +//! S_1 provides the inputs to S_2 and reads the output from S_1. To ensure that +//! the operation was correctly carried out, we must check that the provided +//! inputs and outputs are correctly read. Cross-table lookups carry out that +//! check. +//! +//! To achieve this, smaller CTL tables are created on both sides: looking and +//! looked tables. In our example, we create a table S_1' comprised of columns +//! -- or linear combinations of columns -- of S_1, and rows that call +//! operations carried out in S_2. We also create a table S_2' comprised of +//! columns -- or linear combinations od columns -- of S_2 and rows that carry +//! out the operations needed by other STARKs. Then, S_1' is a looking table for +//! the looked S_2', since we want to check that the operation outputs in S_1' +//! are indeeed in S_2'. Furthermore, the concatenation of all tables looking +//! into S_2' must be equal to S_2'. +//! +//! To achieve this, we construct, for each table, a permutation polynomial +//! Z(x). Z(x) is computed as the product of all its column combinations. +//! To check it was correctly constructed, we check: +//! - Z(gw) = Z(w) * combine(w) where combine(w) is the column combination at +//! point w. +//! - Z(g^(n-1)) = combine(1). +//! - The verifier also checks that the product of looking table Z polynomials +//! is equal +//! to the associated looked table Z polynomial. +//! Note that the first two checks are written that way because Z polynomials +//! are computed upside down for convenience. +//! +//! Additionally, we support cross-table lookups over two rows. The permutation +//! principle is similar, but we provide not only `local_values` but also +//! `next_values` -- corresponding to the current and next row values -- when +//! computing the linear combinations. + +#[cfg(not(feature = "std"))] +use alloc::{vec, vec::Vec}; +use core::cmp::min; +use core::fmt::Debug; +use core::iter::once; + +use anyhow::{ensure, Result}; +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::challenger::Challenger; +use plonky2::iop::ext_target::ExtensionTarget; +use plonky2::iop::target::Target; +use plonky2::plonk::circuit_builder::CircuitBuilder; +use plonky2::plonk::config::GenericConfig; +use plonky2::util::ceil_div_usize; + +use crate::config::StarkConfig; +use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; +use crate::evaluation_frame::StarkEvaluationFrame; +use crate::lookup::{ + eval_helper_columns, eval_helper_columns_circuit, get_grand_product_challenge_set, + get_helper_cols, Column, ColumnFilter, Filter, GrandProductChallenge, GrandProductChallengeSet, +}; +use crate::proof::{MultiProof, StarkProofTarget, StarkProofWithMetadata}; +use crate::stark::Stark; + +/// An alias for `usize`, to represent the index of a STARK table in a +/// multi-STARK setting. +pub type TableIdx = usize; + +/// A `table` index with a linear combination of columns and a filter. +/// `filter` is used to determine the rows to select in `table`. +/// `columns` represents linear combinations of the columns of `table`. +#[derive(Clone, Debug)] +pub struct TableWithColumns { + table: TableIdx, + columns: Vec>, + filter: Option>, +} + +impl TableWithColumns { + /// Generates a new `TableWithColumns` given a `table` index, a linear + /// combination of columns `columns` and a `filter`. + pub fn new(table: TableIdx, columns: Vec>, filter: Option>) -> Self { + Self { + table, + columns, + filter, + } + } +} + +/// Cross-table lookup data consisting in the lookup table (`looked_table`) and +/// all the tables that look into `looked_table` (`looking_tables`). +/// Each `looking_table` corresponds to a STARK's table whose rows have been +/// filtered out and whose columns have been through a linear combination (see +/// `eval_table`). The concatenation of those smaller tables should result in +/// the `looked_table`. +#[derive(Clone, Debug)] +pub struct CrossTableLookup { + /// Column linear combinations for all tables that are looking into the + /// current table. + pub(crate) looking_tables: Vec>, + /// Column linear combination for the current table. + pub(crate) looked_table: TableWithColumns, +} + +impl CrossTableLookup { + /// Creates a new `CrossTableLookup` given some looking tables and a looked + /// table. All tables should have the same width. + pub fn new( + looking_tables: Vec>, + looked_table: TableWithColumns, + ) -> Self { + assert!(looking_tables + .iter() + .all(|twc| twc.columns.len() == looked_table.columns.len())); + Self { + looking_tables, + looked_table, + } + } + + /// Given a table, returns: + /// - the total number of helper columns for this table, over all + /// Cross-table lookups, + /// - the total number of z polynomials for this table, over all Cross-table + /// lookups, + /// - the number of helper columns for this table, for each Cross-table + /// lookup. + pub fn num_ctl_helpers_zs_all( + ctls: &[Self], + table: TableIdx, + num_challenges: usize, + constraint_degree: usize, + ) -> (usize, usize, Vec) { + let mut num_helpers = 0; + let mut num_ctls = 0; + let mut num_helpers_by_ctl = vec![0; ctls.len()]; + for (i, ctl) in ctls.iter().enumerate() { + let all_tables = once(&ctl.looked_table).chain(&ctl.looking_tables); + let num_appearances = all_tables.filter(|twc| twc.table == table).count(); + let is_helpers = num_appearances > 2; + if is_helpers { + num_helpers_by_ctl[i] = ceil_div_usize(num_appearances, constraint_degree - 1); + num_helpers += num_helpers_by_ctl[i]; + } + + if num_appearances > 0 { + num_ctls += 1; + } + } + ( + num_helpers * num_challenges, + num_ctls * num_challenges, + num_helpers_by_ctl, + ) + } +} + +/// Cross-table lookup data for one table. +#[derive(Clone, Default, Debug)] +pub struct CtlData<'a, F: Field> { + /// Data associated with all Z(x) polynomials for one table. + pub zs_columns: Vec>, +} + +/// Cross-table lookup data associated with one Z(x) polynomial. +/// One Z(x) polynomial can be associated to multiple tables, +/// built from the same STARK. +#[derive(Clone, Debug)] +pub struct CtlZData<'a, F: Field> { + /// Helper columns to verify the Z polynomial values. + pub(crate) helper_columns: Vec>, + /// Z polynomial values. + pub(crate) z: PolynomialValues, + /// Cross-table lookup challenge. + pub challenge: GrandProductChallenge, + /// Vector of column linear combinations for the current tables. + pub(crate) columns: Vec<&'a [Column]>, + /// Vector of filter columns for the current table. + /// Each filter evaluates to either 1 or 0. + pub(crate) filter: Vec>>, +} + +impl<'a, F: Field> CtlZData<'a, F> { + /// Returs new CTL data from the provided arguments. + pub fn new( + helper_columns: Vec>, + z: PolynomialValues, + challenge: GrandProductChallenge, + columns: Vec<&'a [Column]>, + filter: Vec>>, + ) -> Self { + Self { + helper_columns, + z, + challenge, + columns, + filter, + } + } +} + +impl<'a, F: Field> CtlData<'a, F> { + /// Returns all the cross-table lookup helper polynomials. + pub(crate) fn ctl_helper_polys(&self) -> Vec> { + let num_polys = self + .zs_columns + .iter() + .fold(0, |acc, z| acc + z.helper_columns.len()); + let mut res = Vec::with_capacity(num_polys); + for z in &self.zs_columns { + res.extend(z.helper_columns.clone()); + } + + res + } + + /// Returns all the Z cross-table-lookup polynomials. + pub(crate) fn ctl_z_polys(&self) -> Vec> { + let mut res = Vec::with_capacity(self.zs_columns.len()); + for z in &self.zs_columns { + res.push(z.z.clone()); + } + + res + } + /// Returns the number of helper columns for each STARK in each + /// `CtlZData`. + pub(crate) fn num_ctl_helper_polys(&self) -> Vec { + let mut res = Vec::with_capacity(self.zs_columns.len()); + for z in &self.zs_columns { + res.push(z.helper_columns.len()); + } + + res + } +} + +/// Outputs a tuple of (challenges, data) of CTL challenges and all +/// the CTL data necessary to prove a multi-STARK system. +pub fn get_ctl_data<'a, F, C, const D: usize, const N: usize>( + config: &StarkConfig, + trace_poly_values: &[Vec>; N], + all_cross_table_lookups: &'a [CrossTableLookup], + challenger: &mut Challenger, + max_constraint_degree: usize, +) -> (GrandProductChallengeSet, [CtlData<'a, F>; N]) +where + F: RichField + Extendable, + C: GenericConfig, +{ + // Get challenges for the cross-table lookups. + let ctl_challenges = get_grand_product_challenge_set(challenger, config.num_challenges); + + // For each STARK, compute its cross-table lookup Z polynomials + // and get the associated `CtlData`. + let ctl_data = cross_table_lookup_data::( + trace_poly_values, + all_cross_table_lookups, + &ctl_challenges, + max_constraint_degree, + ); + + (ctl_challenges, ctl_data) +} + +/// Outputs all the CTL data necessary to prove a multi-STARK system. +pub fn get_ctl_vars_from_proofs<'a, F, C, const D: usize, const N: usize>( + multi_proof: &MultiProof, + all_cross_table_lookups: &'a [CrossTableLookup], + ctl_challenges: &'a GrandProductChallengeSet, + num_lookup_columns: &'a [usize; N], + max_constraint_degree: usize, +) -> [Vec>::Extension, >::Extension, D>>; + N] +where + F: RichField + Extendable, + C: GenericConfig, +{ + let num_ctl_helper_cols = + num_ctl_helper_columns_by_table(all_cross_table_lookups, max_constraint_degree); + + CtlCheckVars::from_proofs( + &multi_proof.stark_proofs, + all_cross_table_lookups, + ctl_challenges, + num_lookup_columns, + &num_ctl_helper_cols, + ) +} +/// Returns the number of helper columns for each `Table`. +pub(crate) fn num_ctl_helper_columns_by_table( + ctls: &[CrossTableLookup], + constraint_degree: usize, +) -> Vec<[usize; N]> { + let mut res = vec![[0; N]; ctls.len()]; + for (i, ctl) in ctls.iter().enumerate() { + let CrossTableLookup { + looking_tables, + looked_table: _, + } = ctl; + let mut num_by_table = [0; N]; + + let grouped_lookups = looking_tables.iter().group_by(|&a| a.table); + + for (table, group) in grouped_lookups.into_iter() { + let sum = group.count(); + if sum > 2 { + // We only need helper columns if there are more than 2 columns. + num_by_table[table] = ceil_div_usize(sum, constraint_degree - 1); + } + } + + res[i] = num_by_table; + } + res +} + +/// Gets the auxiliary polynomials associated to these CTL data. +pub(crate) fn get_ctl_auxiliary_polys( + ctl_data: Option<&CtlData>, +) -> Option>> { + ctl_data.map(|data| { + let mut ctl_polys = data.ctl_helper_polys(); + ctl_polys.extend(data.ctl_z_polys()); + ctl_polys + }) +} + +/// Generates all the cross-table lookup data, for all tables. +/// - `trace_poly_values` corresponds to the trace values for all tables. +/// - `cross_table_lookups` corresponds to all the cross-table lookups, i.e. the +/// looked and looking tables, as described in `CrossTableLookup`. +/// - `ctl_challenges` corresponds to the challenges used for CTLs. +/// - `constraint_degree` is the maximal constraint degree for the table. +/// For each `CrossTableLookup`, and each looking/looked table, the partial +/// products for the CTL are computed, and added to the said table's `CtlZData`. +pub(crate) fn cross_table_lookup_data<'a, F: RichField, const D: usize, const N: usize>( + trace_poly_values: &[Vec>; N], + cross_table_lookups: &'a [CrossTableLookup], + ctl_challenges: &GrandProductChallengeSet, + constraint_degree: usize, +) -> [CtlData<'a, F>; N] { + let mut ctl_data_per_table = [0; N].map(|_| CtlData::default()); + for CrossTableLookup { + looking_tables, + looked_table, + } in cross_table_lookups + { + log::debug!("Processing CTL for {:?}", looked_table.table); + for &challenge in &ctl_challenges.challenges { + let helper_zs_looking = ctl_helper_zs_cols( + trace_poly_values, + looking_tables.clone(), + challenge, + constraint_degree, + ); + + let z_looked = partial_sums( + &trace_poly_values[looked_table.table], + &[(&looked_table.columns, &looked_table.filter)], + challenge, + constraint_degree, + ); + + for (table, helpers_zs) in helper_zs_looking { + let num_helpers = helpers_zs.len() - 1; + let count = looking_tables + .iter() + .filter(|looking_table| looking_table.table == table) + .count(); + let cols_filts = looking_tables.iter().filter_map(|looking_table| { + if looking_table.table == table { + Some((&looking_table.columns, &looking_table.filter)) + } else { + None + } + }); + let mut columns = Vec::with_capacity(count); + let mut filter = Vec::with_capacity(count); + for (col, filt) in cols_filts { + columns.push(&col[..]); + filter.push(filt.clone()); + } + ctl_data_per_table[table].zs_columns.push(CtlZData { + helper_columns: helpers_zs[..num_helpers].to_vec(), + z: helpers_zs[num_helpers].clone(), + challenge, + columns, + filter, + }); + } + // There is no helper column for the looking table. + let looked_poly = z_looked[0].clone(); + ctl_data_per_table[looked_table.table] + .zs_columns + .push(CtlZData { + helper_columns: vec![], + z: looked_poly, + challenge, + columns: vec![&looked_table.columns[..]], + filter: vec![looked_table.filter.clone()], + }); + } + } + ctl_data_per_table +} + +/// Computes helper columns and Z polynomials for all looking tables +/// of one cross-table lookup (i.e. for one looked table). +fn ctl_helper_zs_cols( + all_stark_traces: &[Vec>; N], + looking_tables: Vec>, + challenge: GrandProductChallenge, + constraint_degree: usize, +) -> Vec<(usize, Vec>)> { + let grouped_lookups = looking_tables.iter().group_by(|a| a.table); + + grouped_lookups + .into_iter() + .map(|(table, group)| { + let columns_filters = group + .map(|table| (&table.columns[..], &table.filter)) + .collect::], &Option>)>>(); + ( + table, + partial_sums( + &all_stark_traces[table], + &columns_filters, + challenge, + constraint_degree, + ), + ) + }) + .collect::>)>>() +} + +/// Computes the cross-table lookup partial sums for one table and given column +/// linear combinations. `trace` represents the trace values for the given +/// table. `columns` is a vector of column linear combinations to evaluate. Each +/// element in the vector represents columns that need to be combined. +/// `filter_cols` are column linear combinations used to determine whether a row +/// should be selected. `challenge` is a cross-table lookup challenge. +/// The initial sum `s` is 0. +/// For each row, if the `filter_column` evaluates to 1, then the row is +/// selected. All the column linear combinations are evaluated at said row. +/// The evaluations of each elements of `columns` are then combined together to +/// form a value `v`. The values `v`` are grouped together, in groups of size +/// `constraint_degree - 1` (2 in our case). For each group, we construct a +/// helper column: h = \sum_i 1/(v_i). +/// +/// The sum is updated: `s += \sum h_i`, and is pushed to the vector of partial +/// sums `z``. Returns the helper columns and `z`. +fn partial_sums( + trace: &[PolynomialValues], + columns_filters: &[ColumnFilter], + challenge: GrandProductChallenge, + constraint_degree: usize, +) -> Vec> { + let degree = trace[0].len(); + let mut z = Vec::with_capacity(degree); + + let mut helper_columns = + get_helper_cols(trace, degree, columns_filters, challenge, constraint_degree); + + let x = helper_columns + .iter() + .map(|col| col.values[degree - 1]) + .sum::(); + z.push(x); + + for i in (0..degree - 1).rev() { + let x = helper_columns.iter().map(|col| col.values[i]).sum::(); + + z.push(z[z.len() - 1] + x); + } + z.reverse(); + if columns_filters.len() > 2 { + helper_columns.push(z.into()); + } else { + helper_columns = vec![z.into()]; + } + + helper_columns +} + +/// Data necessary to check the cross-table lookups of a given table. +#[derive(Clone, Debug)] +pub struct CtlCheckVars<'a, F, FE, P, const D2: usize> +where + F: Field, + FE: FieldExtension, + P: PackedField, +{ + /// Helper columns to check that the Z polyomial + /// was constructed correctly. + pub(crate) helper_columns: Vec

, + /// Evaluation of the trace polynomials at point `zeta`. + pub(crate) local_z: P, + /// Evaluation of the trace polynomials at point `g * zeta` + pub(crate) next_z: P, + /// Cross-table lookup challenges. + pub(crate) challenges: GrandProductChallenge, + /// Column linear combinations of the `CrossTableLookup`s. + pub(crate) columns: Vec<&'a [Column]>, + /// Filter that evaluates to either 1 or 0. + pub(crate) filter: Vec>>, +} + +impl<'a, F: RichField + Extendable, const D: usize> + CtlCheckVars<'a, F, F::Extension, F::Extension, D> +{ + /// Extracts the `CtlCheckVars` for each STARK. + pub fn from_proofs, const N: usize>( + proofs: &[StarkProofWithMetadata; N], + cross_table_lookups: &'a [CrossTableLookup], + ctl_challenges: &'a GrandProductChallengeSet, + num_lookup_columns: &[usize; N], + num_helper_ctl_columns: &Vec<[usize; N]>, + ) -> [Vec; N] { + let mut ctl_vars_per_table = [0; N].map(|_| vec![]); + // If there are no auxiliary polys in the proofs `openings`, + // return early. The verifier will reject the proofs when + // calling `validate_proof_shape`. + if proofs + .iter() + .any(|p| p.proof.openings.auxiliary_polys.is_none()) + { + return ctl_vars_per_table; + } + + let mut total_num_helper_cols_by_table = [0; N]; + for p_ctls in num_helper_ctl_columns { + for j in 0..N { + total_num_helper_cols_by_table[j] += p_ctls[j] * ctl_challenges.challenges.len(); + } + } + + // Get all cross-table lookup polynomial openings for each STARK proof. + let ctl_zs = proofs + .iter() + .zip(num_lookup_columns) + .map(|(p, &num_lookup)| { + let openings = &p.proof.openings; + + let ctl_zs = &openings + .auxiliary_polys + .as_ref() + .expect("We cannot have CTls without auxiliary polynomials.")[num_lookup..]; + let ctl_zs_next = &openings + .auxiliary_polys_next + .as_ref() + .expect("We cannot have CTls without auxiliary polynomials.")[num_lookup..]; + ctl_zs.iter().zip(ctl_zs_next).collect::>() + }) + .collect::>(); + + // Put each cross-table lookup polynomial into the correct table data: if a CTL + // polynomial is extracted from looking/looked table t, then we add it to the + // `CtlCheckVars` of table t. + let mut start_indices = [0; N]; + let mut z_indices = [0; N]; + for ( + CrossTableLookup { + looking_tables, + looked_table, + }, + num_ctls, + ) in cross_table_lookups.iter().zip(num_helper_ctl_columns) + { + for &challenges in &ctl_challenges.challenges { + // Group looking tables by `Table`, since we bundle the looking tables taken + // from the same `Table` together thanks to helper columns. + // We want to only iterate on each `Table` once. + let mut filtered_looking_tables = Vec::with_capacity(min(looking_tables.len(), N)); + for table in looking_tables { + if !filtered_looking_tables.contains(&(table.table)) { + filtered_looking_tables.push(table.table); + } + } + + for &table in filtered_looking_tables.iter() { + // We have first all the helper polynomials, then all the z polynomials. + let (looking_z, looking_z_next) = + ctl_zs[table][total_num_helper_cols_by_table[table] + z_indices[table]]; + + let count = looking_tables + .iter() + .filter(|looking_table| looking_table.table == table) + .count(); + let cols_filts = looking_tables.iter().filter_map(|looking_table| { + if looking_table.table == table { + Some((&looking_table.columns, &looking_table.filter)) + } else { + None + } + }); + let mut columns = Vec::with_capacity(count); + let mut filter = Vec::with_capacity(count); + for (col, filt) in cols_filts { + columns.push(&col[..]); + filter.push(filt.clone()); + } + let helper_columns = ctl_zs[table] + [start_indices[table]..start_indices[table] + num_ctls[table]] + .iter() + .map(|&(h, _)| *h) + .collect::>(); + + start_indices[table] += num_ctls[table]; + + z_indices[table] += 1; + ctl_vars_per_table[table].push(Self { + helper_columns, + local_z: *looking_z, + next_z: *looking_z_next, + challenges, + columns, + filter, + }); + } + + let (looked_z, looked_z_next) = ctl_zs[looked_table.table] + [total_num_helper_cols_by_table[looked_table.table] + + z_indices[looked_table.table]]; + + z_indices[looked_table.table] += 1; + + let columns = vec![&looked_table.columns[..]]; + let filter = vec![looked_table.filter.clone()]; + ctl_vars_per_table[looked_table.table].push(Self { + helper_columns: vec![], + local_z: *looked_z, + next_z: *looked_z_next, + challenges, + columns, + filter, + }); + } + } + ctl_vars_per_table + } +} + +/// Checks the cross-table lookup Z polynomials for each table: +/// - Checks that the CTL `Z` partial sums are correctly updated. +/// - Checks that the final value of the CTL sum is the combination of all +/// STARKs' CTL polynomials. +/// CTL `Z` partial sums are upside down: the complete sum is on the first row, +/// and the first term is on the last row. This allows the transition constraint +/// to be: `combine(w) * (Z(w) - Z(gw)) = filter` where combine is called on the +/// local row and not the next. This enables CTLs across two rows. +pub(crate) fn eval_cross_table_lookup_checks( + vars: &S::EvaluationFrame, + ctl_vars: &[CtlCheckVars], + consumer: &mut ConstraintConsumer

, + constraint_degree: usize, +) where + F: RichField + Extendable, + FE: FieldExtension, + P: PackedField, + S: Stark, +{ + let local_values = vars.get_local_values(); + let next_values = vars.get_next_values(); + + for lookup_vars in ctl_vars { + let CtlCheckVars { + helper_columns, + local_z, + next_z, + challenges, + columns, + filter, + } = lookup_vars; + + // Compute all linear combinations on the current table, and combine them using + // the challenge. + let evals = columns + .iter() + .map(|col| { + col.iter() + .map(|c| c.eval_with_next(local_values, next_values)) + .collect::>() + }) + .collect::>(); + + // Check helper columns. + eval_helper_columns( + filter, + &evals, + local_values, + next_values, + helper_columns, + constraint_degree, + challenges, + consumer, + ); + + if !helper_columns.is_empty() { + let h_sum = helper_columns.iter().fold(P::ZEROS, |acc, x| acc + *x); + // Check value of `Z(g^(n-1))` + consumer.constraint_last_row(*local_z - h_sum); + // Check `Z(w) = Z(gw) + \sum h_i` + consumer.constraint_transition(*local_z - *next_z - h_sum); + } else if columns.len() > 1 { + let combin0 = challenges.combine(&evals[0]); + let combin1 = challenges.combine(&evals[1]); + + let f0 = if let Some(filter0) = &filter[0] { + filter0.eval_filter(local_values, next_values) + } else { + P::ONES + }; + let f1 = if let Some(filter1) = &filter[1] { + filter1.eval_filter(local_values, next_values) + } else { + P::ONES + }; + + consumer + .constraint_last_row(combin0 * combin1 * *local_z - f0 * combin1 - f1 * combin0); + consumer.constraint_transition( + combin0 * combin1 * (*local_z - *next_z) - f0 * combin1 - f1 * combin0, + ); + } else { + let combin0 = challenges.combine(&evals[0]); + let f0 = if let Some(filter0) = &filter[0] { + filter0.eval_filter(local_values, next_values) + } else { + P::ONES + }; + consumer.constraint_last_row(combin0 * *local_z - f0); + consumer.constraint_transition(combin0 * (*local_z - *next_z) - f0); + } + } +} + +/// Circuit version of `CtlCheckVars`. Data necessary to check the cross-table +/// lookups of a given table. +#[derive(Clone, Debug)] +pub struct CtlCheckVarsTarget { + ///Evaluation of the helper columns to check that the Z polyomial + /// was constructed correctly. + pub(crate) helper_columns: Vec>, + /// Evaluation of the trace polynomials at point `zeta`. + pub(crate) local_z: ExtensionTarget, + /// Evaluation of the trace polynomials at point `g * zeta`. + pub(crate) next_z: ExtensionTarget, + /// Cross-table lookup challenges. + pub(crate) challenges: GrandProductChallenge, + /// Column linear combinations of the `CrossTableLookup`s. + pub(crate) columns: Vec>>, + /// Filter that evaluates to either 1 or 0. + pub(crate) filter: Vec>>, +} + +impl<'a, F: Field, const D: usize> CtlCheckVarsTarget { + /// Circuit version of `from_proofs`, for a single STARK. + pub fn from_proof( + table: TableIdx, + proof: &StarkProofTarget, + cross_table_lookups: &'a [CrossTableLookup], + ctl_challenges: &'a GrandProductChallengeSet, + num_lookup_columns: usize, + total_num_helper_columns: usize, + num_helper_ctl_columns: &[usize], + ) -> Vec { + // Get all cross-table lookup polynomial openings for each STARK proof. + let ctl_zs = { + let openings = &proof.openings; + let ctl_zs = openings + .auxiliary_polys + .as_ref() + .expect("We cannot have CTls without auxiliary polynomials.") + .iter() + .skip(num_lookup_columns); + let ctl_zs_next = openings + .auxiliary_polys_next + .as_ref() + .expect("We cannot have CTls without auxiliary polynomials.") + .iter() + .skip(num_lookup_columns); + ctl_zs.zip(ctl_zs_next).collect::>() + }; + + // Put each cross-table lookup polynomial into the correct table's data. + // If a CTL polynomial is extracted from the looking/looked table `t``, + // then we add it to the `CtlCheckVars` of table `t``. + let mut z_index = 0; + let mut start_index = 0; + let mut ctl_vars = vec![]; + for ( + i, + CrossTableLookup { + looking_tables, + looked_table, + }, + ) in cross_table_lookups.iter().enumerate() + { + for &challenges in &ctl_challenges.challenges { + // Group looking tables by `Table`, since we bundle the looking tables + // taken from the same `Table` together thanks to helper columns. + + let count = looking_tables + .iter() + .filter(|looking_table| looking_table.table == table) + .count(); + let cols_filts = looking_tables.iter().filter_map(|looking_table| { + if looking_table.table == table { + Some((&looking_table.columns, &looking_table.filter)) + } else { + None + } + }); + if count > 0 { + let mut columns = Vec::with_capacity(count); + let mut filter = Vec::with_capacity(count); + for (col, filt) in cols_filts { + columns.push(col.clone()); + filter.push(filt.clone()); + } + let (looking_z, looking_z_next) = ctl_zs[total_num_helper_columns + z_index]; + let helper_columns = ctl_zs + [start_index..start_index + num_helper_ctl_columns[i]] + .iter() + .map(|(&h, _)| h) + .collect::>(); + + start_index += num_helper_ctl_columns[i]; + z_index += 1; + ctl_vars.push(Self { + helper_columns, + local_z: *looking_z, + next_z: *looking_z_next, + challenges, + columns, + filter, + }); + } + + if looked_table.table == table { + let (looked_z, looked_z_next) = ctl_zs[total_num_helper_columns + z_index]; + z_index += 1; + + let columns = vec![looked_table.columns.clone()]; + let filter = vec![looked_table.filter.clone()]; + ctl_vars.push(Self { + helper_columns: vec![], + local_z: *looked_z, + next_z: *looked_z_next, + challenges, + columns, + filter, + }); + } + } + } + + ctl_vars + } +} + +/// Circuit version of `eval_cross_table_lookup_checks`. Checks the cross-table +/// lookup Z polynomials for each table: +/// - Checks that the CTL `Z` partial sums are correctly updated. +/// - Checks that the final value of the CTL sum is the combination of all +/// STARKs' CTL polynomials. +/// CTL `Z` partial sums are upside down: the complete sum is on the first row, +/// and the first term is on the last row. This allows the transition constraint +/// to be: `combine(w) * (Z(w) - Z(gw)) = filter` where combine is called on the +/// local row and not the next. This enables CTLs across two rows. +pub(crate) fn eval_cross_table_lookup_checks_circuit< + S: Stark, + F: RichField + Extendable, + const D: usize, +>( + builder: &mut CircuitBuilder, + vars: &S::EvaluationFrameTarget, + ctl_vars: &[CtlCheckVarsTarget], + consumer: &mut RecursiveConstraintConsumer, + constraint_degree: usize, +) { + let local_values = vars.get_local_values(); + let next_values = vars.get_next_values(); + + let one = builder.one_extension(); + + for lookup_vars in ctl_vars { + let CtlCheckVarsTarget { + helper_columns, + local_z, + next_z, + challenges, + columns, + filter, + } = lookup_vars; + + // Compute all linear combinations on the current table, and combine them using + // the challenge. + let evals = columns + .iter() + .map(|col| { + col.iter() + .map(|c| c.eval_with_next_circuit(builder, local_values, next_values)) + .collect::>() + }) + .collect::>(); + + // Check helper columns. + eval_helper_columns_circuit( + builder, + filter, + &evals, + local_values, + next_values, + helper_columns, + constraint_degree, + challenges, + consumer, + ); + + let z_diff = builder.sub_extension(*local_z, *next_z); + if !helper_columns.is_empty() { + // Check value of `Z(g^(n-1))` + let h_sum = builder.add_many_extension(helper_columns); + + let last_row = builder.sub_extension(*local_z, h_sum); + consumer.constraint_last_row(builder, last_row); + // Check `Z(w) = Z(gw) * (filter / combination)` + + let transition = builder.sub_extension(z_diff, h_sum); + consumer.constraint_transition(builder, transition); + } else if columns.len() > 1 { + let combin0 = challenges.combine_circuit(builder, &evals[0]); + let combin1 = challenges.combine_circuit(builder, &evals[1]); + + let f0 = if let Some(filter0) = &filter[0] { + filter0.eval_filter_circuit(builder, local_values, next_values) + } else { + one + }; + let f1 = if let Some(filter1) = &filter[1] { + filter1.eval_filter_circuit(builder, local_values, next_values) + } else { + one + }; + + let combined = builder.mul_sub_extension(combin1, *local_z, f1); + let combined = builder.mul_extension(combined, combin0); + let constr = builder.arithmetic_extension(F::NEG_ONE, F::ONE, f0, combin1, combined); + consumer.constraint_last_row(builder, constr); + + let combined = builder.mul_sub_extension(combin1, z_diff, f1); + let combined = builder.mul_extension(combined, combin0); + let constr = builder.arithmetic_extension(F::NEG_ONE, F::ONE, f0, combin1, combined); + consumer.constraint_last_row(builder, constr); + } else { + let combin0 = challenges.combine_circuit(builder, &evals[0]); + let f0 = if let Some(filter0) = &filter[0] { + filter0.eval_filter_circuit(builder, local_values, next_values) + } else { + one + }; + + let constr = builder.mul_sub_extension(combin0, *local_z, f0); + consumer.constraint_last_row(builder, constr); + let constr = builder.mul_sub_extension(combin0, z_diff, f0); + consumer.constraint_transition(builder, constr); + } + } +} + +/// Verifies all cross-table lookups. +pub fn verify_cross_table_lookups, const D: usize, const N: usize>( + cross_table_lookups: &[CrossTableLookup], + ctl_zs_first: [Vec; N], + ctl_extra_looking_sums: Option<&[Vec]>, + config: &StarkConfig, +) -> Result<()> { + let mut ctl_zs_openings = ctl_zs_first.iter().map(|v| v.iter()).collect::>(); + for ( + index, + CrossTableLookup { + looking_tables, + looked_table, + }, + ) in cross_table_lookups.iter().enumerate() + { + // Get elements looking into `looked_table` that are not associated to any + // STARK. + let extra_sum_vec: &[F] = ctl_extra_looking_sums + .map(|v| v[looked_table.table].as_ref()) + .unwrap_or_default(); + // We want to iterate on each looking table only once. + let mut filtered_looking_tables = vec![]; + for table in looking_tables { + if !filtered_looking_tables.contains(&(table.table)) { + filtered_looking_tables.push(table.table); + } + } + for c in 0..config.num_challenges { + // Compute the combination of all looking table CTL polynomial openings. + + let looking_zs_sum = filtered_looking_tables + .iter() + .map(|&table| *ctl_zs_openings[table].next().unwrap()) + .sum::() + + extra_sum_vec[c]; + + // Get the looked table CTL polynomial opening. + let looked_z = *ctl_zs_openings[looked_table.table].next().unwrap(); + // Ensure that the combination of looking table openings is equal to the looked + // table opening. + ensure!( + looking_zs_sum == looked_z, + "Cross-table lookup {:?} verification failed.", + index + ); + } + } + debug_assert!(ctl_zs_openings.iter_mut().all(|iter| iter.next().is_none())); + + Ok(()) +} + +/// Circuit version of `verify_cross_table_lookups`. Verifies all cross-table +/// lookups. +pub fn verify_cross_table_lookups_circuit< + F: RichField + Extendable, + const D: usize, + const N: usize, +>( + builder: &mut CircuitBuilder, + cross_table_lookups: Vec>, + ctl_zs_first: [Vec; N], + ctl_extra_looking_sums: Option<&[Vec]>, + inner_config: &StarkConfig, +) { + let mut ctl_zs_openings = ctl_zs_first.iter().map(|v| v.iter()).collect::>(); + for CrossTableLookup { + looking_tables, + looked_table, + } in cross_table_lookups.into_iter() + { + // Get elements looking into `looked_table` that are not associated to any + // STARK. + let extra_sum_vec: &[Target] = ctl_extra_looking_sums + .map(|v| v[looked_table.table].as_ref()) + .unwrap_or_default(); + // We want to iterate on each looking table only once. + let mut filtered_looking_tables = vec![]; + for table in looking_tables { + if !filtered_looking_tables.contains(&(table.table)) { + filtered_looking_tables.push(table.table); + } + } + for c in 0..inner_config.num_challenges { + // Compute the combination of all looking table CTL polynomial openings. + let mut looking_zs_sum = builder.add_many( + filtered_looking_tables + .iter() + .map(|&table| *ctl_zs_openings[table].next().unwrap()), + ); + + looking_zs_sum = builder.add(looking_zs_sum, extra_sum_vec[c]); + + // Get the looked table CTL polynomial opening. + let looked_z = *ctl_zs_openings[looked_table.table].next().unwrap(); + // Verify that the combination of looking table openings is equal to the looked + // table opening. + builder.connect(looked_z, looking_zs_sum); + } + } + debug_assert!(ctl_zs_openings.iter_mut().all(|iter| iter.next().is_none())); +} + +/// Debugging module, to assert correctness of the different CTLs of a +/// multi-STARK system, that can be used during the proof generation process. +/// +/// **Note**: this is an expensive check, hence is only available when the +/// `debug_assertions` flag is activated, to not hinder performances with +/// regular `release` build. +#[cfg(debug_assertions)] +pub mod debug_utils { + #[cfg(not(feature = "std"))] + use alloc::{vec, vec::Vec}; + + use hashbrown::HashMap; + use plonky2::field::polynomial::PolynomialValues; + use plonky2::field::types::Field; + + use super::{CrossTableLookup, TableIdx, TableWithColumns}; + + type MultiSet = HashMap, Vec<(TableIdx, usize)>>; + + /// Check that the provided traces and cross-table lookups are consistent. + pub fn check_ctls( + trace_poly_values: &[Vec>], + cross_table_lookups: &[CrossTableLookup], + extra_looking_values: &HashMap>>, + ) { + for (i, ctl) in cross_table_lookups.iter().enumerate() { + check_ctl(trace_poly_values, ctl, i, extra_looking_values.get(&i)); + } + } + + fn check_ctl( + trace_poly_values: &[Vec>], + ctl: &CrossTableLookup, + ctl_index: usize, + extra_looking_values: Option<&Vec>>, + ) { + let CrossTableLookup { + looking_tables, + looked_table, + } = ctl; + + // Maps `m` with `(table, i) in m[row]` iff the `i`-th row of `table` is equal + // to `row` and the filter is 1. Without default values, the CTL check + // holds iff `looking_multiset == looked_multiset`. + let mut looking_multiset = MultiSet::::new(); + let mut looked_multiset = MultiSet::::new(); + + for table in looking_tables { + process_table(trace_poly_values, table, &mut looking_multiset); + } + process_table(trace_poly_values, looked_table, &mut looked_multiset); + + // Include extra looking values if any for this `ctl_index`. + if let Some(values) = extra_looking_values { + for row in values.iter() { + // The table and the row index don't matter here, as we just want to enforce + // that the special extra values do appear when looking against the specified + // table. + looking_multiset + .entry(row.to_vec()) + .or_default() + .push((0, 0)); + } + } + + let empty = &vec![]; + // Check that every row in the looking tables appears in the looked table the + // same number of times. + for (row, looking_locations) in &looking_multiset { + let looked_locations = looked_multiset.get(row).unwrap_or(empty); + check_locations(looking_locations, looked_locations, ctl_index, row); + } + // Check that every row in the looked tables appears in the looked table the + // same number of times. + for (row, looked_locations) in &looked_multiset { + let looking_locations = looking_multiset.get(row).unwrap_or(empty); + check_locations(looking_locations, looked_locations, ctl_index, row); + } + } + + fn process_table( + trace_poly_values: &[Vec>], + table: &TableWithColumns, + multiset: &mut MultiSet, + ) { + let trace = &trace_poly_values[table.table]; + for i in 0..trace[0].len() { + let filter = if let Some(combin) = &table.filter { + combin.eval_table(trace, i) + } else { + F::ONE + }; + if filter.is_one() { + let row = table + .columns + .iter() + .map(|c| c.eval_table(trace, i)) + .collect::>(); + multiset.entry(row).or_default().push((table.table, i)); + } else { + assert_eq!(filter, F::ZERO, "Non-binary filter?") + } + } + } + + fn check_locations( + looking_locations: &[(TableIdx, usize)], + looked_locations: &[(TableIdx, usize)], + ctl_index: usize, + row: &[F], + ) { + if looking_locations.len() != looked_locations.len() { + panic!( + "CTL #{ctl_index}:\n\ + Row {row:?} is present {l0} times in the looking tables, but {l1} times in the looked table.\n\ + Looking locations (Table, Row index): {looking_locations:?}.\n\ + Looked locations (Table, Row index): {looked_locations:?}.", + l0 = looking_locations.len(), + l1 = looked_locations.len(), + ); + } + } +} diff --git a/starky/src/evaluation_frame.rs b/starky/src/evaluation_frame.rs new file mode 100644 index 000000000..b455b047e --- /dev/null +++ b/starky/src/evaluation_frame.rs @@ -0,0 +1,74 @@ +//! Implementation of constraint evaluation frames for STARKs. + +/// A trait for viewing an evaluation frame of a STARK table. +/// +/// It allows to access the current and next rows at a given step +/// and can be used to implement constraint evaluation both natively +/// and recursively. +pub trait StarkEvaluationFrame: + Sized +{ + /// The number of columns for the STARK table this evaluation frame views. + const COLUMNS: usize; + /// The number of public inputs for the STARK. + const PUBLIC_INPUTS: usize; + + /// Returns the local values (i.e. current row) for this evaluation frame. + fn get_local_values(&self) -> &[T]; + /// Returns the next values (i.e. next row) for this evaluation frame. + fn get_next_values(&self) -> &[T]; + + /// Returns the public inputs for this evaluation frame. + fn get_public_inputs(&self) -> &[U]; + + /// Outputs a new evaluation frame from the provided local and next values. + /// + /// **NOTE**: Concrete implementations of this method SHOULD ensure that + /// the provided slices lengths match the `Self::COLUMNS` value. + fn from_values(lv: &[T], nv: &[T], pis: &[U]) -> Self; +} + +/// An evaluation frame to be used when defining constraints of a STARK system, +/// that implements the [`StarkEvaluationFrame`] trait. +#[derive(Debug)] +pub struct StarkFrame< + T: Copy + Clone + Default, + U: Copy + Clone + Default, + const N: usize, + const N2: usize, +> { + local_values: [T; N], + next_values: [T; N], + public_inputs: [U; N2], +} + +impl + StarkEvaluationFrame for StarkFrame +{ + const COLUMNS: usize = N; + const PUBLIC_INPUTS: usize = N2; + + fn get_local_values(&self) -> &[T] { + &self.local_values + } + + fn get_next_values(&self) -> &[T] { + &self.next_values + } + + fn get_public_inputs(&self) -> &[U] { + &self.public_inputs + } + + fn from_values(lv: &[T], nv: &[T], pis: &[U]) -> Self { + assert_eq!(lv.len(), Self::COLUMNS); + assert_eq!(nv.len(), Self::COLUMNS); + assert_eq!(pis.len(), Self::PUBLIC_INPUTS); + + Self { + local_values: lv.try_into().unwrap(), + next_values: nv.try_into().unwrap(), + public_inputs: pis.try_into().unwrap(), + } + } +} diff --git a/starky/src/fibonacci_stark.rs b/starky/src/fibonacci_stark.rs new file mode 100644 index 000000000..4089f30c3 --- /dev/null +++ b/starky/src/fibonacci_stark.rs @@ -0,0 +1,444 @@ +//! An example of generating and verifying STARK proofs for the Fibonacci +//! sequence. The toy STARK system also includes two columns that are a +//! permutation of the other, to highlight the use of the permutation argument +//! with logUp. + +#[cfg(not(feature = "std"))] +use alloc::{vec, vec::Vec}; +use core::marker::PhantomData; + +use plonky2::field::extension::{Extendable, FieldExtension}; +use plonky2::field::packed::PackedField; +use plonky2::field::polynomial::PolynomialValues; +use plonky2::hash::hash_types::RichField; +use plonky2::iop::ext_target::ExtensionTarget; +use plonky2::plonk::circuit_builder::CircuitBuilder; + +use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; +use crate::evaluation_frame::{StarkEvaluationFrame, StarkFrame}; +use crate::lookup::{Column, Lookup}; +use crate::stark::Stark; +use crate::util::trace_rows_to_poly_values; + +/// Toy STARK system used for testing. +/// Computes a Fibonacci sequence with state `[x0, x1]` using the state +/// transition `x0' <- x1, x1' <- x0 + x1. +#[derive(Copy, Clone)] +struct FibonacciStark, const D: usize> { + num_rows: usize, + _phantom: PhantomData, +} + +impl, const D: usize> FibonacciStark { + // The first public input is `x0`. + const PI_INDEX_X0: usize = 0; + // The second public input is `x1`. + const PI_INDEX_X1: usize = 1; + // The third public input is the second element of the last row, which should be + // equal to the `num_rows`-th Fibonacci number. + const PI_INDEX_RES: usize = 2; + + const fn new(num_rows: usize) -> Self { + Self { + num_rows, + _phantom: PhantomData, + } + } + + /// Generate the trace using `x0, x1` as initial state values. + fn generate_trace(&self, x0: F, x1: F) -> Vec> { + let trace_rows = (0..self.num_rows) + .scan([x0, x1], |acc, _| { + let tmp = *acc; + acc[0] = tmp[1]; + acc[1] = tmp[0] + tmp[1]; + Some(tmp) + }) + .collect::>(); + trace_rows_to_poly_values(trace_rows) + } +} + +const FIBONACCI_COLUMNS: usize = 2; +const FIBONACCI_PUBLIC_INPUTS: usize = 3; + +impl, const D: usize> Stark for FibonacciStark { + type EvaluationFrame = StarkFrame + where + FE: FieldExtension, + P: PackedField; + + type EvaluationFrameTarget = StarkFrame< + ExtensionTarget, + ExtensionTarget, + FIBONACCI_COLUMNS, + FIBONACCI_PUBLIC_INPUTS, + >; + + fn eval_packed_generic( + &self, + vars: &Self::EvaluationFrame, + yield_constr: &mut ConstraintConsumer

, + ) where + FE: FieldExtension, + P: PackedField, + { + let local_values = vars.get_local_values(); + let next_values = vars.get_next_values(); + let public_inputs = vars.get_public_inputs(); + + // Check public inputs. + yield_constr.constraint_first_row(local_values[0] - public_inputs[Self::PI_INDEX_X0]); + yield_constr.constraint_first_row(local_values[1] - public_inputs[Self::PI_INDEX_X1]); + yield_constr.constraint_last_row(local_values[1] - public_inputs[Self::PI_INDEX_RES]); + + // x0' <- x1 + yield_constr.constraint_transition(next_values[0] - local_values[1]); + // x1' <- x0 + x1 + yield_constr.constraint_transition(next_values[1] - local_values[0] - local_values[1]); + } + + fn eval_ext_circuit( + &self, + builder: &mut CircuitBuilder, + vars: &Self::EvaluationFrameTarget, + yield_constr: &mut RecursiveConstraintConsumer, + ) { + let local_values = vars.get_local_values(); + let next_values = vars.get_next_values(); + let public_inputs = vars.get_public_inputs(); + // Check public inputs. + let pis_constraints = [ + builder.sub_extension(local_values[0], public_inputs[Self::PI_INDEX_X0]), + builder.sub_extension(local_values[1], public_inputs[Self::PI_INDEX_X1]), + builder.sub_extension(local_values[1], public_inputs[Self::PI_INDEX_RES]), + ]; + yield_constr.constraint_first_row(builder, pis_constraints[0]); + yield_constr.constraint_first_row(builder, pis_constraints[1]); + yield_constr.constraint_last_row(builder, pis_constraints[2]); + + // x0' <- x1 + let first_col_constraint = builder.sub_extension(next_values[0], local_values[1]); + yield_constr.constraint_transition(builder, first_col_constraint); + // x1' <- x0 + x1 + let second_col_constraint = { + let tmp = builder.sub_extension(next_values[1], local_values[0]); + builder.sub_extension(tmp, local_values[1]) + }; + yield_constr.constraint_transition(builder, second_col_constraint); + } + + fn constraint_degree(&self) -> usize { + 2 + } +} + +/// Similar system than above, but with extra columns to illustrate the +/// permutation argument. Computes a Fibonacci sequence with state `[x0, x1, i, +/// j]` using the state transition `x0' <- x1, x1' <- x0 + x1, i' <- i+1, j' <- +/// j+1`. Note: The `i, j` columns are the columns used to test the permutation +/// argument. +#[derive(Copy, Clone)] +struct FibonacciWithPermutationStark, const D: usize> { + num_rows: usize, + _phantom: PhantomData, +} + +impl, const D: usize> FibonacciWithPermutationStark { + // The first public input is `x0`. + const PI_INDEX_X0: usize = 0; + // The second public input is `x1`. + const PI_INDEX_X1: usize = 1; + // The third public input is the second element of the last row, which should be + // equal to the `num_rows`-th Fibonacci number. + const PI_INDEX_RES: usize = 2; + + const fn new(num_rows: usize) -> Self { + Self { + num_rows, + _phantom: PhantomData, + } + } + + /// Generate the trace using `x0, x1, 0, 1, 1` as initial state values. + fn generate_trace(&self, x0: F, x1: F) -> Vec> { + let mut trace_rows = (0..self.num_rows) + .scan([x0, x1, F::ZERO, F::ONE, F::ONE], |acc, _| { + let tmp = *acc; + acc[0] = tmp[1]; + acc[1] = tmp[0] + tmp[1]; + acc[2] = tmp[2] + F::ONE; + acc[3] = tmp[3] + F::ONE; + // acc[4] (i.e. frequency column) remains unchanged, as we're permuting a + // strictly monotonous sequence. + Some(tmp) + }) + .collect::>(); + trace_rows[self.num_rows - 1][3] = F::ZERO; // So that column 2 and 3 are permutation of one another. + trace_rows_to_poly_values(trace_rows) + } +} + +const FIBONACCI_PERM_COLUMNS: usize = 5; +const FIBONACCI_PERM_PUBLIC_INPUTS: usize = 3; + +impl, const D: usize> Stark + for FibonacciWithPermutationStark +{ + type EvaluationFrame = StarkFrame + where + FE: FieldExtension, + P: PackedField; + + type EvaluationFrameTarget = StarkFrame< + ExtensionTarget, + ExtensionTarget, + FIBONACCI_PERM_COLUMNS, + FIBONACCI_PERM_PUBLIC_INPUTS, + >; + + fn eval_packed_generic( + &self, + vars: &Self::EvaluationFrame, + yield_constr: &mut ConstraintConsumer

, + ) where + FE: FieldExtension, + P: PackedField, + { + let local_values = vars.get_local_values(); + let next_values = vars.get_next_values(); + let public_inputs = vars.get_public_inputs(); + + // Check public inputs. + yield_constr.constraint_first_row(local_values[0] - public_inputs[Self::PI_INDEX_X0]); + yield_constr.constraint_first_row(local_values[1] - public_inputs[Self::PI_INDEX_X1]); + yield_constr.constraint_last_row(local_values[1] - public_inputs[Self::PI_INDEX_RES]); + + // x0' <- x1 + yield_constr.constraint_transition(next_values[0] - local_values[1]); + // x1' <- x0 + x1 + yield_constr.constraint_transition(next_values[1] - local_values[0] - local_values[1]); + } + + fn eval_ext_circuit( + &self, + builder: &mut CircuitBuilder, + vars: &Self::EvaluationFrameTarget, + yield_constr: &mut RecursiveConstraintConsumer, + ) { + let local_values = vars.get_local_values(); + let next_values = vars.get_next_values(); + let public_inputs = vars.get_public_inputs(); + // Check public inputs. + let pis_constraints = [ + builder.sub_extension(local_values[0], public_inputs[Self::PI_INDEX_X0]), + builder.sub_extension(local_values[1], public_inputs[Self::PI_INDEX_X1]), + builder.sub_extension(local_values[1], public_inputs[Self::PI_INDEX_RES]), + ]; + yield_constr.constraint_first_row(builder, pis_constraints[0]); + yield_constr.constraint_first_row(builder, pis_constraints[1]); + yield_constr.constraint_last_row(builder, pis_constraints[2]); + + // x0' <- x1 + let first_col_constraint = builder.sub_extension(next_values[0], local_values[1]); + yield_constr.constraint_transition(builder, first_col_constraint); + // x1' <- x0 + x1 + let second_col_constraint = { + let tmp = builder.sub_extension(next_values[1], local_values[0]); + builder.sub_extension(tmp, local_values[1]) + }; + yield_constr.constraint_transition(builder, second_col_constraint); + } + + fn constraint_degree(&self) -> usize { + 2 + } + + fn lookups(&self) -> Vec> { + vec![Lookup { + columns: vec![Column::single(2)], + table_column: Column::single(3), + frequencies_column: Column::single(4), + filter_columns: vec![None; 1], + }] + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + use plonky2::field::extension::Extendable; + use plonky2::field::types::Field; + use plonky2::hash::hash_types::RichField; + use plonky2::iop::witness::PartialWitness; + use plonky2::plonk::circuit_builder::CircuitBuilder; + use plonky2::plonk::circuit_data::CircuitConfig; + use plonky2::plonk::config::{AlgebraicHasher, GenericConfig, PoseidonGoldilocksConfig}; + use plonky2::util::timing::TimingTree; + + use crate::config::StarkConfig; + use crate::fibonacci_stark::{FibonacciStark, FibonacciWithPermutationStark}; + use crate::proof::StarkProofWithPublicInputs; + use crate::prover::prove; + use crate::recursive_verifier::{ + add_virtual_stark_proof_with_pis, set_stark_proof_with_pis_target, + verify_stark_proof_circuit, + }; + use crate::stark::Stark; + use crate::stark_testing::{test_stark_circuit_constraints, test_stark_low_degree}; + use crate::verifier::verify_stark_proof; + + fn fibonacci(n: usize, x0: F, x1: F) -> F { + (0..n).fold((x0, x1), |x, _| (x.1, x.0 + x.1)).1 + } + + #[test] + fn test_fibonacci_stark() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type S1 = FibonacciStark; + type S2 = FibonacciWithPermutationStark; + + let config = StarkConfig::standard_fast_config(); + let num_rows = 1 << 5; + let public_inputs = [F::ZERO, F::ONE, fibonacci(num_rows - 1, F::ZERO, F::ONE)]; + + // Test first STARK + let stark = S1::new(num_rows); + let trace = stark.generate_trace(public_inputs[0], public_inputs[1]); + let proof = prove::( + stark, + &config, + trace, + &public_inputs, + &mut TimingTree::default(), + )?; + + verify_stark_proof(stark, proof, &config)?; + + // Test second STARK + let stark = S2::new(num_rows); + let trace = stark.generate_trace(public_inputs[0], public_inputs[1]); + let proof = prove::( + stark, + &config, + trace, + &public_inputs, + &mut TimingTree::default(), + )?; + + verify_stark_proof(stark, proof, &config) + } + + #[test] + fn test_fibonacci_stark_degree() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type S1 = FibonacciStark; + type S2 = FibonacciWithPermutationStark; + + let num_rows = 1 << 5; + let stark = S1::new(num_rows); + test_stark_low_degree(stark)?; + + let stark = S2::new(num_rows); + test_stark_low_degree(stark) + } + + #[test] + fn test_fibonacci_stark_circuit() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type S1 = FibonacciStark; + type S2 = FibonacciWithPermutationStark; + + let num_rows = 1 << 5; + let stark = S1::new(num_rows); + test_stark_circuit_constraints::(stark)?; + let stark = S2::new(num_rows); + test_stark_circuit_constraints::(stark) + } + + #[test] + fn test_recursive_stark_verifier() -> Result<()> { + init_logger(); + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type S1 = FibonacciStark; + type S2 = FibonacciWithPermutationStark; + + let config = StarkConfig::standard_fast_config(); + let num_rows = 1 << 5; + let public_inputs = [F::ZERO, F::ONE, fibonacci(num_rows - 1, F::ZERO, F::ONE)]; + + // Test first STARK + let stark = S1::new(num_rows); + let trace = stark.generate_trace(public_inputs[0], public_inputs[1]); + let proof = prove::( + stark, + &config, + trace, + &public_inputs, + &mut TimingTree::default(), + )?; + verify_stark_proof(stark, proof.clone(), &config)?; + + recursive_proof::(stark, proof, &config, true)?; + + // Test second STARK + let stark = S2::new(num_rows); + let trace = stark.generate_trace(public_inputs[0], public_inputs[1]); + let proof = prove::( + stark, + &config, + trace, + &public_inputs, + &mut TimingTree::default(), + )?; + verify_stark_proof(stark, proof.clone(), &config)?; + + recursive_proof::(stark, proof, &config, true) + } + + fn recursive_proof< + F: RichField + Extendable, + C: GenericConfig, + S: Stark + Copy, + InnerC: GenericConfig, + const D: usize, + >( + stark: S, + inner_proof: StarkProofWithPublicInputs, + inner_config: &StarkConfig, + print_gate_counts: bool, + ) -> Result<()> + where + InnerC::Hasher: AlgebraicHasher, + { + let circuit_config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(circuit_config); + let mut pw = PartialWitness::new(); + let degree_bits = inner_proof.proof.recover_degree_bits(inner_config); + let pt = + add_virtual_stark_proof_with_pis(&mut builder, &stark, inner_config, degree_bits, 0, 0); + set_stark_proof_with_pis_target(&mut pw, &pt, &inner_proof, builder.zero()); + + verify_stark_proof_circuit::(&mut builder, stark, pt, inner_config); + + if print_gate_counts { + builder.print_gate_counts(0); + } + + let data = builder.build::(); + let proof = data.prove(pw)?; + data.verify(proof) + } + + fn init_logger() { + let _ = env_logger::builder().format_timestamp(None).try_init(); + } +} diff --git a/starky/src/get_challenges.rs b/starky/src/get_challenges.rs new file mode 100644 index 000000000..98f247ff1 --- /dev/null +++ b/starky/src/get_challenges.rs @@ -0,0 +1,415 @@ +use plonky2::field::extension::Extendable; +use plonky2::field::polynomial::PolynomialCoeffs; +use plonky2::fri::proof::{FriProof, FriProofTarget}; +use plonky2::gadgets::polynomial::PolynomialCoeffsExtTarget; +use plonky2::hash::hash_types::{MerkleCapTarget, RichField}; +use plonky2::hash::merkle_tree::MerkleCap; +use plonky2::iop::challenger::{Challenger, RecursiveChallenger}; +use plonky2::iop::target::Target; +use plonky2::plonk::circuit_builder::CircuitBuilder; +use plonky2::plonk::config::{AlgebraicHasher, GenericConfig}; + +use crate::config::StarkConfig; +use crate::lookup::{ + get_grand_product_challenge_set, get_grand_product_challenge_set_target, + GrandProductChallengeSet, +}; +use crate::proof::*; + +/// Generates challenges for a STARK proof from a challenger and given +/// all the arguments needed to update the challenger state. +/// +/// Note: `trace_cap` is passed as `Option` to signify whether to observe it +/// or not by the challenger. Observing it here could be redundant in a +/// multi-STARK system where trace caps would have already been observed +/// before proving individually each STARK. +fn get_challenges( + challenger: &mut Challenger, + challenges: Option<&GrandProductChallengeSet>, + trace_cap: Option<&MerkleCap>, + auxiliary_polys_cap: Option<&MerkleCap>, + quotient_polys_cap: &MerkleCap, + openings: &StarkOpeningSet, + commit_phase_merkle_caps: &[MerkleCap], + final_poly: &PolynomialCoeffs, + pow_witness: F, + config: &StarkConfig, + degree_bits: usize, +) -> StarkProofChallenges +where + F: RichField + Extendable, + C: GenericConfig, +{ + let num_challenges = config.num_challenges; + + if let Some(cap) = &trace_cap { + challenger.observe_cap(cap); + } + + let lookup_challenge_set = if let Some(&challenges) = challenges.as_ref() { + Some(challenges.clone()) + } else { + auxiliary_polys_cap + .is_some() + .then(|| get_grand_product_challenge_set(challenger, num_challenges)) + }; + + if let Some(cap) = &auxiliary_polys_cap { + challenger.observe_cap(cap); + } + + let stark_alphas = challenger.get_n_challenges(num_challenges); + + challenger.observe_cap(quotient_polys_cap); + let stark_zeta = challenger.get_extension_challenge::(); + + challenger.observe_openings(&openings.to_fri_openings()); + + StarkProofChallenges { + lookup_challenge_set, + stark_alphas, + stark_zeta, + fri_challenges: challenger.fri_challenges::( + commit_phase_merkle_caps, + final_poly, + pow_witness, + degree_bits, + &config.fri_config, + ), + } +} + +impl StarkProof +where + F: RichField + Extendable, + C: GenericConfig, +{ + /// Computes all Fiat-Shamir challenges used in the STARK proof. + /// For a single STARK system, the `ignore_trace_cap` boolean should + /// always be set to `false`. + /// + /// Multi-STARK systems may already observe individual trace caps + /// ahead of proving each table, and hence may ignore observing + /// again the cap when generating individual challenges. + pub fn get_challenges( + &self, + challenger: &mut Challenger, + challenges: Option<&GrandProductChallengeSet>, + ignore_trace_cap: bool, + config: &StarkConfig, + ) -> StarkProofChallenges { + let degree_bits = self.recover_degree_bits(config); + + let StarkProof { + trace_cap, + auxiliary_polys_cap, + quotient_polys_cap, + openings, + opening_proof: + FriProof { + commit_phase_merkle_caps, + final_poly, + pow_witness, + .. + }, + } = &self; + + let trace_cap = if ignore_trace_cap { + None + } else { + Some(trace_cap) + }; + + get_challenges::( + challenger, + challenges, + trace_cap, + auxiliary_polys_cap.as_ref(), + quotient_polys_cap, + openings, + commit_phase_merkle_caps, + final_poly, + *pow_witness, + config, + degree_bits, + ) + } +} + +impl StarkProofWithPublicInputs +where + F: RichField + Extendable, + C: GenericConfig, +{ + /// Computes all Fiat-Shamir challenges used in the STARK proof. + /// For a single STARK system, the `ignore_trace_cap` boolean should + /// always be set to `false`. + /// + /// Multi-STARK systems may already observe individual trace caps + /// ahead of proving each table, and hence may ignore observing + /// again the cap when generating individual challenges. + pub fn get_challenges( + &self, + challenger: &mut Challenger, + challenges: Option<&GrandProductChallengeSet>, + ignore_trace_cap: bool, + config: &StarkConfig, + ) -> StarkProofChallenges { + self.proof + .get_challenges(challenger, challenges, ignore_trace_cap, config) + } +} + +/// Circuit version of `get_challenges`, with the same flexibility around +/// `trace_cap` being passed as an `Option`. +fn get_challenges_target( + builder: &mut CircuitBuilder, + challenger: &mut RecursiveChallenger, + challenges: Option<&GrandProductChallengeSet>, + trace_cap: Option<&MerkleCapTarget>, + auxiliary_polys_cap: Option<&MerkleCapTarget>, + quotient_polys_cap: &MerkleCapTarget, + openings: &StarkOpeningSetTarget, + commit_phase_merkle_caps: &[MerkleCapTarget], + final_poly: &PolynomialCoeffsExtTarget, + pow_witness: Target, + config: &StarkConfig, +) -> StarkProofChallengesTarget +where + F: RichField + Extendable, + C: GenericConfig, + C::Hasher: AlgebraicHasher, +{ + let num_challenges = config.num_challenges; + + if let Some(trace_cap) = trace_cap { + challenger.observe_cap(trace_cap); + } + + let lookup_challenge_set = if let Some(&challenges) = challenges.as_ref() { + Some(challenges.clone()) + } else { + auxiliary_polys_cap + .is_some() + .then(|| get_grand_product_challenge_set_target(builder, challenger, num_challenges)) + }; + + if let Some(cap) = auxiliary_polys_cap { + challenger.observe_cap(cap); + } + + let stark_alphas = challenger.get_n_challenges(builder, num_challenges); + + challenger.observe_cap(quotient_polys_cap); + let stark_zeta = challenger.get_extension_challenge(builder); + + challenger.observe_openings(&openings.to_fri_openings(builder.zero())); + + StarkProofChallengesTarget { + lookup_challenge_set, + stark_alphas, + stark_zeta, + fri_challenges: challenger.fri_challenges( + builder, + commit_phase_merkle_caps, + final_poly, + pow_witness, + &config.fri_config, + ), + } +} + +impl StarkProofTarget { + /// Creates all Fiat-Shamir `Target` challenges used in the STARK proof. + /// For a single STARK system, the `ignore_trace_cap` boolean should + /// always be set to `false`. + /// + /// Multi-STARK systems may already observe individual trace caps + /// ahead of proving each table, and hence may ignore observing + /// again the cap when generating individual challenges. + pub fn get_challenges( + &self, + builder: &mut CircuitBuilder, + challenger: &mut RecursiveChallenger, + challenges: Option<&GrandProductChallengeSet>, + ignore_trace_cap: bool, + config: &StarkConfig, + ) -> StarkProofChallengesTarget + where + F: RichField + Extendable, + C: GenericConfig, + C::Hasher: AlgebraicHasher, + { + let StarkProofTarget { + trace_cap, + auxiliary_polys_cap, + quotient_polys_cap, + openings, + opening_proof: + FriProofTarget { + commit_phase_merkle_caps, + final_poly, + pow_witness, + .. + }, + } = self; + + let trace_cap = if ignore_trace_cap { + None + } else { + Some(trace_cap) + }; + + get_challenges_target::( + builder, + challenger, + challenges, + trace_cap, + auxiliary_polys_cap.as_ref(), + quotient_polys_cap, + openings, + commit_phase_merkle_caps, + final_poly, + *pow_witness, + config, + ) + } +} + +impl StarkProofWithPublicInputsTarget { + /// Creates all Fiat-Shamir `Target` challenges used in the STARK proof. + /// For a single STARK system, the `ignore_trace_cap` boolean should + /// always be set to `false`. + /// + /// Multi-STARK systems may already observe individual trace caps + /// ahead of proving each table, and hence may ignore observing + /// again the cap when generating individual challenges. + pub fn get_challenges( + &self, + builder: &mut CircuitBuilder, + challenger: &mut RecursiveChallenger, + challenges: Option<&GrandProductChallengeSet>, + ignore_trace_cap: bool, + config: &StarkConfig, + ) -> StarkProofChallengesTarget + where + F: RichField + Extendable, + C: GenericConfig, + C::Hasher: AlgebraicHasher, + { + self.proof + .get_challenges::(builder, challenger, challenges, ignore_trace_cap, config) + } +} + +// TODO: Deal with the compressed stuff. +// impl, C: GenericConfig, const D: +// usize> CompressedProofWithPublicInputs +// { +// /// Computes all Fiat-Shamir challenges used in the Plonk proof. +// pub(crate) fn get_challenges( +// &self, +// common_data: &CommonCircuitData, +// ) -> anyhow::Result> { +// let CompressedProof { +// wires_cap, +// plonk_zs_partial_products_cap, +// quotient_polys_cap, +// openings, +// opening_proof: +// CompressedFriProof { +// commit_phase_merkle_caps, +// final_poly, +// pow_witness, +// .. +// }, +// } = &self.proof; +// +// get_challenges( +// self.get_public_inputs_hash(), +// wires_cap, +// plonk_zs_partial_products_cap, +// quotient_polys_cap, +// openings, +// commit_phase_merkle_caps, +// final_poly, +// *pow_witness, +// common_data, +// ) +// } +// +// /// Computes all coset elements that can be inferred in the FRI reduction +// steps. pub(crate) fn get_inferred_elements( +// &self, +// challenges: &ProofChallenges, +// common_data: &CommonCircuitData, +// ) -> FriInferredElements { +// let ProofChallenges { +// plonk_zeta, +// fri_alpha, +// fri_betas, +// fri_query_indices, +// .. +// } = challenges; +// let mut fri_inferred_elements = Vec::new(); +// // Holds the indices that have already been seen at each reduction +// depth. let mut seen_indices_by_depth = +// vec![HashSet::new(); +// common_data.fri_params.reduction_arity_bits.len()]; let +// precomputed_reduced_evals = PrecomputedReducedOpenings::from_os_and_alpha( +// &self.proof.openings.to_fri_openings(), +// *fri_alpha, +// ); +// let log_n = common_data.degree_bits + +// common_data.config.fri_config.rate_bits; // Simulate the proof +// verification and collect the inferred elements. // The content of the +// loop is basically the same as the `fri_verifier_query_round` function. +// for &(mut x_index) in fri_query_indices { +// let mut subgroup_x = F::MULTIPLICATIVE_GROUP_GENERATOR +// * F::primitive_root_of_unity(log_n). +// exp_u64(reverse_bits(x_index, log_n) as u64); +// let mut old_eval = fri_combine_initial::( +// &common_data.get_fri_instance(*plonk_zeta), +// &self +// .proof +// .opening_proof +// .query_round_proofs +// .initial_trees_proofs[&x_index], +// *fri_alpha, +// subgroup_x, +// &precomputed_reduced_evals, +// &common_data.fri_params, +// ); +// for (i, &arity_bits) in common_data +// .fri_params +// .reduction_arity_bits +// .iter() +// .enumerate() +// { +// let coset_index = x_index >> arity_bits; +// if !seen_indices_by_depth[i].insert(coset_index) { +// // If this index has already been seen, we can skip the +// rest of the reductions. break; +// } +// fri_inferred_elements.push(old_eval); +// let arity = 1 << arity_bits; +// let mut evals = +// self.proof.opening_proof.query_round_proofs.steps[i][&coset_index] +// .evals +// .clone(); +// let x_index_within_coset = x_index & (arity - 1); +// evals.insert(x_index_within_coset, old_eval); +// old_eval = compute_evaluation( +// subgroup_x, +// x_index_within_coset, +// arity_bits, +// &evals, +// fri_betas[i], +// ); +// subgroup_x = subgroup_x.exp_power_of_2(arity_bits); +// x_index = coset_index; +// } +// } +// FriInferredElements(fri_inferred_elements) +// } +// } diff --git a/starky/src/lib.rs b/starky/src/lib.rs new file mode 100644 index 000000000..a5c96e56e --- /dev/null +++ b/starky/src/lib.rs @@ -0,0 +1,342 @@ +//! A FRI-based STARK implementation over the Goldilocks field, with support +//! for recursive proof verification through the plonky2 SNARK backend. +//! +//! This library is intended to provide all the necessary tools to prove, +//! verify, and recursively verify STARK statements. While the library +//! is tailored for a system with a single STARK, it also is flexible +//! enough to support a multi-STARK system, i.e. a system of independent +//! STARK statements possibly sharing common values. See section below for +//! more information on how to define such a system. +//! +//! +//! # Defining a STARK statement +//! +//! A STARK system is configured by a +//! [`StarkConfig`][crate::config::StarkConfig] defining all the parameters to +//! be used when generating proofs associated to the statement. How constraints +//! should be defined over the STARK trace is defined through the +//! [`Stark`][crate::stark::Stark] trait, that takes a +//! [`StarkEvaluationFrame`][crate::evaluation_frame::StarkEvaluationFrame] of +//! two consecutive rows and a list of public inputs. +//! +//! ### Example: Fibonacci sequence +//! +//! To build a STARK for the modified Fibonacci sequence starting with two +//! user-provided values `x0` and `x1`, one can do the following: +//! +//! ```rust +//! # use core::marker::PhantomData; +//! // Imports all basic types. +//! use plonky2::field::extension::{Extendable, FieldExtension}; +//! use plonky2::field::packed::PackedField; +//! use plonky2::field::polynomial::PolynomialValues; +//! use plonky2::hash::hash_types::RichField; +//! # use starky::util::trace_rows_to_poly_values; +//! +//! // Imports to define the constraints of our STARK. +//! use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; +//! use starky::evaluation_frame::{StarkEvaluationFrame, StarkFrame}; +//! use starky::stark::Stark; +//! +//! // Imports to define the recursive constraints of our STARK. +//! use plonky2::iop::ext_target::ExtensionTarget; +//! use plonky2::plonk::circuit_builder::CircuitBuilder; +//! +//! pub struct FibonacciStark, const D: usize> { +//! num_rows: usize, +//! _phantom: PhantomData, +//! } +//! +//! // Define witness generation. +//! impl, const D: usize> FibonacciStark { +//! // The first public input is `x0`. +//! const PI_INDEX_X0: usize = 0; +//! // The second public input is `x1`. +//! const PI_INDEX_X1: usize = 1; +//! // The third public input is the second element of the last row, +//! // which should be equal to the `num_rows`-th Fibonacci number. +//! const PI_INDEX_RES: usize = 2; +//! +//! /// Generate the trace using `x0, x1, 0` as initial state values. +//! fn generate_trace(&self, x0: F, x1: F) -> Vec> { +//! let mut trace_rows = (0..self.num_rows) +//! .scan([x0, x1, F::ZERO], |acc, _| { +//! let tmp = *acc; +//! acc[0] = tmp[1]; +//! acc[1] = tmp[0] + tmp[1]; +//! acc[2] = tmp[2] + F::ONE; +//! Some(tmp) +//! }) +//! .collect::>(); +//! +//! // Transpose the row-wise trace for the prover. +//! trace_rows_to_poly_values(trace_rows) +//! } +//! } +//! +//! // Define constraints. +//! const COLUMNS: usize = 3; +//! const PUBLIC_INPUTS: usize = 3; +//! +//! impl, const D: usize> Stark for FibonacciStark { +//! type EvaluationFrame = StarkFrame +//! where +//! FE: FieldExtension, +//! P: PackedField; +//! +//! type EvaluationFrameTarget = +//! StarkFrame, ExtensionTarget, COLUMNS, PUBLIC_INPUTS>; +//! +//! // Define this STARK's constraints. +//! fn eval_packed_generic( +//! &self, +//! vars: &Self::EvaluationFrame, +//! yield_constr: &mut ConstraintConsumer

, +//! ) where +//! FE: FieldExtension, +//! P: PackedField, +//! { +//! let local_values = vars.get_local_values(); +//! let next_values = vars.get_next_values(); +//! let public_inputs = vars.get_public_inputs(); +//! +//! // Check public inputs. +//! yield_constr.constraint_first_row(local_values[0] - public_inputs[Self::PI_INDEX_X0]); +//! yield_constr.constraint_first_row(local_values[1] - public_inputs[Self::PI_INDEX_X1]); +//! yield_constr.constraint_last_row(local_values[1] - public_inputs[Self::PI_INDEX_RES]); +//! +//! // Enforce the Fibonacci transition constraints. +//! // x0' <- x1 +//! yield_constr.constraint_transition(next_values[0] - local_values[1]); +//! // x1' <- x0 + x1 +//! yield_constr.constraint_transition(next_values[1] - local_values[0] - local_values[1]); +//! } +//! +//! // Define the constraints to recursively verify this STARK. +//! fn eval_ext_circuit( +//! &self, +//! builder: &mut CircuitBuilder, +//! vars: &Self::EvaluationFrameTarget, +//! yield_constr: &mut RecursiveConstraintConsumer, +//! ) { +//! let local_values = vars.get_local_values(); +//! let next_values = vars.get_next_values(); +//! let public_inputs = vars.get_public_inputs(); +//! +//! // Check public inputs. +//! let pis_constraints = [ +//! builder.sub_extension(local_values[0], public_inputs[Self::PI_INDEX_X0]), +//! builder.sub_extension(local_values[1], public_inputs[Self::PI_INDEX_X1]), +//! builder.sub_extension(local_values[1], public_inputs[Self::PI_INDEX_RES]), +//! ]; +//! +//! yield_constr.constraint_first_row(builder, pis_constraints[0]); +//! yield_constr.constraint_first_row(builder, pis_constraints[1]); +//! yield_constr.constraint_last_row(builder, pis_constraints[2]); +//! +//! // Enforce the Fibonacci transition constraints. +//! // x0' <- x1 +//! let first_col_constraint = builder.sub_extension(next_values[0], local_values[1]); +//! yield_constr.constraint_transition(builder, first_col_constraint); +//! // x1' <- x0 + x1 +//! let second_col_constraint = { +//! let tmp = builder.sub_extension(next_values[1], local_values[0]); +//! builder.sub_extension(tmp, local_values[1]) +//! }; +//! yield_constr.constraint_transition(builder, second_col_constraint); +//! } +//! +//! fn constraint_degree(&self) -> usize { +//! 2 +//! } +//! } +//! ``` +//! +//! One can then instantiate a new `FibonacciStark` instance, generate an +//! associated STARK trace, and generate a proof for it. +//! +//! ```rust +//! # use anyhow::Result; +//! # use core::marker::PhantomData; +//! # // Imports all basic types. +//! # use plonky2::field::extension::{Extendable, FieldExtension}; +//! # use plonky2::field::types::Field; +//! # use plonky2::field::packed::PackedField; +//! # use plonky2::field::polynomial::PolynomialValues; +//! # use plonky2::hash::hash_types::RichField; +//! # use starky::util::trace_rows_to_poly_values; +//! # // Imports to define the constraints of our STARK. +//! # use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; +//! # use starky::evaluation_frame::{StarkEvaluationFrame, StarkFrame}; +//! # use starky::stark::Stark; +//! # // Imports to define the recursive constraints of our STARK. +//! # use plonky2::iop::ext_target::ExtensionTarget; +//! # use plonky2::plonk::circuit_builder::CircuitBuilder; +//! # use plonky2::util::timing::TimingTree; +//! # use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; +//! # use starky::prover::prove; +//! # use starky::verifier::verify_stark_proof; +//! # use starky::config::StarkConfig; +//! # +//! # #[derive(Copy, Clone)] +//! # pub struct FibonacciStark, const D: usize> { +//! # num_rows: usize, +//! # _phantom: PhantomData, +//! # } +//! # // Define witness generation. +//! # impl, const D: usize> FibonacciStark { +//! # // The first public input is `x0`. +//! # const PI_INDEX_X0: usize = 0; +//! # // The second public input is `x1`. +//! # const PI_INDEX_X1: usize = 1; +//! # // The third public input is the second element of the last row, +//! # // which should be equal to the `num_rows`-th Fibonacci number. +//! # const PI_INDEX_RES: usize = 2; +//! # /// Generate the trace using `x0, x1, 0` as initial state values. +//! # fn generate_trace(&self, x0: F, x1: F) -> Vec> { +//! # let mut trace_rows = (0..self.num_rows) +//! # .scan([x0, x1, F::ZERO], |acc, _| { +//! # let tmp = *acc; +//! # acc[0] = tmp[1]; +//! # acc[1] = tmp[0] + tmp[1]; +//! # acc[2] = tmp[2] + F::ONE; +//! # Some(tmp) +//! # }) +//! # .collect::>(); +//! # // Transpose the row-wise trace for the prover. +//! # trace_rows_to_poly_values(trace_rows) +//! # } +//! # const fn new(num_rows: usize) -> Self { +//! # Self { +//! # num_rows, +//! # _phantom: PhantomData, +//! # } +//! # } +//! # } +//! # // Define constraints. +//! # const COLUMNS: usize = 3; +//! # const PUBLIC_INPUTS: usize = 3; +//! # impl, const D: usize> Stark for FibonacciStark { +//! # type EvaluationFrame = StarkFrame +//! # where +//! # FE: FieldExtension, +//! # P: PackedField; +//! # type EvaluationFrameTarget = +//! # StarkFrame, ExtensionTarget, COLUMNS, PUBLIC_INPUTS>; +//! # // Define this STARK's constraints. +//! # fn eval_packed_generic( +//! # &self, +//! # vars: &Self::EvaluationFrame, +//! # yield_constr: &mut ConstraintConsumer

, +//! # ) where +//! # FE: FieldExtension, +//! # P: PackedField, +//! # { +//! # let local_values = vars.get_local_values(); +//! # let next_values = vars.get_next_values(); +//! # let public_inputs = vars.get_public_inputs(); +//! # // Check public inputs. +//! # yield_constr.constraint_first_row(local_values[0] - public_inputs[Self::PI_INDEX_X0]); +//! # yield_constr.constraint_first_row(local_values[1] - public_inputs[Self::PI_INDEX_X1]); +//! # yield_constr.constraint_last_row(local_values[1] - public_inputs[Self::PI_INDEX_RES]); +//! # // Enforce the Fibonacci transition constraints. +//! # // x0' <- x1 +//! # yield_constr.constraint_transition(next_values[0] - local_values[1]); +//! # // x1' <- x0 + x1 +//! # yield_constr.constraint_transition(next_values[1] - local_values[0] - local_values[1]); +//! # } +//! # // Define the constraints to recursively verify this STARK. +//! # fn eval_ext_circuit( +//! # &self, +//! # builder: &mut CircuitBuilder, +//! # vars: &Self::EvaluationFrameTarget, +//! # yield_constr: &mut RecursiveConstraintConsumer, +//! # ) { +//! # let local_values = vars.get_local_values(); +//! # let next_values = vars.get_next_values(); +//! # let public_inputs = vars.get_public_inputs(); +//! # // Check public inputs. +//! # let pis_constraints = [ +//! # builder.sub_extension(local_values[0], public_inputs[Self::PI_INDEX_X0]), +//! # builder.sub_extension(local_values[1], public_inputs[Self::PI_INDEX_X1]), +//! # builder.sub_extension(local_values[1], public_inputs[Self::PI_INDEX_RES]), +//! # ]; +//! # yield_constr.constraint_first_row(builder, pis_constraints[0]); +//! # yield_constr.constraint_first_row(builder, pis_constraints[1]); +//! # yield_constr.constraint_last_row(builder, pis_constraints[2]); +//! # // Enforce the Fibonacci transition constraints. +//! # // x0' <- x1 +//! # let first_col_constraint = builder.sub_extension(next_values[0], local_values[1]); +//! # yield_constr.constraint_transition(builder, first_col_constraint); +//! # // x1' <- x0 + x1 +//! # let second_col_constraint = { +//! # let tmp = builder.sub_extension(next_values[1], local_values[0]); +//! # builder.sub_extension(tmp, local_values[1]) +//! # }; +//! # yield_constr.constraint_transition(builder, second_col_constraint); +//! # } +//! # fn constraint_degree(&self) -> usize { +//! # 2 +//! # } +//! # } +//! # fn fibonacci(n: usize, x0: F, x1: F) -> F { +//! # (0..n).fold((x0, x1), |x, _| (x.1, x.0 + x.1)).1 +//! # } +//! # +//! const D: usize = 2; +//! const CONFIG: StarkConfig = StarkConfig::standard_fast_config(); +//! type C = PoseidonGoldilocksConfig; +//! type F = >::F; +//! type S = FibonacciStark; +//! +//! fn main() { +//! let num_rows = 1 << 10; +//! let x0 = F::from_canonical_u32(2); +//! let x1 = F::from_canonical_u32(7); +//! +//! let public_inputs = [x0, x1, fibonacci(num_rows - 1, x0, x1)]; +//! let stark = FibonacciStark::::new(num_rows); +//! let trace = stark.generate_trace(public_inputs[0], public_inputs[1]); +//! +//! let proof = prove::( +//! stark, +//! &CONFIG, +//! trace, +//! &public_inputs, +//! &mut TimingTree::default(), +//! ).expect("We should have a valid proof!"); +//! +//! verify_stark_proof(stark, proof, &CONFIG) +//! .expect("We should be able to verify this proof!") +//! } +//! ``` + +#![allow(clippy::too_many_arguments)] +#![allow(clippy::needless_range_loop)] +#![allow(clippy::type_complexity)] +#![deny(rustdoc::broken_intra_doc_links)] +#![deny(missing_debug_implementations)] +#![deny(missing_docs)] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(not(feature = "std"))] +extern crate alloc; + +mod get_challenges; + +pub mod config; +pub mod constraint_consumer; +pub mod cross_table_lookup; +pub mod evaluation_frame; +pub mod lookup; +pub mod proof; +pub mod prover; +pub mod recursive_verifier; +pub mod stark; +pub mod stark_testing; +pub mod util; +mod vanishing_poly; +pub mod verifier; + +#[cfg(test)] +pub mod fibonacci_stark; diff --git a/starky/src/lookup.rs b/starky/src/lookup.rs new file mode 100644 index 000000000..7c0c4d7ec --- /dev/null +++ b/starky/src/lookup.rs @@ -0,0 +1,1042 @@ +//! A Lookup protocol leveraging logarithmic derivatives, +//! introduced in . + +#[cfg(not(feature = "std"))] +use alloc::{vec, vec::Vec}; +use core::borrow::Borrow; +use core::fmt::Debug; +use core::iter::repeat; + +use itertools::Itertools; +use num_bigint::BigUint; +use plonky2::field::batch_util::batch_add_inplace; +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::challenger::{Challenger, RecursiveChallenger}; +use plonky2::iop::ext_target::ExtensionTarget; +use plonky2::iop::target::Target; +use plonky2::plonk::circuit_builder::CircuitBuilder; +use plonky2::plonk::config::{AlgebraicHasher, Hasher}; +use plonky2::plonk::plonk_common::{ + reduce_with_powers, reduce_with_powers_circuit, reduce_with_powers_ext_circuit, +}; +use plonky2::util::serialization::{Buffer, IoResult, Read, Write}; +use plonky2_util::ceil_div_usize; + +use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; +use crate::evaluation_frame::StarkEvaluationFrame; +use crate::stark::Stark; + +/// Represents a filter, which evaluates to 1 if the row must be considered and +/// 0 if it should be ignored. It's an arbitrary degree 2 combination of +/// columns: `products` are the degree 2 terms, and `constants` are the degree 1 +/// terms. +#[derive(Clone, Debug)] +pub struct Filter { + products: Vec<(Column, Column)>, + constants: Vec>, +} + +impl Filter { + /// Returns a filter from the provided `products` and `constants` vectors. + pub fn new(products: Vec<(Column, Column)>, constants: Vec>) -> Self { + Self { + products, + constants, + } + } + + /// Returns a filter made of a single column. + pub fn new_simple(col: Column) -> Self { + Self { + products: vec![], + constants: vec![col], + } + } + + /// Given the column values for the current and next rows, evaluates the + /// filter. + pub(crate) fn eval_filter(&self, v: &[P], next_v: &[P]) -> P + where + FE: FieldExtension, + P: PackedField, + { + self.products + .iter() + .map(|(col1, col2)| col1.eval_with_next(v, next_v) * col2.eval_with_next(v, next_v)) + .sum::

() + + self + .constants + .iter() + .map(|col| col.eval_with_next(v, next_v)) + .sum::

() + } + + /// Circuit version of `eval_filter`: + /// Given the column values for the current and next rows, evaluates the + /// filter. + pub(crate) fn eval_filter_circuit( + &self, + builder: &mut CircuitBuilder, + v: &[ExtensionTarget], + next_v: &[ExtensionTarget], + ) -> ExtensionTarget + where + F: RichField + Extendable, + { + let prods = self + .products + .iter() + .map(|(col1, col2)| { + let col1_eval = col1.eval_with_next_circuit(builder, v, next_v); + let col2_eval = col2.eval_with_next_circuit(builder, v, next_v); + builder.mul_extension(col1_eval, col2_eval) + }) + .collect::>(); + + let consts = self + .constants + .iter() + .map(|col| col.eval_with_next_circuit(builder, v, next_v)) + .collect::>(); + + let prods = builder.add_many_extension(prods); + let consts = builder.add_many_extension(consts); + builder.add_extension(prods, consts) + } + + /// Evaluate on a row of a table given in column-major form. + pub(crate) fn eval_table(&self, table: &[PolynomialValues], row: usize) -> F { + self.products + .iter() + .map(|(col1, col2)| col1.eval_table(table, row) * col2.eval_table(table, row)) + .sum::() + + self + .constants + .iter() + .map(|col| col.eval_table(table, row)) + .sum() + } +} + +/// Represent two linear combination of columns, corresponding to the current +/// and next row values. Each linear combination is represented as: +/// - a vector of `(usize, F)` corresponding to the column number and the +/// associated multiplicand +/// - the constant of the linear combination. +#[derive(Clone, Debug)] +pub struct Column { + linear_combination: Vec<(usize, F)>, + next_row_linear_combination: Vec<(usize, F)>, + constant: F, +} + +impl Column { + /// Returns the representation of a single column in the current row. + pub fn single(c: usize) -> Self { + Self { + linear_combination: vec![(c, F::ONE)], + next_row_linear_combination: vec![], + constant: F::ZERO, + } + } + + /// Returns multiple single columns in the current row. + pub fn singles>>( + cs: I, + ) -> impl Iterator { + cs.into_iter().map(|c| Self::single(*c.borrow())) + } + + /// Returns the representation of a single column in the next row. + pub fn single_next_row(c: usize) -> Self { + Self { + linear_combination: vec![], + next_row_linear_combination: vec![(c, F::ONE)], + constant: F::ZERO, + } + } + + /// Returns multiple single columns for the next row. + pub fn singles_next_row>>( + cs: I, + ) -> impl Iterator { + cs.into_iter().map(|c| Self::single_next_row(*c.borrow())) + } + + /// Returns a linear combination corresponding to a constant. + pub fn constant(constant: F) -> Self { + Self { + linear_combination: vec![], + next_row_linear_combination: vec![], + constant, + } + } + + /// Returns a linear combination corresponding to 0. + pub fn zero() -> Self { + Self::constant(F::ZERO) + } + + /// Returns a linear combination corresponding to 1. + pub fn one() -> Self { + Self::constant(F::ONE) + } + + /// Given an iterator of `(usize, F)` and a constant, returns the + /// association linear combination of columns for the current row. + pub fn linear_combination_with_constant>( + iter: I, + constant: F, + ) -> Self { + let v = iter.into_iter().collect::>(); + assert!(!v.is_empty()); + + // Because this is a debug assertion, we only check it when the `std` + // feature is activated, as `Itertools::unique` relies on collections. + #[cfg(feature = "std")] + debug_assert_eq!( + v.iter().map(|(c, _)| c).unique().count(), + v.len(), + "Duplicate columns." + ); + + Self { + linear_combination: v, + next_row_linear_combination: vec![], + constant, + } + } + + /// Given an iterator of `(usize, F)` and a constant, returns the associated + /// linear combination of columns for the current and the next rows. + pub fn linear_combination_and_next_row_with_constant>( + iter: I, + next_row_iter: I, + constant: F, + ) -> Self { + let v = iter.into_iter().collect::>(); + let next_row_v = next_row_iter.into_iter().collect::>(); + + assert!(!v.is_empty() || !next_row_v.is_empty()); + + // Because these are debug assertions, we only check them when the `std` + // feature is activated, as `Itertools::unique` relies on collections. + #[cfg(feature = "std")] + { + debug_assert_eq!( + v.iter().map(|(c, _)| c).unique().count(), + v.len(), + "Duplicate columns." + ); + debug_assert_eq!( + next_row_v.iter().map(|(c, _)| c).unique().count(), + next_row_v.len(), + "Duplicate columns." + ); + } + + Self { + linear_combination: v, + next_row_linear_combination: next_row_v, + constant, + } + } + + /// Returns a linear combination of columns, with no additional constant. + pub fn linear_combination>(iter: I) -> Self { + Self::linear_combination_with_constant(iter, F::ZERO) + } + + /// Given an iterator of columns (c_0, ..., c_n) containing bits in little + /// endian order: returns the representation of c_0 + 2 * c_1 + ... + + /// 2^n * c_n. + pub fn le_bits>>(cs: I) -> Self { + Self::linear_combination(cs.into_iter().map(|c| *c.borrow()).zip(F::TWO.powers())) + } + + /// Given an iterator of columns (c_0, ..., c_n) containing bits in little + /// endian order: returns the representation of c_0 + 2 * c_1 + ... + + /// 2^n * c_n + k where `k` is an additional constant. + pub fn le_bits_with_constant>>( + cs: I, + constant: F, + ) -> Self { + Self::linear_combination_with_constant( + cs.into_iter().map(|c| *c.borrow()).zip(F::TWO.powers()), + constant, + ) + } + + /// Given an iterator of columns (c_0, ..., c_n) containing bytes in little + /// endian order: returns the representation of c_0 + 256 * c_1 + ... + + /// 256^n * c_n. + pub fn le_bytes>>(cs: I) -> Self { + Self::linear_combination( + cs.into_iter() + .map(|c| *c.borrow()) + .zip(F::from_canonical_u16(256).powers()), + ) + } + + /// Given an iterator of columns, returns the representation of their sum. + pub fn sum>>(cs: I) -> Self { + Self::linear_combination(cs.into_iter().map(|c| *c.borrow()).zip(repeat(F::ONE))) + } + + /// Given the column values for the current row, returns the evaluation of + /// the linear combination. + pub(crate) fn eval(&self, v: &[P]) -> P + where + FE: FieldExtension, + P: PackedField, + { + self.linear_combination + .iter() + .map(|&(c, f)| v[c] * FE::from_basefield(f)) + .sum::

() + + FE::from_basefield(self.constant) + } + + /// Given the column values for the current and next rows, evaluates the + /// current and next linear combinations and returns their sum. + pub(crate) fn eval_with_next(&self, v: &[P], next_v: &[P]) -> P + where + FE: FieldExtension, + P: PackedField, + { + self.linear_combination + .iter() + .map(|&(c, f)| v[c] * FE::from_basefield(f)) + .sum::

() + + self + .next_row_linear_combination + .iter() + .map(|&(c, f)| next_v[c] * FE::from_basefield(f)) + .sum::

() + + FE::from_basefield(self.constant) + } + + /// Evaluate on a row of a table given in column-major form. + pub(crate) fn eval_table(&self, table: &[PolynomialValues], row: usize) -> F { + let mut res = self + .linear_combination + .iter() + .map(|&(c, f)| table[c].values[row] * f) + .sum::() + + self.constant; + + // If we access the next row at the last row, for sanity, we consider the next + // row's values to be 0. If the lookups are correctly written, the + // filter should be 0 in that case anyway. + if !self.next_row_linear_combination.is_empty() && row < table[0].values.len() - 1 { + res += self + .next_row_linear_combination + .iter() + .map(|&(c, f)| table[c].values[row + 1] * f) + .sum::(); + } + + res + } + + /// Evaluates the column on all rows. + pub(crate) fn eval_all_rows(&self, table: &[PolynomialValues]) -> Vec { + let length = table[0].len(); + (0..length) + .map(|row| self.eval_table(table, row)) + .collect::>() + } + + /// Circuit version of `eval`: Given a row's targets, returns their linear + /// combination. + pub(crate) fn eval_circuit( + &self, + builder: &mut CircuitBuilder, + v: &[ExtensionTarget], + ) -> ExtensionTarget + where + F: RichField + Extendable, + { + let pairs = self + .linear_combination + .iter() + .map(|&(c, f)| { + ( + v[c], + builder.constant_extension(F::Extension::from_basefield(f)), + ) + }) + .collect::>(); + let constant = builder.constant_extension(F::Extension::from_basefield(self.constant)); + builder.inner_product_extension(F::ONE, constant, pairs) + } + + /// Circuit version of `eval_with_next`: + /// Given the targets of the current and next row, returns the sum of their + /// linear combinations. + pub(crate) fn eval_with_next_circuit( + &self, + builder: &mut CircuitBuilder, + v: &[ExtensionTarget], + next_v: &[ExtensionTarget], + ) -> ExtensionTarget + where + F: RichField + Extendable, + { + let mut pairs = self + .linear_combination + .iter() + .map(|&(c, f)| { + ( + v[c], + builder.constant_extension(F::Extension::from_basefield(f)), + ) + }) + .collect::>(); + let next_row_pairs = self.next_row_linear_combination.iter().map(|&(c, f)| { + ( + next_v[c], + builder.constant_extension(F::Extension::from_basefield(f)), + ) + }); + pairs.extend(next_row_pairs); + let constant = builder.constant_extension(F::Extension::from_basefield(self.constant)); + builder.inner_product_extension(F::ONE, constant, pairs) + } +} + +pub(crate) type ColumnFilter<'a, F> = (&'a [Column], &'a Option>); + +/// A [`Lookup`] defines a set of `columns`` whose values should appear in a +/// `table_column` (i.e. the lookup table associated to these looking columns), +/// along with a `frequencies_column` indicating the frequency of each looking +/// column in the looked table. +/// +/// It also features a `filter_columns` vector, optionally adding at most one +/// filter per looking column. +/// +/// The lookup argumented implemented here is based on logarithmic derivatives, +/// a technique described with the whole lookup protocol in +/// . +#[derive(Debug)] +pub struct Lookup { + /// Columns whose values should be contained in the lookup table. + /// These are the f_i(x) polynomials in the logUp paper. + pub columns: Vec>, + /// Column containing the lookup table. + /// This is the t(x) polynomial in the logUp paper. + pub table_column: Column, + /// Column containing the frequencies of `columns` in `table_column`. + /// This is the m(x) polynomial in the paper. + pub frequencies_column: Column, + + /// Columns to filter some elements. There is at most one filter + /// column per column to lookup. + pub filter_columns: Vec>>, +} + +impl Lookup { + /// Outputs the number of helper columns needed by this [`Lookup`]. + pub fn num_helper_columns(&self, constraint_degree: usize) -> usize { + // One helper column for each column batch of size `constraint_degree-1`, + // then one column for the inverse of `table + challenge` and one for the `Z` + // polynomial. + ceil_div_usize(self.columns.len(), constraint_degree - 1) + 1 + } +} + +/// Randomness for a single instance of a permutation check protocol. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct GrandProductChallenge { + /// Randomness used to combine multiple columns into one. + pub beta: T, + /// Random offset that's added to the beta-reduced column values. + pub gamma: T, +} + +impl GrandProductChallenge { + /// Combines a series of values `t_i` with these challenge random values. + /// In particular, given `beta` and `gamma` challenges, this will compute + /// `(Σ t_i * beta^i) + gamma`. + pub fn combine<'a, FE, P, T: IntoIterator, const D2: usize>(&self, terms: T) -> P + where + FE: FieldExtension, + P: PackedField, + T::IntoIter: DoubleEndedIterator, + { + reduce_with_powers(terms, FE::from_basefield(self.beta)) + FE::from_basefield(self.gamma) + } +} + +impl GrandProductChallenge { + pub(crate) fn combine_circuit, const D: usize>( + &self, + builder: &mut CircuitBuilder, + terms: &[ExtensionTarget], + ) -> ExtensionTarget { + let reduced = reduce_with_powers_ext_circuit(builder, terms, self.beta); + let gamma = builder.convert_to_ext(self.gamma); + builder.add_extension(reduced, gamma) + } +} + +impl GrandProductChallenge { + /// Circuit version of `combine`. + pub fn combine_base_circuit, const D: usize>( + &self, + builder: &mut CircuitBuilder, + terms: &[Target], + ) -> Target { + let reduced = reduce_with_powers_circuit(builder, terms, self.beta); + builder.add(reduced, self.gamma) + } +} + +/// Like `GrandProductChallenge`, but with `num_challenges` copies to boost +/// soundness. +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct GrandProductChallengeSet { + /// A sequence of `num_challenges` challenge pairs, where `num_challenges` + /// is defined in [`StarkConfig`][crate::config::StarkConfig]. + pub challenges: Vec>, +} + +impl GrandProductChallengeSet { + /// Serializes this `GrandProductChallengeSet` of `Target`s. + pub fn to_buffer(&self, buffer: &mut Vec) -> IoResult<()> { + buffer.write_usize(self.challenges.len())?; + for challenge in &self.challenges { + buffer.write_target(challenge.beta)?; + buffer.write_target(challenge.gamma)?; + } + Ok(()) + } + + /// Serializes a `GrandProductChallengeSet` of `Target`s from the provided + /// buffer. + pub fn from_buffer(buffer: &mut Buffer) -> IoResult { + let length = buffer.read_usize()?; + let mut challenges = Vec::with_capacity(length); + for _ in 0..length { + challenges.push(GrandProductChallenge { + beta: buffer.read_target()?, + gamma: buffer.read_target()?, + }); + } + + Ok(GrandProductChallengeSet { challenges }) + } +} + +fn get_grand_product_challenge>( + challenger: &mut Challenger, +) -> GrandProductChallenge { + let beta = challenger.get_challenge(); + let gamma = challenger.get_challenge(); + GrandProductChallenge { beta, gamma } +} + +/// Generates a new `GrandProductChallengeSet` containing `num_challenges` +/// pairs of challenges from the current `challenger` state. +pub fn get_grand_product_challenge_set>( + challenger: &mut Challenger, + num_challenges: usize, +) -> GrandProductChallengeSet { + let challenges = (0..num_challenges) + .map(|_| get_grand_product_challenge(challenger)) + .collect(); + GrandProductChallengeSet { challenges } +} + +fn get_grand_product_challenge_target< + F: RichField + Extendable, + H: AlgebraicHasher, + const D: usize, +>( + builder: &mut CircuitBuilder, + challenger: &mut RecursiveChallenger, +) -> GrandProductChallenge { + let beta = challenger.get_challenge(builder); + let gamma = challenger.get_challenge(builder); + GrandProductChallenge { beta, gamma } +} + +/// Circuit version of `get_grand_product_challenge_set`. +pub fn get_grand_product_challenge_set_target< + F: RichField + Extendable, + H: AlgebraicHasher, + const D: usize, +>( + builder: &mut CircuitBuilder, + challenger: &mut RecursiveChallenger, + num_challenges: usize, +) -> GrandProductChallengeSet { + let challenges = (0..num_challenges) + .map(|_| get_grand_product_challenge_target(builder, challenger)) + .collect(); + GrandProductChallengeSet { challenges } +} + +/// logUp protocol from +/// Compute the helper columns for the lookup argument. +/// Given columns `f0,...,fk` and a column `t`, such that `∪fi ⊆ t`, and +/// challenges `x`, this computes the helper columns `h_i = 1/(x+f_2i) + +/// 1/(x+f_2i+1)`, `g = 1/(x+t)`, and `Z(gx) = Z(x) + sum h_i(x) - m(x)g(x)` +/// where `m` is the frequencies column. +pub(crate) fn lookup_helper_columns( + lookup: &Lookup, + trace_poly_values: &[PolynomialValues], + challenge: F, + constraint_degree: usize, +) -> Vec> { + assert!( + constraint_degree == 2 || constraint_degree == 3, + "TODO: Allow other constraint degrees." + ); + + assert_eq!(lookup.columns.len(), lookup.filter_columns.len()); + + let num_total_logup_entries = trace_poly_values[0].values.len() * lookup.columns.len(); + assert!(BigUint::from(num_total_logup_entries) < F::characteristic()); + + let num_helper_columns = lookup.num_helper_columns(constraint_degree); + + let looking_cols = lookup + .columns + .iter() + .map(|col| vec![col.clone()]) + .collect::>>>(); + + let grand_challenge = GrandProductChallenge { + beta: F::ONE, + gamma: challenge, + }; + + let columns_filters = looking_cols + .iter() + .zip(lookup.filter_columns.iter()) + .map(|(col, filter)| (&col[..], filter)) + .collect::>(); + // For each batch of `constraint_degree-1` columns `fi`, compute `sum + // 1/(f_i+challenge)` and add it to the helper columns. + // Note: these are the h_k(x) polynomials in the paper, with a few differences: + // * Here, the first ratio m_0(x)/phi_0(x) is not included with the + // columns batched up to create the h_k polynomials; instead there's a + // separate helper column for it (see below). + // * Here, we use 1 instead of -1 as the numerator (and subtract later). + // * Here, for now, the batch size (l) is always constraint_degree - 1 = + // 2. + // * Here, there are filters for the columns, to only select some rows in + // a given column. + let mut helper_columns = get_helper_cols( + trace_poly_values, + trace_poly_values[0].len(), + &columns_filters, + grand_challenge, + constraint_degree, + ); + + // Add `1/(table+challenge)` to the helper columns. + // This is 1/phi_0(x) = 1/(x + t(x)) from the paper. + // Here, we don't include m(x) in the numerator, instead multiplying it with + // this column later. + let mut table = lookup.table_column.eval_all_rows(trace_poly_values); + for x in table.iter_mut() { + *x = challenge + *x; + } + let table_inverse: Vec = F::batch_multiplicative_inverse(&table); + + // Compute the `Z` polynomial with `Z(1)=0` and `Z(gx) = Z(x) + sum h_i(x) - + // frequencies(x)g(x)`. This enforces the check from the paper, that the sum + // of the h_k(x) polynomials is 0 over H. In the paper, that sum includes + // m(x)/(x + t(x)) = frequencies(x)/g(x), because that was bundled + // into the h_k(x) polynomials. + let frequencies = &lookup.frequencies_column.eval_all_rows(trace_poly_values); + let mut z = Vec::with_capacity(frequencies.len()); + z.push(F::ZERO); + for i in 0..frequencies.len() - 1 { + let x = helper_columns[..num_helper_columns - 1] + .iter() + .map(|col| col.values[i]) + .sum::() + - frequencies[i] * table_inverse[i]; + z.push(z[i] + x); + } + helper_columns.push(z.into()); + + helper_columns +} + +/// Given data associated to a lookup, check the associated helper polynomials. +pub(crate) fn eval_helper_columns( + filter: &[Option>], + columns: &[Vec

], + local_values: &[P], + next_values: &[P], + helper_columns: &[P], + constraint_degree: usize, + challenges: &GrandProductChallenge, + consumer: &mut ConstraintConsumer

, +) where + F: RichField + Extendable, + FE: FieldExtension, + P: PackedField, +{ + if !helper_columns.is_empty() { + for (j, chunk) in columns.chunks(constraint_degree - 1).enumerate() { + let fs = + &filter[(constraint_degree - 1) * j..(constraint_degree - 1) * j + chunk.len()]; + let h = helper_columns[j]; + + match chunk.len() { + 2 => { + let combin0 = challenges.combine(&chunk[0]); + let combin1 = challenges.combine(chunk[1].iter()); + + let f0 = if let Some(filter0) = &fs[0] { + filter0.eval_filter(local_values, next_values) + } else { + P::ONES + }; + let f1 = if let Some(filter1) = &fs[1] { + filter1.eval_filter(local_values, next_values) + } else { + P::ONES + }; + + consumer.constraint(combin1 * combin0 * h - f0 * combin1 - f1 * combin0); + } + 1 => { + let combin = challenges.combine(&chunk[0]); + let f0 = if let Some(filter1) = &fs[0] { + filter1.eval_filter(local_values, next_values) + } else { + P::ONES + }; + consumer.constraint(combin * h - f0); + } + + _ => todo!("Allow other constraint degrees"), + } + } + } +} + +/// Circuit version of `eval_helper_columns`. +/// Given data associated to a lookup (either a CTL or a range-check), check the +/// associated helper polynomials. +pub(crate) fn eval_helper_columns_circuit, const D: usize>( + builder: &mut CircuitBuilder, + filter: &[Option>], + columns: &[Vec>], + local_values: &[ExtensionTarget], + next_values: &[ExtensionTarget], + helper_columns: &[ExtensionTarget], + constraint_degree: usize, + challenges: &GrandProductChallenge, + consumer: &mut RecursiveConstraintConsumer, +) { + if !helper_columns.is_empty() { + for (j, chunk) in columns.chunks(constraint_degree - 1).enumerate() { + let fs = + &filter[(constraint_degree - 1) * j..(constraint_degree - 1) * j + chunk.len()]; + let h = helper_columns[j]; + + let one = builder.one_extension(); + match chunk.len() { + 2 => { + let combin0 = challenges.combine_circuit(builder, &chunk[0]); + let combin1 = challenges.combine_circuit(builder, &chunk[1]); + + let f0 = if let Some(filter0) = &fs[0] { + filter0.eval_filter_circuit(builder, local_values, next_values) + } else { + one + }; + let f1 = if let Some(filter1) = &fs[1] { + filter1.eval_filter_circuit(builder, local_values, next_values) + } else { + one + }; + + let constr = builder.mul_sub_extension(combin0, h, f0); + let constr = builder.mul_extension(constr, combin1); + let f1_constr = builder.mul_extension(f1, combin0); + let constr = builder.sub_extension(constr, f1_constr); + + consumer.constraint(builder, constr); + } + 1 => { + let combin = challenges.combine_circuit(builder, &chunk[0]); + let f0 = if let Some(filter1) = &fs[0] { + filter1.eval_filter_circuit(builder, local_values, next_values) + } else { + one + }; + let constr = builder.mul_sub_extension(combin, h, f0); + consumer.constraint(builder, constr); + } + + _ => todo!("Allow other constraint degrees"), + } + } + } +} + +/// Given a STARK's trace, and the data associated to one lookup (either CTL or +/// range check), returns the associated helper polynomials. +pub(crate) fn get_helper_cols( + trace: &[PolynomialValues], + degree: usize, + columns_filters: &[ColumnFilter], + challenge: GrandProductChallenge, + constraint_degree: usize, +) -> Vec> { + let num_helper_columns = ceil_div_usize(columns_filters.len(), constraint_degree - 1); + + let mut helper_columns = Vec::with_capacity(num_helper_columns); + + for mut cols_filts in &columns_filters.iter().chunks(constraint_degree - 1) { + let (first_col, first_filter) = cols_filts.next().unwrap(); + + let mut filter_col = Vec::with_capacity(degree); + let first_combined = (0..degree) + .map(|d| { + let f = if let Some(filter) = first_filter { + let f = filter.eval_table(trace, d); + filter_col.push(f); + f + } else { + filter_col.push(F::ONE); + F::ONE + }; + if f.is_one() { + let evals = first_col + .iter() + .map(|c| c.eval_table(trace, d)) + .collect::>(); + challenge.combine(evals.iter()) + } else { + assert_eq!(f, F::ZERO, "Non-binary filter?"); + // Dummy value. Cannot be zero since it will be batch-inverted. + F::ONE + } + }) + .collect::>(); + + let mut acc = F::batch_multiplicative_inverse(&first_combined); + for d in 0..degree { + if filter_col[d].is_zero() { + acc[d] = F::ZERO; + } + } + + for (col, filt) in cols_filts { + let mut filter_col = Vec::with_capacity(degree); + let mut combined = (0..degree) + .map(|d| { + let f = if let Some(filter) = filt { + let f = filter.eval_table(trace, d); + filter_col.push(f); + f + } else { + filter_col.push(F::ONE); + F::ONE + }; + if f.is_one() { + let evals = col + .iter() + .map(|c| c.eval_table(trace, d)) + .collect::>(); + challenge.combine(evals.iter()) + } else { + assert_eq!(f, F::ZERO, "Non-binary filter?"); + // Dummy value. Cannot be zero since it will be batch-inverted. + F::ONE + } + }) + .collect::>(); + + combined = F::batch_multiplicative_inverse(&combined); + + for d in 0..degree { + if filter_col[d].is_zero() { + combined[d] = F::ZERO; + } + } + + batch_add_inplace(&mut acc, &combined); + } + + helper_columns.push(acc.into()); + } + assert_eq!(helper_columns.len(), num_helper_columns); + + helper_columns +} + +#[derive(Debug)] +pub(crate) struct LookupCheckVars +where + F: Field, + FE: FieldExtension, + P: PackedField, +{ + pub(crate) local_values: Vec

, + pub(crate) next_values: Vec

, + pub(crate) challenges: Vec, +} + +/// Constraints for the logUp lookup argument. +pub(crate) fn eval_packed_lookups_generic( + stark: &S, + lookups: &[Lookup], + vars: &S::EvaluationFrame, + lookup_vars: LookupCheckVars, + yield_constr: &mut ConstraintConsumer

, +) where + F: RichField + Extendable, + FE: FieldExtension, + P: PackedField, + S: Stark, +{ + let local_values = vars.get_local_values(); + let next_values = vars.get_next_values(); + let degree = stark.constraint_degree(); + assert!( + degree == 2 || degree == 3, + "TODO: Allow other constraint degrees." + ); + let mut start = 0; + for lookup in lookups { + let num_helper_columns = lookup.num_helper_columns(degree); + for &challenge in &lookup_vars.challenges { + let grand_challenge = GrandProductChallenge { + beta: F::ONE, + gamma: challenge, + }; + let lookup_columns = lookup + .columns + .iter() + .map(|col| vec![col.eval_with_next(local_values, next_values)]) + .collect::>>(); + + // For each chunk, check that `h_i (x+f_2i) (x+f_{2i+1}) = (x+f_2i) * + // filter_{2i+1} + (x+f_{2i+1}) * filter_2i` if the chunk has length + // 2 or if it has length 1, check that `h_i * (x+f_2i) = filter_2i`, where x is + // the challenge + eval_helper_columns( + &lookup.filter_columns, + &lookup_columns, + local_values, + next_values, + &lookup_vars.local_values[start..start + num_helper_columns - 1], + degree, + &grand_challenge, + yield_constr, + ); + + let challenge = FE::from_basefield(challenge); + + // Check the `Z` polynomial. + let z = lookup_vars.local_values[start + num_helper_columns - 1]; + let next_z = lookup_vars.next_values[start + num_helper_columns - 1]; + let table_with_challenge = lookup.table_column.eval(local_values) + challenge; + let y = lookup_vars.local_values[start..start + num_helper_columns - 1] + .iter() + .fold(P::ZEROS, |acc, x| acc + *x) + * table_with_challenge + - lookup.frequencies_column.eval(local_values); + // Check that in the first row, z = 0; + yield_constr.constraint_first_row(z); + yield_constr.constraint((next_z - z) * table_with_challenge - y); + start += num_helper_columns; + } + } +} + +#[derive(Debug)] +pub(crate) struct LookupCheckVarsTarget { + pub(crate) local_values: Vec>, + pub(crate) next_values: Vec>, + pub(crate) challenges: Vec, +} + +pub(crate) fn eval_ext_lookups_circuit< + F: RichField + Extendable, + S: Stark, + const D: usize, +>( + builder: &mut CircuitBuilder, + stark: &S, + vars: &S::EvaluationFrameTarget, + lookup_vars: LookupCheckVarsTarget, + yield_constr: &mut RecursiveConstraintConsumer, +) { + let degree = stark.constraint_degree(); + let lookups = stark.lookups(); + + let local_values = vars.get_local_values(); + let next_values = vars.get_next_values(); + assert!( + degree == 2 || degree == 3, + "TODO: Allow other constraint degrees." + ); + let mut start = 0; + for lookup in lookups { + let num_helper_columns = lookup.num_helper_columns(degree); + let col_values = lookup + .columns + .iter() + .map(|col| vec![col.eval_with_next_circuit(builder, local_values, next_values)]) + .collect::>(); + + for &challenge in &lookup_vars.challenges { + let grand_challenge = GrandProductChallenge { + beta: builder.one(), + gamma: challenge, + }; + + eval_helper_columns_circuit( + builder, + &lookup.filter_columns, + &col_values, + local_values, + next_values, + &lookup_vars.local_values[start..start + num_helper_columns - 1], + degree, + &grand_challenge, + yield_constr, + ); + let challenge = builder.convert_to_ext(challenge); + + let z = lookup_vars.local_values[start + num_helper_columns - 1]; + let next_z = lookup_vars.next_values[start + num_helper_columns - 1]; + let table_column = lookup + .table_column + .eval_circuit(builder, vars.get_local_values()); + let table_with_challenge = builder.add_extension(table_column, challenge); + let mut y = builder.add_many_extension( + &lookup_vars.local_values[start..start + num_helper_columns - 1], + ); + + let frequencies_column = lookup + .frequencies_column + .eval_circuit(builder, vars.get_local_values()); + y = builder.mul_extension(y, table_with_challenge); + y = builder.sub_extension(y, frequencies_column); + + // Check that in the first row, z = 0; + yield_constr.constraint_first_row(builder, z); + let mut constraint = builder.sub_extension(next_z, z); + constraint = builder.mul_extension(constraint, table_with_challenge); + constraint = builder.sub_extension(constraint, y); + yield_constr.constraint(builder, constraint); + start += num_helper_columns; + } + } +} diff --git a/starky/src/proof.rs b/starky/src/proof.rs new file mode 100644 index 000000000..ff384c001 --- /dev/null +++ b/starky/src/proof.rs @@ -0,0 +1,475 @@ +//! All the different proof types and their associated `circuit` versions +//! to be used when proving (recursive) [`Stark`][crate::stark::Stark] +//! statements + +#[cfg(not(feature = "std"))] +use alloc::{vec, vec::Vec}; + +use itertools::Itertools; +use plonky2::field::extension::{Extendable, FieldExtension}; +use plonky2::fri::oracle::PolynomialBatch; +use plonky2::fri::proof::{ + CompressedFriProof, FriChallenges, FriChallengesTarget, FriProof, FriProofTarget, +}; +use plonky2::fri::structure::{ + FriOpeningBatch, FriOpeningBatchTarget, FriOpenings, FriOpeningsTarget, +}; +use plonky2::hash::hash_types::{MerkleCapTarget, RichField}; +use plonky2::hash::merkle_tree::MerkleCap; +use plonky2::iop::ext_target::ExtensionTarget; +use plonky2::iop::target::Target; +use plonky2::plonk::config::{GenericConfig, Hasher}; +use plonky2::util::serialization::{Buffer, IoResult, Read, Write}; +use plonky2_maybe_rayon::*; + +use crate::config::StarkConfig; +use crate::lookup::GrandProductChallengeSet; + +/// Merkle caps and openings that form the proof of a single STARK. +#[derive(Debug, Clone)] +pub struct StarkProof, C: GenericConfig, const D: usize> { + /// Merkle cap of LDEs of trace values. + pub trace_cap: MerkleCap, + /// Optional merkle cap of LDEs of permutation Z values, if any. + pub auxiliary_polys_cap: Option>, + /// Merkle cap of LDEs of trace values. + pub quotient_polys_cap: MerkleCap, + /// Purported values of each polynomial at the challenge point. + pub openings: StarkOpeningSet, + /// A batch FRI argument for all openings. + pub opening_proof: FriProof, +} + +impl, C: GenericConfig, const D: usize> StarkProof { + /// Recover the length of the trace from a STARK proof and a STARK config. + pub fn recover_degree_bits(&self, config: &StarkConfig) -> usize { + let initial_merkle_proof = &self.opening_proof.query_round_proofs[0] + .initial_trees_proof + .evals_proofs[0] + .1; + let lde_bits = config.fri_config.cap_height + initial_merkle_proof.siblings.len(); + lde_bits - config.fri_config.rate_bits + } +} + +/// Circuit version of [`StarkProof`]. +/// Merkle caps and openings that form the proof of a single STARK. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct StarkProofTarget { + /// `Target` for the Merkle cap trace values LDEs. + pub trace_cap: MerkleCapTarget, + /// Optional `Target` for the Merkle cap of lookup helper and CTL columns + /// LDEs, if any. + pub auxiliary_polys_cap: Option, + /// `Target` for the Merkle cap of quotient polynomial evaluations LDEs. + pub quotient_polys_cap: MerkleCapTarget, + /// `Target`s for the purported values of each polynomial at the challenge + /// point. + pub openings: StarkOpeningSetTarget, + /// `Target`s for the batch FRI argument for all openings. + pub opening_proof: FriProofTarget, +} + +impl StarkProofTarget { + /// Serializes a STARK proof. + pub fn to_buffer(&self, buffer: &mut Vec) -> IoResult<()> { + buffer.write_target_merkle_cap(&self.trace_cap)?; + buffer.write_bool(self.auxiliary_polys_cap.is_some())?; + if let Some(poly) = &self.auxiliary_polys_cap { + buffer.write_target_merkle_cap(poly)?; + } + buffer.write_target_merkle_cap(&self.quotient_polys_cap)?; + buffer.write_target_fri_proof(&self.opening_proof)?; + self.openings.to_buffer(buffer)?; + Ok(()) + } + + /// Deserializes a STARK proof. + pub fn from_buffer(buffer: &mut Buffer) -> IoResult { + let trace_cap = buffer.read_target_merkle_cap()?; + let auxiliary_polys_cap = if buffer.read_bool()? { + Some(buffer.read_target_merkle_cap()?) + } else { + None + }; + let quotient_polys_cap = buffer.read_target_merkle_cap()?; + let opening_proof = buffer.read_target_fri_proof()?; + let openings = StarkOpeningSetTarget::from_buffer(buffer)?; + + Ok(Self { + trace_cap, + auxiliary_polys_cap, + quotient_polys_cap, + openings, + opening_proof, + }) + } + + /// Recover the length of the trace from a STARK proof and a STARK config. + pub fn recover_degree_bits(&self, config: &StarkConfig) -> usize { + let initial_merkle_proof = &self.opening_proof.query_round_proofs[0] + .initial_trees_proof + .evals_proofs[0] + .1; + let lde_bits = config.fri_config.cap_height + initial_merkle_proof.siblings.len(); + lde_bits - config.fri_config.rate_bits + } +} + +/// Merkle caps and openings that form the proof of a single STARK, along with +/// its public inputs. +#[derive(Debug, Clone)] +pub struct StarkProofWithPublicInputs< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +> { + /// A STARK proof. + pub proof: StarkProof, + /// Public inputs associated to this STARK proof. + // TODO: Maybe make it generic over a `S: Stark` and replace with `[F; S::PUBLIC_INPUTS]`. + pub public_inputs: Vec, +} + +/// Circuit version of [`StarkProofWithPublicInputs`]. +#[derive(Debug, Clone)] +pub struct StarkProofWithPublicInputsTarget { + /// `Target` STARK proof. + pub proof: StarkProofTarget, + /// `Target` public inputs for this STARK proof. + pub public_inputs: Vec, +} + +/// A compressed proof format of a single STARK. +#[derive(Debug, Clone)] +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, +} + +/// A compressed [`StarkProof`] format of a single STARK with its public inputs. +#[derive(Debug, Clone)] +pub struct CompressedStarkProofWithPublicInputs< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +> { + /// A compressed STARK proof. + pub proof: CompressedStarkProof, + /// Public inputs for this compressed STARK proof. + pub public_inputs: Vec, +} + +/// A [`StarkProof`] along with metadata about the initial Fiat-Shamir state, +/// which is used when creating a recursive wrapper proof around a STARK proof. +#[derive(Debug, Clone)] +pub struct StarkProofWithMetadata +where + F: RichField + Extendable, + C: GenericConfig, +{ + /// Initial Fiat-Shamir state. + pub init_challenger_state: >::Permutation, + /// Proof for a single STARK. + pub proof: StarkProof, +} + +/// A combination of STARK proofs for independent statements operating on +/// possibly shared variables, along with Cross-Table Lookup (CTL) challenges to +/// assert consistency of common variables across tables. +#[derive(Debug, Clone)] +pub struct MultiProof< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, + const N: usize, +> { + /// Proofs for all the different STARK modules. + pub stark_proofs: [StarkProofWithMetadata; N], + /// Cross-table lookup challenges. + pub ctl_challenges: GrandProductChallengeSet, +} + +impl, C: GenericConfig, const D: usize, const N: usize> + MultiProof +{ + /// Returns the degree (i.e. the trace length) of each STARK proof, + /// from their common [`StarkConfig`]. + pub fn recover_degree_bits(&self, config: &StarkConfig) -> [usize; N] { + core::array::from_fn(|i| self.stark_proofs[i].proof.recover_degree_bits(config)) + } +} + +/// Randomness used for a STARK proof. +#[derive(Debug)] +pub struct StarkProofChallenges, const D: usize> { + /// Optional randomness used in any permutation argument. + pub lookup_challenge_set: Option>, + /// Random values used to combine STARK constraints. + pub stark_alphas: Vec, + /// Point at which the STARK polynomials are opened. + pub stark_zeta: F::Extension, + /// Randomness used in FRI. + pub fri_challenges: FriChallenges, +} + +/// Circuit version of [`StarkProofChallenges`]. +#[derive(Debug)] +pub struct StarkProofChallengesTarget { + /// Optional `Target`'s randomness used in any permutation argument. + pub lookup_challenge_set: Option>, + /// `Target`s for the random values used to combine STARK constraints. + pub stark_alphas: Vec, + /// `ExtensionTarget` for the point at which the STARK polynomials are + /// opened. + pub stark_zeta: ExtensionTarget, + /// `Target`s for the randomness used in FRI. + pub fri_challenges: FriChallengesTarget, +} + +/// Randomness for all STARK proofs contained in a [`MultiProof`]`. +#[derive(Debug)] +pub struct MultiProofChallenges, const D: usize, const N: usize> { + /// Randomness used in each STARK proof. + pub stark_challenges: [StarkProofChallenges; N], + /// Randomness used for cross-table lookups. It is shared by all STARKs. + pub ctl_challenges: GrandProductChallengeSet, +} + +/// Purported values of each polynomial at the challenge point. +#[derive(Debug, Clone)] +pub struct StarkOpeningSet, const D: usize> { + /// Openings of trace polynomials at `zeta`. + pub local_values: Vec, + /// Openings of trace polynomials at `g * zeta`. + pub next_values: Vec, + /// Openings of lookups and cross-table lookups `Z` polynomials at `zeta`. + pub auxiliary_polys: Option>, + /// Openings of lookups and cross-table lookups `Z` polynomials at `g * + /// zeta`. + pub auxiliary_polys_next: Option>, + /// Openings of cross-table lookups `Z` polynomials at `1`. + pub ctl_zs_first: Option>, + /// Openings of quotient polynomials at `zeta`. + pub quotient_polys: Vec, +} + +impl, const D: usize> StarkOpeningSet { + /// Returns a `StarkOpeningSet` given all the polynomial commitments, the + /// number of permutation `Z`polynomials, the evaluation point and a + /// generator `g`. + /// + /// Polynomials are evaluated at point `zeta` and, if necessary, at `g * + /// zeta`. + pub fn new>( + zeta: F::Extension, + g: F, + trace_commitment: &PolynomialBatch, + auxiliary_polys_commitment: Option<&PolynomialBatch>, + quotient_commitment: &PolynomialBatch, + num_lookup_columns: usize, + requires_ctl: bool, + num_ctl_polys: &[usize], + ) -> Self { + // Batch evaluates polynomials on the LDE, at a point `z`. + let eval_commitment = |z: F::Extension, c: &PolynomialBatch| { + c.polynomials + .par_iter() + .map(|p| p.to_extension().eval(z)) + .collect::>() + }; + // Batch evaluates polynomials at a base field point `z`. + let eval_commitment_base = |z: F, c: &PolynomialBatch| { + c.polynomials + .par_iter() + .map(|p| p.eval(z)) + .collect::>() + }; + + let auxiliary_first = auxiliary_polys_commitment.map(|c| eval_commitment_base(F::ONE, c)); + // `g * zeta`. + let zeta_next = zeta.scalar_mul(g); + Self { + local_values: eval_commitment(zeta, trace_commitment), + next_values: eval_commitment(zeta_next, trace_commitment), + auxiliary_polys: auxiliary_polys_commitment.map(|c| eval_commitment(zeta, c)), + auxiliary_polys_next: auxiliary_polys_commitment.map(|c| eval_commitment(zeta_next, c)), + ctl_zs_first: requires_ctl.then(|| { + let total_num_helper_cols: usize = num_ctl_polys.iter().sum(); + auxiliary_first.unwrap()[num_lookup_columns + total_num_helper_cols..].to_vec() + }), + quotient_polys: eval_commitment(zeta, quotient_commitment), + } + } + + /// Constructs the openings required by FRI. + /// All openings but `ctl_zs_first` are grouped together. + pub(crate) fn to_fri_openings(&self) -> FriOpenings { + let zeta_batch = FriOpeningBatch { + values: self + .local_values + .iter() + .chain(self.auxiliary_polys.iter().flatten()) + .chain(&self.quotient_polys) + .copied() + .collect_vec(), + }; + let zeta_next_batch = FriOpeningBatch { + values: self + .next_values + .iter() + .chain(self.auxiliary_polys_next.iter().flatten()) + .copied() + .collect_vec(), + }; + + let mut batches = vec![zeta_batch, zeta_next_batch]; + + if let Some(ctl_zs_first) = self.ctl_zs_first.as_ref() { + debug_assert!(!ctl_zs_first.is_empty()); + debug_assert!(self.auxiliary_polys.is_some()); + debug_assert!(self.auxiliary_polys_next.is_some()); + + let ctl_first_batch = FriOpeningBatch { + values: ctl_zs_first + .iter() + .copied() + .map(F::Extension::from_basefield) + .collect(), + }; + + batches.push(ctl_first_batch); + } + + FriOpenings { batches } + } +} + +/// Circuit version of [`StarkOpeningSet`]. +/// `Target`s for the purported values of each polynomial at the challenge +/// point. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct StarkOpeningSetTarget { + /// `ExtensionTarget`s for the openings of trace polynomials at `zeta`. + pub local_values: Vec>, + /// `ExtensionTarget`s for the opening of trace polynomials at `g * zeta`. + pub next_values: Vec>, + /// `ExtensionTarget`s for the opening of lookups and cross-table lookups + /// `Z` polynomials at `zeta`. + pub auxiliary_polys: Option>>, + /// `ExtensionTarget`s for the opening of lookups and cross-table lookups + /// `Z` polynomials at `g * zeta`. + pub auxiliary_polys_next: Option>>, + /// `ExtensionTarget`s for the opening of lookups and cross-table lookups + /// `Z` polynomials at 1. + pub ctl_zs_first: Option>, + /// `ExtensionTarget`s for the opening of quotient polynomials at `zeta`. + pub quotient_polys: Vec>, +} + +impl StarkOpeningSetTarget { + /// Serializes a STARK's opening set. + pub(crate) fn to_buffer(&self, buffer: &mut Vec) -> IoResult<()> { + buffer.write_target_ext_vec(&self.local_values)?; + buffer.write_target_ext_vec(&self.next_values)?; + if let Some(poly) = &self.auxiliary_polys { + buffer.write_bool(true)?; + buffer.write_target_ext_vec(poly)?; + } else { + buffer.write_bool(false)?; + } + if let Some(poly_next) = &self.auxiliary_polys_next { + buffer.write_bool(true)?; + buffer.write_target_ext_vec(poly_next)?; + } else { + buffer.write_bool(false)?; + } + if let Some(ctl_zs_first) = &self.ctl_zs_first { + buffer.write_bool(true)?; + buffer.write_target_vec(ctl_zs_first)?; + } else { + buffer.write_bool(false)?; + } + buffer.write_target_ext_vec(&self.quotient_polys)?; + Ok(()) + } + + /// Deserializes a STARK's opening set. + pub(crate) fn from_buffer(buffer: &mut Buffer) -> IoResult { + let local_values = buffer.read_target_ext_vec::()?; + let next_values = buffer.read_target_ext_vec::()?; + let auxiliary_polys = if buffer.read_bool()? { + Some(buffer.read_target_ext_vec::()?) + } else { + None + }; + let auxiliary_polys_next = if buffer.read_bool()? { + Some(buffer.read_target_ext_vec::()?) + } else { + None + }; + let ctl_zs_first = if buffer.read_bool()? { + Some(buffer.read_target_vec()?) + } else { + None + }; + let quotient_polys = buffer.read_target_ext_vec::()?; + + Ok(Self { + local_values, + next_values, + auxiliary_polys, + auxiliary_polys_next, + ctl_zs_first, + quotient_polys, + }) + } + + /// Circuit version of `to_fri_openings`for [`FriOpeningsTarget`]. + pub(crate) fn to_fri_openings(&self, zero: Target) -> FriOpeningsTarget { + let zeta_batch = FriOpeningBatchTarget { + values: self + .local_values + .iter() + .chain(self.auxiliary_polys.iter().flatten()) + .chain(&self.quotient_polys) + .copied() + .collect_vec(), + }; + let zeta_next_batch = FriOpeningBatchTarget { + values: self + .next_values + .iter() + .chain(self.auxiliary_polys_next.iter().flatten()) + .copied() + .collect_vec(), + }; + + let mut batches = vec![zeta_batch, zeta_next_batch]; + + if let Some(ctl_zs_first) = self.ctl_zs_first.as_ref() { + debug_assert!(!ctl_zs_first.is_empty()); + debug_assert!(self.auxiliary_polys.is_some()); + debug_assert!(self.auxiliary_polys_next.is_some()); + + let ctl_first_batch = FriOpeningBatchTarget { + values: ctl_zs_first + .iter() + .copied() + .map(|t| t.to_ext_target(zero)) + .collect(), + }; + + batches.push(ctl_first_batch); + } + FriOpeningsTarget { batches } + } +} diff --git a/starky/src/prover.rs b/starky/src/prover.rs new file mode 100644 index 000000000..6fdf55c3e --- /dev/null +++ b/starky/src/prover.rs @@ -0,0 +1,661 @@ +//! Implementation of the STARK prover. + +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; +use core::iter::once; + +use anyhow::{ensure, Result}; +use itertools::Itertools; +use plonky2::field::extension::Extendable; +use plonky2::field::packable::Packable; +use plonky2::field::packed::PackedField; +use plonky2::field::polynomial::{PolynomialCoeffs, PolynomialValues}; +use plonky2::field::types::Field; +use plonky2::field::zero_poly_coset::ZeroPolyOnCoset; +use plonky2::fri::oracle::PolynomialBatch; +use plonky2::hash::hash_types::RichField; +use plonky2::iop::challenger::Challenger; +use plonky2::plonk::config::GenericConfig; +use plonky2::timed; +use plonky2::util::timing::TimingTree; +use plonky2::util::{log2_ceil, log2_strict, transpose}; +use plonky2_maybe_rayon::*; + +use crate::config::StarkConfig; +use crate::constraint_consumer::ConstraintConsumer; +use crate::cross_table_lookup::{get_ctl_auxiliary_polys, CtlCheckVars, CtlData}; +use crate::evaluation_frame::StarkEvaluationFrame; +use crate::lookup::{ + get_grand_product_challenge_set, lookup_helper_columns, GrandProductChallengeSet, Lookup, + LookupCheckVars, +}; +use crate::proof::{StarkOpeningSet, StarkProof, StarkProofWithPublicInputs}; +use crate::stark::Stark; +use crate::vanishing_poly::eval_vanishing_poly; + +/// From a STARK trace, computes a STARK proof to attest its correctness. +pub fn prove( + stark: S, + config: &StarkConfig, + trace_poly_values: Vec>, + public_inputs: &[F], + timing: &mut TimingTree, +) -> Result> +where + F: RichField + Extendable, + C: GenericConfig, + S: Stark, +{ + let degree = trace_poly_values[0].len(); + let degree_bits = log2_strict(degree); + let fri_params = config.fri_params(degree_bits); + let rate_bits = config.fri_config.rate_bits; + let cap_height = config.fri_config.cap_height; + assert!( + fri_params.total_arities() <= degree_bits + rate_bits - cap_height, + "FRI total reduction arity is too large.", + ); + + let trace_commitment = timed!( + timing, + "compute trace commitment", + PolynomialBatch::::from_values( + trace_poly_values.clone(), + rate_bits, + false, + cap_height, + timing, + None, + ) + ); + + let trace_cap = trace_commitment.merkle_tree.cap.clone(); + let mut challenger = Challenger::new(); + challenger.observe_cap(&trace_cap); + + prove_with_commitment( + &stark, + config, + &trace_poly_values, + &trace_commitment, + None, + None, + &mut challenger, + public_inputs, + timing, + ) +} + +/// Generates a proof for a single STARK table, including: +/// +/// - the initial state of the challenger, +/// - all the required Merkle caps, +/// - all the required polynomial and FRI argument openings. +/// - individual `ctl_data` and common `ctl_challenges` if the STARK is part +/// of a multi-STARK system. +pub fn prove_with_commitment( + stark: &S, + config: &StarkConfig, + trace_poly_values: &[PolynomialValues], + trace_commitment: &PolynomialBatch, + ctl_data: Option<&CtlData>, + ctl_challenges: Option<&GrandProductChallengeSet>, + challenger: &mut Challenger, + public_inputs: &[F], + timing: &mut TimingTree, +) -> Result> +where + F: RichField + Extendable, + C: GenericConfig, + S: Stark, +{ + let degree = trace_poly_values[0].len(); + let degree_bits = log2_strict(degree); + let fri_params = config.fri_params(degree_bits); + let rate_bits = config.fri_config.rate_bits; + let cap_height = config.fri_config.cap_height; + assert!( + fri_params.total_arities() <= degree_bits + rate_bits - cap_height, + "FRI total reduction arity is too large.", + ); + + // Permutation arguments. + + let constraint_degree = stark.constraint_degree(); + let lookup_challenges = stark.uses_lookups().then(|| { + if let Some(c) = ctl_challenges { + c.challenges.iter().map(|ch| ch.beta).collect::>() + } else { + get_grand_product_challenge_set(challenger, config.num_challenges) + .challenges + .iter() + .map(|ch| ch.beta) + .collect::>() + } + }); + + let lookups = stark.lookups(); + let lookup_helper_columns = timed!( + timing, + "compute lookup helper columns", + lookup_challenges.as_ref().map(|challenges| { + let mut columns = Vec::new(); + for lookup in &lookups { + for &challenge in challenges { + columns.extend(lookup_helper_columns( + lookup, + trace_poly_values, + challenge, + constraint_degree, + )); + } + } + columns + }) + ); + let num_lookup_columns = lookup_helper_columns.as_ref().map_or(0, |v| v.len()); + + // We add CTLs, if there are any, to the permutation arguments so that + // we can batch commit to all auxiliary polynomials. + let auxiliary_polys = match lookup_helper_columns { + None => get_ctl_auxiliary_polys(ctl_data), + Some(mut lookup_columns) => { + if let Some(p) = get_ctl_auxiliary_polys(ctl_data) { + lookup_columns.extend(p) + }; + + Some(lookup_columns) + } + }; + + debug_assert!( + (stark.uses_lookups() || stark.requires_ctls()) || auxiliary_polys.is_none(), + "There should be auxiliary polynomials if and only if we have either lookups or require cross-table lookups." + ); + + // Get the polynomial commitments for all auxiliary polynomials. + let auxiliary_polys_commitment = auxiliary_polys.map(|aux_polys| { + timed!( + timing, + "compute auxiliary polynomials commitment", + PolynomialBatch::from_values( + aux_polys, + rate_bits, + false, + config.fri_config.cap_height, + timing, + None, + ) + ) + }); + + let auxiliary_polys_cap = auxiliary_polys_commitment + .as_ref() + .map(|commit| commit.merkle_tree.cap.clone()); + if let Some(cap) = &auxiliary_polys_cap { + challenger.observe_cap(cap); + } + + let alphas = challenger.get_n_challenges(config.num_challenges); + + let num_ctl_polys = ctl_data + .map(|data| data.num_ctl_helper_polys()) + .unwrap_or_default(); + + // This is an expensive check, hence is only run when `debug_assertions` are + // enabled. + #[cfg(debug_assertions)] + { + check_constraints( + stark, + trace_commitment, + public_inputs, + &auxiliary_polys_commitment, + lookup_challenges.as_ref(), + &lookups, + ctl_data, + alphas.clone(), + degree_bits, + num_lookup_columns, + &num_ctl_polys, + ); + } + + let quotient_polys = timed!( + timing, + "compute quotient polys", + compute_quotient_polys::::Packing, C, S, D>( + stark, + trace_commitment, + &auxiliary_polys_commitment, + lookup_challenges.as_ref(), + &lookups, + ctl_data, + public_inputs, + alphas.clone(), + degree_bits, + num_lookup_columns, + &num_ctl_polys, + config, + ) + ); + let all_quotient_chunks = timed!( + timing, + "split quotient polys", + quotient_polys + .into_par_iter() + .flat_map(|mut quotient_poly| { + quotient_poly + .trim_to_len(degree * stark.quotient_degree_factor()) + .expect( + "Quotient has failed, the vanishing polynomial is not divisible by Z_H", + ); + // Split quotient into degree-n chunks. + quotient_poly.chunks(degree) + }) + .collect() + ); + // Commit to the quotient polynomials. + let quotient_commitment = timed!( + timing, + "compute quotient commitment", + PolynomialBatch::from_coeffs( + all_quotient_chunks, + rate_bits, + false, + config.fri_config.cap_height, + timing, + None, + ) + ); + // Observe the quotient polynomials Merkle cap. + let quotient_polys_cap = quotient_commitment.merkle_tree.cap.clone(); + challenger.observe_cap("ient_polys_cap); + + let zeta = challenger.get_extension_challenge::(); + + // To avoid leaking witness data, we want to ensure that our opening locations, + // `zeta` and `g * zeta`, are not in our subgroup `H`. It suffices to check + // `zeta` only, since `(g * zeta)^n = zeta^n`, where `n` is the order of + // `g`. + let g = F::primitive_root_of_unity(degree_bits); + ensure!( + zeta.exp_power_of_2(degree_bits) != F::Extension::ONE, + "Opening point is in the subgroup." + ); + + // Compute all openings: evaluate all committed polynomials at `zeta` and, when + // necessary, at `g * zeta`. + let openings = StarkOpeningSet::new( + zeta, + g, + trace_commitment, + auxiliary_polys_commitment.as_ref(), + "ient_commitment, + stark.num_lookup_helper_columns(config), + stark.requires_ctls(), + &num_ctl_polys, + ); + // Get the FRI openings and observe them. + challenger.observe_openings(&openings.to_fri_openings()); + + let initial_merkle_trees = once(trace_commitment) + .chain(&auxiliary_polys_commitment) + .chain(once("ient_commitment)) + .collect_vec(); + + let opening_proof = timed!( + timing, + "compute openings proof", + PolynomialBatch::prove_openings( + &stark.fri_instance(zeta, g, num_ctl_polys.iter().sum(), num_ctl_polys, config), + &initial_merkle_trees, + challenger, + &fri_params, + timing, + ) + ); + + let proof = StarkProof { + trace_cap: trace_commitment.merkle_tree.cap.clone(), + auxiliary_polys_cap, + quotient_polys_cap, + openings, + opening_proof, + }; + + Ok(StarkProofWithPublicInputs { + proof, + public_inputs: public_inputs.to_vec(), + }) +} + +/// Computes the quotient polynomials `(sum alpha^i C_i(x)) / Z_H(x)` for +/// `alpha` in `alphas`, where the `C_i`s are the STARK constraints. +fn compute_quotient_polys<'a, F, P, C, S, const D: usize>( + stark: &S, + trace_commitment: &'a PolynomialBatch, + auxiliary_polys_commitment: &'a Option>, + lookup_challenges: Option<&'a Vec>, + lookups: &[Lookup], + ctl_data: Option<&CtlData>, + public_inputs: &[F], + alphas: Vec, + degree_bits: usize, + num_lookup_columns: usize, + num_ctl_columns: &[usize], + config: &StarkConfig, +) -> Vec> +where + F: RichField + Extendable, + P: PackedField, + C: GenericConfig, + S: Stark, +{ + let degree = 1 << degree_bits; + let rate_bits = config.fri_config.rate_bits; + let total_num_helper_cols: usize = num_ctl_columns.iter().sum(); + + let quotient_degree_bits = log2_ceil(stark.quotient_degree_factor()); + assert!( + quotient_degree_bits <= rate_bits, + "Having constraints of degree higher than the rate is not supported yet." + ); + let step = 1 << (rate_bits - quotient_degree_bits); + // When opening the `Z`s polys at the "next" point, need to look at the point + // `next_step` steps away. + let next_step = 1 << quotient_degree_bits; + + // Evaluation of the first Lagrange polynomial on the LDE domain. + let lagrange_first = PolynomialValues::selector(degree, 0).lde_onto_coset(quotient_degree_bits); + // Evaluation of the last Lagrange polynomial on the LDE domain. + let lagrange_last = + PolynomialValues::selector(degree, degree - 1).lde_onto_coset(quotient_degree_bits); + + let z_h_on_coset = ZeroPolyOnCoset::::new(degree_bits, quotient_degree_bits); + + // Retrieve the LDE values at index `i`. + let get_trace_values_packed = + |i_start| -> Vec

{ trace_commitment.get_lde_values_packed(i_start, step) }; + + // Last element of the subgroup. + let last = F::primitive_root_of_unity(degree_bits).inverse(); + let size = degree << quotient_degree_bits; + let coset = F::cyclic_subgroup_coset_known_order( + F::primitive_root_of_unity(degree_bits + quotient_degree_bits), + F::coset_shift(), + size, + ); + + // We will step by `P::WIDTH`, and in each iteration, evaluate the quotient + // polynomial at a batch of `P::WIDTH` points. + let quotient_values = (0..size) + .into_par_iter() + .step_by(P::WIDTH) + .flat_map_iter(|i_start| { + let i_next_start = (i_start + next_step) % size; + let i_range = i_start..i_start + P::WIDTH; + + let x = *P::from_slice(&coset[i_range.clone()]); + let z_last = x - last; + let lagrange_basis_first = *P::from_slice(&lagrange_first.values[i_range.clone()]); + let lagrange_basis_last = *P::from_slice(&lagrange_last.values[i_range]); + + let mut consumer = ConstraintConsumer::new( + alphas.clone(), + z_last, + lagrange_basis_first, + lagrange_basis_last, + ); + // Get the local and next row evaluations for the current STARK, + // as well as the public inputs. + let vars = S::EvaluationFrame::from_values( + &get_trace_values_packed(i_start), + &get_trace_values_packed(i_next_start), + public_inputs, + ); + // Get the local and next row evaluations for the permutation argument, + // as well as the associated challenges. + let lookup_vars = lookup_challenges.map(|challenges| LookupCheckVars { + local_values: auxiliary_polys_commitment + .as_ref() + .unwrap() + .get_lde_values_packed(i_start, step)[..num_lookup_columns] + .to_vec(), + next_values: auxiliary_polys_commitment + .as_ref() + .unwrap() + .get_lde_values_packed(i_next_start, step)[..num_lookup_columns] + .to_vec(), + challenges: challenges.to_vec(), + }); + + // Get all the data for this STARK's CTLs, if any: + // - the local and next row evaluations for the CTL Z polynomials + // - the associated challenges. + // - for each CTL: + // - the filter `Column` + // - the `Column`s that form the looking/looked table. + + let ctl_vars = ctl_data.map(|data| { + let mut start_index = 0; + data.zs_columns + .iter() + .enumerate() + .map(|(i, zs_columns)| { + let num_ctl_helper_cols = num_ctl_columns[i]; + let helper_columns = auxiliary_polys_commitment + .as_ref() + .unwrap() + .get_lde_values_packed(i_start, step) + [num_lookup_columns + start_index + ..num_lookup_columns + start_index + num_ctl_helper_cols] + .to_vec(); + + let ctl_vars = CtlCheckVars:: { + helper_columns, + local_z: auxiliary_polys_commitment + .as_ref() + .unwrap() + .get_lde_values_packed(i_start, step) + [num_lookup_columns + total_num_helper_cols + i], + next_z: auxiliary_polys_commitment + .as_ref() + .unwrap() + .get_lde_values_packed(i_next_start, step) + [num_lookup_columns + total_num_helper_cols + i], + challenges: zs_columns.challenge, + columns: zs_columns.columns.clone(), + filter: zs_columns.filter.clone(), + }; + + start_index += num_ctl_helper_cols; + + ctl_vars + }) + .collect::>() + }); + + // Evaluate the polynomial combining all constraints, including + // those associated to the permutation arguments. + eval_vanishing_poly::( + stark, + &vars, + lookups, + lookup_vars, + ctl_vars.as_deref(), + &mut consumer, + ); + + let mut constraints_evals = consumer.accumulators(); + // We divide the constraints evaluations by `Z_H(x)`. + let denominator_inv: P = z_h_on_coset.eval_inverse_packed(i_start); + + for eval in &mut constraints_evals { + *eval *= denominator_inv; + } + + let num_challenges = alphas.len(); + + (0..P::WIDTH).map(move |i| { + (0..num_challenges) + .map(|j| constraints_evals[j].as_slice()[i]) + .collect() + }) + }) + .collect::>(); + + transpose("ient_values) + .into_par_iter() + .map(PolynomialValues::new) + .map(|values| values.coset_ifft(F::coset_shift())) + .collect() +} + +/// Check that all constraints evaluate to zero on `H`. +/// Can also be used to check the degree of the constraints by evaluating on a +/// larger subgroup. +/// +/// Debugging module, to assert that all constraints evaluate to zero on `H`. +/// It can also be used to check the degree of the constraints by evaluating on +/// a larger subgroup. +/// +/// **Note**: this is an expensive check, hence is only available when the +/// `debug_assertions` flag is activated, to not hinder performances with +/// regular `release` build. +#[cfg(debug_assertions)] +fn check_constraints<'a, F, C, S, const D: usize>( + stark: &S, + trace_commitment: &'a PolynomialBatch, + public_inputs: &[F], + auxiliary_commitment: &'a Option>, + lookup_challenges: Option<&'a Vec>, + lookups: &[Lookup], + ctl_data: Option<&CtlData>, + alphas: Vec, + degree_bits: usize, + num_lookup_columns: usize, + num_ctl_helper_cols: &[usize], +) where + F: RichField + Extendable, + C: GenericConfig, + S: Stark, +{ + let degree = 1 << degree_bits; + let rate_bits = 0; // Set this to higher value to check constraint degree. + let total_num_helper_cols: usize = num_ctl_helper_cols.iter().sum(); + + let size = degree << rate_bits; + let step = 1 << rate_bits; + + // Evaluation of the first Lagrange polynomial. + let lagrange_first = PolynomialValues::selector(degree, 0).lde(rate_bits); + // Evaluation of the last Lagrange polynomial. + let lagrange_last = PolynomialValues::selector(degree, degree - 1).lde(rate_bits); + + let subgroup = F::two_adic_subgroup(degree_bits + rate_bits); + + // Get the evaluations of a batch of polynomials over our subgroup. + let get_subgroup_evals = |comm: &PolynomialBatch| -> Vec> { + let values = comm + .polynomials + .par_iter() + .map(|coeffs| coeffs.clone().fft().values) + .collect::>(); + transpose(&values) + }; + + // Get batch evaluations of the trace and permutation polynomials over our + // subgroup. + let trace_subgroup_evals = get_subgroup_evals(trace_commitment); + let auxiliary_subgroup_evals = auxiliary_commitment.as_ref().map(get_subgroup_evals); + + // Last element of the subgroup. + let last = F::primitive_root_of_unity(degree_bits).inverse(); + + let constraint_values = (0..size) + .map(|i| { + let i_next = (i + step) % size; + + let x = subgroup[i]; + let z_last = x - last; + let lagrange_basis_first = lagrange_first.values[i]; + let lagrange_basis_last = lagrange_last.values[i]; + + let mut consumer = ConstraintConsumer::new( + alphas.clone(), + z_last, + lagrange_basis_first, + lagrange_basis_last, + ); + // Get the local and next row evaluations for the current STARK's trace. + let vars = S::EvaluationFrame::from_values( + &trace_subgroup_evals[i], + &trace_subgroup_evals[i_next], + public_inputs, + ); + // Get the local and next row evaluations for the current STARK's permutation + // argument. + let lookup_vars = lookup_challenges.map(|challenges| LookupCheckVars { + local_values: auxiliary_subgroup_evals.as_ref().unwrap()[i][..num_lookup_columns] + .to_vec(), + next_values: auxiliary_subgroup_evals.as_ref().unwrap()[i_next] + [..num_lookup_columns] + .to_vec(), + challenges: challenges.to_vec(), + }); + + // Get the local and next row evaluations for the current STARK's CTL Z + // polynomials. + let mut start_index = 0; + let ctl_vars = ctl_data.map(|data| { + data.zs_columns + .iter() + .enumerate() + .map(|(iii, zs_columns)| { + let num_helper_cols = num_ctl_helper_cols[iii]; + let helper_columns = auxiliary_subgroup_evals.as_ref().unwrap()[i] + [num_lookup_columns + start_index + ..num_lookup_columns + start_index + num_helper_cols] + .to_vec(); + let ctl_vars = CtlCheckVars:: { + helper_columns, + local_z: auxiliary_subgroup_evals.as_ref().unwrap()[i] + [num_lookup_columns + total_num_helper_cols + iii], + next_z: auxiliary_subgroup_evals.as_ref().unwrap()[i_next] + [num_lookup_columns + total_num_helper_cols + iii], + challenges: zs_columns.challenge, + columns: zs_columns.columns.clone(), + filter: zs_columns.filter.clone(), + }; + + start_index += num_helper_cols; + + ctl_vars + }) + .collect::>() + }); + + // Evaluate the polynomial combining all constraints, including those associated + // to the permutation arguments. + eval_vanishing_poly::( + stark, + &vars, + lookups, + lookup_vars, + ctl_vars.as_deref(), + &mut consumer, + ); + consumer.accumulators() + }) + .collect::>(); + + // Assert that all constraints evaluate to 0 over our subgroup. + for v in constraint_values { + assert!( + v.iter().all(|x| x.is_zero()), + "Constraint failed in {}", + core::any::type_name::() + ); + } +} diff --git a/starky/src/recursive_verifier.rs b/starky/src/recursive_verifier.rs new file mode 100644 index 000000000..75932efbb --- /dev/null +++ b/starky/src/recursive_verifier.rs @@ -0,0 +1,390 @@ +//! Implementation of the STARK recursive verifier, i.e. where proof +//! verification if encoded in a plonky2 circuit. + +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; +use core::iter::once; + +use anyhow::{ensure, Result}; +use itertools::Itertools; +use plonky2::field::extension::Extendable; +use plonky2::field::types::Field; +use plonky2::fri::witness_util::set_fri_proof_target; +use plonky2::hash::hash_types::RichField; +use plonky2::iop::challenger::RecursiveChallenger; +use plonky2::iop::ext_target::ExtensionTarget; +use plonky2::iop::target::Target; +use plonky2::iop::witness::Witness; +use plonky2::plonk::circuit_builder::CircuitBuilder; +use plonky2::plonk::config::{AlgebraicHasher, GenericConfig}; +use plonky2::util::reducing::ReducingFactorTarget; +use plonky2::with_context; + +use crate::config::StarkConfig; +use crate::constraint_consumer::RecursiveConstraintConsumer; +use crate::cross_table_lookup::CtlCheckVarsTarget; +use crate::evaluation_frame::StarkEvaluationFrame; +use crate::lookup::LookupCheckVarsTarget; +use crate::proof::{ + StarkOpeningSetTarget, StarkProof, StarkProofChallengesTarget, StarkProofTarget, + StarkProofWithPublicInputs, StarkProofWithPublicInputsTarget, +}; +use crate::stark::Stark; +use crate::vanishing_poly::eval_vanishing_poly_circuit; + +/// Encodes the verification of a [`StarkProofWithPublicInputsTarget`] +/// for some statement in a circuit. +pub fn verify_stark_proof_circuit< + F: RichField + Extendable, + C: GenericConfig, + S: Stark, + const D: usize, +>( + builder: &mut CircuitBuilder, + stark: S, + proof_with_pis: StarkProofWithPublicInputsTarget, + inner_config: &StarkConfig, +) where + C::Hasher: AlgebraicHasher, +{ + assert_eq!(proof_with_pis.public_inputs.len(), S::PUBLIC_INPUTS); + + let mut challenger = RecursiveChallenger::::new(builder); + let challenges = with_context!( + builder, + "compute challenges", + proof_with_pis.get_challenges::(builder, &mut challenger, None, false, inner_config) + ); + + verify_stark_proof_with_challenges_circuit::( + builder, + &stark, + &proof_with_pis.proof, + &proof_with_pis.public_inputs, + challenges, + None, + inner_config, + ); +} + +/// Recursively verifies an inner STARK proof. +pub fn verify_stark_proof_with_challenges_circuit< + F: RichField + Extendable, + C: GenericConfig, + S: Stark, + const D: usize, +>( + builder: &mut CircuitBuilder, + stark: &S, + proof: &StarkProofTarget, + public_inputs: &[Target], + challenges: StarkProofChallengesTarget, + ctl_vars: Option<&[CtlCheckVarsTarget]>, + inner_config: &StarkConfig, +) where + C::Hasher: AlgebraicHasher, +{ + check_lookup_options(stark, proof, &challenges).unwrap(); + + let zero = builder.zero(); + let one = builder.one_extension(); + + let num_ctl_polys = ctl_vars + .map(|v| v.iter().map(|ctl| ctl.helper_columns.len()).sum::()) + .unwrap_or_default(); + + let StarkOpeningSetTarget { + local_values, + next_values, + auxiliary_polys, + auxiliary_polys_next, + ctl_zs_first, + quotient_polys, + } = &proof.openings; + + let vars = S::EvaluationFrameTarget::from_values( + local_values, + next_values, + &public_inputs + .iter() + .map(|&t| builder.convert_to_ext(t)) + .collect::>(), + ); + + 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_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); + + let mut consumer = RecursiveConstraintConsumer::::new( + builder.zero_extension(), + challenges.stark_alphas, + z_last, + l_0, + l_last, + ); + + let num_lookup_columns = stark.num_lookup_helper_columns(inner_config); + let lookup_challenges = stark.uses_lookups().then(|| { + challenges + .lookup_challenge_set + .as_ref() + .unwrap() + .challenges + .iter() + .map(|ch| ch.beta) + .collect::>() + }); + + let lookup_vars = stark.uses_lookups().then(|| LookupCheckVarsTarget { + local_values: auxiliary_polys.as_ref().unwrap()[..num_lookup_columns].to_vec(), + next_values: auxiliary_polys_next.as_ref().unwrap()[..num_lookup_columns].to_vec(), + challenges: lookup_challenges.unwrap(), + }); + + with_context!( + builder, + "evaluate vanishing polynomial", + eval_vanishing_poly_circuit::( + builder, + stark, + &vars, + lookup_vars, + ctl_vars, + &mut consumer + ) + ); + let vanishing_polys_zeta = consumer.accumulators(); + + // Check each polynomial identity, of the form `vanishing(x) = Z_H(x) + // quotient(x)`, at zeta. + let mut scale = ReducingFactorTarget::new(zeta_pow_deg); + for (i, chunk) in quotient_polys + .chunks(stark.quotient_degree_factor()) + .enumerate() + { + let recombined_quotient = scale.reduce(chunk, builder); + let computed_vanishing_poly = builder.mul_extension(z_h_zeta, recombined_quotient); + builder.connect_extension(vanishing_polys_zeta[i], computed_vanishing_poly); + } + + let merkle_caps = once(proof.trace_cap.clone()) + .chain(proof.auxiliary_polys_cap.clone()) + .chain(once(proof.quotient_polys_cap.clone())) + .collect_vec(); + + let fri_instance = stark.fri_instance_target( + builder, + challenges.stark_zeta, + F::primitive_root_of_unity(degree_bits), + num_ctl_polys, + ctl_zs_first.as_ref().map_or(0, |c| c.len()), + inner_config, + ); + builder.verify_fri_proof::( + &fri_instance, + &proof.openings.to_fri_openings(zero), + &challenges.fri_challenges, + &merkle_caps, + &proof.opening_proof, + &inner_config.fri_params(degree_bits), + ); +} + +fn eval_l_0_and_l_last_circuit, const D: usize>( + builder: &mut CircuitBuilder, + log_n: usize, + x: ExtensionTarget, + z_x: ExtensionTarget, +) -> (ExtensionTarget, ExtensionTarget) { + 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_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_0_deno), + builder.div_extension(z_x, l_last_deno), + ) +} + +/// Adds a new `StarkProofWithPublicInputsTarget` to this circuit. +pub fn add_virtual_stark_proof_with_pis< + F: RichField + Extendable, + S: Stark, + const D: usize, +>( + builder: &mut CircuitBuilder, + stark: &S, + config: &StarkConfig, + degree_bits: usize, + num_ctl_helper_zs: usize, + num_ctl_zs: usize, +) -> StarkProofWithPublicInputsTarget { + let proof = add_virtual_stark_proof::( + builder, + stark, + config, + degree_bits, + num_ctl_helper_zs, + num_ctl_zs, + ); + let public_inputs = builder.add_virtual_targets(S::PUBLIC_INPUTS); + StarkProofWithPublicInputsTarget { + proof, + public_inputs, + } +} + +/// Adds a new `StarkProofTarget` to this circuit. +pub fn add_virtual_stark_proof, S: Stark, const D: usize>( + builder: &mut CircuitBuilder, + stark: &S, + config: &StarkConfig, + degree_bits: usize, + num_ctl_helper_zs: usize, + num_ctl_zs: usize, +) -> StarkProofTarget { + let fri_params = config.fri_params(degree_bits); + let cap_height = fri_params.config.cap_height; + + let num_leaves_per_oracle = once(S::COLUMNS) + .chain( + (stark.uses_lookups() || stark.requires_ctls()) + .then(|| stark.num_lookup_helper_columns(config) + num_ctl_helper_zs), + ) + .chain(once(stark.quotient_degree_factor() * config.num_challenges)) + .collect_vec(); + + let auxiliary_polys_cap = (stark.uses_lookups() || stark.requires_ctls()) + .then(|| builder.add_virtual_cap(cap_height)); + + StarkProofTarget { + trace_cap: builder.add_virtual_cap(cap_height), + auxiliary_polys_cap, + quotient_polys_cap: builder.add_virtual_cap(cap_height), + openings: add_virtual_stark_opening_set::( + builder, + stark, + num_ctl_helper_zs, + num_ctl_zs, + config, + ), + opening_proof: builder.add_virtual_fri_proof(&num_leaves_per_oracle, &fri_params), + } +} + +fn add_virtual_stark_opening_set, S: Stark, const D: usize>( + builder: &mut CircuitBuilder, + stark: &S, + num_ctl_helper_zs: usize, + num_ctl_zs: usize, + config: &StarkConfig, +) -> StarkOpeningSetTarget { + StarkOpeningSetTarget { + local_values: builder.add_virtual_extension_targets(S::COLUMNS), + next_values: builder.add_virtual_extension_targets(S::COLUMNS), + auxiliary_polys: (stark.uses_lookups() || stark.requires_ctls()).then(|| { + builder.add_virtual_extension_targets( + stark.num_lookup_helper_columns(config) + num_ctl_helper_zs, + ) + }), + auxiliary_polys_next: (stark.uses_lookups() || stark.requires_ctls()).then(|| { + builder.add_virtual_extension_targets( + stark.num_lookup_helper_columns(config) + num_ctl_helper_zs, + ) + }), + ctl_zs_first: stark + .requires_ctls() + .then(|| builder.add_virtual_targets(num_ctl_zs)), + quotient_polys: builder + .add_virtual_extension_targets(stark.quotient_degree_factor() * config.num_challenges), + } +} + +/// Set the targets in a `StarkProofWithPublicInputsTarget` to +/// their corresponding values in a `StarkProofWithPublicInputs`. +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 the targets in a [`StarkProofTarget`] to their corresponding values in a +/// [`StarkProof`]. +pub fn set_stark_proof_target, W, const D: usize>( + witness: &mut W, + proof_target: &StarkProofTarget, + proof: &StarkProof, + zero: Target, +) where + F: RichField + Extendable, + C::Hasher: AlgebraicHasher, + W: Witness, +{ + witness.set_cap_target(&proof_target.trace_cap, &proof.trace_cap); + witness.set_cap_target(&proof_target.quotient_polys_cap, &proof.quotient_polys_cap); + + witness.set_fri_openings( + &proof_target.openings.to_fri_openings(zero), + &proof.openings.to_fri_openings(), + ); + + if let (Some(auxiliary_polys_cap_target), Some(auxiliary_polys_cap)) = ( + &proof_target.auxiliary_polys_cap, + &proof.auxiliary_polys_cap, + ) { + witness.set_cap_target(auxiliary_polys_cap_target, auxiliary_polys_cap); + } + + set_fri_proof_target(witness, &proof_target.opening_proof, &proof.opening_proof); +} + +/// Utility function to check that all lookups data wrapped in `Option`s are +/// `Some` iff the STARK uses a permutation argument. +fn check_lookup_options, S: Stark, const D: usize>( + stark: &S, + proof: &StarkProofTarget, + challenges: &StarkProofChallengesTarget, +) -> Result<()> { + let options_is_some = [ + proof.auxiliary_polys_cap.is_some(), + proof.openings.auxiliary_polys.is_some(), + proof.openings.auxiliary_polys_next.is_some(), + challenges.lookup_challenge_set.is_some(), + ]; + ensure!( + options_is_some + .iter() + .all(|&b| b == stark.uses_lookups() || stark.requires_ctls()), + "Lookups data doesn't match with STARK configuration." + ); + Ok(()) +} diff --git a/starky/src/stark.rs b/starky/src/stark.rs new file mode 100644 index 000000000..e903588fe --- /dev/null +++ b/starky/src/stark.rs @@ -0,0 +1,271 @@ +//! Implementation of the [`Stark`] trait that defines the set of constraints +//! related to a statement. + +#[cfg(not(feature = "std"))] +use alloc::{vec, vec::Vec}; + +use plonky2::field::extension::{Extendable, FieldExtension}; +use plonky2::field::packed::PackedField; +use plonky2::field::types::Field; +use plonky2::fri::structure::{ + FriBatchInfo, FriBatchInfoTarget, FriInstanceInfo, FriInstanceInfoTarget, FriOracleInfo, + FriPolynomialInfo, +}; +use plonky2::hash::hash_types::RichField; +use plonky2::iop::ext_target::ExtensionTarget; +use plonky2::plonk::circuit_builder::CircuitBuilder; + +use crate::config::StarkConfig; +use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; +use crate::evaluation_frame::StarkEvaluationFrame; +use crate::lookup::Lookup; + +/// Represents a STARK system. +pub trait Stark, const D: usize>: Sync { + /// The total number of columns in the trace. + const COLUMNS: usize = Self::EvaluationFrameTarget::COLUMNS; + /// The total number of public inputs. + const PUBLIC_INPUTS: usize = Self::EvaluationFrameTarget::PUBLIC_INPUTS; + + /// This is used to evaluate constraints natively. + type EvaluationFrame: StarkEvaluationFrame + where + FE: FieldExtension, + P: PackedField; + + /// The `Target` version of `Self::EvaluationFrame`, used to evaluate + /// constraints recursively. + type EvaluationFrameTarget: StarkEvaluationFrame, ExtensionTarget>; + + /// Evaluates constraints at a vector of points. + /// + /// The points are elements of a field `FE`, a degree `D2` extension of `F`. + /// This lets us evaluate constraints over a larger domain if desired. + /// This can also be called with `FE = F` and `D2 = 1`, in which case we + /// are using the trivial extension, i.e. just evaluating constraints + /// over `F`. + fn eval_packed_generic( + &self, + vars: &Self::EvaluationFrame, + yield_constr: &mut ConstraintConsumer

, + ) where + FE: FieldExtension, + P: PackedField; + + /// Evaluates constraints at a vector of points from the base field `F`. + fn eval_packed_base>( + &self, + vars: &Self::EvaluationFrame, + yield_constr: &mut ConstraintConsumer

, + ) { + self.eval_packed_generic(vars, yield_constr) + } + + /// Evaluates constraints at a single point from the degree `D` extension + /// field. + fn eval_ext( + &self, + vars: &Self::EvaluationFrame, + yield_constr: &mut ConstraintConsumer, + ) { + self.eval_packed_generic(vars, yield_constr) + } + + /// Evaluates constraints at a vector of points from the degree `D` + /// extension field. This is like `eval_ext`, except in the context of a + /// recursive circuit. Note: constraints must be added + /// through`yield_constr.constraint(builder, constraint)` in the same + /// order as they are given in `eval_packed_generic`. + fn eval_ext_circuit( + &self, + builder: &mut CircuitBuilder, + vars: &Self::EvaluationFrameTarget, + yield_constr: &mut RecursiveConstraintConsumer, + ); + + /// Outputs the maximum constraint degree of this [`Stark`]. + fn constraint_degree(&self) -> usize; + + /// Outputs the maximum quotient polynomial's degree factor of this + /// [`Stark`]. + fn quotient_degree_factor(&self) -> usize { + 1.max(self.constraint_degree() - 1) + } + + /// Outputs the number of quotient polynomials this [`Stark`] would require + /// with the provided [`StarkConfig`] + fn num_quotient_polys(&self, config: &StarkConfig) -> usize { + self.quotient_degree_factor() * config.num_challenges + } + + /// Computes the FRI instance used to prove this Stark. + fn fri_instance( + &self, + zeta: F::Extension, + g: F, + num_ctl_helpers: usize, + num_ctl_zs: Vec, + config: &StarkConfig, + ) -> FriInstanceInfo { + let mut oracles = vec![]; + let trace_info = FriPolynomialInfo::from_range(oracles.len(), 0..Self::COLUMNS); + oracles.push(FriOracleInfo { + num_polys: Self::COLUMNS, + blinding: false, + }); + + let num_lookup_columns = self.num_lookup_helper_columns(config); + let num_auxiliary_polys = num_lookup_columns + num_ctl_helpers + num_ctl_zs.len(); + let auxiliary_polys_info = if self.uses_lookups() || self.requires_ctls() { + let aux_polys = FriPolynomialInfo::from_range(oracles.len(), 0..num_auxiliary_polys); + oracles.push(FriOracleInfo { + num_polys: num_auxiliary_polys, + blinding: false, + }); + aux_polys + } else { + vec![] + }; + + let num_quotient_polys = self.num_quotient_polys(config); + let quotient_info = FriPolynomialInfo::from_range(oracles.len(), 0..num_quotient_polys); + oracles.push(FriOracleInfo { + num_polys: num_quotient_polys, + blinding: false, + }); + + let zeta_batch = FriBatchInfo { + point: zeta, + polynomials: [ + trace_info.clone(), + auxiliary_polys_info.clone(), + quotient_info, + ] + .concat(), + }; + let zeta_next_batch = FriBatchInfo { + point: zeta.scalar_mul(g), + polynomials: [trace_info, auxiliary_polys_info].concat(), + }; + + let mut batches = vec![zeta_batch, zeta_next_batch]; + + if self.requires_ctls() { + let ctl_zs_info = FriPolynomialInfo::from_range( + 1, // auxiliary oracle index + num_lookup_columns + num_ctl_helpers..num_auxiliary_polys, + ); + let ctl_first_batch = FriBatchInfo { + point: F::Extension::ONE, + polynomials: ctl_zs_info, + }; + + batches.push(ctl_first_batch); + } + + FriInstanceInfo { oracles, batches } + } + + /// Computes the FRI instance used to prove this Stark. + fn fri_instance_target( + &self, + builder: &mut CircuitBuilder, + zeta: ExtensionTarget, + g: F, + num_ctl_helper_polys: usize, + num_ctl_zs: usize, + config: &StarkConfig, + ) -> FriInstanceInfoTarget { + let mut oracles = vec![]; + let trace_info = FriPolynomialInfo::from_range(oracles.len(), 0..Self::COLUMNS); + oracles.push(FriOracleInfo { + num_polys: Self::COLUMNS, + blinding: false, + }); + + let num_lookup_columns = self.num_lookup_helper_columns(config); + let num_auxiliary_polys = num_lookup_columns + num_ctl_helper_polys + num_ctl_zs; + let auxiliary_polys_info = if self.uses_lookups() || self.requires_ctls() { + let aux_polys = FriPolynomialInfo::from_range(oracles.len(), 0..num_auxiliary_polys); + oracles.push(FriOracleInfo { + num_polys: num_auxiliary_polys, + blinding: false, + }); + aux_polys + } else { + vec![] + }; + + let num_quotient_polys = self.num_quotient_polys(config); + let quotient_info = FriPolynomialInfo::from_range(oracles.len(), 0..num_quotient_polys); + oracles.push(FriOracleInfo { + num_polys: num_quotient_polys, + blinding: false, + }); + + let zeta_batch = FriBatchInfoTarget { + point: zeta, + polynomials: [ + trace_info.clone(), + auxiliary_polys_info.clone(), + quotient_info, + ] + .concat(), + }; + let zeta_next = builder.mul_const_extension(g, zeta); + let zeta_next_batch = FriBatchInfoTarget { + point: zeta_next, + polynomials: [trace_info, auxiliary_polys_info].concat(), + }; + + let mut batches = vec![zeta_batch, zeta_next_batch]; + + if self.requires_ctls() { + let ctl_zs_info = FriPolynomialInfo::from_range( + 1, // auxiliary oracle index + num_lookup_columns + num_ctl_helper_polys..num_auxiliary_polys, + ); + let ctl_first_batch = FriBatchInfoTarget { + point: builder.one_extension(), + polynomials: ctl_zs_info, + }; + + batches.push(ctl_first_batch); + } + + FriInstanceInfoTarget { oracles, batches } + } + + /// Outputs all the [`Lookup`] this STARK table needs to perform across its + /// columns. + fn lookups(&self) -> Vec> { + vec![] + } + + /// Outputs the number of total lookup helper columns, based on this STARK's + /// vector of [`Lookup`] and the number of challenges used by this + /// [`StarkConfig`]. + fn num_lookup_helper_columns(&self, config: &StarkConfig) -> usize { + self.lookups() + .iter() + .map(|lookup| lookup.num_helper_columns(self.constraint_degree())) + .sum::() + * config.num_challenges + } + + /// Indicates whether this STARK uses lookups over some of its columns, and + /// as such requires additional steps during proof generation to handle + /// auxiliary polynomials. + fn uses_lookups(&self) -> bool { + !self.lookups().is_empty() + } + + /// Indicates whether this STARK belongs to a multi-STARK system, and as + /// such may require cross-table lookups to connect shared values across + /// different traces. + /// + /// It defaults to `false`, i.e. for simple uni-STARK systems. + fn requires_ctls(&self) -> bool { + false + } +} diff --git a/starky/src/stark_testing.rs b/starky/src/stark_testing.rs new file mode 100644 index 000000000..c84f5b52e --- /dev/null +++ b/starky/src/stark_testing.rs @@ -0,0 +1,158 @@ +//! Utility module for testing [`Stark`] implementation. + +#[cfg(not(feature = "std"))] +use alloc::{vec, vec::Vec}; + +use anyhow::{ensure, Result}; +use plonky2::field::extension::{Extendable, FieldExtension}; +use plonky2::field::polynomial::{PolynomialCoeffs, PolynomialValues}; +use plonky2::field::types::{Field, Sample}; +use plonky2::hash::hash_types::RichField; +use plonky2::iop::witness::{PartialWitness, WitnessWrite}; +use plonky2::plonk::circuit_builder::CircuitBuilder; +use plonky2::plonk::circuit_data::CircuitConfig; +use plonky2::plonk::config::GenericConfig; +use plonky2::util::{log2_ceil, log2_strict, transpose}; + +use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; +use crate::evaluation_frame::StarkEvaluationFrame; +use crate::stark::Stark; + +const WITNESS_SIZE: usize = 1 << 5; + +/// Tests that the constraints imposed by the given STARK are low-degree by +/// applying them to random low-degree witness polynomials. +pub fn test_stark_low_degree, S: Stark, const D: usize>( + stark: S, +) -> Result<()> { + 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_vec(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); + + let last = F::primitive_root_of_unity(log2_strict(WITNESS_SIZE)).inverse(); + let subgroup = + F::cyclic_subgroup_known_order(F::primitive_root_of_unity(log2_strict(size)), size); + let alpha = F::rand(); + let constraint_evals = (0..size) + .map(|i| { + let vars = S::EvaluationFrame::from_values( + &trace_ldes[i], + &trace_ldes[(i + (1 << rate_bits)) % size], + &public_inputs, + ); + + let mut consumer = ConstraintConsumer::::new( + vec![alpha], + subgroup[i] - last, + lagrange_first.values[i], + lagrange_last.values[i], + ); + stark.eval_packed_base(&vars, &mut consumer); + consumer.accumulators()[0] + }) + .collect::>(); + + let constraint_eval_degree = PolynomialValues::new(constraint_evals).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 + ); + + Ok(()) +} + +/// Tests that the circuit constraints imposed by the given STARK are coherent +/// with the native constraints. +pub fn test_stark_circuit_constraints< + F: RichField + Extendable, + C: GenericConfig, + S: Stark, + const D: usize, +>( + stark: S, +) -> Result<()> { + // Compute native constraint evaluation on random values. + let vars = S::EvaluationFrame::from_values( + &F::Extension::rand_vec(S::COLUMNS), + &F::Extension::rand_vec(S::COLUMNS), + &F::Extension::rand_vec(S::PUBLIC_INPUTS), + ); + let alphas = F::rand_vec(1); + let z_last = F::Extension::rand(); + let lagrange_first = F::Extension::rand(); + let lagrange_last = F::Extension::rand(); + let mut consumer = ConstraintConsumer::::new( + alphas + .iter() + .copied() + .map(F::Extension::from_basefield) + .collect(), + z_last, + lagrange_first, + lagrange_last, + ); + stark.eval_ext(&vars, &mut consumer); + let native_eval = consumer.accumulators()[0]; + // Compute circuit constraint evaluation on same random values. + let circuit_config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(circuit_config); + let mut pw = PartialWitness::::new(); + + let locals_t = builder.add_virtual_extension_targets(S::COLUMNS); + pw.set_extension_targets(&locals_t, vars.get_local_values()); + let nexts_t = builder.add_virtual_extension_targets(S::COLUMNS); + pw.set_extension_targets(&nexts_t, vars.get_next_values()); + let pis_t = builder.add_virtual_extension_targets(S::PUBLIC_INPUTS); + pw.set_extension_targets(&pis_t, vars.get_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(); + pw.set_extension_target(z_last_t, z_last); + let lagrange_first_t = builder.add_virtual_extension_target(); + pw.set_extension_target(lagrange_first_t, lagrange_first); + let lagrange_last_t = builder.add_virtual_extension_target(); + pw.set_extension_target(lagrange_last_t, lagrange_last); + + let vars = S::EvaluationFrameTarget::from_values(&locals_t, &nexts_t, &pis_t); + let mut consumer = RecursiveConstraintConsumer::::new( + builder.zero_extension(), + alphas_t, + z_last_t, + lagrange_first_t, + lagrange_last_t, + ); + stark.eval_ext_circuit(&mut builder, &vars, &mut consumer); + let circuit_eval = consumer.accumulators()[0]; + let native_eval_t = builder.constant_extension(native_eval); + builder.connect_extension(circuit_eval, native_eval_t); + + let data = builder.build::(); + let proof = data.prove(pw)?; + data.verify(proof) +} + +fn random_low_degree_matrix(num_polys: usize, rate_bits: usize) -> Vec> { + let polys = (0..num_polys) + .map(|_| random_low_degree_values(rate_bits)) + .collect::>(); + + transpose(&polys) +} + +fn random_low_degree_values(rate_bits: usize) -> Vec { + PolynomialCoeffs::new(F::rand_vec(WITNESS_SIZE)) + .lde(rate_bits) + .fft() + .values +} diff --git a/starky/src/util.rs b/starky/src/util.rs new file mode 100644 index 000000000..6b17223cf --- /dev/null +++ b/starky/src/util.rs @@ -0,0 +1,22 @@ +//! Utility module providing some helper functions. + +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + +use itertools::Itertools; +use plonky2::field::polynomial::PolynomialValues; +use plonky2::field::types::Field; +use plonky2::util::transpose; + +/// A helper function to transpose a row-wise trace and put it in the format +/// that `prove` expects. +pub fn trace_rows_to_poly_values( + trace_rows: Vec<[F; COLUMNS]>, +) -> Vec> { + let trace_row_vecs = trace_rows.into_iter().map(|row| row.to_vec()).collect_vec(); + let trace_col_vecs: Vec> = transpose(&trace_row_vecs); + trace_col_vecs + .into_iter() + .map(|column| PolynomialValues::new(column)) + .collect() +} diff --git a/starky/src/vanishing_poly.rs b/starky/src/vanishing_poly.rs new file mode 100644 index 000000000..c5ea5c107 --- /dev/null +++ b/starky/src/vanishing_poly.rs @@ -0,0 +1,85 @@ +use plonky2::field::extension::{Extendable, FieldExtension}; +use plonky2::field::packed::PackedField; +use plonky2::hash::hash_types::RichField; +use plonky2::plonk::circuit_builder::CircuitBuilder; + +use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; +use crate::cross_table_lookup::{ + eval_cross_table_lookup_checks, eval_cross_table_lookup_checks_circuit, CtlCheckVars, + CtlCheckVarsTarget, +}; +use crate::lookup::{ + eval_ext_lookups_circuit, eval_packed_lookups_generic, Lookup, LookupCheckVars, + LookupCheckVarsTarget, +}; +use crate::stark::Stark; + +/// Evaluates all constraint, permutation and cross-table lookup polynomials +/// of the current STARK at the local and next values. +pub(crate) fn eval_vanishing_poly( + stark: &S, + vars: &S::EvaluationFrame, + lookups: &[Lookup], + lookup_vars: Option>, + ctl_vars: Option<&[CtlCheckVars]>, + consumer: &mut ConstraintConsumer

, +) where + F: RichField + Extendable, + FE: FieldExtension, + P: PackedField, + S: Stark, +{ + // Evaluate all of the STARK's table constraints. + stark.eval_packed_generic(vars, consumer); + if let Some(lookup_vars) = lookup_vars { + // Evaluate the STARK constraints related to the permutation arguments. + eval_packed_lookups_generic::( + stark, + lookups, + vars, + lookup_vars, + consumer, + ); + } + if let Some(ctl_vars) = ctl_vars { + // Evaluate the STARK constraints related to the CTLs. + eval_cross_table_lookup_checks::( + vars, + ctl_vars, + consumer, + stark.constraint_degree(), + ); + } +} + +/// Circuit version of `eval_vanishing_poly`. +/// Evaluates all constraint, permutation and cross-table lookup polynomials +/// of the current STARK at the local and next values. +pub(crate) fn eval_vanishing_poly_circuit( + builder: &mut CircuitBuilder, + stark: &S, + vars: &S::EvaluationFrameTarget, + lookup_vars: Option>, + ctl_vars: Option<&[CtlCheckVarsTarget]>, + consumer: &mut RecursiveConstraintConsumer, +) where + F: RichField + Extendable, + S: Stark, +{ + // Evaluate all of the STARK's table constraints. + stark.eval_ext_circuit(builder, vars, consumer); + if let Some(lookup_vars) = lookup_vars { + // Evaluate all of the STARK's constraints related to the permutation argument. + eval_ext_lookups_circuit::(builder, stark, vars, lookup_vars, consumer); + } + if let Some(ctl_vars) = ctl_vars { + // Evaluate all of the STARK's constraints related to the CTLs. + eval_cross_table_lookup_checks_circuit::( + builder, + vars, + ctl_vars, + consumer, + stark.constraint_degree(), + ); + } +} diff --git a/starky/src/verifier.rs b/starky/src/verifier.rs new file mode 100644 index 000000000..bac9dbfe2 --- /dev/null +++ b/starky/src/verifier.rs @@ -0,0 +1,353 @@ +//! Implementation of the STARK verifier. + +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; +use core::any::type_name; +use core::iter::once; + +use anyhow::{anyhow, ensure, Result}; +use itertools::Itertools; +use plonky2::field::extension::{Extendable, FieldExtension}; +use plonky2::field::types::Field; +use plonky2::fri::verifier::verify_fri_proof; +use plonky2::hash::hash_types::RichField; +use plonky2::hash::merkle_tree::MerkleCap; +use plonky2::iop::challenger::Challenger; +use plonky2::plonk::config::GenericConfig; +use plonky2::plonk::plonk_common::reduce_with_powers; + +use crate::config::StarkConfig; +use crate::constraint_consumer::ConstraintConsumer; +use crate::cross_table_lookup::CtlCheckVars; +use crate::evaluation_frame::StarkEvaluationFrame; +use crate::lookup::LookupCheckVars; +use crate::proof::{StarkOpeningSet, StarkProof, StarkProofChallenges, StarkProofWithPublicInputs}; +use crate::stark::Stark; +use crate::vanishing_poly::eval_vanishing_poly; + +/// Verifies a [`StarkProofWithPublicInputs`] against a STARK statement. +pub fn verify_stark_proof< + F: RichField + Extendable, + C: GenericConfig, + S: Stark, + const D: usize, +>( + stark: S, + proof_with_pis: StarkProofWithPublicInputs, + config: &StarkConfig, +) -> Result<()> { + ensure!(proof_with_pis.public_inputs.len() == S::PUBLIC_INPUTS); + let mut challenger = Challenger::::new(); + + let challenges = proof_with_pis.get_challenges(&mut challenger, None, false, config); + + verify_stark_proof_with_challenges( + &stark, + &proof_with_pis.proof, + &challenges, + None, + &proof_with_pis.public_inputs, + config, + ) +} + +/// Verifies a [`StarkProofWithPublicInputs`] against a STARK statement, +/// with the provided [`StarkProofChallenges`]. +/// It also supports optional cross-table lookups data and challenges, +/// in case this proof is part of a multi-STARK system. +pub fn verify_stark_proof_with_challenges( + stark: &S, + proof: &StarkProof, + challenges: &StarkProofChallenges, + ctl_vars: Option<&[CtlCheckVars]>, + public_inputs: &[F], + config: &StarkConfig, +) -> Result<()> +where + F: RichField + Extendable, + C: GenericConfig, + S: Stark, +{ + log::debug!("Checking proof: {}", type_name::()); + + let (num_ctl_z_polys, num_ctl_polys) = ctl_vars + .map(|ctls| { + ( + ctls.len(), + ctls.iter().map(|ctl| ctl.helper_columns.len()).sum(), + ) + }) + .unwrap_or_default(); + + validate_proof_shape( + stark, + proof, + public_inputs, + config, + num_ctl_polys, + num_ctl_z_polys, + )?; + + let StarkOpeningSet { + local_values, + next_values, + auxiliary_polys, + auxiliary_polys_next, + ctl_zs_first: _, + quotient_polys, + } = &proof.openings; + + let vars = S::EvaluationFrame::from_values( + local_values, + next_values, + &public_inputs + .iter() + .copied() + .map(F::Extension::from_basefield) + .collect::>(), + ); + + let degree_bits = proof.recover_degree_bits(config); + 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( + challenges + .stark_alphas + .iter() + .map(|&alpha| F::Extension::from_basefield(alpha)) + .collect::>(), + z_last, + l_0, + l_last, + ); + + let num_lookup_columns = stark.num_lookup_helper_columns(config); + let lookup_challenges = if stark.uses_lookups() { + Some( + challenges + .lookup_challenge_set + .as_ref() + .unwrap() + .challenges + .iter() + .map(|ch| ch.beta) + .collect::>(), + ) + } else { + None + }; + + let lookup_vars = stark.uses_lookups().then(|| LookupCheckVars { + local_values: auxiliary_polys.as_ref().unwrap()[..num_lookup_columns].to_vec(), + next_values: auxiliary_polys_next.as_ref().unwrap()[..num_lookup_columns].to_vec(), + challenges: lookup_challenges.unwrap(), + }); + let lookups = stark.lookups(); + + eval_vanishing_poly::( + stark, + &vars, + &lookups, + lookup_vars, + ctl_vars, + &mut consumer, + ); + let vanishing_polys_zeta = consumer.accumulators(); + + // Check each polynomial identity, of the form `vanishing(x) = Z_H(x) + // quotient(x)`, at zeta. + let zeta_pow_deg = challenges.stark_zeta.exp_power_of_2(degree_bits); + let z_h_zeta = zeta_pow_deg - F::Extension::ONE; + // `quotient_polys_zeta` holds `num_challenges * quotient_degree_factor` + // evaluations. Each chunk of `quotient_degree_factor` holds the evaluations + // of `t_0(zeta),...,t_{quotient_degree_factor-1}(zeta)` where the "real" + // quotient polynomial is `t(X) = t_0(X) + t_1(X)*X^n + t_2(X)*X^{2n} + ...`. + // So to reconstruct `t(zeta)` we can compute `reduce_with_powers(chunk, + // zeta^n)` for each `quotient_degree_factor`-sized chunk of the original + // evaluations. + for (i, chunk) in quotient_polys + .chunks(stark.quotient_degree_factor()) + .enumerate() + { + ensure!( + vanishing_polys_zeta[i] == z_h_zeta * reduce_with_powers(chunk, zeta_pow_deg), + "Mismatch between evaluation and opening of quotient polynomial" + ); + } + + let merkle_caps = once(proof.trace_cap.clone()) + .chain(proof.auxiliary_polys_cap.clone()) + .chain(once(proof.quotient_polys_cap.clone())) + .collect_vec(); + + let num_ctl_zs = ctl_vars + .map(|vars| { + vars.iter() + .map(|ctl| ctl.helper_columns.len()) + .collect::>() + }) + .unwrap_or_default(); + + verify_fri_proof::( + &stark.fri_instance( + challenges.stark_zeta, + F::primitive_root_of_unity(degree_bits), + num_ctl_polys, + num_ctl_zs, + config, + ), + &proof.openings.to_fri_openings(), + &challenges.fri_challenges, + &merkle_caps, + &proof.opening_proof, + &config.fri_params(degree_bits), + )?; + + Ok(()) +} + +fn validate_proof_shape( + stark: &S, + proof: &StarkProof, + public_inputs: &[F], + config: &StarkConfig, + num_ctl_helpers: usize, + num_ctl_zs: usize, +) -> anyhow::Result<()> +where + F: RichField + Extendable, + C: GenericConfig, + S: Stark, +{ + let degree_bits = proof.recover_degree_bits(config); + + let StarkProof { + trace_cap, + auxiliary_polys_cap, + quotient_polys_cap, + openings, + // The shape of the opening proof will be checked in the FRI verifier (see + // validate_fri_proof_shape), so we ignore it here. + opening_proof: _, + } = proof; + + let StarkOpeningSet { + local_values, + next_values, + auxiliary_polys, + auxiliary_polys_next, + ctl_zs_first, + quotient_polys, + } = openings; + + ensure!(public_inputs.len() == S::PUBLIC_INPUTS); + + let fri_params = config.fri_params(degree_bits); + let cap_height = fri_params.config.cap_height; + + ensure!(trace_cap.height() == cap_height); + ensure!(quotient_polys_cap.height() == cap_height); + + ensure!(local_values.len() == S::COLUMNS); + ensure!(next_values.len() == S::COLUMNS); + ensure!(quotient_polys.len() == stark.num_quotient_polys(config)); + + check_lookup_options::( + stark, + auxiliary_polys_cap, + auxiliary_polys, + auxiliary_polys_next, + num_ctl_helpers, + num_ctl_zs, + ctl_zs_first, + config, + )?; + + Ok(()) +} + +/// 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; + let invs = F::batch_multiplicative_inverse(&[n * (x - F::ONE), n * (g * x - F::ONE)]); + + (z_x * invs[0], z_x * invs[1]) +} + +/// Utility function to check that all lookups data wrapped in `Option`s are +/// `Some` iff the STARK uses a permutation argument. +fn check_lookup_options( + stark: &S, + auxiliary_polys_cap: &Option>::Hasher>>, + auxiliary_polys: &Option>::Extension>>, + auxiliary_polys_next: &Option>::Extension>>, + num_ctl_helpers: usize, + num_ctl_zs: usize, + ctl_zs_first: &Option>, + config: &StarkConfig, +) -> Result<()> +where + F: RichField + Extendable, + C: GenericConfig, + S: Stark, +{ + if stark.uses_lookups() || stark.requires_ctls() { + let num_auxiliary = stark.num_lookup_helper_columns(config) + num_ctl_helpers + num_ctl_zs; + let cap_height = config.fri_config.cap_height; + + let auxiliary_polys_cap = auxiliary_polys_cap + .as_ref() + .ok_or_else(|| anyhow!("Missing auxiliary_polys_cap"))?; + let auxiliary_polys = auxiliary_polys + .as_ref() + .ok_or_else(|| anyhow!("Missing auxiliary_polys"))?; + let auxiliary_polys_next = auxiliary_polys_next + .as_ref() + .ok_or_else(|| anyhow!("Missing auxiliary_polys_next"))?; + + if let Some(ctl_zs_first) = ctl_zs_first { + ensure!(ctl_zs_first.len() == num_ctl_zs); + } + + ensure!(auxiliary_polys_cap.height() == cap_height); + ensure!(auxiliary_polys.len() == num_auxiliary); + ensure!(auxiliary_polys_next.len() == num_auxiliary); + } else { + ensure!(auxiliary_polys_cap.is_none()); + ensure!(auxiliary_polys.is_none()); + ensure!(auxiliary_polys_next.is_none()); + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use plonky2::field::goldilocks_field::GoldilocksField; + use plonky2::field::polynomial::PolynomialValues; + use plonky2::field::types::Sample; + + use crate::verifier::eval_l_0_and_l_last; + + #[test] + fn test_eval_l_0_and_l_last() { + type F = GoldilocksField; + let log_n = 5; + let n = 1 << log_n; + + let x = F::rand(); // challenge point + 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_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/util/.cargo/katex-header.html b/util/.cargo/katex-header.html new file mode 100644 index 000000000..20723b5d2 --- /dev/null +++ b/util/.cargo/katex-header.html @@ -0,0 +1 @@ +../../.cargo/katex-header.html \ No newline at end of file diff --git a/util/Cargo.toml b/util/Cargo.toml new file mode 100644 index 000000000..758391c3b --- /dev/null +++ b/util/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "plonky2_util" +description = "Utilities used by Plonky2" +version = "0.1.1" +license = "MIT OR Apache-2.0" +edition = "2021" + +[dev-dependencies] +rand = { version = "0.8.5", default-features = false, features = ["getrandom"] } + +# Display math equations properly in documentation +[package.metadata.docs.rs] +rustdoc-args = ["--html-in-header", ".cargo/katex-header.html"] diff --git a/util/LICENSE-APACHE b/util/LICENSE-APACHE new file mode 100644 index 000000000..1e5006dc1 --- /dev/null +++ b/util/LICENSE-APACHE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + diff --git a/util/LICENSE-MIT b/util/LICENSE-MIT new file mode 100644 index 000000000..86d690b22 --- /dev/null +++ b/util/LICENSE-MIT @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2022 The Plonky2 Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/util/src/lib.rs b/util/src/lib.rs new file mode 100644 index 000000000..b5ead3c1e --- /dev/null +++ b/util/src/lib.rs @@ -0,0 +1,411 @@ +#![allow(clippy::needless_range_loop)] +#![no_std] + +extern crate alloc; + +use alloc::vec::Vec; +use core::hint::unreachable_unchecked; +use core::mem::size_of; +use core::ptr::{swap, swap_nonoverlapping}; + +use crate::transpose_util::transpose_in_place_square; + +mod transpose_util; + +pub const fn bits_u64(n: u64) -> usize { + (64 - n.leading_zeros()) as usize +} + +pub const fn ceil_div_usize(a: usize, b: usize) -> usize { + (a + b - 1) / b +} + +/// Computes `ceil(log_2(n))`. +#[must_use] +pub const fn log2_ceil(n: usize) -> usize { + (usize::BITS - n.saturating_sub(1).leading_zeros()) as usize +} + +/// Computes `log_2(n)`, panicking if `n` is not a power of two. +pub fn log2_strict(n: usize) -> usize { + let res = n.trailing_zeros(); + assert!(n.wrapping_shr(res) == 1, "Not a power of two: {n}"); + // Tell the optimizer about the semantics of `log2_strict`. i.e. it can replace + // `n` with `1 << res` and vice versa. + assume(n == 1 << res); + res as usize +} + +/// Returns the largest integer `i` such that `base**i <= n`. +pub const fn log_floor(n: u64, base: u64) -> usize { + assert!(n > 0); + assert!(base > 1); + let mut i = 0; + let mut cur: u64 = 1; + loop { + let (mul, overflow) = cur.overflowing_mul(base); + if overflow || mul > n { + return i; + } else { + i += 1; + cur = mul; + } + } +} + +/// Permutes `arr` such that each index is mapped to its reverse in binary. +pub fn reverse_index_bits(arr: &[T]) -> Vec { + let n = arr.len(); + let n_power = log2_strict(n); + + if n_power <= 6 { + reverse_index_bits_small(arr, n_power) + } else { + reverse_index_bits_large(arr, n_power) + } +} + +/* Both functions below are semantically equivalent to: + for i in 0..n { + result.push(arr[reverse_bits(i, n_power)]); + } + where reverse_bits(i, n_power) computes the n_power-bit reverse. The complications are there + to guide the compiler to generate optimal assembly. +*/ + +fn reverse_index_bits_small(arr: &[T], n_power: usize) -> Vec { + let n = arr.len(); + let mut result = Vec::with_capacity(n); + // BIT_REVERSE_6BIT holds 6-bit reverses. This shift makes them n_power-bit + // reverses. + let dst_shr_amt = 6 - n_power; + for i in 0..n { + let src = (BIT_REVERSE_6BIT[i] as usize) >> dst_shr_amt; + result.push(arr[src]); + } + result +} + +fn reverse_index_bits_large(arr: &[T], n_power: usize) -> Vec { + let n = arr.len(); + // LLVM does not know that it does not need to reverse src at each iteration + // (which is expensive on x86). We take advantage of the fact that the low + // bits of dst change rarely and the high bits of dst are dependent only on + // the low bits of src. + let src_lo_shr_amt = 64 - (n_power - 6); + let src_hi_shl_amt = n_power - 6; + let mut result = Vec::with_capacity(n); + for i_chunk in 0..(n >> 6) { + let src_lo = i_chunk.reverse_bits() >> src_lo_shr_amt; + for i_lo in 0..(1 << 6) { + let src_hi = (BIT_REVERSE_6BIT[i_lo] as usize) << src_hi_shl_amt; + let src = src_hi + src_lo; + result.push(arr[src]); + } + } + result +} + +/// Bit-reverse the order of elements in `arr`. +/// SAFETY: ensure that `arr.len() == 1 << lb_n`. +#[cfg(not(target_arch = "aarch64"))] +unsafe fn reverse_index_bits_in_place_small(arr: &mut [T], lb_n: usize) { + if lb_n <= 6 { + // BIT_REVERSE_6BIT holds 6-bit reverses. This shift makes them lb_n-bit + // reverses. + let dst_shr_amt = 6 - lb_n as u32; + for src in 0..arr.len() { + // `wrapping_shr` handles the case when `arr.len() == 1`. In that case `src == + // 0`, so `src.reverse_bits() == 0`. `usize::wrapping_shr` by 64 is + // a no-op, but it gives the correct result. + let dst = (BIT_REVERSE_6BIT[src] as usize).wrapping_shr(dst_shr_amt); + if src < dst { + swap(arr.get_unchecked_mut(src), arr.get_unchecked_mut(dst)); + } + } + } else { + // LLVM does not know that it does not need to reverse src at each iteration + // (which is expensive on x86). We take advantage of the fact that the + // low bits of dst change rarely and the high bits of dst are dependent + // only on the low bits of src. + let dst_lo_shr_amt = usize::BITS - (lb_n - 6) as u32; + let dst_hi_shl_amt = lb_n - 6; + for src_chunk in 0..(arr.len() >> 6) { + let src_hi = src_chunk << 6; + // `wrapping_shr` handles the case when `arr.len() == 1`. In that case `src == + // 0`, so `src.reverse_bits() == 0`. `usize::wrapping_shr` by 64 is + // a no-op, but it gives the correct result. + let dst_lo = src_chunk.reverse_bits().wrapping_shr(dst_lo_shr_amt); + for src_lo in 0..(1 << 6) { + let dst_hi = (BIT_REVERSE_6BIT[src_lo] as usize) << dst_hi_shl_amt; + let src = src_hi + src_lo; + let dst = dst_hi + dst_lo; + if src < dst { + swap(arr.get_unchecked_mut(src), arr.get_unchecked_mut(dst)); + } + } + } + } +} + +/// Bit-reverse the order of elements in `arr`. +/// SAFETY: ensure that `arr.len() == 1 << lb_n`. +#[cfg(target_arch = "aarch64")] +unsafe fn reverse_index_bits_in_place_small(arr: &mut [T], lb_n: usize) { + // Aarch64 can reverse bits in one instruction, so the trivial version works + // best. + for src in 0..arr.len() { + // `wrapping_shr` handles the case when `arr.len() == 1`. In that case `src == + // 0`, so `src.reverse_bits() == 0`. `usize::wrapping_shr` by 64 is a + // no-op, but it gives the correct result. + let dst = src.reverse_bits().wrapping_shr(usize::BITS - lb_n as u32); + if src < dst { + swap(arr.get_unchecked_mut(src), arr.get_unchecked_mut(dst)); + } + } +} + +/// Split `arr` chunks and bit-reverse the order of the chunks. There are `1 << +/// lb_num_chunks` chunks, each of length `1 << lb_chunk_size`. +/// SAFETY: ensure that `arr.len() == 1 << lb_num_chunks + lb_chunk_size`. +unsafe fn reverse_index_bits_in_place_chunks( + arr: &mut [T], + lb_num_chunks: usize, + lb_chunk_size: usize, +) { + for i in 0..1usize << lb_num_chunks { + // `wrapping_shr` handles the silly case when `lb_num_chunks == 0`. + let j = i + .reverse_bits() + .wrapping_shr(usize::BITS - lb_num_chunks as u32); + if i < j { + swap_nonoverlapping( + arr.get_unchecked_mut(i << lb_chunk_size), + arr.get_unchecked_mut(j << lb_chunk_size), + 1 << lb_chunk_size, + ); + } + } +} + +// Ensure that SMALL_ARR_SIZE >= 4 * BIG_T_SIZE. +const BIG_T_SIZE: usize = 1 << 14; +const SMALL_ARR_SIZE: usize = 1 << 16; +pub fn reverse_index_bits_in_place(arr: &mut [T]) { + let n = arr.len(); + let lb_n = log2_strict(n); + // If the whole array fits in fast cache, then the trivial algorithm is cache + // friendly. Also, if `T` is really big, then the trivial algorithm is + // cache-friendly, no matter the size of the array. + if size_of::() << lb_n <= SMALL_ARR_SIZE || size_of::() >= BIG_T_SIZE { + unsafe { + reverse_index_bits_in_place_small(arr, lb_n); + } + } else { + debug_assert!(n >= 4); // By our choice of `BIG_T_SIZE` and `SMALL_ARR_SIZE`. + + // Algorithm: + // + // Treat `arr` as a `sqrt(n)` by `sqrt(n)` row-major matrix. (Assume for now + // that `lb_n` is even, i.e., `n` is a square number.) To perform + // bit-order reversal we: + // 1. Bit-reverse the order of the rows. (They are contiguous in memory, so + // this is basically a series of large `memcpy`s.) + // 2. Transpose the matrix. + // 3. Bit-reverse the order of the rows. + // This is equivalent to, for every index `0 <= i < n`: + // 1. bit-reversing `i[lb_n / 2..lb_n]`, + // 2. swapping `i[0..lb_n / 2]` and `i[lb_n / 2..lb_n]`, + // 3. bit-reversing `i[lb_n / 2..lb_n]`. + // + // If `lb_n` is odd, i.e., `n` is not a square number, then the above procedure + // requires slight modification. At steps 1 and 3 we bit-reverse bits + // `ceil(lb_n / 2)..lb_n`, of the index (shuffling `floor(lb_n / 2)` + // chunks of length `ceil(lb_n / 2)`). At step 2, we perform _two_ + // transposes. We treat `arr` as two matrices, one where the middle bit of the + // index is `0` and another, where the middle bit is `1`; we transpose each + // individually. + + let lb_num_chunks = lb_n >> 1; + let lb_chunk_size = lb_n - lb_num_chunks; + unsafe { + reverse_index_bits_in_place_chunks(arr, lb_num_chunks, lb_chunk_size); + transpose_in_place_square(arr, lb_chunk_size, lb_num_chunks, 0); + if lb_num_chunks != lb_chunk_size { + // `arr` cannot be interpreted as a square matrix. We instead interpret it as a + // `1 << lb_num_chunks` by `2` by `1 << lb_num_chunks` tensor, in row-major + // order. The above transpose acted on `tensor[..., 0, ...]` + // (all indices with middle bit `0`). We still need to transpose + // `tensor[..., 1, ...]`. To do so, we advance arr by `1 << + // lb_num_chunks` effectively, adding that to every index. + let arr_with_offset = &mut arr[1 << lb_num_chunks..]; + transpose_in_place_square(arr_with_offset, lb_chunk_size, lb_num_chunks, 0); + } + reverse_index_bits_in_place_chunks(arr, lb_num_chunks, lb_chunk_size); + } + } +} + +// Lookup table of 6-bit reverses. +// NB: 2^6=64 bytes is a cacheline. A smaller table wastes cache space. +#[rustfmt::skip] +const BIT_REVERSE_6BIT: &[u8] = &[ + 0o00, 0o40, 0o20, 0o60, 0o10, 0o50, 0o30, 0o70, + 0o04, 0o44, 0o24, 0o64, 0o14, 0o54, 0o34, 0o74, + 0o02, 0o42, 0o22, 0o62, 0o12, 0o52, 0o32, 0o72, + 0o06, 0o46, 0o26, 0o66, 0o16, 0o56, 0o36, 0o76, + 0o01, 0o41, 0o21, 0o61, 0o11, 0o51, 0o31, 0o71, + 0o05, 0o45, 0o25, 0o65, 0o15, 0o55, 0o35, 0o75, + 0o03, 0o43, 0o23, 0o63, 0o13, 0o53, 0o33, 0o73, + 0o07, 0o47, 0o27, 0o67, 0o17, 0o57, 0o37, 0o77, +]; + +#[inline(always)] +pub fn assume(p: bool) { + debug_assert!(p); + if !p { + unsafe { + unreachable_unchecked(); + } + } +} + +/// Try to force Rust to emit a branch. Example: +/// if x > 2 { +/// y = foo(); +/// branch_hint(); +/// } else { +/// y = bar(); +/// } +/// This function has no semantics. It is a hint only. +#[inline(always)] +pub fn branch_hint() { + // NOTE: These are the currently supported assembly architectures. See the + // [nightly reference](https://doc.rust-lang.org/nightly/reference/inline-assembly.html) for + // the most up-to-date list. + #[cfg(any( + target_arch = "aarch64", + target_arch = "arm", + target_arch = "riscv32", + target_arch = "riscv64", + target_arch = "x86", + target_arch = "x86_64", + ))] + unsafe { + core::arch::asm!("", options(nomem, nostack, preserves_flags)); + } +} + +#[cfg(test)] +mod tests { + use alloc::vec; + use alloc::vec::Vec; + + use rand::rngs::OsRng; + use rand::Rng; + + use crate::{log2_ceil, log2_strict}; + + #[test] + fn test_reverse_index_bits() { + let lengths = [32, 128, 1 << 16]; + let mut rng = OsRng; + for _ in 0..32 { + for length in lengths { + let mut rand_list: Vec = Vec::with_capacity(length); + rand_list.resize_with(length, || rng.gen()); + + let out = super::reverse_index_bits(&rand_list); + let expect = reverse_index_bits_naive(&rand_list); + + for (out, expect) in out.iter().zip(&expect) { + assert_eq!(out, expect); + } + } + } + } + + #[test] + fn test_reverse_index_bits_in_place() { + let lengths = [32, 128, 1 << 16]; + let mut rng = OsRng; + for _ in 0..32 { + for length in lengths { + let mut rand_list: Vec = Vec::with_capacity(length); + rand_list.resize_with(length, || rng.gen()); + + let expect = reverse_index_bits_naive(&rand_list); + + super::reverse_index_bits_in_place(&mut rand_list); + + for (got, expect) in rand_list.iter().zip(&expect) { + assert_eq!(got, expect); + } + } + } + } + + #[test] + fn test_log2_strict() { + assert_eq!(log2_strict(1), 0); + assert_eq!(log2_strict(2), 1); + assert_eq!(log2_strict(1 << 18), 18); + assert_eq!(log2_strict(1 << 31), 31); + assert_eq!( + log2_strict(1 << (usize::BITS - 1)), + usize::BITS as usize - 1 + ); + } + + #[test] + #[should_panic] + fn test_log2_strict_zero() { + log2_strict(0); + } + + #[test] + #[should_panic] + fn test_log2_strict_nonpower_2() { + log2_strict(0x78c341c65ae6d262); + } + + #[test] + #[should_panic] + fn test_log2_strict_usize_max() { + log2_strict(usize::MAX); + } + + #[test] + fn test_log2_ceil() { + // Powers of 2 + assert_eq!(log2_ceil(0), 0); + assert_eq!(log2_ceil(1), 0); + assert_eq!(log2_ceil(2), 1); + assert_eq!(log2_ceil(1 << 18), 18); + assert_eq!(log2_ceil(1 << 31), 31); + assert_eq!(log2_ceil(1 << (usize::BITS - 1)), usize::BITS as usize - 1); + + // Nonpowers; want to round up + assert_eq!(log2_ceil(3), 2); + assert_eq!(log2_ceil(0x14fe901b), 29); + assert_eq!( + log2_ceil((1 << (usize::BITS - 1)) + 1), + usize::BITS as usize + ); + assert_eq!(log2_ceil(usize::MAX - 1), usize::BITS as usize); + assert_eq!(log2_ceil(usize::MAX), usize::BITS as usize); + } + + fn reverse_index_bits_naive(arr: &[T]) -> Vec { + let n = arr.len(); + let n_power = log2_strict(n); + + let mut out = vec![None; n]; + for (i, v) in arr.iter().enumerate() { + let dst = i.reverse_bits() >> (64 - n_power); + out[dst] = Some(*v); + } + + out.into_iter().map(|x| x.unwrap()).collect() + } +} diff --git a/util/src/transpose_util.rs b/util/src/transpose_util.rs new file mode 100644 index 000000000..69d42152e --- /dev/null +++ b/util/src/transpose_util.rs @@ -0,0 +1,116 @@ +use core::ptr::swap; + +const LB_BLOCK_SIZE: usize = 3; + +/// Transpose square matrix in-place +/// The matrix is of size `1 << lb_size` by `1 << lb_size`. It occupies +/// `M[i, j] == arr[(i + x << lb_stride) + j + x]` for `0 <= i, j < 1 << +/// lb_size`. The transposition swaps `M[i, j]` and `M[j, i]`. +/// +/// SAFETY: +/// Make sure that `(i + x << lb_stride) + j + x` is a valid index in `arr` for +/// all `0 <= i, j < 1 << lb_size`. Ensure also that `lb_size <= lb_stride` to +/// prevent overlap. +unsafe fn transpose_in_place_square_small( + arr: &mut [T], + lb_stride: usize, + lb_size: usize, + x: usize, +) { + for i in x + 1..x + (1 << lb_size) { + for j in x..i { + swap( + arr.get_unchecked_mut(i + (j << lb_stride)), + arr.get_unchecked_mut((i << lb_stride) + j), + ); + } + } +} + +/// Transpose square matrices and swap +/// The matrices are of of size `1 << lb_size` by `1 << lb_size`. They occupy +/// `M0[i, j] == arr[(i + x << lb_stride) + j + y]`, `M1[i, j] == arr[i + x + (j +/// + y << lb_stride)]` for `0 <= i, j < 1 << lb_size. The transposition swaps +/// `M0[i, j]` and `M1[j, i]`. +/// +/// SAFETY: +/// Make sure that `(i + x << lb_stride) + j + y` and `i + x + (j + y << +/// lb_stride)` are valid indices in `arr` for all `0 <= i, j < 1 << lb_size`. +/// Ensure also that `lb_size <= lb_stride` to prevent overlap. +unsafe fn transpose_swap_square_small( + arr: &mut [T], + lb_stride: usize, + lb_size: usize, + x: usize, + y: usize, +) { + for i in x..x + (1 << lb_size) { + for j in y..y + (1 << lb_size) { + swap( + arr.get_unchecked_mut(i + (j << lb_stride)), + arr.get_unchecked_mut((i << lb_stride) + j), + ); + } + } +} + +/// Transpose square matrices and swap +/// The matrices are of of size `1 << lb_size` by `1 << lb_size`. They occupy +/// `M0[i, j] == arr[(i + x << lb_stride) + j + y]`, `M1[i, j] == arr[i + x + (j +/// + y << lb_stride)]` for `0 <= i, j < 1 << lb_size. The transposition swaps +/// `M0[i, j]` and `M1[j, i]`. +/// +/// SAFETY: +/// Make sure that `(i + x << lb_stride) + j + y` and `i + x + (j + y << +/// lb_stride)` are valid indices in `arr` for all `0 <= i, j < 1 << lb_size`. +/// Ensure also that `lb_size <= lb_stride` to prevent overlap. +unsafe fn transpose_swap_square( + arr: &mut [T], + lb_stride: usize, + lb_size: usize, + x: usize, + y: usize, +) { + if lb_size <= LB_BLOCK_SIZE { + transpose_swap_square_small(arr, lb_stride, lb_size, x, y); + } else { + let lb_block_size = lb_size - 1; + let block_size = 1 << lb_block_size; + transpose_swap_square(arr, lb_stride, lb_block_size, x, y); + transpose_swap_square(arr, lb_stride, lb_block_size, x + block_size, y); + transpose_swap_square(arr, lb_stride, lb_block_size, x, y + block_size); + transpose_swap_square( + arr, + lb_stride, + lb_block_size, + x + block_size, + y + block_size, + ); + } +} + +/// Transpose square matrix in-place +/// The matrix is of size `1 << lb_size` by `1 << lb_size`. It occupies +/// `M[i, j] == arr[(i + x << lb_stride) + j + x]` for `0 <= i, j < 1 << +/// lb_size`. The transposition swaps `M[i, j]` and `M[j, i]`. +/// +/// SAFETY: +/// Make sure that `(i + x << lb_stride) + j + x` is a valid index in `arr` for +/// all `0 <= i, j < 1 << lb_size`. Ensure also that `lb_size <= lb_stride` to +/// prevent overlap. +pub(crate) unsafe fn transpose_in_place_square( + arr: &mut [T], + lb_stride: usize, + lb_size: usize, + x: usize, +) { + if lb_size <= LB_BLOCK_SIZE { + transpose_in_place_square_small(arr, lb_stride, lb_size, x); + } else { + let lb_block_size = lb_size - 1; + let block_size = 1 << lb_block_size; + transpose_in_place_square(arr, lb_stride, lb_block_size, x); + transpose_swap_square(arr, lb_stride, lb_block_size, x, x + block_size); + transpose_in_place_square(arr, lb_stride, lb_block_size, x + block_size); + } +} From d43c8e260c6cb6a98bb9eacf43108b4d35cc1630 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Sun, 18 Feb 2024 09:48:24 -0500 Subject: [PATCH 02/40] Implement EIP-4788 for Cancun (#40) * Implement EIP-4788 * Cleanup test suite * Fix tests * Apply comments * Update PROVER_INPUT opcode value (#42) --- docs/arithmetization/cpulogic.tex | 4 +- evm_arithmetization/src/cpu/control_flow.rs | 8 +- evm_arithmetization/src/cpu/cpu_stark.rs | 13 +- evm_arithmetization/src/cpu/decode.rs | 15 +- evm_arithmetization/src/cpu/gas.rs | 16 +- .../src/cpu/kernel/aggregator.rs | 1 + .../src/cpu/kernel/asm/beacon_roots.asm | 48 ++++ .../src/cpu/kernel/asm/main.asm | 7 +- .../src/cpu/kernel/asm/memory/metadata.asm | 4 + .../src/cpu/kernel/asm/mpt/accounts.asm | 22 ++ .../src/cpu/kernel/asm/mpt/insert/insert.asm | 4 +- .../src/cpu/kernel/asm/mpt/read.asm | 6 +- .../cpu/kernel/constants/global_metadata.rs | 6 +- .../src/cpu/kernel/constants/mod.rs | 41 +++- .../src/cpu/kernel/interpreter.rs | 24 +- evm_arithmetization/src/cpu/kernel/mod.rs | 3 + evm_arithmetization/src/cpu/kernel/opcodes.rs | 2 +- .../src/cpu/kernel/tests/add11.rs | 102 +++++--- evm_arithmetization/src/generation/mod.rs | 4 + evm_arithmetization/src/get_challenges.rs | 2 + evm_arithmetization/src/lib.rs | 1 + evm_arithmetization/src/proof.rs | 30 ++- evm_arithmetization/src/recursive_verifier.rs | 16 +- evm_arithmetization/src/testing_utils.rs | 121 ++++++++++ evm_arithmetization/src/verifier.rs | 8 + evm_arithmetization/src/witness/operation.rs | 2 +- evm_arithmetization/src/witness/transition.rs | 2 +- evm_arithmetization/tests/add11_yml.rs | 31 ++- .../tests/basic_smart_contract.rs | 66 +++--- evm_arithmetization/tests/empty_txn_list.rs | 107 +++++++-- evm_arithmetization/tests/erc20.rs | 81 +++---- evm_arithmetization/tests/erc721.rs | 220 +++++++++--------- evm_arithmetization/tests/log_opcode.rs | 99 ++++++-- .../tests/self_balance_gas_cost.rs | 32 ++- evm_arithmetization/tests/selfdestruct.rs | 38 +-- evm_arithmetization/tests/simple_transfer.rs | 41 ++-- evm_arithmetization/tests/withdrawals.rs | 26 ++- 37 files changed, 865 insertions(+), 388 deletions(-) create mode 100644 evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm create mode 100644 evm_arithmetization/src/testing_utils.rs diff --git a/docs/arithmetization/cpulogic.tex b/docs/arithmetization/cpulogic.tex index 318e2db48..4f68faa7d 100644 --- a/docs/arithmetization/cpulogic.tex +++ b/docs/arithmetization/cpulogic.tex @@ -78,8 +78,6 @@ \subsection{Privileged instructions} \item[0x21.] \texttt{KECCAK\_GENERAL}. Pops 2 elements (a Memory address, followed by a length $\ell$) and pushes the hash of the memory portion starting at the constructed address and of length $\ell$. It is similar to KECCAK256 (0x20) instruction, but can be applied to any memory section (i.e. even privileged ones). - \item[0x49.] \texttt{PROVER\_INPUT}. Pushes a single prover input onto the stack. - \item[0xC0-0xDF.] \texttt{MSTORE\_32BYTES}. Pops 2 elements from the stack (a Memory address, and then a value), and pushes a new address' onto the stack. The value is being decomposed into bytes and written to memory, starting from the fetched address. The new address being pushed is computed as the initial address + the length of the byte sequence being written to memory. Note that similarly to PUSH (0x60-0x7F) instructions, there are 32 MSTORE\_32BYTES instructions, each @@ -87,6 +85,8 @@ \subsection{Privileged instructions} result in the integer being truncated. On the other hand, specifying a length $\ell$ greater than the byte size of the value being written will result in padding with zeroes. This process is heavily used when resetting memory sections (by calling MSTORE\_32BYTES\_32 with the value 0). + \item[0xEE.] \texttt{PROVER\_INPUT}. Pushes a single prover input onto the stack. + \item[0xF6.] \texttt{GET\_CONTEXT}. Pushes the current context onto the stack. The kernel always has context 0. \item[0xF7.] \texttt{SET\_CONTEXT}. Pops the top element of the stack and updates the current context to this value. It is usually used when calling another contract or precompile, diff --git a/evm_arithmetization/src/cpu/control_flow.rs b/evm_arithmetization/src/cpu/control_flow.rs index 832db1961..c4fcb91ec 100644 --- a/evm_arithmetization/src/cpu/control_flow.rs +++ b/evm_arithmetization/src/cpu/control_flow.rs @@ -72,7 +72,7 @@ pub(crate) fn eval_packed_generic( .constraint_transition(is_native_instruction * (lv.is_kernel_mode - nv.is_kernel_mode)); // Apply the same checks as before, for PROVER_INPUT. - let is_prover_input: P = lv.op.push_prover_input * (lv.opcode_bits[5] - P::ONES); + let is_prover_input: P = lv.op.push_prover_input * lv.opcode_bits[7]; yield_constr.constraint_transition( is_prover_input * (lv.program_counter - nv.program_counter + P::ONES), ); @@ -129,11 +129,7 @@ pub(crate) fn eval_ext_circuit, const D: usize>( yield_constr.constraint_transition(builder, kernel_constr); // Same constraints as before, for PROVER_INPUT. - let is_prover_input = builder.mul_sub_extension( - lv.op.push_prover_input, - lv.opcode_bits[5], - lv.op.push_prover_input, - ); + let is_prover_input = builder.mul_extension(lv.op.push_prover_input, lv.opcode_bits[7]); let pc_constr = builder.mul_add_extension(is_prover_input, pc_diff, is_prover_input); yield_constr.constraint_transition(builder, pc_constr); let kernel_constr = builder.mul_extension(is_prover_input, kernel_diff); diff --git a/evm_arithmetization/src/cpu/cpu_stark.rs b/evm_arithmetization/src/cpu/cpu_stark.rs index 4e60694b0..47a4b5e91 100644 --- a/evm_arithmetization/src/cpu/cpu_stark.rs +++ b/evm_arithmetization/src/cpu/cpu_stark.rs @@ -109,10 +109,7 @@ pub(crate) fn ctl_arithmetic_base_rows() -> TableWithColumns { // (also `ops` is used as the operation filter). The list of // operations includes binary operations which will simply ignore // the third input. - let col_bit = Column::linear_combination_with_constant( - vec![(COL_MAP.opcode_bits[5], F::NEG_ONE)], - F::ONE, - ); + let col_bit = Column::single(COL_MAP.opcode_bits[7]); TableWithColumns::new( *Table::Cpu, columns, @@ -263,9 +260,13 @@ pub(crate) fn ctl_data_byte_packing_push() -> Vec> { /// CTL filter for the `PUSH` operation. pub(crate) fn ctl_filter_byte_packing_push() -> Filter { - let bit_col = Column::single(COL_MAP.opcode_bits[5]); + let col_bit = Column::linear_combination_with_constant( + vec![(COL_MAP.opcode_bits[7], F::NEG_ONE)], + F::ONE, + ); + Filter::new( - vec![(Column::single(COL_MAP.op.push_prover_input), bit_col)], + vec![(Column::single(COL_MAP.op.push_prover_input), col_bit)], vec![], ) } diff --git a/evm_arithmetization/src/cpu/decode.rs b/evm_arithmetization/src/cpu/decode.rs index 081e3862c..f44d69fe3 100644 --- a/evm_arithmetization/src/cpu/decode.rs +++ b/evm_arithmetization/src/cpu/decode.rs @@ -197,12 +197,11 @@ pub(crate) fn eval_packed_generic( // Manually check PUSH and PROVER_INPUT. // PROVER_INPUT is a kernel-only instruction, but not PUSH. - let push_prover_input_constr = (opcode - P::Scalar::from_canonical_usize(0x49_usize)) + let push_prover_input_constr = (opcode - P::Scalar::from_canonical_usize(0xee_usize)) * (opcode_high_three - P::Scalar::from_canonical_usize(0x60_usize)) * lv.op.push_prover_input; yield_constr.constraint(push_prover_input_constr); - let prover_input_constr = - lv.op.push_prover_input * (lv.opcode_bits[5] - P::ONES) * (P::ONES - kernel_mode); + let prover_input_constr = lv.op.push_prover_input * lv.opcode_bits[7] * (P::ONES - kernel_mode); yield_constr.constraint(prover_input_constr); } @@ -389,8 +388,8 @@ pub(crate) fn eval_ext_circuit, const D: usize>( // Manually check PUSH and PROVER_INPUT. // PROVER_INPUT is a kernel-only instruction, but not PUSH. let prover_input_opcode = - builder.constant_extension(F::Extension::from_canonical_usize(0x49usize)); - let push_opcodes = builder.constant_extension(F::Extension::from_canonical_usize(0x60usize)); + builder.constant_extension(F::Extension::from_canonical_usize(0xee_usize)); + let push_opcodes = builder.constant_extension(F::Extension::from_canonical_usize(0x60_usize)); let push_constr = builder.sub_extension(opcode_high_three, push_opcodes); let prover_input_constr = builder.sub_extension(opcode, prover_input_opcode); @@ -398,11 +397,7 @@ pub(crate) fn eval_ext_circuit, const D: usize>( let push_prover_input_constr = builder.mul_many_extension([lv.op.push_prover_input, prover_input_constr, push_constr]); yield_constr.constraint(builder, push_prover_input_constr); - let prover_input_filter = builder.mul_sub_extension( - lv.op.push_prover_input, - lv.opcode_bits[5], - lv.op.push_prover_input, - ); + let prover_input_filter = builder.mul_extension(lv.op.push_prover_input, lv.opcode_bits[7]); let constr = builder.mul_extension(prover_input_filter, is_not_kernel_mode); yield_constr.constraint(builder, constr); } diff --git a/evm_arithmetization/src/cpu/gas.rs b/evm_arithmetization/src/cpu/gas.rs index 69ebf2c51..c3ec89089 100644 --- a/evm_arithmetization/src/cpu/gas.rs +++ b/evm_arithmetization/src/cpu/gas.rs @@ -114,11 +114,11 @@ fn eval_packed_accumulate( ); // For PROVER_INPUT and PUSH operations. - // PUSH operations are differentiated from PROVER_INPUT by their 6th bit set to + // PUSH operations are differentiated from PROVER_INPUT by their 8th bit set to // 1. - let push_prover_input_gas_cost = lv.opcode_bits[5] + let push_prover_input_gas_cost = (P::ONES - lv.opcode_bits[7]) * P::Scalar::from_canonical_u32(G_VERYLOW.unwrap()) - + (P::ONES - lv.opcode_bits[5]) * P::Scalar::from_canonical_u32(KERNEL_ONLY_INSTR.unwrap()); + + lv.opcode_bits[7] * P::Scalar::from_canonical_u32(KERNEL_ONLY_INSTR.unwrap()); yield_constr .constraint_transition(lv.op.push_prover_input * (gas_diff - push_prover_input_gas_cost)); } @@ -282,13 +282,13 @@ fn eval_ext_circuit_accumulate, const D: usize>( yield_constr.constraint_transition(builder, constr); // For PROVER_INPUT and PUSH operations. - // PUSH operations are differentiated from PROVER_INPUT by their 6th bit set to + // PUSH operations are differentiated from PROVER_INPUT by their 8th bit set to // 1. let push_prover_input_gas_cost = builder.arithmetic_extension( - F::from_canonical_u32(G_VERYLOW.unwrap()) - - F::from_canonical_u32(KERNEL_ONLY_INSTR.unwrap()), - F::from_canonical_u32(KERNEL_ONLY_INSTR.unwrap()), - lv.opcode_bits[5], + F::from_canonical_u32(KERNEL_ONLY_INSTR.unwrap()) + - F::from_canonical_u32(G_VERYLOW.unwrap()), + F::from_canonical_u32(G_VERYLOW.unwrap()), + lv.opcode_bits[7], one, one, ); diff --git a/evm_arithmetization/src/cpu/kernel/aggregator.rs b/evm_arithmetization/src/cpu/kernel/aggregator.rs index 637655255..b3b30ba93 100644 --- a/evm_arithmetization/src/cpu/kernel/aggregator.rs +++ b/evm_arithmetization/src/cpu/kernel/aggregator.rs @@ -13,6 +13,7 @@ pub(crate) fn combined_kernel() -> Kernel { let files = vec![ "global jumped_to_0: PANIC", "global jumped_to_1: PANIC", + include_str!("asm/beacon_roots.asm"), include_str!("asm/bignum/add.asm"), include_str!("asm/bignum/addmul.asm"), include_str!("asm/bignum/cmp.asm"), diff --git a/evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm b/evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm new file mode 100644 index 000000000..edcd53a3e --- /dev/null +++ b/evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm @@ -0,0 +1,48 @@ +/// EIP-4788: Beacon block root in the EVM +/// + +global set_beacon_root: + PUSH start_txn + %timestamp + // stack: timestamp, start_txns + PUSH @HISTORY_BUFFER_LENGTH + DUP2 + // stack: timestamp, 8191, timestamp, start_txns + MOD + // stack: timestamp_idx, timestamp, start_txns + PUSH write_beacon_roots_to_storage + %parent_beacon_block_root + // stack: calldata, write_beacon_roots_to_storage, timestamp_idx, timestamp, start_txns + DUP3 + %add_const(@HISTORY_BUFFER_LENGTH) + // stack: root_idx, calldata, write_beacon_roots_to_storage, timestamp_idx, timestamp, start_txns + +write_beacon_roots_to_storage: + // stack: slot, value, retdest + // First we write the value to MPT data, and get a pointer to it. + %get_trie_data_size + // stack: value_ptr, slot, value, retdest + SWAP2 + // stack: value, slot, value_ptr, retdest + %append_to_trie_data + // stack: slot, value_ptr, retdest + + // Next, call mpt_insert on the current account's storage root. + %stack (slot, value_ptr) -> (slot, value_ptr, after_beacon_roots_storage_insert) + %slot_to_storage_key + // stack: storage_key, value_ptr, after_beacon_roots_storage_insert, retdest + PUSH 64 // storage_key has 64 nibbles + %get_storage_trie(@BEACON_ROOTS_ADDRESS) + // stack: storage_root_ptr, 64, storage_key, value_ptr, after_beacon_roots_storage_insert, retdest + %jump(mpt_insert) + +after_beacon_roots_storage_insert: + // stack: new_storage_root_ptr, retdest + %get_account_data(@BEACON_ROOTS_ADDRESS) + // stack: account_ptr, new_storage_root_ptr, retdest + + // Update the copied account with our new storage root pointer. + %add_const(2) + // stack: account_storage_root_ptr_ptr, new_storage_root_ptr, retdest + %mstore_trie_data + JUMP diff --git a/evm_arithmetization/src/cpu/kernel/asm/main.asm b/evm_arithmetization/src/cpu/kernel/asm/main.asm index d78152f4b..1307f6d5d 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/main.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/main.asm @@ -41,10 +41,13 @@ global hash_initial_tries: // stack: trie_data_full_len %mstore_global_metadata(@GLOBAL_METADATA_TRIE_DATA_SIZE) + // If txn_idx == 0, update the beacon_root. + %mload_global_metadata(@GLOBAL_METADATA_TXN_NUMBER_BEFORE) + ISZERO + %jumpi(set_beacon_root) + global start_txn: // stack: (empty) - // The special case of an empty trie (i.e. for the first transaction) - // is handled outside of the kernel. %mload_global_metadata(@GLOBAL_METADATA_TXN_NUMBER_BEFORE) // stack: txn_nb DUP1 %scalar_to_rlp diff --git a/evm_arithmetization/src/cpu/kernel/asm/memory/metadata.asm b/evm_arithmetization/src/cpu/kernel/asm/memory/metadata.asm index f2dc897a1..8a7867118 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/memory/metadata.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/memory/metadata.asm @@ -447,3 +447,7 @@ global sys_prevrandao: %mload_global_metadata(@GLOBAL_METADATA_BLOCK_RANDOM) %stack (random, kexit_info) -> (kexit_info, random) EXIT_KERNEL + +%macro parent_beacon_block_root + %mload_global_metadata(@GLOBAL_METADATA_PARENT_BEACON_BLOCK_ROOT) +%endmacro diff --git a/evm_arithmetization/src/cpu/kernel/asm/mpt/accounts.asm b/evm_arithmetization/src/cpu/kernel/asm/mpt/accounts.asm index 0ee987b4c..cb8eede70 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/mpt/accounts.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/mpt/accounts.asm @@ -19,3 +19,25 @@ %mload_trie_data // stack: storage_root_ptr %endmacro + +// Return a pointer to the provided account's data in the state trie. +%macro get_account_data(addr) + PUSH $addr %mpt_read_state_trie + // stack: account_ptr + // account_ptr should be non-null as long as the prover provided the proper + // Merkle data. But a bad prover may not have, and we don't want return a + // null pointer for security reasons. + DUP1 ISZERO %jumpi(panic) + // stack: account_ptr +%endmacro + +// Returns a pointer to the root of the storage trie associated with the provided account. +%macro get_storage_trie(addr) + // stack: (empty) + %get_account_data($addr) + // stack: account_ptr + %add_const(2) + // stack: storage_root_ptr_ptr + %mload_trie_data + // stack: storage_root_ptr +%endmacro \ No newline at end of file diff --git a/evm_arithmetization/src/cpu/kernel/asm/mpt/insert/insert.asm b/evm_arithmetization/src/cpu/kernel/asm/mpt/insert/insert.asm index 34889a33f..33eadd755 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/mpt/insert/insert.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/mpt/insert/insert.asm @@ -20,7 +20,7 @@ global mpt_insert: global mpt_insert_hash_node: PANIC -mpt_insert_empty: +global mpt_insert_empty: // stack: node_type, node_payload_ptr, num_nibbles, key, value_ptr, retdest %pop2 // stack: num_nibbles, key, value_ptr, retdest @@ -38,7 +38,7 @@ mpt_insert_empty: SWAP1 JUMP -mpt_insert_branch: +global mpt_insert_branch: // stack: node_type, node_payload_ptr, num_nibbles, key, value_ptr, retdest POP diff --git a/evm_arithmetization/src/cpu/kernel/asm/mpt/read.asm b/evm_arithmetization/src/cpu/kernel/asm/mpt/read.asm index 4a57dd4db..86926d130 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/mpt/read.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/mpt/read.asm @@ -106,7 +106,7 @@ global mpt_read_extension_not_found: // Not found; return 0. %stack (key_part, future_nibbles, key, node_payload_ptr, retdest) -> (retdest, 0) JUMP -mpt_read_extension_found: +global mpt_read_extension_found: // stack: key_part, future_nibbles, key, node_payload_ptr, retdest DUP2 %mul_const(4) SHL // key_part_shifted = (key_part << (future_nibbles * 4)) // stack: key_part_shifted, future_nibbles, key, node_payload_ptr, retdest @@ -121,7 +121,7 @@ mpt_read_extension_found: // stack: child_ptr, future_nibbles, key, retdest %jump(mpt_read) // recurse -mpt_read_leaf: +global mpt_read_leaf: // stack: node_type, node_payload_ptr, num_nibbles, key, retdest POP // stack: node_payload_ptr, num_nibbles, key, retdest @@ -142,7 +142,7 @@ global mpt_read_leaf_not_found: // Not found; return 0. %stack (node_payload_ptr, retdest) -> (retdest, 0) JUMP -mpt_read_leaf_found: +global mpt_read_leaf_found: // stack: node_payload_ptr, retdest %add_const(2) // The value pointer is located after num_nibbles and the key. // stack: value_ptr_ptr, retdest diff --git a/evm_arithmetization/src/cpu/kernel/constants/global_metadata.rs b/evm_arithmetization/src/cpu/kernel/constants/global_metadata.rs index 227ec5a16..8e866da16 100644 --- a/evm_arithmetization/src/cpu/kernel/constants/global_metadata.rs +++ b/evm_arithmetization/src/cpu/kernel/constants/global_metadata.rs @@ -56,6 +56,8 @@ pub(crate) enum GlobalMetadata { BlockGasUsedAfter, /// Current block header hash BlockCurrentHash, + /// EIP-4788: hash tree root of the beacon chain parent block. + ParentBeaconBlockRoot, /// Gas to refund at the end of the transaction. RefundCounter, @@ -101,7 +103,7 @@ pub(crate) enum GlobalMetadata { } impl GlobalMetadata { - pub(crate) const COUNT: usize = 49; + pub(crate) const COUNT: usize = 50; /// Unscales this virtual offset by their respective `Segment` value. pub(crate) const fn unscale(&self) -> usize { @@ -134,6 +136,7 @@ impl GlobalMetadata { Self::BlockGasUsed, Self::BlockGasUsedBefore, Self::BlockGasUsedAfter, + Self::ParentBeaconBlockRoot, Self::RefundCounter, Self::AccessedAddressesLen, Self::AccessedStorageKeysLen, @@ -190,6 +193,7 @@ impl GlobalMetadata { Self::BlockGasUsedBefore => "GLOBAL_METADATA_BLOCK_GAS_USED_BEFORE", Self::BlockGasUsedAfter => "GLOBAL_METADATA_BLOCK_GAS_USED_AFTER", Self::BlockCurrentHash => "GLOBAL_METADATA_BLOCK_CURRENT_HASH", + Self::ParentBeaconBlockRoot => "GLOBAL_METADATA_PARENT_BEACON_BLOCK_ROOT", Self::RefundCounter => "GLOBAL_METADATA_REFUND_COUNTER", Self::AccessedAddressesLen => "GLOBAL_METADATA_ACCESSED_ADDRESSES_LEN", Self::AccessedStorageKeysLen => "GLOBAL_METADATA_ACCESSED_STORAGE_KEYS_LEN", diff --git a/evm_arithmetization/src/cpu/kernel/constants/mod.rs b/evm_arithmetization/src/cpu/kernel/constants/mod.rs index 8aea84883..c85f061db 100644 --- a/evm_arithmetization/src/cpu/kernel/constants/mod.rs +++ b/evm_arithmetization/src/cpu/kernel/constants/mod.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use ethereum_types::U256; +use ethereum_types::{H256, U256}; use hex_literal::hex; use crate::cpu::kernel::constants::context_metadata::ContextMetadata; @@ -8,6 +8,7 @@ use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; use crate::cpu::kernel::constants::journal_entry::JournalEntry; use crate::cpu::kernel::constants::trie_type::PartialTrieType; use crate::cpu::kernel::constants::txn_fields::NormalizedTxnField; +use crate::generation::mpt::AccountRlp; use crate::memory::segments::Segment; pub(crate) mod context_metadata; @@ -56,6 +57,14 @@ pub(crate) fn evm_constants() -> HashMap { c.insert(MAX_NONCE.0.into(), U256::from(MAX_NONCE.1)); c.insert(CALL_STACK_LIMIT.0.into(), U256::from(CALL_STACK_LIMIT.1)); + c.insert( + cancun_constants::BEACON_ROOTS_ADDRESS.0.into(), + U256::from_big_endian(&cancun_constants::BEACON_ROOTS_ADDRESS.1), + ); + c.insert( + cancun_constants::HISTORY_BUFFER_LENGTH.0.into(), + cancun_constants::HISTORY_BUFFER_LENGTH.1.into(), + ); for segment in Segment::all() { c.insert(segment.var_name().into(), (segment as usize).into()); @@ -285,3 +294,33 @@ const CODE_SIZE_LIMIT: [(&str, u64); 3] = [ const MAX_NONCE: (&str, u64) = ("MAX_NONCE", 0xffffffffffffffff); const CALL_STACK_LIMIT: (&str, u64) = ("CALL_STACK_LIMIT", 1024); + +/// Cancun-related constants +/// See . +pub mod cancun_constants { + use super::*; + + pub const BEACON_ROOTS_ADDRESS: (&str, [u8; 20]) = ( + "BEACON_ROOTS_ADDRESS", + hex!("000F3df6D732807Ef1319fB7B8bB8522d0Beac02"), + ); + + pub const HISTORY_BUFFER_LENGTH: (&str, u64) = ("HISTORY_BUFFER_LENGTH", 8191); + + pub const BEACON_ROOTS_CONTRACT_CODE: [u8; 106] = hex!("60618060095f395ff33373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500"); + pub const BEACON_ROOTS_CONTRACT_CODE_HASH: [u8; 32] = + hex!("468e991a328ab315e08296896adc222230a4960692e90cb6e096006ba6ae75d5"); + + pub const BEACON_ROOTS_CONTRACT_ADDRESS_HASHED: [u8; 32] = + hex!("37d65eaa92c6bc4c13a5ec45527f0c18ea8932588728769ec7aecfe6d9f32e42"); + + pub const BEACON_ROOTS_ACCOUNT: AccountRlp = AccountRlp { + nonce: U256::zero(), + balance: U256::zero(), + // Storage root for this account at genesis. + storage_root: H256(hex!( + "f58e5f1eae7ce386de44266ff0286a88dafe7e4269c1ffa97f79dbbcf4f59e5c" + )), + code_hash: H256(BEACON_ROOTS_CONTRACT_CODE_HASH), + }; +} diff --git a/evm_arithmetization/src/cpu/kernel/interpreter.rs b/evm_arithmetization/src/cpu/kernel/interpreter.rs index 6b8924c17..2b0718b55 100644 --- a/evm_arithmetization/src/cpu/kernel/interpreter.rs +++ b/evm_arithmetization/src/cpu/kernel/interpreter.rs @@ -841,7 +841,6 @@ impl<'a, F: Field> Interpreter<'a, F> { 0x46 => self.run_syscall(opcode, 0, true), // "CHAINID", 0x47 => self.run_syscall(opcode, 0, true), // SELFABALANCE, 0x48 => self.run_syscall(opcode, 0, true), // "BASEFEE", - 0x49 => self.run_prover_input(), // "PROVER_INPUT", 0x4a => self.run_syscall(opcode, 0, true), // "BLOBBASEFEE", 0x50 => self.run_pop(), // "POP", 0x51 => self.run_syscall(opcode, 1, false), // "MLOAD", @@ -874,19 +873,20 @@ impl<'a, F: Field> Interpreter<'a, F> { Err(ProgramError::KernelPanic) } // "PANIC", x if (0xc0..0xe0).contains(&x) => self.run_mstore_32bytes(x - 0xc0 + 1), /* "MSTORE_32BYTES", */ - 0xf0 => self.run_syscall(opcode, 3, false), // "CREATE", - 0xf1 => self.run_syscall(opcode, 7, false), // "CALL", - 0xf2 => self.run_syscall(opcode, 7, false), // "CALLCODE", - 0xf3 => self.run_syscall(opcode, 2, false), // "RETURN", + 0xee => self.run_prover_input(), // "PROVER_INPUT", + 0xf0 => self.run_syscall(opcode, 3, false), // "CREATE", + 0xf1 => self.run_syscall(opcode, 7, false), // "CALL", + 0xf2 => self.run_syscall(opcode, 7, false), // "CALLCODE", + 0xf3 => self.run_syscall(opcode, 2, false), // "RETURN", 0xf4 => self.run_syscall(opcode, 6, false), // "DELEGATECALL", 0xf5 => self.run_syscall(opcode, 4, false), // "CREATE2", - 0xf6 => self.run_get_context(), // "GET_CONTEXT", - 0xf7 => self.run_set_context(), // "SET_CONTEXT", - 0xf8 => self.run_mload_32bytes(), // "MLOAD_32BYTES", - 0xf9 => self.run_exit_kernel(), // "EXIT_KERNEL", + 0xf6 => self.run_get_context(), // "GET_CONTEXT", + 0xf7 => self.run_set_context(), // "SET_CONTEXT", + 0xf8 => self.run_mload_32bytes(), // "MLOAD_32BYTES", + 0xf9 => self.run_exit_kernel(), // "EXIT_KERNEL", 0xfa => self.run_syscall(opcode, 6, false), // "STATICCALL", - 0xfb => self.run_mload_general(), // "MLOAD_GENERAL", - 0xfc => self.run_mstore_general(), // "MSTORE_GENERAL", + 0xfb => self.run_mload_general(), // "MLOAD_GENERAL", + 0xfc => self.run_mstore_general(), // "MSTORE_GENERAL", 0xfd => self.run_syscall(opcode, 2, false), // "REVERT", 0xfe => { log::warn!( @@ -1604,7 +1604,6 @@ fn get_mnemonic(opcode: u8) -> &'static str { 0x45 => "GASLIMIT", 0x46 => "CHAINID", 0x48 => "BASEFEE", - 0x49 => "PROVER_INPUT", 0x4a => "BLOBBASEFEE", 0x50 => "POP", 0x51 => "MLOAD", @@ -1722,6 +1721,7 @@ fn get_mnemonic(opcode: u8) -> &'static str { 0xdd => "MSTORE_32BYTES_30", 0xde => "MSTORE_32BYTES_31", 0xdf => "MSTORE_32BYTES_32", + 0xee => "PROVER_INPUT", 0xf0 => "CREATE", 0xf1 => "CALL", 0xf2 => "CALLCODE", diff --git a/evm_arithmetization/src/cpu/kernel/mod.rs b/evm_arithmetization/src/cpu/kernel/mod.rs index 5a6717f21..d39b015cf 100644 --- a/evm_arithmetization/src/cpu/kernel/mod.rs +++ b/evm_arithmetization/src/cpu/kernel/mod.rs @@ -11,6 +11,9 @@ pub mod stack; mod utils; pub(crate) mod interpreter; + +pub use constants::cancun_constants; + #[cfg(test)] mod tests; diff --git a/evm_arithmetization/src/cpu/kernel/opcodes.rs b/evm_arithmetization/src/cpu/kernel/opcodes.rs index 59cc31754..cfa44e01f 100644 --- a/evm_arithmetization/src/cpu/kernel/opcodes.rs +++ b/evm_arithmetization/src/cpu/kernel/opcodes.rs @@ -63,7 +63,6 @@ pub fn get_opcode(mnemonic: &str) -> u8 { "GASLIMIT" => 0x45, "CHAINID" => 0x46, "BASEFEE" => 0x48, - "PROVER_INPUT" => 0x49, "BLOBBASEFEE" => 0x4a, "POP" => 0x50, "MLOAD" => 0x51, @@ -148,6 +147,7 @@ pub fn get_opcode(mnemonic: &str) -> u8 { "MSTORE_32BYTES_30" => 0xdd, "MSTORE_32BYTES_31" => 0xde, "MSTORE_32BYTES_32" => 0xdf, + "PROVER_INPUT" => 0xee, "CREATE" => 0xf0, "CALL" => 0xf1, "CALLCODE" => 0xf2, diff --git a/evm_arithmetization/src/cpu/kernel/tests/add11.rs b/evm_arithmetization/src/cpu/kernel/tests/add11.rs index 9d24bf00f..157fc1a13 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/add11.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/add11.rs @@ -14,6 +14,10 @@ use crate::cpu::kernel::interpreter::Interpreter; use crate::generation::mpt::{AccountRlp, LegacyReceiptRlp}; use crate::generation::TrieInputs; use crate::proof::{BlockHashes, BlockMetadata, TrieRoots}; +use crate::testing_utils::{ + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, + initial_state_and_storage_tries_with_beacon_roots, update_beacon_roots_account_storage, +}; use crate::GenerationInputs; #[test] @@ -51,7 +55,9 @@ fn test_add11_yml() { ..AccountRlp::default() }; - let mut state_trie_before = HashedPartialTrie::from(Node::Empty); + let (mut state_trie_before, mut storage_tries) = + initial_state_and_storage_tries_with_beacon_roots(); + let mut beacon_roots_account_storage = storage_tries[0].1.clone(); state_trie_before.insert( beneficiary_nibbles, rlp::encode(&beneficiary_account_before).to_vec(), @@ -59,17 +65,33 @@ fn test_add11_yml() { state_trie_before.insert(sender_nibbles, rlp::encode(&sender_account_before).to_vec()); state_trie_before.insert(to_nibbles, rlp::encode(&to_account_before).to_vec()); + storage_tries.push((to_hashed, Node::Empty.into())); + let tries_before = TrieInputs { state_trie: state_trie_before, transactions_trie: Node::Empty.into(), receipts_trie: Node::Empty.into(), - storage_tries: vec![(to_hashed, Node::Empty.into())], + storage_tries, }; let txn = hex!("f863800a83061a8094095e7baea6a6c7c4c2dfeb977efac326af552d87830186a0801ba0ffb600e63115a7362e7811894a91d8ba4330e526f22121c994c4692035dfdfd5a06198379fcac8de3dbfac48b165df4bf88e2088f294b61efb9a65fe2281c76e16"); let gas_used = 0xa868u64.into(); + let block_metadata = BlockMetadata { + block_beneficiary: Address::from(beneficiary), + block_timestamp: 0x03e8.into(), + block_number: 1.into(), + block_difficulty: 0x020000.into(), + block_random: H256::from_uint(&0x020000.into()), + block_gaslimit: 0xff112233u32.into(), + block_chain_id: 1.into(), + block_base_fee: 0xa.into(), + block_gas_used: gas_used, + block_blob_base_fee: 0x2.into(), + ..Default::default() + }; + let expected_state_trie_after = { let beneficiary_account_after = AccountRlp { nonce: 1.into(), @@ -91,6 +113,13 @@ fn test_add11_yml() { .hash(), ..AccountRlp::default() }; + update_beacon_roots_account_storage( + &mut beacon_roots_account_storage, + block_metadata.block_timestamp, + block_metadata.parent_beacon_block_root, + ); + let beacon_roots_account = + beacon_roots_contract_from_storage(&beacon_roots_account_storage); let mut expected_state_trie_after = HashedPartialTrie::from(Node::Empty); expected_state_trie_after.insert( @@ -100,6 +129,10 @@ fn test_add11_yml() { expected_state_trie_after .insert(sender_nibbles, rlp::encode(&sender_account_after).to_vec()); expected_state_trie_after.insert(to_nibbles, rlp::encode(&to_account_after).to_vec()); + expected_state_trie_after.insert( + beacon_roots_account_nibbles(), + rlp::encode(&beacon_roots_account).to_vec(), + ); expected_state_trie_after }; let receipt_0 = LegacyReceiptRlp { @@ -125,20 +158,6 @@ fn test_add11_yml() { receipts_root: receipts_trie.hash(), }; - let block_metadata = BlockMetadata { - block_beneficiary: Address::from(beneficiary), - block_timestamp: 0x03e8.into(), - block_number: 1.into(), - block_difficulty: 0x020000.into(), - block_random: H256::from_uint(&0x020000.into()), - block_gaslimit: 0xff112233u32.into(), - block_chain_id: 1.into(), - block_base_fee: 0xa.into(), - block_gas_used: gas_used, - block_blob_base_fee: 0x2.into(), - block_bloom: [0.into(); 8], - }; - let tries_inputs = GenerationInputs { signed_txn: Some(txn.to_vec()), withdrawals: vec![], @@ -205,7 +224,9 @@ fn test_add11_yml_with_exception() { ..AccountRlp::default() }; - let mut state_trie_before = HashedPartialTrie::from(Node::Empty); + let (mut state_trie_before, mut storage_tries) = + initial_state_and_storage_tries_with_beacon_roots(); + let mut beacon_roots_account_storage = storage_tries[0].1.clone(); state_trie_before.insert( beneficiary_nibbles, rlp::encode(&beneficiary_account_before).to_vec(), @@ -213,19 +234,36 @@ fn test_add11_yml_with_exception() { state_trie_before.insert(sender_nibbles, rlp::encode(&sender_account_before).to_vec()); state_trie_before.insert(to_nibbles, rlp::encode(&to_account_before).to_vec()); + storage_tries.push((to_hashed, Node::Empty.into())); + let tries_before = TrieInputs { state_trie: state_trie_before, transactions_trie: Node::Empty.into(), receipts_trie: Node::Empty.into(), - storage_tries: vec![(to_hashed, Node::Empty.into())], + storage_tries, }; let txn = hex!("f863800a83061a8094095e7baea6a6c7c4c2dfeb977efac326af552d87830186a0801ba0ffb600e63115a7362e7811894a91d8ba4330e526f22121c994c4692035dfdfd5a06198379fcac8de3dbfac48b165df4bf88e2088f294b61efb9a65fe2281c76e16"); let txn_gas_limit = 400_000; let gas_price = 10; + let block_metadata = BlockMetadata { + block_beneficiary: Address::from(beneficiary), + block_timestamp: 0x03e8.into(), + block_number: 1.into(), + block_difficulty: 0x020000.into(), + block_random: H256::from_uint(&0x020000.into()), + block_gaslimit: 0xff112233u32.into(), + block_chain_id: 1.into(), + block_base_fee: 0xa.into(), + block_gas_used: txn_gas_limit.into(), + block_blob_base_fee: 0x2.into(), + ..Default::default() + }; + // Here, since the transaction fails, it consumes its gas limit, and does - // nothing else. + // nothing else. The beacon roots contract is still updated prior transaction + // execution. let expected_state_trie_after = { let beneficiary_account_after = beneficiary_account_before; // This is the only account that changes: the nonce and the balance are updated. @@ -236,6 +274,14 @@ fn test_add11_yml_with_exception() { }; let to_account_after = to_account_before; + update_beacon_roots_account_storage( + &mut beacon_roots_account_storage, + block_metadata.block_timestamp, + block_metadata.parent_beacon_block_root, + ); + let beacon_roots_account = + beacon_roots_contract_from_storage(&beacon_roots_account_storage); + let mut expected_state_trie_after = HashedPartialTrie::from(Node::Empty); expected_state_trie_after.insert( beneficiary_nibbles, @@ -244,6 +290,10 @@ fn test_add11_yml_with_exception() { expected_state_trie_after .insert(sender_nibbles, rlp::encode(&sender_account_after).to_vec()); expected_state_trie_after.insert(to_nibbles, rlp::encode(&to_account_after).to_vec()); + expected_state_trie_after.insert( + beacon_roots_account_nibbles(), + rlp::encode(&beacon_roots_account).to_vec(), + ); expected_state_trie_after }; @@ -270,20 +320,6 @@ fn test_add11_yml_with_exception() { receipts_root: receipts_trie.hash(), }; - let block_metadata = BlockMetadata { - block_beneficiary: Address::from(beneficiary), - block_timestamp: 0x03e8.into(), - block_number: 1.into(), - block_difficulty: 0x020000.into(), - block_random: H256::from_uint(&0x020000.into()), - block_gaslimit: 0xff112233u32.into(), - block_chain_id: 1.into(), - block_base_fee: 0xa.into(), - block_gas_used: txn_gas_limit.into(), - block_blob_base_fee: 0x2.into(), - block_bloom: [0.into(); 8], - }; - let tries_inputs = GenerationInputs { signed_txn: Some(txn.to_vec()), withdrawals: vec![], diff --git a/evm_arithmetization/src/generation/mod.rs b/evm_arithmetization/src/generation/mod.rs index cf0557d66..138b87d47 100644 --- a/evm_arithmetization/src/generation/mod.rs +++ b/evm_arithmetization/src/generation/mod.rs @@ -131,6 +131,10 @@ fn apply_metadata_and_tries_memops, const D: usize> GlobalMetadata::BlockBlobBaseFee, metadata.block_blob_base_fee, ), + ( + GlobalMetadata::ParentBeaconBlockRoot, + h2u(metadata.parent_beacon_block_root), + ), (GlobalMetadata::BlockGasUsedBefore, inputs.gas_used_before), (GlobalMetadata::BlockGasUsedAfter, inputs.gas_used_after), (GlobalMetadata::TxnNumberBefore, inputs.txn_number_before), diff --git a/evm_arithmetization/src/get_challenges.rs b/evm_arithmetization/src/get_challenges.rs index 5cf8579b7..89b55ca93 100644 --- a/evm_arithmetization/src/get_challenges.rs +++ b/evm_arithmetization/src/get_challenges.rs @@ -68,6 +68,7 @@ fn observe_block_metadata< let blob_basefee = u256_to_u64(block_metadata.block_blob_base_fee)?; challenger.observe_element(blob_basefee.0); challenger.observe_element(blob_basefee.1); + challenger.observe_elements(&h256_limbs::(block_metadata.parent_beacon_block_root)); for i in 0..8 { challenger.observe_elements(&u256_limbs(block_metadata.block_bloom[i])); } @@ -95,6 +96,7 @@ fn observe_block_metadata_target< challenger.observe_elements(&block_metadata.block_base_fee); challenger.observe_element(block_metadata.block_gas_used); challenger.observe_elements(&block_metadata.block_blob_base_fee); + challenger.observe_elements(&block_metadata.parent_beacon_block_root); challenger.observe_elements(&block_metadata.block_bloom); } diff --git a/evm_arithmetization/src/lib.rs b/evm_arithmetization/src/lib.rs index b3cdc0e37..8bea00692 100644 --- a/evm_arithmetization/src/lib.rs +++ b/evm_arithmetization/src/lib.rs @@ -209,6 +209,7 @@ pub mod witness; // Utility modules pub mod curve_pairings; pub mod extension_tower; +pub mod testing_utils; pub mod util; // Set up Jemalloc diff --git a/evm_arithmetization/src/proof.rs b/evm_arithmetization/src/proof.rs index e5100b26e..4d96decd8 100644 --- a/evm_arithmetization/src/proof.rs +++ b/evm_arithmetization/src/proof.rs @@ -187,6 +187,8 @@ pub struct BlockMetadata { pub block_gas_used: U256, /// The blob base fee. It must fit in a `u64`. pub block_blob_base_fee: U256, + /// The hash tree root of the parent beacon block. + pub parent_beacon_block_root: H256, /// The block bloom of this block, represented as the consecutive /// 32-byte chunks of a block's final bloom filter string. pub block_bloom: [U256; 8], @@ -208,8 +210,9 @@ impl BlockMetadata { let block_gas_used = pis[20].to_canonical_u64().into(); let block_blob_base_fee = (pis[21].to_canonical_u64() + (pis[22].to_canonical_u64() << 32)).into(); + let parent_beacon_block_root = get_h256(&pis[23..31]); let block_bloom = - core::array::from_fn(|i| h2u(get_h256(&pis[23 + 8 * i..23 + 8 * (i + 1)]))); + core::array::from_fn(|i| h2u(get_h256(&pis[31 + 8 * i..31 + 8 * (i + 1)]))); Self { block_beneficiary, @@ -222,6 +225,7 @@ impl BlockMetadata { block_base_fee, block_gas_used, block_blob_base_fee, + parent_beacon_block_root, block_bloom, } } @@ -318,6 +322,7 @@ impl PublicValuesTarget { block_base_fee, block_gas_used, block_blob_base_fee, + parent_beacon_block_root, block_bloom, } = self.block_metadata; @@ -331,6 +336,7 @@ impl PublicValuesTarget { buffer.write_target_array(&block_base_fee)?; buffer.write_target(block_gas_used)?; buffer.write_target_array(&block_blob_base_fee)?; + buffer.write_target_array(&parent_beacon_block_root)?; buffer.write_target_array(&block_bloom)?; let BlockHashesTarget { @@ -381,6 +387,7 @@ impl PublicValuesTarget { block_base_fee: buffer.read_target_array()?, block_gas_used: buffer.read_target()?, block_blob_base_fee: buffer.read_target_array()?, + parent_beacon_block_root: buffer.read_target_array()?, block_bloom: buffer.read_target_array()?, }; @@ -583,13 +590,15 @@ pub struct BlockMetadataTarget { pub(crate) block_gas_used: Target, /// `Target`s for the blob base fee of this block. pub(crate) block_blob_base_fee: [Target; 2], + /// `Target`s for the parent beacon block root. + pub parent_beacon_block_root: [Target; 8], /// `Target`s for the block bloom of this block. pub(crate) block_bloom: [Target; 64], } impl BlockMetadataTarget { /// Number of `Target`s required for the block metadata. - pub(crate) const SIZE: usize = 87; + pub(crate) const SIZE: usize = 95; /// Extracts block metadata `Target`s from the provided public input /// `Target`s. The provided `pis` should start with the block metadata. @@ -604,7 +613,8 @@ impl BlockMetadataTarget { let block_base_fee = pis[18..20].try_into().unwrap(); let block_gas_used = pis[20]; let block_blob_base_fee = pis[21..23].try_into().unwrap(); - let block_bloom = pis[23..87].try_into().unwrap(); + let parent_beacon_block_root = pis[23..31].try_into().unwrap(); + let block_bloom = pis[31..95].try_into().unwrap(); Self { block_beneficiary, @@ -617,6 +627,7 @@ impl BlockMetadataTarget { block_base_fee, block_gas_used, block_blob_base_fee, + parent_beacon_block_root, block_bloom, } } @@ -656,6 +667,13 @@ impl BlockMetadataTarget { bm1.block_blob_base_fee[i], ) }), + parent_beacon_block_root: core::array::from_fn(|i| { + builder.select( + condition, + bm0.parent_beacon_block_root[i], + bm1.parent_beacon_block_root[i], + ) + }), block_bloom: core::array::from_fn(|i| { builder.select(condition, bm0.block_bloom[i], bm1.block_bloom[i]) }), @@ -686,6 +704,12 @@ impl BlockMetadataTarget { for i in 0..2 { builder.connect(bm0.block_blob_base_fee[i], bm1.block_blob_base_fee[i]) } + for i in 0..8 { + builder.connect( + bm0.parent_beacon_block_root[i], + bm1.parent_beacon_block_root[i], + ); + } for i in 0..64 { builder.connect(bm0.block_bloom[i], bm1.block_bloom[i]) } diff --git a/evm_arithmetization/src/recursive_verifier.rs b/evm_arithmetization/src/recursive_verifier.rs index e63d4731b..4cfcaee1c 100644 --- a/evm_arithmetization/src/recursive_verifier.rs +++ b/evm_arithmetization/src/recursive_verifier.rs @@ -377,8 +377,8 @@ pub(crate) fn get_memory_extra_looking_sum_circuit, ]; // This contains the `block_beneficiary`, `block_random`, `block_base_fee`, - // `block_blob_base_fee` as well as `cur_hash`. - let block_fields_arrays: [(GlobalMetadata, &[Target]); 5] = [ + // `block_blob_base_fee`, `parent_beacon_block_root` as well as `cur_hash`. + let block_fields_arrays: [(GlobalMetadata, &[Target]); 6] = [ ( GlobalMetadata::BlockBeneficiary, &public_values.block_metadata.block_beneficiary, @@ -395,6 +395,10 @@ pub(crate) fn get_memory_extra_looking_sum_circuit, GlobalMetadata::BlockBlobBaseFee, &public_values.block_metadata.block_blob_base_fee, ), + ( + GlobalMetadata::ParentBeaconBlockRoot, + &public_values.block_metadata.parent_beacon_block_root, + ), ( GlobalMetadata::BlockCurrentHash, &public_values.block_hashes.cur_hash, @@ -600,6 +604,7 @@ pub(crate) fn add_virtual_block_metadata, const D: let block_base_fee = builder.add_virtual_public_input_arr(); let block_gas_used = builder.add_virtual_public_input(); let block_blob_base_fee = builder.add_virtual_public_input_arr(); + let parent_beacon_block_root = builder.add_virtual_public_input_arr(); let block_bloom = builder.add_virtual_public_input_arr(); BlockMetadataTarget { block_beneficiary, @@ -612,6 +617,7 @@ pub(crate) fn add_virtual_block_metadata, const D: block_base_fee, block_gas_used, block_blob_base_fee, + parent_beacon_block_root, block_bloom, } } @@ -785,6 +791,12 @@ where let blob_basefee = u256_to_u64(block_metadata.block_blob_base_fee)?; witness.set_target(block_metadata_target.block_blob_base_fee[0], blob_basefee.0); witness.set_target(block_metadata_target.block_blob_base_fee[1], blob_basefee.1); + + witness.set_target_arr( + &block_metadata_target.parent_beacon_block_root, + &h256_limbs(block_metadata.parent_beacon_block_root), + ); + let mut block_bloom_limbs = [F::ZERO; 64]; for (i, limbs) in block_bloom_limbs.chunks_exact_mut(8).enumerate() { limbs.copy_from_slice(&u256_limbs(block_metadata.block_bloom[i])); diff --git a/evm_arithmetization/src/testing_utils.rs b/evm_arithmetization/src/testing_utils.rs new file mode 100644 index 000000000..c7371b4b6 --- /dev/null +++ b/evm_arithmetization/src/testing_utils.rs @@ -0,0 +1,121 @@ +//! A set of utility functions and constants to be used by `evm_arithmetization` +//! unit and integration tests. + +use env_logger::{try_init_from_env, Env, DEFAULT_FILTER_ENV}; +use ethereum_types::{H256, U256}; +use hex_literal::hex; +use keccak_hash::keccak; +use mpt_trie::{ + nibbles::Nibbles, + partial_trie::{HashedPartialTrie, Node, PartialTrie}, +}; + +pub use crate::cpu::kernel::cancun_constants::*; +use crate::{generation::mpt::AccountRlp, util::h2u}; + +pub const EMPTY_NODE_HASH: H256 = H256(hex!( + "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" +)); + +pub fn init_logger() { + let _ = try_init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info")); +} + +/// Converts a decimal string to a `U256`. +pub fn sd2u(s: &str) -> U256 { + U256::from_dec_str(s).unwrap() +} + +/// Converts an hexadecimal string to a `U256`. +pub fn sh2u(s: &str) -> U256 { + U256::from_str_radix(s, 16).unwrap() +} + +/// Inserts a new pair `(slot, value)` into the provided storage trie. +fn insert_storage(trie: &mut HashedPartialTrie, slot: U256, value: U256) { + let mut bytes = [0; 32]; + slot.to_big_endian(&mut bytes); + let key = keccak(bytes); + let nibbles = Nibbles::from_bytes_be(key.as_bytes()).unwrap(); + let r = rlp::encode(&value); + trie.insert(nibbles, r.freeze().to_vec()); +} + +/// Creates a storage trie for an account, given a list of `(slot, value)` +/// pairs. +pub fn create_account_storage(storage_pairs: &[(U256, U256)]) -> HashedPartialTrie { + let mut trie = HashedPartialTrie::from(Node::Empty); + for (slot, value) in storage_pairs { + insert_storage(&mut trie, *slot, *value); + } + trie +} + +/// Creates the storage trie of the beacon roots contract account at the +/// provided timestamp. Not passing any parent root will consider the parent +/// root at genesis, i.e. the empty hash. +fn beacon_roots_contract_storage(timestamp: U256, parent_root: H256) -> HashedPartialTrie { + let timestamp_idx = timestamp % HISTORY_BUFFER_LENGTH.1; + let root_idx = timestamp_idx + HISTORY_BUFFER_LENGTH.1; + + create_account_storage(&[(timestamp_idx, timestamp), (root_idx, h2u(parent_root))]) +} + +/// Updates the beacon roots account storage with the provided timestamp and +/// block parent root. +pub fn update_beacon_roots_account_storage( + storage_trie: &mut HashedPartialTrie, + timestamp: U256, + parent_root: H256, +) { + let timestamp_idx = timestamp % HISTORY_BUFFER_LENGTH.1; + let root_idx = timestamp_idx + HISTORY_BUFFER_LENGTH.1; + + insert_storage(storage_trie, timestamp_idx, timestamp); + insert_storage(storage_trie, root_idx, h2u(parent_root)); +} + +/// Returns the beacon roots contract account from its provided storage trie. +pub fn beacon_roots_contract_from_storage(storage_trie: &HashedPartialTrie) -> AccountRlp { + AccountRlp { + nonce: 0.into(), + balance: 0.into(), + storage_root: storage_trie.hash(), + code_hash: H256(BEACON_ROOTS_CONTRACT_CODE_HASH), + } +} + +/// Returns an initial state trie containing nothing but the beacon roots +/// contract, along with its storage trie. +pub fn initial_state_and_storage_tries_with_beacon_roots( +) -> (HashedPartialTrie, Vec<(H256, HashedPartialTrie)>) { + let state_trie = Node::Leaf { + nibbles: Nibbles::from_bytes_be(&BEACON_ROOTS_CONTRACT_ADDRESS_HASHED).unwrap(), + value: rlp::encode(&AccountRlp { + nonce: 0.into(), + balance: 0.into(), + storage_root: EMPTY_NODE_HASH, + code_hash: H256(BEACON_ROOTS_CONTRACT_CODE_HASH), + }) + .to_vec(), + } + .into(); + + let storage_tries = vec![( + H256(BEACON_ROOTS_CONTRACT_ADDRESS_HASHED), + Node::Empty.into(), + )]; + + (state_trie, storage_tries) +} + +/// Returns the `Nibbles` corresponding to the beacon roots contract account. +pub fn beacon_roots_account_nibbles() -> Nibbles { + Nibbles::from_bytes_be(&BEACON_ROOTS_CONTRACT_ADDRESS_HASHED).unwrap() +} + +/// Converts an amount in `ETH` to `wei` units. +pub fn eth_to_wei(eth: U256) -> U256 { + // 1 ether = 10^18 wei. + eth * U256::from(10).pow(18.into()) +} diff --git a/evm_arithmetization/src/verifier.rs b/evm_arithmetization/src/verifier.rs index 2e40a177c..d6e73fcad 100644 --- a/evm_arithmetization/src/verifier.rs +++ b/evm_arithmetization/src/verifier.rs @@ -182,6 +182,10 @@ where GlobalMetadata::BlockBaseFee, public_values.block_metadata.block_base_fee, ), + ( + GlobalMetadata::ParentBeaconBlockRoot, + h2u(public_values.block_metadata.parent_beacon_block_root), + ), ( GlobalMetadata::BlockCurrentHash, h2u(public_values.block_hashes.cur_hash), @@ -344,6 +348,10 @@ pub(crate) mod debug_utils { GlobalMetadata::BlockBlobBaseFee, public_values.block_metadata.block_blob_base_fee, ), + ( + GlobalMetadata::ParentBeaconBlockRoot, + h2u(public_values.block_metadata.parent_beacon_block_root), + ), ( GlobalMetadata::TxnNumberBefore, public_values.extra_block_data.txn_number_before, diff --git a/evm_arithmetization/src/witness/operation.rs b/evm_arithmetization/src/witness/operation.rs index 78e158ab2..17c3b157e 100644 --- a/evm_arithmetization/src/witness/operation.rs +++ b/evm_arithmetization/src/witness/operation.rs @@ -166,7 +166,7 @@ pub(crate) fn generate_prover_input( let pc = state.registers.program_counter; let input_fn = &KERNEL.prover_inputs[&pc]; let input = state.prover_input(input_fn)?; - let opcode = 0x49.into(); + let opcode = 0xee.into(); // `ArithmeticStark` range checks `mem_channels[0]`, which contains // the top of the stack, `mem_channels[1]`, `mem_channels[2]` and // next_row's `mem_channels[0]` which contains the next top of the stack. diff --git a/evm_arithmetization/src/witness/transition.rs b/evm_arithmetization/src/witness/transition.rs index d0215e3e3..67afcefb3 100644 --- a/evm_arithmetization/src/witness/transition.rs +++ b/evm_arithmetization/src/witness/transition.rs @@ -107,7 +107,6 @@ pub(crate) fn decode(registers: RegistersState, opcode: u8) -> Result Ok(Operation::Syscall(opcode, 0, true)), // CHAINID (0x47, _) => Ok(Operation::Syscall(opcode, 0, true)), // SELFBALANCE (0x48, _) => Ok(Operation::Syscall(opcode, 0, true)), // BASEFEE - (0x49, true) => Ok(Operation::ProverInput), (0x4a, _) => Ok(Operation::Syscall(opcode, 0, true)), // BLOBBASEFEE (0x50, _) => Ok(Operation::Pop), (0x51, _) => Ok(Operation::Syscall(opcode, 1, false)), // MLOAD @@ -138,6 +137,7 @@ pub(crate) fn decode(registers: RegistersState, opcode: u8) -> Result Ok(Operation::Mstore32Bytes(opcode - 0xc0 + 1)), + (0xee, true) => Ok(Operation::ProverInput), (0xf0, _) => Ok(Operation::Syscall(opcode, 3, false)), // CREATE (0xf1, _) => Ok(Operation::Syscall(opcode, 7, false)), // CALL (0xf2, _) => Ok(Operation::Syscall(opcode, 7, false)), // CALLCODE diff --git a/evm_arithmetization/tests/add11_yml.rs b/evm_arithmetization/tests/add11_yml.rs index 2f1b41c00..67b313ec8 100644 --- a/evm_arithmetization/tests/add11_yml.rs +++ b/evm_arithmetization/tests/add11_yml.rs @@ -2,12 +2,15 @@ use std::collections::HashMap; use std::str::FromStr; use std::time::Duration; -use env_logger::{try_init_from_env, Env, DEFAULT_FILTER_ENV}; use ethereum_types::{Address, BigEndianHash, H256}; use evm_arithmetization::generation::mpt::{AccountRlp, LegacyReceiptRlp}; use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::prove; +use evm_arithmetization::testing_utils::{ + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, init_logger, + initial_state_and_storage_tries_with_beacon_roots, update_beacon_roots_account_storage, +}; use evm_arithmetization::verifier::verify_proof; use evm_arithmetization::{AllStark, Node, StarkConfig}; use hex_literal::hex; @@ -59,7 +62,9 @@ fn add11_yml() -> anyhow::Result<()> { ..AccountRlp::default() }; - let mut state_trie_before = HashedPartialTrie::from(Node::Empty); + let (mut state_trie_before, mut storage_tries) = + initial_state_and_storage_tries_with_beacon_roots(); + let mut beacon_roots_account_storage = storage_tries[0].1.clone(); state_trie_before.insert( beneficiary_nibbles, rlp::encode(&beneficiary_account_before).to_vec(), @@ -67,11 +72,13 @@ fn add11_yml() -> anyhow::Result<()> { state_trie_before.insert(sender_nibbles, rlp::encode(&sender_account_before).to_vec()); state_trie_before.insert(to_nibbles, rlp::encode(&to_account_before).to_vec()); + storage_tries.push((to_hashed, Node::Empty.into())); + let tries_before = TrieInputs { state_trie: state_trie_before, transactions_trie: Node::Empty.into(), receipts_trie: Node::Empty.into(), - storage_tries: vec![(to_hashed, Node::Empty.into())], + storage_tries, }; let txn = hex!("f863800a83061a8094095e7baea6a6c7c4c2dfeb977efac326af552d87830186a0801ba0ffb600e63115a7362e7811894a91d8ba4330e526f22121c994c4692035dfdfd5a06198379fcac8de3dbfac48b165df4bf88e2088f294b61efb9a65fe2281c76e16"); @@ -87,7 +94,7 @@ fn add11_yml() -> anyhow::Result<()> { block_base_fee: 0xa.into(), block_gas_used: 0xa868u64.into(), block_blob_base_fee: 0x2.into(), - block_bloom: [0.into(); 8], + ..Default::default() }; let mut contract_code = HashMap::new(); @@ -95,6 +102,14 @@ fn add11_yml() -> anyhow::Result<()> { contract_code.insert(code_hash, code.to_vec()); let expected_state_trie_after = { + update_beacon_roots_account_storage( + &mut beacon_roots_account_storage, + block_metadata.block_timestamp, + block_metadata.parent_beacon_block_root, + ); + let beacon_roots_account = + beacon_roots_contract_from_storage(&beacon_roots_account_storage); + let beneficiary_account_after = AccountRlp { nonce: 1.into(), ..AccountRlp::default() @@ -124,6 +139,10 @@ fn add11_yml() -> anyhow::Result<()> { expected_state_trie_after .insert(sender_nibbles, rlp::encode(&sender_account_after).to_vec()); expected_state_trie_after.insert(to_nibbles, rlp::encode(&to_account_after).to_vec()); + expected_state_trie_after.insert( + beacon_roots_account_nibbles(), + rlp::encode(&beacon_roots_account).to_vec(), + ); expected_state_trie_after }; @@ -172,7 +191,3 @@ fn add11_yml() -> anyhow::Result<()> { verify_proof(&all_stark, proof, &config) } - -fn init_logger() { - let _ = try_init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info")); -} diff --git a/evm_arithmetization/tests/basic_smart_contract.rs b/evm_arithmetization/tests/basic_smart_contract.rs index b305e7628..06b83d711 100644 --- a/evm_arithmetization/tests/basic_smart_contract.rs +++ b/evm_arithmetization/tests/basic_smart_contract.rs @@ -2,13 +2,16 @@ use std::collections::HashMap; use std::str::FromStr; use std::time::Duration; -use env_logger::{try_init_from_env, Env, DEFAULT_FILTER_ENV}; use ethereum_types::{Address, H256, U256}; use evm_arithmetization::cpu::kernel::opcodes::{get_opcode, get_push_opcode}; use evm_arithmetization::generation::mpt::{AccountRlp, LegacyReceiptRlp}; use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::prove; +use evm_arithmetization::testing_utils::{ + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, eth_to_wei, init_logger, + initial_state_and_storage_tries_with_beacon_roots, update_beacon_roots_account_storage, +}; use evm_arithmetization::verifier::verify_proof; use evm_arithmetization::{AllStark, Node, StarkConfig}; use hex_literal::hex; @@ -65,35 +68,22 @@ fn test_basic_smart_contract() -> anyhow::Result<()> { ..AccountRlp::default() }; - let state_trie_before = { - let mut children = core::array::from_fn(|_| Node::Empty.into()); - children[beneficiary_nibbles.get_nibble(0) as usize] = Node::Leaf { - nibbles: beneficiary_nibbles.truncate_n_nibbles_front(1), - value: rlp::encode(&beneficiary_account_before).to_vec(), - } - .into(); - children[sender_nibbles.get_nibble(0) as usize] = Node::Leaf { - nibbles: sender_nibbles.truncate_n_nibbles_front(1), - value: rlp::encode(&sender_account_before).to_vec(), - } - .into(); - children[to_nibbles.get_nibble(0) as usize] = Node::Leaf { - nibbles: to_nibbles.truncate_n_nibbles_front(1), - value: rlp::encode(&to_account_before).to_vec(), - } - .into(); - Node::Branch { - children, - value: vec![], - } - } - .into(); + let (mut state_trie_before, storage_tries) = + initial_state_and_storage_tries_with_beacon_roots(); + let mut beacon_roots_account_storage = storage_tries[0].1.clone(); + + state_trie_before.insert( + beneficiary_nibbles, + rlp::encode(&beneficiary_account_before).to_vec(), + ); + state_trie_before.insert(sender_nibbles, rlp::encode(&sender_account_before).to_vec()); + state_trie_before.insert(to_nibbles, rlp::encode(&to_account_before).to_vec()); let tries_before = TrieInputs { state_trie: state_trie_before, transactions_trie: Node::Empty.into(), receipts_trie: Node::Empty.into(), - storage_tries: vec![], + storage_tries, }; let txdata_gas = 2 * 16; @@ -111,10 +101,9 @@ fn test_basic_smart_contract() -> anyhow::Result<()> { block_timestamp: 0x03e8.into(), block_gaslimit: 0xff112233u32.into(), block_gas_used: gas_used.into(), - block_bloom: [0.into(); 8], block_base_fee: 0xa.into(), block_blob_base_fee: 0x2.into(), - block_random: Default::default(), + ..Default::default() }; let mut contract_code = HashMap::new(); @@ -122,6 +111,14 @@ fn test_basic_smart_contract() -> anyhow::Result<()> { contract_code.insert(code_hash, code.to_vec()); let expected_state_trie_after: HashedPartialTrie = { + update_beacon_roots_account_storage( + &mut beacon_roots_account_storage, + block_metadata.block_timestamp, + block_metadata.parent_beacon_block_root, + ); + let beacon_roots_account = + beacon_roots_contract_from_storage(&beacon_roots_account_storage); + let beneficiary_account_after = AccountRlp { nonce: 1.into(), ..AccountRlp::default() @@ -152,6 +149,12 @@ fn test_basic_smart_contract() -> anyhow::Result<()> { value: rlp::encode(&to_account_after).to_vec(), } .into(); + children[beacon_roots_account_nibbles().get_nibble(0) as usize] = Node::Leaf { + nibbles: beacon_roots_account_nibbles().truncate_n_nibbles_front(1), + value: rlp::encode(&beacon_roots_account).to_vec(), + } + .into(); + Node::Branch { children, value: vec![], @@ -204,12 +207,3 @@ fn test_basic_smart_contract() -> anyhow::Result<()> { verify_proof(&all_stark, proof, &config) } - -fn eth_to_wei(eth: U256) -> U256 { - // 1 ether = 10^18 wei. - eth * U256::from(10).pow(18.into()) -} - -fn init_logger() { - let _ = try_init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info")); -} diff --git a/evm_arithmetization/tests/empty_txn_list.rs b/evm_arithmetization/tests/empty_txn_list.rs index 1205414f6..e343f7e53 100644 --- a/evm_arithmetization/tests/empty_txn_list.rs +++ b/evm_arithmetization/tests/empty_txn_list.rs @@ -2,11 +2,16 @@ use core::marker::PhantomData; use std::collections::HashMap; use std::time::Duration; -use env_logger::{try_init_from_env, Env, DEFAULT_FILTER_ENV}; use ethereum_types::{BigEndianHash, H256}; use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; use evm_arithmetization::proof::{BlockHashes, BlockMetadata, PublicValues, TrieRoots}; +use evm_arithmetization::testing_utils::{ + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, init_logger, + initial_state_and_storage_tries_with_beacon_roots, update_beacon_roots_account_storage, + BEACON_ROOTS_CONTRACT_ADDRESS_HASHED, +}; use evm_arithmetization::{AllRecursiveCircuits, AllStark, Node, StarkConfig}; +use hex_literal::hex; use keccak_hash::keccak; use log::info; use mpt_trie::partial_trie::{HashedPartialTrie, PartialTrie}; @@ -30,43 +35,62 @@ fn test_empty_txn_list() -> anyhow::Result<()> { let block_metadata = BlockMetadata { block_number: 1.into(), + parent_beacon_block_root: H256(hex!( + "44e2566c06c03b132e0ede3e90af477ebca74393b89dd6cb29f9c79cbcb6e963" + )), ..Default::default() }; - let state_trie = HashedPartialTrie::from(Node::Empty); + let (state_trie, storage_tries) = initial_state_and_storage_tries_with_beacon_roots(); + let mut beacon_roots_account_storage = storage_tries[0].1.clone(); let transactions_trie = HashedPartialTrie::from(Node::Empty); let receipts_trie = HashedPartialTrie::from(Node::Empty); - let storage_tries = vec![]; + + let state_trie_after: HashedPartialTrie = { + update_beacon_roots_account_storage( + &mut beacon_roots_account_storage, + block_metadata.block_timestamp, + block_metadata.parent_beacon_block_root, + ); + let beacon_roots_account = + beacon_roots_contract_from_storage(&beacon_roots_account_storage); + + Node::Leaf { + nibbles: beacon_roots_account_nibbles(), + value: rlp::encode(&beacon_roots_account).to_vec(), + } + .into() + }; let mut contract_code = HashMap::new(); contract_code.insert(keccak(vec![]), vec![]); - // No transactions, so no trie roots change. + // No transactions, but the beacon roots contract has been updated. let trie_roots_after = TrieRoots { - state_root: state_trie.hash(), + state_root: state_trie_after.hash(), transactions_root: transactions_trie.hash(), receipts_root: receipts_trie.hash(), }; let mut initial_block_hashes = vec![H256::default(); 256]; initial_block_hashes[255] = H256::from_uint(&0x200.into()); - let inputs = GenerationInputs { + let inputs1 = GenerationInputs { signed_txn: None, withdrawals: vec![], tries: TrieInputs { - state_trie, - transactions_trie, - receipts_trie, - storage_tries, + state_trie: state_trie.clone(), + transactions_trie: transactions_trie.clone(), + receipts_trie: receipts_trie.clone(), + storage_tries: storage_tries.clone(), }, trie_roots_after, - contract_code, - checkpoint_state_trie_root: HashedPartialTrie::from(Node::Empty).hash(), - block_metadata, + contract_code: contract_code.clone(), + checkpoint_state_trie_root: state_trie.hash(), + block_metadata: block_metadata.clone(), txn_number_before: 0.into(), gas_used_before: 0.into(), gas_used_after: 0.into(), block_hashes: BlockHashes { - prev_hashes: initial_block_hashes, + prev_hashes: initial_block_hashes.clone(), cur_hash: H256::default(), }, }; @@ -74,8 +98,8 @@ fn test_empty_txn_list() -> anyhow::Result<()> { // Initialize the preprocessed circuits for the zkEVM. let all_circuits = AllRecursiveCircuits::::new( &all_stark, - &[16..17, 9..11, 12..13, 14..15, 9..11, 12..13, 17..18], /* Minimal ranges to prove an - * empty list */ + // Minimal ranges to prove an empty list + &[16..17, 11..13, 13..15, 14..15, 9..10, 12..13, 17..18], &config, ); @@ -108,9 +132,9 @@ fn test_empty_txn_list() -> anyhow::Result<()> { assert_eq!(all_circuits, all_circuits_from_bytes); } - let mut timing = TimingTree::new("prove", log::Level::Info); + let mut timing = TimingTree::new("prove first dummy", log::Level::Info); let (root_proof, public_values) = - all_circuits.prove_root(&all_stark, &config, inputs, &mut timing, None)?; + all_circuits.prove_root(&all_stark, &config, inputs1, &mut timing, None)?; timing.filter(Duration::from_millis(100)).print(); all_circuits.verify_root(root_proof.clone())?; @@ -118,14 +142,51 @@ fn test_empty_txn_list() -> anyhow::Result<()> { let retrieved_public_values = PublicValues::from_public_inputs(&root_proof.public_inputs); assert_eq!(retrieved_public_values, public_values); - // We can duplicate the proofs here because the state hasn't mutated. + // We cannot duplicate the proof here because even though there weren't any + // transactions, the state has mutated when updating the beacon roots contract. + let trie_roots_after = TrieRoots { + state_root: state_trie_after.hash(), + transactions_root: transactions_trie.hash(), + receipts_root: receipts_trie.hash(), + }; + let inputs2 = GenerationInputs { + signed_txn: None, + withdrawals: vec![], + tries: TrieInputs { + state_trie: state_trie_after, + transactions_trie, + receipts_trie, + storage_tries: vec![( + H256(BEACON_ROOTS_CONTRACT_ADDRESS_HASHED), + beacon_roots_account_storage, + )], + }, + trie_roots_after, + contract_code, + checkpoint_state_trie_root: state_trie.hash(), + block_metadata, + txn_number_before: 0.into(), + gas_used_before: 0.into(), + gas_used_after: 0.into(), + block_hashes: BlockHashes { + prev_hashes: initial_block_hashes, + cur_hash: H256::default(), + }, + }; + + let mut timing = TimingTree::new("prove second dummy", log::Level::Info); + let (root_proof2, public_values2) = + all_circuits.prove_root(&all_stark, &config, inputs2, &mut timing, None)?; + timing.filter(Duration::from_millis(100)).print(); + all_circuits.verify_root(root_proof2.clone())?; + let (agg_proof, agg_public_values) = all_circuits.prove_aggregation( false, &root_proof, public_values.clone(), false, - &root_proof, - public_values, + &root_proof2, + public_values2, )?; all_circuits.verify_aggregation(&agg_proof)?; @@ -146,7 +207,3 @@ fn test_empty_txn_list() -> anyhow::Result<()> { let verifier = all_circuits.final_verifier_data(); verifier.verify(block_proof) } - -fn init_logger() { - let _ = try_init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info")); -} diff --git a/evm_arithmetization/tests/erc20.rs b/evm_arithmetization/tests/erc20.rs index 119fb6ec1..c3235dd9b 100644 --- a/evm_arithmetization/tests/erc20.rs +++ b/evm_arithmetization/tests/erc20.rs @@ -1,12 +1,16 @@ use std::str::FromStr; use std::time::Duration; -use env_logger::{try_init_from_env, Env, DEFAULT_FILTER_ENV}; use ethereum_types::{Address, BigEndianHash, H160, H256, U256}; use evm_arithmetization::generation::mpt::{AccountRlp, LegacyReceiptRlp, LogRlp}; use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::prove; +use evm_arithmetization::testing_utils::{ + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, create_account_storage, + init_logger, initial_state_and_storage_tries_with_beacon_roots, sd2u, + update_beacon_roots_account_storage, +}; use evm_arithmetization::verifier::verify_proof; use evm_arithmetization::{AllStark, Node, StarkConfig}; use hex_literal::hex; @@ -61,15 +65,17 @@ fn test_erc20() -> anyhow::Result<()> { let giver_nibbles = Nibbles::from_bytes_be(giver_state_key.as_bytes()).unwrap(); let token_nibbles = Nibbles::from_bytes_be(token_state_key.as_bytes()).unwrap(); - let mut state_trie_before = HashedPartialTrie::from(Node::Empty); + let (mut state_trie_before, mut storage_tries) = + initial_state_and_storage_tries_with_beacon_roots(); + let mut beacon_roots_account_storage = storage_tries[0].1.clone(); state_trie_before.insert(sender_nibbles, rlp::encode(&sender_account()).to_vec()); state_trie_before.insert(giver_nibbles, rlp::encode(&giver_account()).to_vec()); state_trie_before.insert(token_nibbles, rlp::encode(&token_account()).to_vec()); - let storage_tries = vec![ + storage_tries.extend([ (giver_state_key, giver_storage()), (token_state_key, token_storage()), - ]; + ]); let tries_before = TrieInputs { state_trie: state_trie_before, @@ -94,6 +100,7 @@ fn test_erc20() -> anyhow::Result<()> { block_gas_used: gas_used, block_blob_base_fee: 0x2.into(), block_bloom: bloom, + ..Default::default() }; let contract_code = [giver_bytecode(), token_bytecode(), vec![]] @@ -101,6 +108,14 @@ fn test_erc20() -> anyhow::Result<()> { .into(); let expected_state_trie_after: HashedPartialTrie = { + update_beacon_roots_account_storage( + &mut beacon_roots_account_storage, + block_metadata.block_timestamp, + block_metadata.parent_beacon_block_root, + ); + let beacon_roots_account = + beacon_roots_contract_from_storage(&beacon_roots_account_storage); + let mut state_trie_after = HashedPartialTrie::from(Node::Empty); let sender_account = sender_account(); let sender_account_after = AccountRlp { @@ -115,6 +130,10 @@ fn test_erc20() -> anyhow::Result<()> { ..token_account() }; state_trie_after.insert(token_nibbles, rlp::encode(&token_account_after).to_vec()); + state_trie_after.insert( + beacon_roots_account_nibbles(), + rlp::encode(&beacon_roots_account).to_vec(), + ); state_trie_after }; @@ -181,10 +200,6 @@ fn test_erc20() -> anyhow::Result<()> { verify_proof(&all_stark, proof, &config) } -fn init_logger() { - let _ = try_init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info")); -} - fn giver_bytecode() -> Vec { hex!("608060405234801561001057600080fd5b50600436106100365760003560e01c8063a52c101e1461003b578063fc0c546a14610050575b600080fd5b61004e61004936600461010c565b61007f565b005b600054610063906001600160a01b031681565b6040516001600160a01b03909116815260200160405180910390f35b60005460405163a9059cbb60e01b8152731f9090aae28b8a3dceadf281b0f12828e676c3266004820152602481018390526001600160a01b039091169063a9059cbb906044016020604051808303816000875af11580156100e4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101089190610125565b5050565b60006020828403121561011e57600080fd5b5035919050565b60006020828403121561013757600080fd5b8151801515811461014757600080fd5b939250505056fea264697066735822122050741efdbac11eb0bbb776ce3ac6004e596b7d7559658a12506164388c371cfd64736f6c63430008140033").into() } @@ -193,53 +208,31 @@ fn token_bytecode() -> Vec { hex!("608060405234801561001057600080fd5b50600436106100935760003560e01c8063313ce56711610066578063313ce567146100fe57806370a082311461010d57806395d89b4114610136578063a9059cbb1461013e578063dd62ed3e1461015157600080fd5b806306fdde0314610098578063095ea7b3146100b657806318160ddd146100d957806323b872dd146100eb575b600080fd5b6100a061018a565b6040516100ad919061056a565b60405180910390f35b6100c96100c43660046105d4565b61021c565b60405190151581526020016100ad565b6002545b6040519081526020016100ad565b6100c96100f93660046105fe565b610236565b604051601281526020016100ad565b6100dd61011b36600461063a565b6001600160a01b031660009081526020819052604090205490565b6100a061025a565b6100c961014c3660046105d4565b610269565b6100dd61015f36600461065c565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6060600380546101999061068f565b80601f01602080910402602001604051908101604052809291908181526020018280546101c59061068f565b80156102125780601f106101e757610100808354040283529160200191610212565b820191906000526020600020905b8154815290600101906020018083116101f557829003601f168201915b5050505050905090565b60003361022a818585610277565b60019150505b92915050565b600033610244858285610289565b61024f85858561030c565b506001949350505050565b6060600480546101999061068f565b60003361022a81858561030c565b610284838383600161036b565b505050565b6001600160a01b03838116600090815260016020908152604080832093861683529290522054600019811461030657818110156102f757604051637dc7a0d960e11b81526001600160a01b038416600482015260248101829052604481018390526064015b60405180910390fd5b6103068484848403600061036b565b50505050565b6001600160a01b03831661033657604051634b637e8f60e11b8152600060048201526024016102ee565b6001600160a01b0382166103605760405163ec442f0560e01b8152600060048201526024016102ee565b610284838383610440565b6001600160a01b0384166103955760405163e602df0560e01b8152600060048201526024016102ee565b6001600160a01b0383166103bf57604051634a1406b160e11b8152600060048201526024016102ee565b6001600160a01b038085166000908152600160209081526040808320938716835292905220829055801561030657826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258460405161043291815260200190565b60405180910390a350505050565b6001600160a01b03831661046b57806002600082825461046091906106c9565b909155506104dd9050565b6001600160a01b038316600090815260208190526040902054818110156104be5760405163391434e360e21b81526001600160a01b038516600482015260248101829052604481018390526064016102ee565b6001600160a01b03841660009081526020819052604090209082900390555b6001600160a01b0382166104f957600280548290039055610518565b6001600160a01b03821660009081526020819052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161055d91815260200190565b60405180910390a3505050565b600060208083528351808285015260005b818110156105975785810183015185820160400152820161057b565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b03811681146105cf57600080fd5b919050565b600080604083850312156105e757600080fd5b6105f0836105b8565b946020939093013593505050565b60008060006060848603121561061357600080fd5b61061c846105b8565b925061062a602085016105b8565b9150604084013590509250925092565b60006020828403121561064c57600080fd5b610655826105b8565b9392505050565b6000806040838503121561066f57600080fd5b610678836105b8565b9150610686602084016105b8565b90509250929050565b600181811c908216806106a357607f821691505b6020821081036106c357634e487b7160e01b600052602260045260246000fd5b50919050565b8082018082111561023057634e487b7160e01b600052601160045260246000fdfea2646970667358221220266a323ae4a816f6c6342a5be431fedcc0d45c44b02ea75f5474eb450b5d45b364736f6c63430008140033").into() } -fn insert_storage(trie: &mut HashedPartialTrie, slot: U256, value: U256) { - let mut bytes = [0; 32]; - slot.to_big_endian(&mut bytes); - let key = keccak(bytes); - let nibbles = Nibbles::from_bytes_be(key.as_bytes()).unwrap(); - let r = rlp::encode(&value); - let r = r.freeze().to_vec(); - trie.insert(nibbles, r); -} - -fn sd2u(s: &str) -> U256 { - U256::from_dec_str(s).unwrap() -} - fn giver_storage() -> HashedPartialTrie { - let mut trie = HashedPartialTrie::from(Node::Empty); - insert_storage( - &mut trie, + create_account_storage(&[( U256::zero(), sd2u("546584486846459126461364135121053344201067465379"), - ); - trie + )]) } fn token_storage() -> HashedPartialTrie { - let mut trie = HashedPartialTrie::from(Node::Empty); - insert_storage( - &mut trie, + create_account_storage(&[( sd2u("82183438603287090451672504949863617512989139203883434767553028632841710582583"), sd2u("1000000000000000000000"), - ); - trie + )]) } fn token_storage_after() -> HashedPartialTrie { - let mut trie = HashedPartialTrie::from(Node::Empty); - insert_storage( - &mut trie, - sd2u("82183438603287090451672504949863617512989139203883434767553028632841710582583"), - sd2u("900000000000000000000"), - ); - insert_storage( - &mut trie, - sd2u("53006154680716014998529145169423020330606407246856709517064848190396281160729"), - sd2u("100000000000000000000"), - ); - trie + create_account_storage(&[ + ( + sd2u("82183438603287090451672504949863617512989139203883434767553028632841710582583"), + sd2u("900000000000000000000"), + ), + ( + sd2u("53006154680716014998529145169423020330606407246856709517064848190396281160729"), + sd2u("100000000000000000000"), + ), + ]) } fn giver_account() -> AccountRlp { diff --git a/evm_arithmetization/tests/erc721.rs b/evm_arithmetization/tests/erc721.rs index 13ea05a79..29db8f1a8 100644 --- a/evm_arithmetization/tests/erc721.rs +++ b/evm_arithmetization/tests/erc721.rs @@ -1,12 +1,16 @@ use std::str::FromStr; use std::time::Duration; -use env_logger::{try_init_from_env, Env, DEFAULT_FILTER_ENV}; use ethereum_types::{Address, BigEndianHash, H160, H256, U256}; use evm_arithmetization::generation::mpt::{AccountRlp, LegacyReceiptRlp, LogRlp}; use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::prove; +use evm_arithmetization::testing_utils::{ + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, create_account_storage, + init_logger, initial_state_and_storage_tries_with_beacon_roots, sd2u, sh2u, + update_beacon_roots_account_storage, +}; use evm_arithmetization::verifier::verify_proof; use evm_arithmetization::{AllStark, Node, StarkConfig}; use hex_literal::hex; @@ -61,11 +65,13 @@ fn test_erc721() -> anyhow::Result<()> { let owner_nibbles = Nibbles::from_bytes_be(owner_state_key.as_bytes()).unwrap(); let contract_nibbles = Nibbles::from_bytes_be(contract_state_key.as_bytes()).unwrap(); - let mut state_trie_before = HashedPartialTrie::from(Node::Empty); + let (mut state_trie_before, mut storage_tries) = + initial_state_and_storage_tries_with_beacon_roots(); + let mut beacon_roots_account_storage = storage_tries[0].1.clone(); state_trie_before.insert(owner_nibbles, rlp::encode(&owner_account()).to_vec()); state_trie_before.insert(contract_nibbles, rlp::encode(&contract_account()).to_vec()); - let storage_tries = vec![(contract_state_key, contract_storage())]; + storage_tries.push((contract_state_key, contract_storage())); let tries_before = TrieInputs { state_trie: state_trie_before, @@ -82,8 +88,55 @@ fn test_erc721() -> anyhow::Result<()> { .map(|v| (keccak(v.clone()), v)) .into(); + let logs = vec![LogRlp { + address: H160::from_str("0xf2B1114C644cBb3fF63Bf1dD284c8Cd716e95BE9").unwrap(), + topics: vec![ + H256::from_str("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") + .unwrap(), + H256::from_str("0x0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4") + .unwrap(), + H256::from_str("0x000000000000000000000000ab8483f64d9c6d1ecf9b849ae677dd3315835cb2") + .unwrap(), + H256::from_str("0x0000000000000000000000000000000000000000000000000000000000000539") + .unwrap(), + ], + data: vec![].into(), + }]; + + let mut bloom_bytes = [0u8; 256]; + add_logs_to_bloom(&mut bloom_bytes, &logs); + + let bloom = bloom_bytes + .chunks_exact(32) + .map(U256::from_big_endian) + .collect::>(); + + let block_metadata = BlockMetadata { + block_beneficiary: Address::from(beneficiary), + block_timestamp: 0x03e8.into(), + block_number: 1.into(), + block_difficulty: 0x020000.into(), + block_random: H256::from_uint(&0x020000.into()), + block_gaslimit: 0xff112233u32.into(), + block_chain_id: 1.into(), + block_base_fee: 0xa.into(), + block_gas_used: gas_used, + block_blob_base_fee: 0x2.into(), + block_bloom: bloom.try_into().unwrap(), + ..Default::default() + }; + let expected_state_trie_after: HashedPartialTrie = { let mut state_trie_after = HashedPartialTrie::from(Node::Empty); + + update_beacon_roots_account_storage( + &mut beacon_roots_account_storage, + block_metadata.block_timestamp, + block_metadata.parent_beacon_block_root, + ); + let beacon_roots_account = + beacon_roots_contract_from_storage(&beacon_roots_account_storage); + let owner_account = owner_account(); let owner_account_after = AccountRlp { nonce: owner_account.nonce + 1, @@ -99,28 +152,14 @@ fn test_erc721() -> anyhow::Result<()> { contract_nibbles, rlp::encode(&contract_account_after).to_vec(), ); + state_trie_after.insert( + beacon_roots_account_nibbles(), + rlp::encode(&beacon_roots_account).to_vec(), + ); state_trie_after }; - let logs = vec![LogRlp { - address: H160::from_str("0xf2B1114C644cBb3fF63Bf1dD284c8Cd716e95BE9").unwrap(), - topics: vec![ - H256::from_str("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") - .unwrap(), - H256::from_str("0x0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4") - .unwrap(), - H256::from_str("0x000000000000000000000000ab8483f64d9c6d1ecf9b849ae677dd3315835cb2") - .unwrap(), - H256::from_str("0x0000000000000000000000000000000000000000000000000000000000000539") - .unwrap(), - ], - data: vec![].into(), - }]; - - let mut bloom_bytes = [0u8; 256]; - add_logs_to_bloom(&mut bloom_bytes, &logs); - let receipt_0 = LegacyReceiptRlp { status: true, cum_gas_used: gas_used, @@ -141,25 +180,6 @@ fn test_erc721() -> anyhow::Result<()> { receipts_root: receipts_trie.hash(), }; - let bloom = bloom_bytes - .chunks_exact(32) - .map(U256::from_big_endian) - .collect::>(); - - let block_metadata = BlockMetadata { - block_beneficiary: Address::from(beneficiary), - block_timestamp: 0x03e8.into(), - block_number: 1.into(), - block_difficulty: 0x020000.into(), - block_random: H256::from_uint(&0x020000.into()), - block_gaslimit: 0xff112233u32.into(), - block_chain_id: 1.into(), - block_base_fee: 0xa.into(), - block_gas_used: gas_used, - block_blob_base_fee: 0x2.into(), - block_bloom: bloom.try_into().unwrap(), - }; - let inputs = GenerationInputs { signed_txn: Some(txn.to_vec()), withdrawals: vec![], @@ -184,90 +204,58 @@ fn test_erc721() -> anyhow::Result<()> { verify_proof(&all_stark, proof, &config) } -fn init_logger() { - let _ = try_init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info")); -} - fn contract_bytecode() -> Vec { hex!("608060405234801561000f575f80fd5b5060043610610109575f3560e01c8063715018a6116100a0578063a22cb4651161006f578063a22cb465146102a1578063b88d4fde146102bd578063c87b56dd146102d9578063e985e9c514610309578063f2fde38b1461033957610109565b8063715018a61461023f5780638da5cb5b1461024957806395d89b4114610267578063a14481941461028557610109565b806323b872dd116100dc57806323b872dd146101a757806342842e0e146101c35780636352211e146101df57806370a082311461020f57610109565b806301ffc9a71461010d57806306fdde031461013d578063081812fc1461015b578063095ea7b31461018b575b5f80fd5b61012760048036038101906101229190611855565b610355565b604051610134919061189a565b60405180910390f35b610145610436565b604051610152919061193d565b60405180910390f35b61017560048036038101906101709190611990565b6104c5565b60405161018291906119fa565b60405180910390f35b6101a560048036038101906101a09190611a3d565b6104e0565b005b6101c160048036038101906101bc9190611a7b565b6104f6565b005b6101dd60048036038101906101d89190611a7b565b6105f5565b005b6101f960048036038101906101f49190611990565b610614565b60405161020691906119fa565b60405180910390f35b61022960048036038101906102249190611acb565b610625565b6040516102369190611b05565b60405180910390f35b6102476106db565b005b6102516106ee565b60405161025e91906119fa565b60405180910390f35b61026f610716565b60405161027c919061193d565b60405180910390f35b61029f600480360381019061029a9190611a3d565b6107a6565b005b6102bb60048036038101906102b69190611b48565b6107bc565b005b6102d760048036038101906102d29190611cb2565b6107d2565b005b6102f360048036038101906102ee9190611990565b6107ef565b604051610300919061193d565b60405180910390f35b610323600480360381019061031e9190611d32565b610855565b604051610330919061189a565b60405180910390f35b610353600480360381019061034e9190611acb565b6108e3565b005b5f7f80ac58cd000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916148061041f57507f5b5e139f000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b8061042f575061042e82610967565b5b9050919050565b60605f805461044490611d9d565b80601f016020809104026020016040519081016040528092919081815260200182805461047090611d9d565b80156104bb5780601f10610492576101008083540402835291602001916104bb565b820191905f5260205f20905b81548152906001019060200180831161049e57829003601f168201915b5050505050905090565b5f6104cf826109d0565b506104d982610a56565b9050919050565b6104f282826104ed610a8f565b610a96565b5050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610566575f6040517f64a0ae9200000000000000000000000000000000000000000000000000000000815260040161055d91906119fa565b60405180910390fd5b5f6105798383610574610a8f565b610aa8565b90508373ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16146105ef578382826040517f64283d7b0000000000000000000000000000000000000000000000000000000081526004016105e693929190611dcd565b60405180910390fd5b50505050565b61060f83838360405180602001604052805f8152506107d2565b505050565b5f61061e826109d0565b9050919050565b5f8073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610696575f6040517f89c62b6400000000000000000000000000000000000000000000000000000000815260040161068d91906119fa565b60405180910390fd5b60035f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b6106e3610cb3565b6106ec5f610d3a565b565b5f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60606001805461072590611d9d565b80601f016020809104026020016040519081016040528092919081815260200182805461075190611d9d565b801561079c5780601f106107735761010080835404028352916020019161079c565b820191905f5260205f20905b81548152906001019060200180831161077f57829003601f168201915b5050505050905090565b6107ae610cb3565b6107b88282610dfd565b5050565b6107ce6107c7610a8f565b8383610e1a565b5050565b6107dd8484846104f6565b6107e984848484610f83565b50505050565b60606107fa826109d0565b505f610804611135565b90505f8151116108225760405180602001604052805f81525061084d565b8061082c8461114b565b60405160200161083d929190611e3c565b6040516020818303038152906040525b915050919050565b5f60055f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff16905092915050565b6108eb610cb3565b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361095b575f6040517f1e4fbdf700000000000000000000000000000000000000000000000000000000815260040161095291906119fa565b60405180910390fd5b61096481610d3a565b50565b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b5f806109db83611215565b90505f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610a4d57826040517f7e273289000000000000000000000000000000000000000000000000000000008152600401610a449190611b05565b60405180910390fd5b80915050919050565b5f60045f8381526020019081526020015f205f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b5f33905090565b610aa3838383600161124e565b505050565b5f80610ab384611215565b90505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614610af457610af381848661140d565b5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614610b7f57610b335f855f8061124e565b600160035f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825403925050819055505b5f73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1614610bfe57600160035f8773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825401925050819055505b8460025f8681526020019081526020015f205f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550838573ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a4809150509392505050565b610cbb610a8f565b73ffffffffffffffffffffffffffffffffffffffff16610cd96106ee565b73ffffffffffffffffffffffffffffffffffffffff1614610d3857610cfc610a8f565b6040517f118cdaa7000000000000000000000000000000000000000000000000000000008152600401610d2f91906119fa565b60405180910390fd5b565b5f60065f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508160065f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b610e16828260405180602001604052805f8152506114d0565b5050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610e8a57816040517f5b08ba18000000000000000000000000000000000000000000000000000000008152600401610e8191906119fa565b60405180910390fd5b8060055f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3183604051610f76919061189a565b60405180910390a3505050565b5f8373ffffffffffffffffffffffffffffffffffffffff163b111561112f578273ffffffffffffffffffffffffffffffffffffffff1663150b7a02610fc6610a8f565b8685856040518563ffffffff1660e01b8152600401610fe89493929190611eb1565b6020604051808303815f875af192505050801561102357506040513d601f19601f820116820180604052508101906110209190611f0f565b60015b6110a4573d805f8114611051576040519150601f19603f3d011682016040523d82523d5f602084013e611056565b606091505b505f81510361109c57836040517f64a0ae9200000000000000000000000000000000000000000000000000000000815260040161109391906119fa565b60405180910390fd5b805181602001fd5b63150b7a0260e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161461112d57836040517f64a0ae9200000000000000000000000000000000000000000000000000000000815260040161112491906119fa565b60405180910390fd5b505b50505050565b606060405180602001604052805f815250905090565b60605f6001611159846114eb565b0190505f8167ffffffffffffffff81111561117757611176611b8e565b5b6040519080825280601f01601f1916602001820160405280156111a95781602001600182028036833780820191505090505b5090505f82602001820190505b60011561120a578080600190039150507f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a85816111ff576111fe611f3a565b5b0494505f85036111b6575b819350505050919050565b5f60025f8381526020019081526020015f205f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b808061128657505f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b156113b8575f611295846109d0565b90505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141580156112ff57508273ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b801561131257506113108184610855565b155b1561135457826040517fa9fbf51f00000000000000000000000000000000000000000000000000000000815260040161134b91906119fa565b60405180910390fd5b81156113b657838573ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45b505b8360045f8581526020019081526020015f205f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050505050565b61141883838361163c565b6114cb575f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361148c57806040517f7e2732890000000000000000000000000000000000000000000000000000000081526004016114839190611b05565b60405180910390fd5b81816040517f177e802f0000000000000000000000000000000000000000000000000000000081526004016114c2929190611f67565b60405180910390fd5b505050565b6114da83836116fc565b6114e65f848484610f83565b505050565b5f805f90507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008310611547577a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000838161153d5761153c611f3a565b5b0492506040810190505b6d04ee2d6d415b85acef81000000008310611584576d04ee2d6d415b85acef8100000000838161157a57611579611f3a565b5b0492506020810190505b662386f26fc1000083106115b357662386f26fc1000083816115a9576115a8611f3a565b5b0492506010810190505b6305f5e10083106115dc576305f5e10083816115d2576115d1611f3a565b5b0492506008810190505b61271083106116015761271083816115f7576115f6611f3a565b5b0492506004810190505b60648310611624576064838161161a57611619611f3a565b5b0492506002810190505b600a8310611633576001810190505b80915050919050565b5f8073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141580156116f357508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1614806116b457506116b38484610855565b5b806116f257508273ffffffffffffffffffffffffffffffffffffffff166116da83610a56565b73ffffffffffffffffffffffffffffffffffffffff16145b5b90509392505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160361176c575f6040517f64a0ae9200000000000000000000000000000000000000000000000000000000815260040161176391906119fa565b60405180910390fd5b5f61177883835f610aa8565b90505f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16146117ea575f6040517f73c6ac6e0000000000000000000000000000000000000000000000000000000081526004016117e191906119fa565b60405180910390fd5b505050565b5f604051905090565b5f80fd5b5f80fd5b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b61183481611800565b811461183e575f80fd5b50565b5f8135905061184f8161182b565b92915050565b5f6020828403121561186a576118696117f8565b5b5f61187784828501611841565b91505092915050565b5f8115159050919050565b61189481611880565b82525050565b5f6020820190506118ad5f83018461188b565b92915050565b5f81519050919050565b5f82825260208201905092915050565b5f5b838110156118ea5780820151818401526020810190506118cf565b5f8484015250505050565b5f601f19601f8301169050919050565b5f61190f826118b3565b61191981856118bd565b93506119298185602086016118cd565b611932816118f5565b840191505092915050565b5f6020820190508181035f8301526119558184611905565b905092915050565b5f819050919050565b61196f8161195d565b8114611979575f80fd5b50565b5f8135905061198a81611966565b92915050565b5f602082840312156119a5576119a46117f8565b5b5f6119b28482850161197c565b91505092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6119e4826119bb565b9050919050565b6119f4816119da565b82525050565b5f602082019050611a0d5f8301846119eb565b92915050565b611a1c816119da565b8114611a26575f80fd5b50565b5f81359050611a3781611a13565b92915050565b5f8060408385031215611a5357611a526117f8565b5b5f611a6085828601611a29565b9250506020611a718582860161197c565b9150509250929050565b5f805f60608486031215611a9257611a916117f8565b5b5f611a9f86828701611a29565b9350506020611ab086828701611a29565b9250506040611ac18682870161197c565b9150509250925092565b5f60208284031215611ae057611adf6117f8565b5b5f611aed84828501611a29565b91505092915050565b611aff8161195d565b82525050565b5f602082019050611b185f830184611af6565b92915050565b611b2781611880565b8114611b31575f80fd5b50565b5f81359050611b4281611b1e565b92915050565b5f8060408385031215611b5e57611b5d6117f8565b5b5f611b6b85828601611a29565b9250506020611b7c85828601611b34565b9150509250929050565b5f80fd5b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b611bc4826118f5565b810181811067ffffffffffffffff82111715611be357611be2611b8e565b5b80604052505050565b5f611bf56117ef565b9050611c018282611bbb565b919050565b5f67ffffffffffffffff821115611c2057611c1f611b8e565b5b611c29826118f5565b9050602081019050919050565b828183375f83830152505050565b5f611c56611c5184611c06565b611bec565b905082815260208101848484011115611c7257611c71611b8a565b5b611c7d848285611c36565b509392505050565b5f82601f830112611c9957611c98611b86565b5b8135611ca9848260208601611c44565b91505092915050565b5f805f8060808587031215611cca57611cc96117f8565b5b5f611cd787828801611a29565b9450506020611ce887828801611a29565b9350506040611cf98782880161197c565b925050606085013567ffffffffffffffff811115611d1a57611d196117fc565b5b611d2687828801611c85565b91505092959194509250565b5f8060408385031215611d4857611d476117f8565b5b5f611d5585828601611a29565b9250506020611d6685828601611a29565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680611db457607f821691505b602082108103611dc757611dc6611d70565b5b50919050565b5f606082019050611de05f8301866119eb565b611ded6020830185611af6565b611dfa60408301846119eb565b949350505050565b5f81905092915050565b5f611e16826118b3565b611e208185611e02565b9350611e308185602086016118cd565b80840191505092915050565b5f611e478285611e0c565b9150611e538284611e0c565b91508190509392505050565b5f81519050919050565b5f82825260208201905092915050565b5f611e8382611e5f565b611e8d8185611e69565b9350611e9d8185602086016118cd565b611ea6816118f5565b840191505092915050565b5f608082019050611ec45f8301876119eb565b611ed160208301866119eb565b611ede6040830185611af6565b8181036060830152611ef08184611e79565b905095945050505050565b5f81519050611f098161182b565b92915050565b5f60208284031215611f2457611f236117f8565b5b5f611f3184828501611efb565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f604082019050611f7a5f8301856119eb565b611f876020830184611af6565b939250505056fea2646970667358221220432b30673e00c0eb009e1718c271f4cfdfbeded17345829703b06d322360990164736f6c63430008160033").into() } -fn insert_storage(trie: &mut HashedPartialTrie, slot: U256, value: U256) { - let mut bytes = [0; 32]; - slot.to_big_endian(&mut bytes); - let key = keccak(bytes); - let nibbles = Nibbles::from_bytes_be(key.as_bytes()).unwrap(); - let r = rlp::encode(&value); - let r = r.freeze().to_vec(); - trie.insert(nibbles, r); -} - -fn sd2u(s: &str) -> U256 { - U256::from_dec_str(s).unwrap() -} - -fn sh2u(s: &str) -> U256 { - U256::from_str_radix(s, 16).unwrap() -} - fn contract_storage() -> HashedPartialTrie { - let mut trie = HashedPartialTrie::from(Node::Empty); - insert_storage( - &mut trie, - U256::zero(), - sh2u("0x54657374546f6b656e0000000000000000000000000000000000000000000012"), - ); - insert_storage( - &mut trie, - U256::one(), - sh2u("0x5445535400000000000000000000000000000000000000000000000000000008"), - ); - insert_storage( - &mut trie, - sd2u("6"), - sh2u("0x5b38da6a701c568545dcfcb03fcb875f56beddc4"), - ); - insert_storage( - &mut trie, - sh2u("0x343ff8127bd64f680be4e996254dc3528603c6ecd54364b4cf956ebdd28f0028"), - sh2u("0x5b38da6a701c568545dcfcb03fcb875f56beddc4"), - ); - insert_storage( - &mut trie, - sh2u("0x118c1ea466562cb796e30ef705e4db752f5c39d773d22c5efd8d46f67194e78a"), - sd2u("1"), - ); - trie + create_account_storage(&[ + ( + U256::zero(), + sh2u("0x54657374546f6b656e0000000000000000000000000000000000000000000012"), + ), + ( + U256::one(), + sh2u("0x5445535400000000000000000000000000000000000000000000000000000008"), + ), + ( + sd2u("6"), + sh2u("0x5b38da6a701c568545dcfcb03fcb875f56beddc4"), + ), + ( + sh2u("0x343ff8127bd64f680be4e996254dc3528603c6ecd54364b4cf956ebdd28f0028"), + sh2u("0x5b38da6a701c568545dcfcb03fcb875f56beddc4"), + ), + ( + sh2u("0x118c1ea466562cb796e30ef705e4db752f5c39d773d22c5efd8d46f67194e78a"), + sd2u("1"), + ), + ]) } fn contract_storage_after() -> HashedPartialTrie { - let mut trie = HashedPartialTrie::from(Node::Empty); - insert_storage( - &mut trie, - U256::zero(), - sh2u("0x54657374546f6b656e0000000000000000000000000000000000000000000012"), - ); - insert_storage( - &mut trie, - U256::one(), - sh2u("0x5445535400000000000000000000000000000000000000000000000000000008"), - ); - insert_storage( - &mut trie, - sd2u("6"), - sh2u("0x5b38da6a701c568545dcfcb03fcb875f56beddc4"), - ); - insert_storage( - &mut trie, - sh2u("0x343ff8127bd64f680be4e996254dc3528603c6ecd54364b4cf956ebdd28f0028"), - sh2u("0xab8483f64d9c6d1ecf9b849ae677dd3315835cb2"), - ); - insert_storage( - &mut trie, - sh2u("0xf3aa6a8a9f7e3707e36cc99c499a27514922afe861ec3d80a1a314409cba92f9"), - sd2u("1"), - ); - trie + create_account_storage(&[ + ( + U256::zero(), + sh2u("0x54657374546f6b656e0000000000000000000000000000000000000000000012"), + ), + ( + U256::one(), + sh2u("0x5445535400000000000000000000000000000000000000000000000000000008"), + ), + ( + sd2u("6"), + sh2u("0x5b38da6a701c568545dcfcb03fcb875f56beddc4"), + ), + ( + sh2u("0x343ff8127bd64f680be4e996254dc3528603c6ecd54364b4cf956ebdd28f0028"), + sh2u("0xab8483f64d9c6d1ecf9b849ae677dd3315835cb2"), + ), + ( + sh2u("0xf3aa6a8a9f7e3707e36cc99c499a27514922afe861ec3d80a1a314409cba92f9"), + sd2u("1"), + ), + ]) } fn owner_account() -> AccountRlp { diff --git a/evm_arithmetization/tests/log_opcode.rs b/evm_arithmetization/tests/log_opcode.rs index c7326ef00..5d3735a6b 100644 --- a/evm_arithmetization/tests/log_opcode.rs +++ b/evm_arithmetization/tests/log_opcode.rs @@ -3,7 +3,6 @@ use std::str::FromStr; use std::time::Duration; use bytes::Bytes; -use env_logger::{try_init_from_env, Env, DEFAULT_FILTER_ENV}; use ethereum_types::{Address, BigEndianHash, H256, U256}; use evm_arithmetization::generation::mpt::transaction_testing::{ AddressOption, LegacyTransactionRlp, @@ -12,6 +11,11 @@ use evm_arithmetization::generation::mpt::{AccountRlp, LegacyReceiptRlp, LogRlp} use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::prove; +use evm_arithmetization::testing_utils::{ + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, init_logger, + initial_state_and_storage_tries_with_beacon_roots, update_beacon_roots_account_storage, + BEACON_ROOTS_CONTRACT_ADDRESS_HASHED, +}; use evm_arithmetization::verifier::verify_proof; use evm_arithmetization::{AllRecursiveCircuits, AllStark, Node, StarkConfig}; use hex_literal::hex; @@ -84,7 +88,9 @@ fn test_log_opcodes() -> anyhow::Result<()> { }; // Initialize the state trie with three accounts. - let mut state_trie_before = HashedPartialTrie::from(Node::Empty); + let (mut state_trie_before, mut storage_tries) = + initial_state_and_storage_tries_with_beacon_roots(); + let mut beacon_roots_account_storage = storage_tries[0].1.clone(); state_trie_before.insert( beneficiary_nibbles, rlp::encode(&beneficiary_account_before).to_vec(), @@ -92,6 +98,8 @@ fn test_log_opcodes() -> anyhow::Result<()> { state_trie_before.insert(sender_nibbles, rlp::encode(&sender_account_before).to_vec()); state_trie_before.insert(to_nibbles, rlp::encode(&to_account_before).to_vec()); + storage_tries.push((to_hashed, Node::Empty.into())); + // We now add two receipts with logs and data. This updates the receipt trie as // well. let log_0 = LogRlp { @@ -125,7 +133,7 @@ fn test_log_opcodes() -> anyhow::Result<()> { state_trie: state_trie_before, transactions_trie: Node::Empty.into(), receipts_trie: receipts_trie.clone(), - storage_tries: vec![(to_hashed, Node::Empty.into())], + storage_tries, }; // Prove a transaction which carries out two LOG opcodes. @@ -141,9 +149,8 @@ fn test_log_opcodes() -> anyhow::Result<()> { block_gaslimit: 0xffffffffu32.into(), block_chain_id: 1.into(), block_base_fee: 0xa.into(), - block_gas_used: 0.into(), block_blob_base_fee: 0x2.into(), - block_bloom: [0.into(); 8], + ..Default::default() }; let mut contract_code = HashMap::new(); @@ -169,6 +176,13 @@ fn test_log_opcodes() -> anyhow::Result<()> { ..AccountRlp::default() }; + update_beacon_roots_account_storage( + &mut beacon_roots_account_storage, + block_metadata.block_timestamp, + block_metadata.parent_beacon_block_root, + ); + let beacon_roots_account = beacon_roots_contract_from_storage(&beacon_roots_account_storage); + // Update the receipt trie. let first_log = LogRlp { address: to.into(), @@ -204,6 +218,10 @@ fn test_log_opcodes() -> anyhow::Result<()> { ); expected_state_trie_after.insert(sender_nibbles, rlp::encode(&sender_account_after).to_vec()); expected_state_trie_after.insert(to_nibbles, rlp::encode(&to_account_after).to_vec()); + expected_state_trie_after.insert( + beacon_roots_account_nibbles(), + rlp::encode(&beacon_roots_account).to_vec(), + ); let transactions_trie: HashedPartialTrie = Node::Leaf { nibbles: Nibbles::from_str("0x80").unwrap(), @@ -316,7 +334,9 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { // `to_account`. let gas_price = 10; let txn_value = 0xau64; - let mut state_trie_before = HashedPartialTrie::from(Node::Empty); + let (mut state_trie_before, storage_tries) = + initial_state_and_storage_tries_with_beacon_roots(); + let mut beacon_roots_account_storage = storage_tries[0].1.clone(); state_trie_before.insert( beneficiary_nibbles, rlp::encode(&beneficiary_account_before).to_vec(), @@ -333,7 +353,7 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { state_trie: state_trie_before, transactions_trie: Node::Empty.into(), receipts_trie: Node::Empty.into(), - storage_tries: vec![], + storage_tries, }; let txn = hex!("f85f800a82520894095e7baea6a6c7c4c2dfeb977efac326af552d870a8026a0122f370ed4023a6c253350c6bfb87d7d7eb2cd86447befee99e0a26b70baec20a07100ab1b3977f2b4571202b9f4b68850858caf5469222794600b5ce1cfb348ad"); @@ -364,7 +384,7 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { .unwrap(), U256::from_dec_str("2722259584404615024560450425766186844160").unwrap(), ], - block_random: Default::default(), + ..Default::default() }; let beneficiary_account_after = AccountRlp { @@ -383,6 +403,13 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { ..AccountRlp::default() }; + update_beacon_roots_account_storage( + &mut beacon_roots_account_storage, + block_1_metadata.block_timestamp, + block_1_metadata.parent_beacon_block_root, + ); + let beacon_roots_account = beacon_roots_contract_from_storage(&beacon_roots_account_storage); + let mut contract_code = HashMap::new(); contract_code.insert(keccak(vec![]), vec![]); contract_code.insert(code_hash, code.to_vec()); @@ -398,6 +425,10 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { to_second_nibbles, rlp::encode(&to_account_second_before).to_vec(), ); + expected_state_trie_after.insert( + beacon_roots_account_nibbles(), + rlp::encode(&beacon_roots_account).to_vec(), + ); // Compute new receipt trie. let mut receipts_trie = HashedPartialTrie::from(Node::Empty); @@ -547,6 +578,14 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { rlp::encode(&to_account_second_after).to_vec(), ); + // Copy without the beacon roots account for the next block. + let mut state_trie_after_block2 = expected_state_trie_after.clone(); + + expected_state_trie_after.insert( + beacon_roots_account_nibbles(), + rlp::encode(&beacon_roots_account).to_vec(), + ); + transactions_trie.insert(Nibbles::from_str("0x01").unwrap(), txn_2.to_vec()); let block_1_state_root = expected_state_trie_after.hash(); @@ -602,7 +641,7 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { let block_2_metadata = BlockMetadata { block_beneficiary: Address::from(beneficiary), - block_timestamp: 0x03e8.into(), + block_timestamp: 0x03e9.into(), block_number: 2.into(), block_difficulty: 0x020000.into(), block_gaslimit: 0x445566u32.into(), @@ -614,6 +653,19 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { let mut contract_code = HashMap::new(); contract_code.insert(keccak(vec![]), vec![]); + let initial_beacon_roots_account_storage = beacon_roots_account_storage.clone(); + update_beacon_roots_account_storage( + &mut beacon_roots_account_storage, + block_2_metadata.block_timestamp, + block_2_metadata.parent_beacon_block_root, + ); + let beacon_roots_account = beacon_roots_contract_from_storage(&beacon_roots_account_storage); + + state_trie_after_block2.insert( + beacon_roots_account_nibbles(), + rlp::encode(&beacon_roots_account).to_vec(), + ); + let inputs = GenerationInputs { signed_txn: None, withdrawals: vec![], @@ -621,10 +673,13 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { state_trie: expected_state_trie_after, transactions_trie: Node::Empty.into(), receipts_trie: Node::Empty.into(), - storage_tries: vec![], + storage_tries: vec![( + H256(BEACON_ROOTS_CONTRACT_ADDRESS_HASHED), + initial_beacon_roots_account_storage, + )], }, trie_roots_after: TrieRoots { - state_root: trie_roots_after.state_root, + state_root: state_trie_after_block2.hash(), transactions_root: HashedPartialTrie::from(Node::Empty).hash(), receipts_root: HashedPartialTrie::from(Node::Empty).hash(), }, @@ -639,19 +694,31 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { cur_hash: block_2_hash, }, }; + let mut inputs2 = inputs.clone(); let (root_proof, public_values) = all_circuits.prove_root(&all_stark, &config, inputs, &mut timing, None)?; all_circuits.verify_root(root_proof.clone())?; - // We can just duplicate the initial proof as the state didn't change. + // We cannot duplicate the proof here because even though there weren't any + // transactions, the state has mutated when updating the beacon roots contract. + inputs2.tries.storage_tries = vec![( + H256(BEACON_ROOTS_CONTRACT_ADDRESS_HASHED), + beacon_roots_account_storage, + )]; + inputs2.tries.state_trie = state_trie_after_block2; + + let (root_proof2, public_values2) = + all_circuits.prove_root(&all_stark, &config, inputs2, &mut timing, None)?; + all_circuits.verify_root(root_proof2.clone())?; + let (agg_proof, updated_agg_public_values) = all_circuits.prove_aggregation( false, &root_proof, public_values.clone(), false, - &root_proof, - public_values, + &root_proof2, + public_values2, )?; all_circuits.verify_aggregation(&agg_proof)?; @@ -777,7 +844,3 @@ fn test_txn_and_receipt_trie_hash() -> anyhow::Result<()> { Ok(()) } - -fn init_logger() { - let _ = try_init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info")); -} diff --git a/evm_arithmetization/tests/self_balance_gas_cost.rs b/evm_arithmetization/tests/self_balance_gas_cost.rs index 40609a140..292c5f95e 100644 --- a/evm_arithmetization/tests/self_balance_gas_cost.rs +++ b/evm_arithmetization/tests/self_balance_gas_cost.rs @@ -2,12 +2,15 @@ use std::collections::HashMap; use std::str::FromStr; use std::time::Duration; -use env_logger::{try_init_from_env, Env, DEFAULT_FILTER_ENV}; use ethereum_types::{Address, H256, U256}; use evm_arithmetization::generation::mpt::{AccountRlp, LegacyReceiptRlp}; use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::prove; +use evm_arithmetization::testing_utils::{ + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, init_logger, + initial_state_and_storage_tries_with_beacon_roots, update_beacon_roots_account_storage, +}; use evm_arithmetization::verifier::verify_proof; use evm_arithmetization::{AllStark, Node, StarkConfig}; use hex_literal::hex; @@ -73,7 +76,9 @@ fn self_balance_gas_cost() -> anyhow::Result<()> { ..AccountRlp::default() }; - let mut state_trie_before = HashedPartialTrie::from(Node::Empty); + let (mut state_trie_before, mut storage_tries) = + initial_state_and_storage_tries_with_beacon_roots(); + let mut beacon_roots_account_storage = storage_tries[0].1.clone(); state_trie_before.insert( beneficiary_nibbles, rlp::encode(&beneficiary_account_before).to_vec(), @@ -81,11 +86,13 @@ fn self_balance_gas_cost() -> anyhow::Result<()> { state_trie_before.insert(sender_nibbles, rlp::encode(&sender_account_before).to_vec()); state_trie_before.insert(to_nibbles, rlp::encode(&to_account_before).to_vec()); + storage_tries.push((to_hashed, Node::Empty.into())); + let tries_before = TrieInputs { state_trie: state_trie_before, transactions_trie: Node::Empty.into(), receipts_trie: Node::Empty.into(), - storage_tries: vec![(to_hashed, Node::Empty.into())], + storage_tries, }; let txn = hex!("f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509b"); @@ -100,10 +107,9 @@ fn self_balance_gas_cost() -> anyhow::Result<()> { block_timestamp: 0x03e8.into(), block_gaslimit: 0xff112233u32.into(), block_gas_used: gas_used.into(), - block_bloom: [0.into(); 8], block_base_fee: 0xa.into(), block_blob_base_fee: 0x2.into(), - block_random: Default::default(), + ..Default::default() }; let mut contract_code = HashMap::new(); @@ -135,6 +141,14 @@ fn self_balance_gas_cost() -> anyhow::Result<()> { ..AccountRlp::default() }; + update_beacon_roots_account_storage( + &mut beacon_roots_account_storage, + block_metadata.block_timestamp, + block_metadata.parent_beacon_block_root, + ); + let beacon_roots_account = + beacon_roots_contract_from_storage(&beacon_roots_account_storage); + let mut expected_state_trie_after = HashedPartialTrie::from(Node::Empty); expected_state_trie_after.insert( beneficiary_nibbles, @@ -143,6 +157,10 @@ fn self_balance_gas_cost() -> anyhow::Result<()> { expected_state_trie_after .insert(sender_nibbles, rlp::encode(&sender_account_after).to_vec()); expected_state_trie_after.insert(to_nibbles, rlp::encode(&to_account_after).to_vec()); + expected_state_trie_after.insert( + beacon_roots_account_nibbles(), + rlp::encode(&beacon_roots_account).to_vec(), + ); expected_state_trie_after }; @@ -191,7 +209,3 @@ fn self_balance_gas_cost() -> anyhow::Result<()> { verify_proof(&all_stark, proof, &config) } - -fn init_logger() { - let _ = try_init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info")); -} diff --git a/evm_arithmetization/tests/selfdestruct.rs b/evm_arithmetization/tests/selfdestruct.rs index ebf01c8da..921852551 100644 --- a/evm_arithmetization/tests/selfdestruct.rs +++ b/evm_arithmetization/tests/selfdestruct.rs @@ -1,12 +1,15 @@ use std::str::FromStr; use std::time::Duration; -use env_logger::{try_init_from_env, Env, DEFAULT_FILTER_ENV}; -use ethereum_types::{Address, BigEndianHash, H256, U256}; +use ethereum_types::{Address, BigEndianHash, H256}; use evm_arithmetization::generation::mpt::{AccountRlp, LegacyReceiptRlp}; use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::prove; +use evm_arithmetization::testing_utils::{ + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, eth_to_wei, init_logger, + initial_state_and_storage_tries_with_beacon_roots, update_beacon_roots_account_storage, +}; use evm_arithmetization::verifier::verify_proof; use evm_arithmetization::{AllStark, Node, StarkConfig}; use hex_literal::hex; @@ -56,7 +59,9 @@ fn test_selfdestruct() -> anyhow::Result<()> { code_hash: keccak(&code), }; - let mut state_trie_before = HashedPartialTrie::from(Node::Empty); + let (mut state_trie_before, storage_tries) = + initial_state_and_storage_tries_with_beacon_roots(); + let mut beacon_roots_account_storage = storage_tries[0].1.clone(); state_trie_before.insert(sender_nibbles, rlp::encode(&sender_account_before).to_vec()); state_trie_before.insert(to_nibbles, rlp::encode(&to_account_before).to_vec()); @@ -64,7 +69,7 @@ fn test_selfdestruct() -> anyhow::Result<()> { state_trie: state_trie_before, transactions_trie: HashedPartialTrie::from(Node::Empty), receipts_trie: HashedPartialTrie::from(Node::Empty), - storage_tries: vec![], + storage_tries, }; // Generated using a little py-evm script. @@ -81,13 +86,22 @@ fn test_selfdestruct() -> anyhow::Result<()> { block_base_fee: 0xa.into(), block_gas_used: 26002.into(), block_blob_base_fee: 0x2.into(), - block_bloom: [0.into(); 8], + ..Default::default() }; let contract_code = [(keccak(&code), code.clone()), (keccak([]), vec![])].into(); let expected_state_trie_after: HashedPartialTrie = { let mut state_trie_after = HashedPartialTrie::from(Node::Empty); + + update_beacon_roots_account_storage( + &mut beacon_roots_account_storage, + block_metadata.block_timestamp, + block_metadata.parent_beacon_block_root, + ); + let beacon_roots_account = + beacon_roots_contract_from_storage(&beacon_roots_account_storage); + let sender_account_after = AccountRlp { nonce: 6.into(), balance: eth_to_wei(110_000.into()) - 26_002 * 0xa, @@ -105,6 +119,11 @@ fn test_selfdestruct() -> anyhow::Result<()> { code_hash: keccak(&code), }; state_trie_after.insert(to_nibbles, rlp::encode(&to_account_before).to_vec()); + state_trie_after.insert( + beacon_roots_account_nibbles(), + rlp::encode(&beacon_roots_account).to_vec(), + ); + state_trie_after }; @@ -153,12 +172,3 @@ fn test_selfdestruct() -> anyhow::Result<()> { verify_proof(&all_stark, proof, &config) } - -fn eth_to_wei(eth: U256) -> U256 { - // 1 ether = 10^18 wei. - eth * U256::from(10).pow(18.into()) -} - -fn init_logger() { - let _ = try_init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info")); -} diff --git a/evm_arithmetization/tests/simple_transfer.rs b/evm_arithmetization/tests/simple_transfer.rs index 08928a076..812677516 100644 --- a/evm_arithmetization/tests/simple_transfer.rs +++ b/evm_arithmetization/tests/simple_transfer.rs @@ -2,12 +2,15 @@ use std::collections::HashMap; use std::str::FromStr; use std::time::Duration; -use env_logger::{try_init_from_env, Env, DEFAULT_FILTER_ENV}; use ethereum_types::{Address, BigEndianHash, H256, U256}; use evm_arithmetization::generation::mpt::{AccountRlp, LegacyReceiptRlp}; use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::prove; +use evm_arithmetization::testing_utils::{ + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, eth_to_wei, init_logger, + initial_state_and_storage_tries_with_beacon_roots, update_beacon_roots_account_storage, +}; use evm_arithmetization::verifier::verify_proof; use evm_arithmetization::{AllStark, Node, StarkConfig}; use hex_literal::hex; @@ -48,17 +51,16 @@ fn test_simple_transfer() -> anyhow::Result<()> { }; let to_account_before = AccountRlp::default(); - let state_trie_before = Node::Leaf { - nibbles: sender_nibbles, - value: rlp::encode(&sender_account_before).to_vec(), - } - .into(); + let (mut state_trie_before, storage_tries) = + initial_state_and_storage_tries_with_beacon_roots(); + let mut beacon_roots_account_storage = storage_tries[0].1.clone(); + state_trie_before.insert(sender_nibbles, rlp::encode(&sender_account_before).to_vec()); let tries_before = TrieInputs { state_trie: state_trie_before, transactions_trie: HashedPartialTrie::from(Node::Empty), receipts_trie: HashedPartialTrie::from(Node::Empty), - storage_tries: vec![], + storage_tries, }; // Generated using a little py-evm script. @@ -76,7 +78,7 @@ fn test_simple_transfer() -> anyhow::Result<()> { block_base_fee: 0xa.into(), block_gas_used: 21032.into(), block_blob_base_fee: 0x2.into(), - block_bloom: [0.into(); 8], + ..Default::default() }; let mut contract_code = HashMap::new(); @@ -86,6 +88,14 @@ fn test_simple_transfer() -> anyhow::Result<()> { let txdata_gas = 2 * 16; let gas_used = 21_000 + txdata_gas; + update_beacon_roots_account_storage( + &mut beacon_roots_account_storage, + block_metadata.block_timestamp, + block_metadata.parent_beacon_block_root, + ); + let beacon_roots_account = + beacon_roots_contract_from_storage(&beacon_roots_account_storage); + let sender_account_after = AccountRlp { balance: sender_account_before.balance - value - gas_used * 10, nonce: sender_account_before.nonce + 1, @@ -107,6 +117,12 @@ fn test_simple_transfer() -> anyhow::Result<()> { value: rlp::encode(&to_account_after).to_vec(), } .into(); + children[beacon_roots_account_nibbles().get_nibble(0) as usize] = Node::Leaf { + nibbles: beacon_roots_account_nibbles().truncate_n_nibbles_front(1), + value: rlp::encode(&beacon_roots_account).to_vec(), + } + .into(); + Node::Branch { children, value: vec![], @@ -159,12 +175,3 @@ fn test_simple_transfer() -> anyhow::Result<()> { verify_proof(&all_stark, proof, &config) } - -fn eth_to_wei(eth: U256) -> U256 { - // 1 ether = 10^18 wei. - eth * U256::from(10).pow(18.into()) -} - -fn init_logger() { - let _ = try_init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info")); -} diff --git a/evm_arithmetization/tests/withdrawals.rs b/evm_arithmetization/tests/withdrawals.rs index 28dc6da59..56742be03 100644 --- a/evm_arithmetization/tests/withdrawals.rs +++ b/evm_arithmetization/tests/withdrawals.rs @@ -1,12 +1,15 @@ use std::collections::HashMap; use std::time::Duration; -use env_logger::{try_init_from_env, Env, DEFAULT_FILTER_ENV}; use ethereum_types::{H160, H256, U256}; use evm_arithmetization::generation::mpt::AccountRlp; use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::prove; +use evm_arithmetization::testing_utils::{ + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, init_logger, + initial_state_and_storage_tries_with_beacon_roots, update_beacon_roots_account_storage, +}; use evm_arithmetization::verifier::verify_proof; use evm_arithmetization::{AllStark, Node, StarkConfig}; use keccak_hash::keccak; @@ -31,10 +34,10 @@ fn test_withdrawals() -> anyhow::Result<()> { let block_metadata = BlockMetadata::default(); - let state_trie_before = HashedPartialTrie::from(Node::Empty); + let (state_trie_before, storage_tries) = initial_state_and_storage_tries_with_beacon_roots(); + let mut beacon_roots_account_storage = storage_tries[0].1.clone(); let transactions_trie = HashedPartialTrie::from(Node::Empty); let receipts_trie = HashedPartialTrie::from(Node::Empty); - let storage_tries = vec![]; let mut contract_code = HashMap::new(); contract_code.insert(keccak(vec![]), vec![]); @@ -44,6 +47,14 @@ fn test_withdrawals() -> anyhow::Result<()> { let state_trie_after = { let mut trie = HashedPartialTrie::from(Node::Empty); + update_beacon_roots_account_storage( + &mut beacon_roots_account_storage, + 0.into(), + block_metadata.parent_beacon_block_root, + ); + let beacon_roots_account = + beacon_roots_contract_from_storage(&beacon_roots_account_storage); + let addr_state_key = keccak(withdrawals[0].0); let addr_nibbles = Nibbles::from_bytes_be(addr_state_key.as_bytes()).unwrap(); let account = AccountRlp { @@ -51,6 +62,11 @@ fn test_withdrawals() -> anyhow::Result<()> { ..AccountRlp::default() }; trie.insert(addr_nibbles, rlp::encode(&account).to_vec()); + trie.insert( + beacon_roots_account_nibbles(), + rlp::encode(&beacon_roots_account).to_vec(), + ); + trie }; @@ -88,7 +104,3 @@ fn test_withdrawals() -> anyhow::Result<()> { verify_proof(&all_stark, proof, &config) } - -fn init_logger() { - let _ = try_init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info")); -} From 8926efdceba8c81ab90187767f96f2937d1f07e4 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Wed, 21 Feb 2024 13:02:59 -0500 Subject: [PATCH 03/40] Implement Blob transactions (type-3) and BLOBHASH opcode (#50) * Implement Blob transactions * Add new header fields blob_gas_used and excess_blob_gas * Add missing 'BlockBlobBaseFee' in interpreter initialization * Implement BLOBHASH opcode * Add BlobTransactionRlp for the parsing phase of the test runner * Apply review * Reduce BytePacking overhead for txn routing * Update BlobTransactionRlp as per EIP specification * Add tests --- .../src/cpu/kernel/aggregator.rs | 1 + .../src/cpu/kernel/asm/core/exception.asm | 6 +- .../src/cpu/kernel/asm/core/syscall.asm | 2 +- .../src/cpu/kernel/asm/memory/metadata.asm | 33 ++++ .../asm/transactions/common_decoding.asm | 52 ++++++ .../cpu/kernel/asm/transactions/router.asm | 22 ++- .../cpu/kernel/asm/transactions/type_0.asm | 2 - .../cpu/kernel/asm/transactions/type_1.asm | 6 +- .../cpu/kernel/asm/transactions/type_2.asm | 6 +- .../cpu/kernel/asm/transactions/type_3.asm | 157 ++++++++++++++++++ .../src/cpu/kernel/constants/exc_bitfields.rs | 2 +- .../cpu/kernel/constants/global_metadata.rs | 27 ++- .../src/cpu/kernel/constants/mod.rs | 3 +- .../src/cpu/kernel/constants/txn_fields.rs | 5 +- .../src/cpu/kernel/interpreter.rs | 13 ++ .../src/cpu/kernel/tests/blobhash.rs | 85 ++++++++++ .../src/cpu/kernel/tests/mod.rs | 1 + .../transaction_parsing/parse_type_0_txn.rs | 4 +- evm_arithmetization/src/generation/mod.rs | 8 + evm_arithmetization/src/generation/mpt.rs | 21 +++ evm_arithmetization/src/get_challenges.rs | 8 + evm_arithmetization/src/memory/segments.rs | 7 +- evm_arithmetization/src/proof.rs | 60 ++++++- evm_arithmetization/src/recursive_verifier.rs | 39 ++++- evm_arithmetization/src/verifier.rs | 16 ++ evm_arithmetization/src/witness/operation.rs | 3 +- evm_arithmetization/src/witness/transition.rs | 1 + 27 files changed, 548 insertions(+), 42 deletions(-) create mode 100644 evm_arithmetization/src/cpu/kernel/asm/transactions/type_3.asm create mode 100644 evm_arithmetization/src/cpu/kernel/tests/blobhash.rs diff --git a/evm_arithmetization/src/cpu/kernel/aggregator.rs b/evm_arithmetization/src/cpu/kernel/aggregator.rs index b3b30ba93..899d30c48 100644 --- a/evm_arithmetization/src/cpu/kernel/aggregator.rs +++ b/evm_arithmetization/src/cpu/kernel/aggregator.rs @@ -154,6 +154,7 @@ pub(crate) fn combined_kernel() -> Kernel { include_str!("asm/transactions/type_0.asm"), include_str!("asm/transactions/type_1.asm"), include_str!("asm/transactions/type_2.asm"), + include_str!("asm/transactions/type_3.asm"), include_str!("asm/util/assertions.asm"), include_str!("asm/util/basic_macros.asm"), include_str!("asm/util/keccak.asm"), diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/exception.asm b/evm_arithmetization/src/cpu/kernel/asm/core/exception.asm index 80bf7dbdf..d8531ca14 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/exception.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/exception.asm @@ -236,7 +236,7 @@ min_stack_len_for_opcode: BYTES 0 // 0x46, CHAINID BYTES 0 // 0x47, SELFBALANCE BYTES 0 // 0x48, BASEFEE - BYTES 0 // 0x49, invalid + BYTES 1 // 0x49, BLOBHASH BYTES 0 // 0x4a, BLOBBASEFEE %rep 5 // 0x4b-0x4f, invalid BYTES 0 @@ -371,11 +371,11 @@ gas_cost_for_opcode: BYTES 0 %endrep - %rep 25 //0x30-0x48, only syscalls + %rep 26 //0x30-0x49, only syscalls BYTES 0 %endrep - %rep 7 // 0x49-0x4f, invalid + %rep 6 // 0x4a-0x4f, invalid BYTES 0 %endrep diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/syscall.asm b/evm_arithmetization/src/cpu/kernel/asm/core/syscall.asm index 673a5fbb9..6a42c5070 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/syscall.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/syscall.asm @@ -69,7 +69,7 @@ global syscall_jumptable: JUMPTABLE sys_chainid JUMPTABLE sys_selfbalance JUMPTABLE sys_basefee - JUMPTABLE panic // 0x49 is invalid + JUMPTABLE sys_blobhash JUMPTABLE sys_blobbasefee %rep 5 JUMPTABLE panic // 0x4b-0x4f are invalid opcodes diff --git a/evm_arithmetization/src/cpu/kernel/asm/memory/metadata.asm b/evm_arithmetization/src/cpu/kernel/asm/memory/metadata.asm index 8a7867118..cded168d3 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/memory/metadata.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/memory/metadata.asm @@ -281,6 +281,39 @@ global sys_basefee: SWAP1 EXIT_KERNEL +global sys_blobhash: + // stack: kexit_info, index + %charge_gas_const(@GAS_HASH_OPCODE) + // stack: kexit_info, index + %blobhash + // stack: blobhash, kexit_info + SWAP1 + EXIT_KERNEL + +%macro blobhash + // stack: kexit_info, index + SWAP1 + // stack: index, kexit_info + %mload_global_metadata(@GLOBAL_METADATA_BLOB_VERSIONED_HASHES_LEN) + DUP2 + LT ISZERO // == GE + // stack: index >= len, index, kexit_info + %jumpi(%%index_too_big) + PUSH @SEGMENT_TXN_BLOB_VERSIONED_HASHES + %build_kernel_address + // stack: read_addr, kexit_info + MLOAD_GENERAL + %jump(%%end) +%%index_too_big: + // The index is larger than the list, just push 0. + // stack: index, kexit_info + POP + PUSH 0 + // stack: 0, kexit_info +%%end: + // stack: blobhash, kexit_info +%endmacro + global sys_blobbasefee: // stack: kexit_info %charge_gas_const(@GAS_BASE) diff --git a/evm_arithmetization/src/cpu/kernel/asm/transactions/common_decoding.asm b/evm_arithmetization/src/cpu/kernel/asm/transactions/common_decoding.asm index 4a8feccaa..6e613e0df 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/transactions/common_decoding.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/transactions/common_decoding.asm @@ -133,6 +133,58 @@ %%after: %endmacro +%macro decode_and_store_max_fee_per_blob_gas + // stack: rlp_addr + %decode_rlp_scalar + %stack (rlp_addr, max_fee_per_blob_gas) -> (max_fee_per_blob_gas, rlp_addr) + %mstore_txn_field(@TXN_FIELD_MAX_FEE_PER_BLOB_GAS) + // stack: rlp_addr +%endmacro + +%macro decode_and_store_blob_versioned_hashes + // stack: rlp_addr + DUP1 %mstore_global_metadata(@GLOBAL_METADATA_BLOB_VERSIONED_HASHES_RLP_START) + %decode_rlp_list_len + %stack (rlp_addr, len) -> (len, len, rlp_addr, %%after) + %jumpi(decode_and_store_blob_versioned_hashes) + // stack: len, rlp_addr, %%after + POP SWAP1 POP + // stack: rlp_addr + %mload_global_metadata(@GLOBAL_METADATA_BLOB_VERSIONED_HASHES_RLP_START) DUP2 SUB %mstore_global_metadata(@GLOBAL_METADATA_BLOB_VERSIONED_HASHES_RLP_LEN) +%%after: +%endmacro + +// The blob versioned hashes are just a list of hashes. +global decode_and_store_blob_versioned_hashes: + // stack: len, rlp_addr + // Store the list length + DUP1 %mstore_global_metadata(@GLOBAL_METADATA_BLOB_VERSIONED_HASHES_LEN) + + // stack: len, rlp_addr + DUP2 ADD + // stack: end_rlp_addr, rlp_addr + // Store the RLP length. + %mload_global_metadata(@GLOBAL_METADATA_BLOB_VERSIONED_HASHES_RLP_START) DUP2 SUB %mstore_global_metadata(@GLOBAL_METADATA_BLOB_VERSIONED_HASHES_RLP_LEN) + // stack: end_rlp_addr, rlp_addr + PUSH @SEGMENT_TXN_BLOB_VERSIONED_HASHES // initial address to write to + SWAP2 +decode_and_store_blob_versioned_hashes_loop: + // stack: rlp_addr, end_rlp_addr, store_addr + DUP2 DUP2 EQ %jumpi(decode_and_store_blob_versioned_hashes_finish) + // stack: rlp_addr, end_rlp_addr, store_addr + %decode_rlp_scalar // blob_versioned_hashes[i] + // stack: rlp_addr, hash, end_rlp_addr, store_addr + SWAP3 DUP1 SWAP2 + // stack: hash, store_addr, store_addr, end_rlp_addr, rlp_addr + MSTORE_GENERAL + // stack: store_addr, end_rlp_addr, rlp_addr + %increment SWAP2 + // stack: rlp_addr, end_rlp_addr, store_addr' + %jump(decode_and_store_blob_versioned_hashes_loop) +decode_and_store_blob_versioned_hashes_finish: + %stack (rlp_addr, end_rlp_addr, store_addr, retdest) -> (retdest, rlp_addr) + JUMP + %macro decode_and_store_y_parity // stack: rlp_addr %decode_rlp_scalar diff --git a/evm_arithmetization/src/cpu/kernel/asm/transactions/router.asm b/evm_arithmetization/src/cpu/kernel/asm/transactions/router.asm index edabfbc43..a46ace5cd 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/transactions/router.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/transactions/router.asm @@ -14,25 +14,33 @@ read_txn_from_memory: // stack: retdest // We will peak at the first byte to determine what type of transaction this is. - // Note that type 1 and 2 transactions have a first byte of 1 and 2, respectively. + // Note that type 1, 2 and 3 transactions have a first byte of 1, 2 and 3, respectively. // Type 0 (legacy) transactions have no such prefix, but their RLP will have a // first byte >= 0xc0, so there is no overlap. PUSH @SEGMENT_RLP_RAW // ctx == virt == 0 + DUP1 MLOAD_GENERAL %eq_const(1) - // stack: first_byte == 1, retdest + // stack: first_byte == 1, rlp_segment, retdest %jumpi(process_type_1_txn) - // stack: retdest + // stack: rlp_segment, retdest - PUSH @SEGMENT_RLP_RAW // ctx == virt == 0 + DUP1 MLOAD_GENERAL %eq_const(2) - // stack: first_byte == 2, retdest + // stack: first_byte == 2, rlp_segment, retdest %jumpi(process_type_2_txn) - // stack: retdest + // stack: rlp_segment, retdest + + DUP1 + MLOAD_GENERAL + %eq_const(3) + // stack: first_byte == 3, rlp_segment, retdest + %jumpi(process_type_3_txn) + // stack: rlp_segment, retdest - // At this point, since it's not a type 1 or 2 transaction, + // At this point, since it's not a type 1, 2 or 3 transaction, // it must be a legacy (aka type 0) transaction. %jump(process_type_0_txn) diff --git a/evm_arithmetization/src/cpu/kernel/asm/transactions/type_0.asm b/evm_arithmetization/src/cpu/kernel/asm/transactions/type_0.asm index a3f3bb0d2..d0d68b57d 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/transactions/type_0.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/transactions/type_0.asm @@ -12,8 +12,6 @@ // keccak256(rlp([nonce, gas_price, gas_limit, to, value, data])) global process_type_0_txn: - // stack: retdest - PUSH @SEGMENT_RLP_RAW // ctx == virt == 0 // stack: rlp_addr, retdest %decode_rlp_list_len // We don't actually need the length. diff --git a/evm_arithmetization/src/cpu/kernel/asm/transactions/type_1.asm b/evm_arithmetization/src/cpu/kernel/asm/transactions/type_1.asm index e64a4aee0..3b386ce6f 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/transactions/type_1.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/transactions/type_1.asm @@ -7,11 +7,9 @@ // data, access_list])) global process_type_1_txn: - // stack: retdest + // stack: rlp_addr, retdest // Initial rlp address offset of 1 (skipping over the 0x01 byte) - PUSH 1 - PUSH @SEGMENT_RLP_RAW - %build_kernel_address + %add_const(1) // stack: rlp_addr, retdest %decode_rlp_list_len // We don't actually need the length. diff --git a/evm_arithmetization/src/cpu/kernel/asm/transactions/type_2.asm b/evm_arithmetization/src/cpu/kernel/asm/transactions/type_2.asm index 5074c5795..150ebac7e 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/transactions/type_2.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/transactions/type_2.asm @@ -8,11 +8,9 @@ // access_list])) global process_type_2_txn: - // stack: retdest + // stack: rlp_addr, retdest // Initial rlp address offset of 1 (skipping over the 0x02 byte) - PUSH 1 - PUSH @SEGMENT_RLP_RAW - %build_kernel_address + %add_const(1) // stack: rlp_addr, retdest %decode_rlp_list_len // We don't actually need the length. diff --git a/evm_arithmetization/src/cpu/kernel/asm/transactions/type_3.asm b/evm_arithmetization/src/cpu/kernel/asm/transactions/type_3.asm new file mode 100644 index 000000000..ba01368d6 --- /dev/null +++ b/evm_arithmetization/src/cpu/kernel/asm/transactions/type_3.asm @@ -0,0 +1,157 @@ +// Type 3 transactions, introduced by EIP 4844, have the format +// 0x03 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value, +// data, access_list, max_fee_per_blob_gas, blob_versioned_hashes, y_parity, r, s]) +// +// The signed data is +// keccak256(0x03 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, +// to, value, data, access_list, max_fee_per_blob_gas, blob_versioned_hashes])) + +global process_type_3_txn: + // stack: rlp_addr, retdest + // Initial rlp address offset of 1 (skipping over the 0x03 byte) + %add_const(1) + // stack: rlp_addr, retdest + %decode_rlp_list_len + // We don't actually need the length. + %stack (rlp_addr, len) -> (rlp_addr) + + // stack: rlp_addr, retdest + %store_chain_id_present_true + %decode_and_store_chain_id + %decode_and_store_nonce + %decode_and_store_max_priority_fee + %decode_and_store_max_fee + %decode_and_store_gas_limit + %decode_and_store_to + %decode_and_store_value + %decode_and_store_data + %decode_and_store_access_list + %decode_and_store_max_fee_per_blob_gas + %decode_and_store_blob_versioned_hashes + %decode_and_store_y_parity + %decode_and_store_r + %decode_and_store_s + + // stack: rlp_addr, retdest + POP + // stack: retdest + +// From EIP-4844: +// The signature_y_parity, signature_r, signature_s elements of this transaction represent a secp256k1 signature over +// keccak256(0x03 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value, data, access_list, max_fee_per_blob_gas, blob_versioned_hashes])) +type_3_compute_signed_data: + %alloc_rlp_block + // stack: rlp_addr_start, retdest + %mload_txn_field(@TXN_FIELD_CHAIN_ID) + // stack: chain_id, rlp_start, retdest + DUP2 + // stack: rlp_addr, chain_id, rlp_start, retdest + %encode_rlp_scalar + // stack: rlp_addr, rlp_start, retdest + + %mload_txn_field(@TXN_FIELD_NONCE) + %encode_rlp_scalar_swapped_inputs + // stack: rlp_addr, rlp_start, retdest + + %mload_txn_field(@TXN_FIELD_MAX_PRIORITY_FEE_PER_GAS) + %encode_rlp_scalar_swapped_inputs + // stack: rlp_addr, rlp_start, retdest + + %mload_txn_field(@TXN_FIELD_MAX_FEE_PER_GAS) + %encode_rlp_scalar_swapped_inputs + // stack: rlp_addr, rlp_start, retdest + + %mload_txn_field(@TXN_FIELD_GAS_LIMIT) + %encode_rlp_scalar_swapped_inputs + // stack: rlp_addr, rlp_start, retdest + + // As per EIP-4844, blob transactions cannot have the form of a create transaction. + %mload_txn_field(@TXN_FIELD_TO) + %mload_global_metadata(@GLOBAL_METADATA_CONTRACT_CREATION) %jumpi(panic) + // stack: to, rlp_addr, rlp_start, retdest + SWAP1 %encode_rlp_160 + // stack: rlp_addr, rlp_start, retdest + + %mload_txn_field(@TXN_FIELD_VALUE) + %encode_rlp_scalar_swapped_inputs + // stack: rlp_addr, rlp_start, retdest + + // Encode txn data. + %mload_txn_field(@TXN_FIELD_DATA_LEN) + PUSH @SEGMENT_TXN_DATA // ctx == virt == 0 + // stack: ADDR, len, rlp_addr, rlp_start, retdest + PUSH after_serializing_txn_data + // stack: after_serializing_txn_data, ADDR, len, rlp_addr, rlp_start, retdest + SWAP3 + // stack: rlp_addr, ADDR, len, after_serializing_txn_data, rlp_start, retdest + %jump(encode_rlp_string) + +after_serializing_txn_data: + // Instead of manually encoding the access list, we just copy the raw RLP from the transaction. + %mload_global_metadata(@GLOBAL_METADATA_ACCESS_LIST_RLP_START) + %mload_global_metadata(@GLOBAL_METADATA_ACCESS_LIST_RLP_LEN) + %stack (al_len, al_start, rlp_addr, rlp_start, retdest) -> + ( + rlp_addr, + al_start, + al_len, + after_serializing_access_list, + rlp_addr, rlp_start, retdest) + %jump(memcpy_bytes) +after_serializing_access_list: + // stack: rlp_addr, rlp_start, retdest + %mload_global_metadata(@GLOBAL_METADATA_ACCESS_LIST_RLP_LEN) ADD + // stack: rlp_addr, rlp_start, retdest + + %mload_txn_field(@TXN_FIELD_MAX_FEE_PER_BLOB_GAS) + %encode_rlp_scalar_swapped_inputs + // stack: rlp_addr, rlp_start, retdest + + // Instead of manually encoding the blob versioned hashes, we just copy the raw RLP from the transaction. + %mload_global_metadata(@GLOBAL_METADATA_BLOB_VERSIONED_HASHES_RLP_START) + %mload_global_metadata(@GLOBAL_METADATA_BLOB_VERSIONED_HASHES_RLP_LEN) + %stack (bvh_len, bvh_start, rlp_addr, rlp_start, retdest) -> + ( + rlp_addr, + bvh_start, + bvh_len, + after_serializing_blob_versioned_hashes, + rlp_addr, rlp_start, retdest) + %jump(memcpy_bytes) +after_serializing_blob_versioned_hashes: + %prepend_rlp_list_prefix + // stack: prefix_start_pos, rlp_len, retdest + + // Store a `3` in front of the RLP + %decrement + %stack (rlp_addr) -> (3, rlp_addr, rlp_addr) + MSTORE_GENERAL + // stack: rlp_addr, rlp_len, retdest + + // Hash the RLP + the leading `3` + SWAP1 %increment SWAP1 + // stack: ADDR, len, retdest + KECCAK_GENERAL + // stack: hash, retdest + + %mload_txn_field(@TXN_FIELD_S) + %mload_txn_field(@TXN_FIELD_R) + %mload_txn_field(@TXN_FIELD_Y_PARITY) %add_const(27) // ecrecover interprets v as y_parity + 27 + + PUSH store_origin + // stack: store_origin, v, r, s, hash, retdest + SWAP4 + // stack: hash, v, r, s, store_origin, retdest + %jump(ecrecover) + +store_origin: + // stack: address, retdest + // If ecrecover returned u256::MAX, that indicates failure. + DUP1 + %eq_const(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) + %jumpi(panic) + + // stack: address, retdest + %mstore_txn_field(@TXN_FIELD_ORIGIN) + // stack: retdest + %jump(process_normalized_txn) diff --git a/evm_arithmetization/src/cpu/kernel/constants/exc_bitfields.rs b/evm_arithmetization/src/cpu/kernel/constants/exc_bitfields.rs index f12b7e252..cb5bb539f 100644 --- a/evm_arithmetization/src/cpu/kernel/constants/exc_bitfields.rs +++ b/evm_arithmetization/src/cpu/kernel/constants/exc_bitfields.rs @@ -46,7 +46,7 @@ pub(crate) const INVALID_OPCODES_USER: U256 = u256_from_set_index_ranges(&[ 0x0c..=0x0f, 0x1e..=0x1f, 0x21..=0x2f, - 0x49..=0x4f, + 0x4a..=0x4f, 0x5c..=0x5e, 0xa5..=0xef, 0xf6..=0xf9, diff --git a/evm_arithmetization/src/cpu/kernel/constants/global_metadata.rs b/evm_arithmetization/src/cpu/kernel/constants/global_metadata.rs index 8e866da16..754d5d520 100644 --- a/evm_arithmetization/src/cpu/kernel/constants/global_metadata.rs +++ b/evm_arithmetization/src/cpu/kernel/constants/global_metadata.rs @@ -49,6 +49,9 @@ pub(crate) enum GlobalMetadata { BlockGasLimit, BlockChainId, BlockBaseFee, + BlockBlobBaseFee, + BlockBlobGasUsed, + BlockExcessBlobGas, BlockGasUsed, /// Before current transactions block values. BlockGasUsedBefore, @@ -93,17 +96,23 @@ pub(crate) enum GlobalMetadata { LogsPayloadLen, TxnNumberBefore, TxnNumberAfter, - BlockBlobBaseFee, /// Number of created contracts during the current transaction. CreatedContractsLen, KernelHash, KernelLen, + + // Start of the blob versioned hashes in the RLP for type-3 txns. + BlobVersionedHashesRlpStart, + // Length of the blob versioned hashes in the RLP for type-3 txns. + BlobVersionedHashesRlpLen, + // Number of blob versioned hashes contained in the current type-3 transaction. + BlobVersionedHashesLen, } impl GlobalMetadata { - pub(crate) const COUNT: usize = 50; + pub(crate) const COUNT: usize = 55; /// Unscales this virtual offset by their respective `Segment` value. pub(crate) const fn unscale(&self) -> usize { @@ -134,6 +143,9 @@ impl GlobalMetadata { Self::BlockChainId, Self::BlockBaseFee, Self::BlockGasUsed, + Self::BlockBlobBaseFee, + Self::BlockBlobGasUsed, + Self::BlockExcessBlobGas, Self::BlockGasUsedBefore, Self::BlockGasUsedAfter, Self::ParentBeaconBlockRoot, @@ -158,10 +170,12 @@ impl GlobalMetadata { Self::BlockCurrentHash, Self::TxnNumberBefore, Self::TxnNumberAfter, - Self::BlockBlobBaseFee, Self::CreatedContractsLen, Self::KernelHash, Self::KernelLen, + Self::BlobVersionedHashesRlpStart, + Self::BlobVersionedHashesRlpLen, + Self::BlobVersionedHashesLen, ] } @@ -189,6 +203,9 @@ impl GlobalMetadata { Self::BlockGasLimit => "GLOBAL_METADATA_BLOCK_GAS_LIMIT", Self::BlockChainId => "GLOBAL_METADATA_BLOCK_CHAIN_ID", Self::BlockBaseFee => "GLOBAL_METADATA_BLOCK_BASE_FEE", + Self::BlockBlobBaseFee => "GLOBAL_METADATA_BLOCK_BLOB_BASE_FEE", + Self::BlockBlobGasUsed => "GLOBAL_METADATA_BLOCK_BLOB_GAS_USED", + Self::BlockExcessBlobGas => "GLOBAL_METADATA_BLOCK_EXCESS_BLOB_GAS", Self::BlockGasUsed => "GLOBAL_METADATA_BLOCK_GAS_USED", Self::BlockGasUsedBefore => "GLOBAL_METADATA_BLOCK_GAS_USED_BEFORE", Self::BlockGasUsedAfter => "GLOBAL_METADATA_BLOCK_GAS_USED_AFTER", @@ -214,10 +231,12 @@ impl GlobalMetadata { Self::LogsPayloadLen => "GLOBAL_METADATA_LOGS_PAYLOAD_LEN", Self::TxnNumberBefore => "GLOBAL_METADATA_TXN_NUMBER_BEFORE", Self::TxnNumberAfter => "GLOBAL_METADATA_TXN_NUMBER_AFTER", - Self::BlockBlobBaseFee => "GLOBAL_METADATA_BLOCK_BLOB_BASE_FEE", Self::CreatedContractsLen => "GLOBAL_METADATA_CREATED_CONTRACTS_LEN", Self::KernelHash => "GLOBAL_METADATA_KERNEL_HASH", Self::KernelLen => "GLOBAL_METADATA_KERNEL_LEN", + Self::BlobVersionedHashesRlpStart => "GLOBAL_METADATA_BLOB_VERSIONED_HASHES_RLP_START", + Self::BlobVersionedHashesRlpLen => "GLOBAL_METADATA_BLOB_VERSIONED_HASHES_RLP_LEN", + Self::BlobVersionedHashesLen => "GLOBAL_METADATA_BLOB_VERSIONED_HASHES_LEN", } } } diff --git a/evm_arithmetization/src/cpu/kernel/constants/mod.rs b/evm_arithmetization/src/cpu/kernel/constants/mod.rs index c85f061db..eb11a5e66 100644 --- a/evm_arithmetization/src/cpu/kernel/constants/mod.rs +++ b/evm_arithmetization/src/cpu/kernel/constants/mod.rs @@ -215,7 +215,7 @@ const EC_CONSTANTS: [(&str, [u8; 32]); 20] = [ ), ]; -const GAS_CONSTANTS: [(&str, u16); 36] = [ +const GAS_CONSTANTS: [(&str, u16); 37] = [ ("GAS_ZERO", 0), ("GAS_JUMPDEST", 1), ("GAS_BASE", 2), @@ -252,6 +252,7 @@ const GAS_CONSTANTS: [(&str, u16); 36] = [ ("GAS_KECCAK256WORD", 6), ("GAS_COPY", 3), ("GAS_BLOCKHASH", 20), + ("GAS_HASH_OPCODE", 3), ]; const REFUND_CONSTANTS: [(&str, u16); 2] = [("REFUND_SCLEAR", 4_800), ("MAX_REFUND_QUOTIENT", 5)]; diff --git a/evm_arithmetization/src/cpu/kernel/constants/txn_fields.rs b/evm_arithmetization/src/cpu/kernel/constants/txn_fields.rs index 7b49cf7ff..e61c6ee51 100644 --- a/evm_arithmetization/src/cpu/kernel/constants/txn_fields.rs +++ b/evm_arithmetization/src/cpu/kernel/constants/txn_fields.rs @@ -24,6 +24,7 @@ pub(crate) enum NormalizedTxnField { /// The length of the data field. The data itself is stored in another /// segment. DataLen, + MaxFeePerBlobGas, YParity, R, S, @@ -37,7 +38,7 @@ pub(crate) enum NormalizedTxnField { } impl NormalizedTxnField { - pub(crate) const COUNT: usize = 16; + pub(crate) const COUNT: usize = 17; /// Unscales this virtual offset by their respective `Segment` value. pub(crate) const fn unscale(&self) -> usize { @@ -56,6 +57,7 @@ impl NormalizedTxnField { Self::To, Self::Value, Self::DataLen, + Self::MaxFeePerBlobGas, Self::YParity, Self::R, Self::S, @@ -78,6 +80,7 @@ impl NormalizedTxnField { NormalizedTxnField::To => "TXN_FIELD_TO", NormalizedTxnField::Value => "TXN_FIELD_VALUE", NormalizedTxnField::DataLen => "TXN_FIELD_DATA_LEN", + NormalizedTxnField::MaxFeePerBlobGas => "TXN_FIELD_MAX_FEE_PER_BLOB_GAS", NormalizedTxnField::YParity => "TXN_FIELD_Y_PARITY", NormalizedTxnField::R => "TXN_FIELD_R", NormalizedTxnField::S => "TXN_FIELD_S", diff --git a/evm_arithmetization/src/cpu/kernel/interpreter.rs b/evm_arithmetization/src/cpu/kernel/interpreter.rs index 2b0718b55..1e42a5a55 100644 --- a/evm_arithmetization/src/cpu/kernel/interpreter.rs +++ b/evm_arithmetization/src/cpu/kernel/interpreter.rs @@ -305,11 +305,23 @@ impl<'a, F: Field> Interpreter<'a, F> { (GlobalMetadata::BlockGasLimit, metadata.block_gaslimit), (GlobalMetadata::BlockChainId, metadata.block_chain_id), (GlobalMetadata::BlockBaseFee, metadata.block_base_fee), + ( + GlobalMetadata::BlockBlobBaseFee, + metadata.block_blob_base_fee, + ), ( GlobalMetadata::BlockCurrentHash, h2u(inputs.block_hashes.cur_hash), ), (GlobalMetadata::BlockGasUsed, metadata.block_gas_used), + ( + GlobalMetadata::BlockBlobGasUsed, + metadata.block_blob_gas_used, + ), + ( + GlobalMetadata::BlockExcessBlobGas, + metadata.block_excess_blob_gas, + ), (GlobalMetadata::BlockGasUsedBefore, inputs.gas_used_before), (GlobalMetadata::BlockGasUsedAfter, inputs.gas_used_after), (GlobalMetadata::TxnNumberBefore, inputs.txn_number_before), @@ -841,6 +853,7 @@ impl<'a, F: Field> Interpreter<'a, F> { 0x46 => self.run_syscall(opcode, 0, true), // "CHAINID", 0x47 => self.run_syscall(opcode, 0, true), // SELFABALANCE, 0x48 => self.run_syscall(opcode, 0, true), // "BASEFEE", + 0x49 => self.run_syscall(opcode, 1, false), // "BLOBHASH", 0x4a => self.run_syscall(opcode, 0, true), // "BLOBBASEFEE", 0x50 => self.run_pop(), // "POP", 0x51 => self.run_syscall(opcode, 1, false), // "MLOAD", diff --git a/evm_arithmetization/src/cpu/kernel/tests/blobhash.rs b/evm_arithmetization/src/cpu/kernel/tests/blobhash.rs new file mode 100644 index 000000000..33eb27689 --- /dev/null +++ b/evm_arithmetization/src/cpu/kernel/tests/blobhash.rs @@ -0,0 +1,85 @@ +use anyhow::Result; +use ethereum_types::{H256, U256}; +use plonky2::field::goldilocks_field::GoldilocksField as F; +use rand::{thread_rng, Rng}; + +use crate::cpu::kernel::aggregator::KERNEL; +use crate::cpu::kernel::constants::context_metadata::ContextMetadata::GasLimit; +use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; +use crate::cpu::kernel::interpreter::Interpreter; +use crate::memory::segments::Segment; +use crate::witness::memory::MemoryContextState; + +#[test] +fn test_valid_blobhash() -> Result<()> { + let blobhash_label = KERNEL.global_labels["sys_blobhash"]; + let retdest = (0xDEADBEEFu64 + (1 << 32)).into(); // kexit_info + + let versioned_hashes: Vec = vec![U256::from_big_endian(&thread_rng().gen::().0); 5]; + let index = 3; + let target_hash = versioned_hashes[index]; + + let mut interpreter: Interpreter = Interpreter::new_with_kernel(blobhash_label, vec![]); + interpreter + .generation_state + .memory + .contexts + .push(MemoryContextState::default()); + interpreter.set_context(1); + interpreter.set_memory_segment(Segment::TxnBlobVersionedHashes, versioned_hashes); + interpreter.set_global_metadata_field(GlobalMetadata::BlobVersionedHashesLen, 5.into()); + + interpreter.set_context_metadata_field(1, GasLimit, U256::from(1000000000000u64)); + + interpreter.push(index.into()); // target hash index + interpreter.push(retdest); // kexit_info + + interpreter.run()?; + + let result = interpreter.stack(); + assert_eq!(interpreter.stack_len(), 1); + assert_eq!( + result[0], target_hash, + "Resulting blobhash {:?} different from expected hash {:?}", + result[0], target_hash + ); + + Ok(()) +} + +#[test] +fn test_invalid_blobhash() -> Result<()> { + let blobhash_label = KERNEL.global_labels["sys_blobhash"]; + let retdest = (0xDEADBEEFu64 + (1 << 32)).into(); // kexit_info + + let versioned_hashes: Vec = vec![U256::from_big_endian(&thread_rng().gen::().0); 5]; + let index = 7; + let target_hash = U256::zero(); // out of bound indexing yields 0. + + let mut interpreter: Interpreter = Interpreter::new_with_kernel(blobhash_label, vec![]); + interpreter + .generation_state + .memory + .contexts + .push(MemoryContextState::default()); + interpreter.set_context(1); + interpreter.set_memory_segment(Segment::TxnBlobVersionedHashes, versioned_hashes); + interpreter.set_global_metadata_field(GlobalMetadata::BlobVersionedHashesLen, 5.into()); + + interpreter.set_context_metadata_field(1, GasLimit, U256::from(1000000000000u64)); + + interpreter.push(index.into()); // target hash index + interpreter.push(retdest); // kexit_info + + interpreter.run()?; + + let result = interpreter.stack(); + assert_eq!(interpreter.stack_len(), 1); + assert_eq!( + result[0], target_hash, + "Resulting blobhash {:?} different from expected hash {:?}", + result[0], target_hash + ); + + Ok(()) +} diff --git a/evm_arithmetization/src/cpu/kernel/tests/mod.rs b/evm_arithmetization/src/cpu/kernel/tests/mod.rs index 7581eefe7..bd61c897e 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/mod.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/mod.rs @@ -3,6 +3,7 @@ mod add11; mod balance; mod bignum; mod blake2_f; +mod blobhash; mod block_hash; mod bls381; mod bn254; diff --git a/evm_arithmetization/src/cpu/kernel/tests/transaction_parsing/parse_type_0_txn.rs b/evm_arithmetization/src/cpu/kernel/tests/transaction_parsing/parse_type_0_txn.rs index 2aaf9ba4a..06dd59e84 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/transaction_parsing/parse_type_0_txn.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/transaction_parsing/parse_type_0_txn.rs @@ -7,6 +7,7 @@ use NormalizedTxnField::*; use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::constants::txn_fields::NormalizedTxnField; use crate::cpu::kernel::interpreter::Interpreter; +use crate::memory::segments::Segment; #[test] fn process_type_0_txn() -> Result<()> { @@ -14,8 +15,9 @@ fn process_type_0_txn() -> Result<()> { let process_normalized_txn = KERNEL.global_labels["process_normalized_txn"]; let retaddr = 0xDEADBEEFu32.into(); + let rlp_addr = (Segment::RlpRaw as usize).into(); let mut interpreter: Interpreter = - Interpreter::new_with_kernel(process_type_0_txn, vec![retaddr]); + Interpreter::new_with_kernel(process_type_0_txn, vec![retaddr, rlp_addr]); // When we reach process_normalized_txn, we're done with parsing and // normalizing. Processing normalized transactions is outside the scope of diff --git a/evm_arithmetization/src/generation/mod.rs b/evm_arithmetization/src/generation/mod.rs index 138b87d47..1a7ea785a 100644 --- a/evm_arithmetization/src/generation/mod.rs +++ b/evm_arithmetization/src/generation/mod.rs @@ -131,6 +131,14 @@ fn apply_metadata_and_tries_memops, const D: usize> GlobalMetadata::BlockBlobBaseFee, metadata.block_blob_base_fee, ), + ( + GlobalMetadata::BlockBlobGasUsed, + metadata.block_blob_gas_used, + ), + ( + GlobalMetadata::BlockExcessBlobGas, + metadata.block_excess_blob_gas, + ), ( GlobalMetadata::ParentBeaconBlockRoot, h2u(metadata.parent_beacon_block_root), diff --git a/evm_arithmetization/src/generation/mpt.rs b/evm_arithmetization/src/generation/mpt.rs index 79e923068..885eadab5 100644 --- a/evm_arithmetization/src/generation/mpt.rs +++ b/evm_arithmetization/src/generation/mpt.rs @@ -347,6 +347,8 @@ pub(crate) fn load_all_mpts( } pub mod transaction_testing { + use ethereum_types::H160; + use super::*; #[derive(RlpEncodable, RlpDecodable, Debug, Clone, PartialEq, Eq)] @@ -424,4 +426,23 @@ pub mod transaction_testing { pub r: U256, pub s: U256, } + + #[derive(RlpEncodable, RlpDecodable, Debug, Clone, PartialEq, Eq)] + pub struct BlobTransactionRlp { + pub chain_id: u64, + pub nonce: U256, + pub max_priority_fee_per_gas: U256, + pub max_fee_per_gas: U256, + pub gas: U256, + // As per EIP-4844, blob transactions cannot have the form of a create transaction. + pub to: H160, + pub value: U256, + pub data: Bytes, + pub access_list: Vec, + pub max_fee_per_blob_gas: U256, + pub blob_versioned_hashes: Vec, + pub y_parity: U256, + pub r: U256, + pub s: U256, + } } diff --git a/evm_arithmetization/src/get_challenges.rs b/evm_arithmetization/src/get_challenges.rs index 89b55ca93..31742c114 100644 --- a/evm_arithmetization/src/get_challenges.rs +++ b/evm_arithmetization/src/get_challenges.rs @@ -68,6 +68,12 @@ fn observe_block_metadata< let blob_basefee = u256_to_u64(block_metadata.block_blob_base_fee)?; challenger.observe_element(blob_basefee.0); challenger.observe_element(blob_basefee.1); + let blob_gas_used = u256_to_u64(block_metadata.block_blob_gas_used)?; + challenger.observe_element(blob_gas_used.0); + challenger.observe_element(blob_gas_used.1); + let excess_blob_gas = u256_to_u64(block_metadata.block_excess_blob_gas)?; + challenger.observe_element(excess_blob_gas.0); + challenger.observe_element(excess_blob_gas.1); challenger.observe_elements(&h256_limbs::(block_metadata.parent_beacon_block_root)); for i in 0..8 { challenger.observe_elements(&u256_limbs(block_metadata.block_bloom[i])); @@ -96,6 +102,8 @@ fn observe_block_metadata_target< challenger.observe_elements(&block_metadata.block_base_fee); challenger.observe_element(block_metadata.block_gas_used); challenger.observe_elements(&block_metadata.block_blob_base_fee); + challenger.observe_elements(&block_metadata.block_blob_gas_used); + challenger.observe_elements(&block_metadata.block_excess_blob_gas); challenger.observe_elements(&block_metadata.parent_beacon_block_root); challenger.observe_elements(&block_metadata.block_bloom); } diff --git a/evm_arithmetization/src/memory/segments.rs b/evm_arithmetization/src/memory/segments.rs index 345f938d3..b2a9cbe31 100644 --- a/evm_arithmetization/src/memory/segments.rs +++ b/evm_arithmetization/src/memory/segments.rs @@ -78,10 +78,12 @@ pub(crate) enum Segment { /// List of contracts which have been created during the current /// transaction. CreatedContracts = 34 << SEGMENT_SCALING_FACTOR, + /// Blob versioned hashes specified in a type-3 transaction. + TxnBlobVersionedHashes = 35 << SEGMENT_SCALING_FACTOR, } impl Segment { - pub(crate) const COUNT: usize = 35; + pub(crate) const COUNT: usize = 36; /// Unscales this segment by `SEGMENT_SCALING_FACTOR`. pub(crate) const fn unscale(&self) -> usize { @@ -125,6 +127,7 @@ impl Segment { Self::ContextCheckpoints, Self::BlockHashes, Self::CreatedContracts, + Self::TxnBlobVersionedHashes, ] } @@ -166,6 +169,7 @@ impl Segment { Segment::ContextCheckpoints => "SEGMENT_CONTEXT_CHECKPOINTS", Segment::BlockHashes => "SEGMENT_BLOCK_HASHES", Segment::CreatedContracts => "SEGMENT_CREATED_CONTRACTS", + Segment::TxnBlobVersionedHashes => "SEGMENT_TXN_BLOB_VERSIONED_HASHES", } } @@ -206,6 +210,7 @@ impl Segment { Segment::ContextCheckpoints => 256, Segment::BlockHashes => 256, Segment::CreatedContracts => 256, + Segment::TxnBlobVersionedHashes => 256, } } diff --git a/evm_arithmetization/src/proof.rs b/evm_arithmetization/src/proof.rs index 4d96decd8..5d01cc2ef 100644 --- a/evm_arithmetization/src/proof.rs +++ b/evm_arithmetization/src/proof.rs @@ -187,6 +187,10 @@ pub struct BlockMetadata { pub block_gas_used: U256, /// The blob base fee. It must fit in a `u64`. pub block_blob_base_fee: U256, + /// The blob base fee. It must fit in a `u64`. + pub block_blob_gas_used: U256, + /// The blob base fee. It must fit in a `u64`. + pub block_excess_blob_gas: U256, /// The hash tree root of the parent beacon block. pub parent_beacon_block_root: H256, /// The block bloom of this block, represented as the consecutive @@ -210,9 +214,13 @@ impl BlockMetadata { let block_gas_used = pis[20].to_canonical_u64().into(); let block_blob_base_fee = (pis[21].to_canonical_u64() + (pis[22].to_canonical_u64() << 32)).into(); - let parent_beacon_block_root = get_h256(&pis[23..31]); + let block_blob_gas_used = + (pis[23].to_canonical_u64() + (pis[24].to_canonical_u64() << 32)).into(); + let block_excess_blob_gas = + (pis[25].to_canonical_u64() + (pis[26].to_canonical_u64() << 32)).into(); + let parent_beacon_block_root = get_h256(&pis[27..35]); let block_bloom = - core::array::from_fn(|i| h2u(get_h256(&pis[31 + 8 * i..31 + 8 * (i + 1)]))); + core::array::from_fn(|i| h2u(get_h256(&pis[35 + 8 * i..35 + 8 * (i + 1)]))); Self { block_beneficiary, @@ -225,6 +233,8 @@ impl BlockMetadata { block_base_fee, block_gas_used, block_blob_base_fee, + block_blob_gas_used, + block_excess_blob_gas, parent_beacon_block_root, block_bloom, } @@ -322,6 +332,8 @@ impl PublicValuesTarget { block_base_fee, block_gas_used, block_blob_base_fee, + block_blob_gas_used, + block_excess_blob_gas, parent_beacon_block_root, block_bloom, } = self.block_metadata; @@ -336,6 +348,8 @@ impl PublicValuesTarget { buffer.write_target_array(&block_base_fee)?; buffer.write_target(block_gas_used)?; buffer.write_target_array(&block_blob_base_fee)?; + buffer.write_target_array(&block_blob_gas_used)?; + buffer.write_target_array(&block_excess_blob_gas)?; buffer.write_target_array(&parent_beacon_block_root)?; buffer.write_target_array(&block_bloom)?; @@ -387,6 +401,8 @@ impl PublicValuesTarget { block_base_fee: buffer.read_target_array()?, block_gas_used: buffer.read_target()?, block_blob_base_fee: buffer.read_target_array()?, + block_blob_gas_used: buffer.read_target_array()?, + block_excess_blob_gas: buffer.read_target_array()?, parent_beacon_block_root: buffer.read_target_array()?, block_bloom: buffer.read_target_array()?, }; @@ -590,15 +606,19 @@ pub struct BlockMetadataTarget { pub(crate) block_gas_used: Target, /// `Target`s for the blob base fee of this block. pub(crate) block_blob_base_fee: [Target; 2], - /// `Target`s for the parent beacon block root. - pub parent_beacon_block_root: [Target; 8], + /// `Target`s for the total blob gas used of this block. + pub(crate) block_blob_gas_used: [Target; 2], + /// `Target`s for the excess blob gas of this block. + pub(crate) block_excess_blob_gas: [Target; 2], /// `Target`s for the block bloom of this block. + /// `Target`s for the parent beacon block root. + pub(crate) parent_beacon_block_root: [Target; 8], pub(crate) block_bloom: [Target; 64], } impl BlockMetadataTarget { /// Number of `Target`s required for the block metadata. - pub(crate) const SIZE: usize = 95; + pub(crate) const SIZE: usize = 99; /// Extracts block metadata `Target`s from the provided public input /// `Target`s. The provided `pis` should start with the block metadata. @@ -613,8 +633,10 @@ impl BlockMetadataTarget { let block_base_fee = pis[18..20].try_into().unwrap(); let block_gas_used = pis[20]; let block_blob_base_fee = pis[21..23].try_into().unwrap(); - let parent_beacon_block_root = pis[23..31].try_into().unwrap(); - let block_bloom = pis[31..95].try_into().unwrap(); + let block_blob_gas_used = pis[23..25].try_into().unwrap(); + let block_excess_blob_gas = pis[25..27].try_into().unwrap(); + let parent_beacon_block_root = pis[27..35].try_into().unwrap(); + let block_bloom = pis[35..99].try_into().unwrap(); Self { block_beneficiary, @@ -627,6 +649,8 @@ impl BlockMetadataTarget { block_base_fee, block_gas_used, block_blob_base_fee, + block_blob_gas_used, + block_excess_blob_gas, parent_beacon_block_root, block_bloom, } @@ -667,6 +691,20 @@ impl BlockMetadataTarget { bm1.block_blob_base_fee[i], ) }), + block_blob_gas_used: core::array::from_fn(|i| { + builder.select( + condition, + bm0.block_blob_gas_used[i], + bm1.block_blob_gas_used[i], + ) + }), + block_excess_blob_gas: core::array::from_fn(|i| { + builder.select( + condition, + bm0.block_excess_blob_gas[i], + bm1.block_excess_blob_gas[i], + ) + }), parent_beacon_block_root: core::array::from_fn(|i| { builder.select( condition, @@ -704,11 +742,17 @@ impl BlockMetadataTarget { for i in 0..2 { builder.connect(bm0.block_blob_base_fee[i], bm1.block_blob_base_fee[i]) } + for i in 0..2 { + builder.connect(bm0.block_blob_gas_used[i], bm1.block_blob_gas_used[i]) + } + for i in 0..2 { + builder.connect(bm0.block_excess_blob_gas[i], bm1.block_excess_blob_gas[i]) + } for i in 0..8 { builder.connect( bm0.parent_beacon_block_root[i], bm1.parent_beacon_block_root[i], - ); + ) } for i in 0..64 { builder.connect(bm0.block_bloom[i], bm1.block_bloom[i]) diff --git a/evm_arithmetization/src/recursive_verifier.rs b/evm_arithmetization/src/recursive_verifier.rs index 4cfcaee1c..34245c24b 100644 --- a/evm_arithmetization/src/recursive_verifier.rs +++ b/evm_arithmetization/src/recursive_verifier.rs @@ -377,8 +377,9 @@ pub(crate) fn get_memory_extra_looking_sum_circuit, ]; // This contains the `block_beneficiary`, `block_random`, `block_base_fee`, - // `block_blob_base_fee`, `parent_beacon_block_root` as well as `cur_hash`. - let block_fields_arrays: [(GlobalMetadata, &[Target]); 6] = [ + // `block_blob_base_fee`, `block_blob_gas_used`, `block_excess_blob_gas`, + // `parent_beacon_block_root` as well as `cur_hash`. + let block_fields_arrays: [(GlobalMetadata, &[Target]); 8] = [ ( GlobalMetadata::BlockBeneficiary, &public_values.block_metadata.block_beneficiary, @@ -395,6 +396,14 @@ pub(crate) fn get_memory_extra_looking_sum_circuit, GlobalMetadata::BlockBlobBaseFee, &public_values.block_metadata.block_blob_base_fee, ), + ( + GlobalMetadata::BlockBlobGasUsed, + &public_values.block_metadata.block_blob_gas_used, + ), + ( + GlobalMetadata::BlockExcessBlobGas, + &public_values.block_metadata.block_excess_blob_gas, + ), ( GlobalMetadata::ParentBeaconBlockRoot, &public_values.block_metadata.parent_beacon_block_root, @@ -604,6 +613,8 @@ pub(crate) fn add_virtual_block_metadata, const D: let block_base_fee = builder.add_virtual_public_input_arr(); let block_gas_used = builder.add_virtual_public_input(); let block_blob_base_fee = builder.add_virtual_public_input_arr(); + let block_blob_gas_used = builder.add_virtual_public_input_arr(); + let block_excess_blob_gas = builder.add_virtual_public_input_arr(); let parent_beacon_block_root = builder.add_virtual_public_input_arr(); let block_bloom = builder.add_virtual_public_input_arr(); BlockMetadataTarget { @@ -617,6 +628,8 @@ pub(crate) fn add_virtual_block_metadata, const D: block_base_fee, block_gas_used, block_blob_base_fee, + block_blob_gas_used, + block_excess_blob_gas, parent_beacon_block_root, block_bloom, } @@ -787,10 +800,30 @@ where block_metadata_target.block_gas_used, u256_to_u32(block_metadata.block_gas_used)?, ); - // Blobbasefee fits in 2 limbs + // BlobBaseFee fits in 2 limbs let blob_basefee = u256_to_u64(block_metadata.block_blob_base_fee)?; witness.set_target(block_metadata_target.block_blob_base_fee[0], blob_basefee.0); witness.set_target(block_metadata_target.block_blob_base_fee[1], blob_basefee.1); + // BlobGasUsed fits in 2 limbs + let blob_gas_used = u256_to_u64(block_metadata.block_blob_gas_used)?; + witness.set_target( + block_metadata_target.block_blob_gas_used[0], + blob_gas_used.0, + ); + witness.set_target( + block_metadata_target.block_blob_gas_used[1], + blob_gas_used.1, + ); + // ExcessBlobGas fits in 2 limbs + let excess_blob_gas = u256_to_u64(block_metadata.block_excess_blob_gas)?; + witness.set_target( + block_metadata_target.block_excess_blob_gas[0], + excess_blob_gas.0, + ); + witness.set_target( + block_metadata_target.block_excess_blob_gas[1], + excess_blob_gas.1, + ); witness.set_target_arr( &block_metadata_target.parent_beacon_block_root, diff --git a/evm_arithmetization/src/verifier.rs b/evm_arithmetization/src/verifier.rs index d6e73fcad..69d8ccbaf 100644 --- a/evm_arithmetization/src/verifier.rs +++ b/evm_arithmetization/src/verifier.rs @@ -198,6 +198,14 @@ where GlobalMetadata::BlockBlobBaseFee, public_values.block_metadata.block_blob_base_fee, ), + ( + GlobalMetadata::BlockBlobGasUsed, + public_values.block_metadata.block_blob_gas_used, + ), + ( + GlobalMetadata::BlockExcessBlobGas, + public_values.block_metadata.block_excess_blob_gas, + ), ( GlobalMetadata::TxnNumberBefore, public_values.extra_block_data.txn_number_before, @@ -348,6 +356,14 @@ pub(crate) mod debug_utils { GlobalMetadata::BlockBlobBaseFee, public_values.block_metadata.block_blob_base_fee, ), + ( + GlobalMetadata::BlockBlobGasUsed, + public_values.block_metadata.block_blob_gas_used, + ), + ( + GlobalMetadata::BlockExcessBlobGas, + public_values.block_metadata.block_excess_blob_gas, + ), ( GlobalMetadata::ParentBeaconBlockRoot, h2u(public_values.block_metadata.parent_beacon_block_root), diff --git a/evm_arithmetization/src/witness/operation.rs b/evm_arithmetization/src/witness/operation.rs index 17c3b157e..213b2d930 100644 --- a/evm_arithmetization/src/witness/operation.rs +++ b/evm_arithmetization/src/witness/operation.rs @@ -34,7 +34,8 @@ use crate::{arithmetic, logic}; pub(crate) enum Operation { Iszero, Not, - Syscall(u8, usize, bool), // (syscall number, minimum stack length, increases stack length) + /// (syscall number, minimum stack length, increases stack length) + Syscall(u8, usize, bool), Eq, BinaryLogic(logic::Op), BinaryArithmetic(arithmetic::BinaryOperator), diff --git a/evm_arithmetization/src/witness/transition.rs b/evm_arithmetization/src/witness/transition.rs index 67afcefb3..aefb30019 100644 --- a/evm_arithmetization/src/witness/transition.rs +++ b/evm_arithmetization/src/witness/transition.rs @@ -107,6 +107,7 @@ pub(crate) fn decode(registers: RegistersState, opcode: u8) -> Result Ok(Operation::Syscall(opcode, 0, true)), // CHAINID (0x47, _) => Ok(Operation::Syscall(opcode, 0, true)), // SELFBALANCE (0x48, _) => Ok(Operation::Syscall(opcode, 0, true)), // BASEFEE + (0x49, _) => Ok(Operation::Syscall(opcode, 1, false)), // BLOBHASH (0x4a, _) => Ok(Operation::Syscall(opcode, 0, true)), // BLOBBASEFEE (0x50, _) => Ok(Operation::Pop), (0x51, _) => Ok(Operation::Syscall(opcode, 1, false)), // MLOAD From f0842875f62b88f9d66007d4b640e681bdc1ee44 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Thu, 29 Feb 2024 19:57:48 +0900 Subject: [PATCH 04/40] Fix beacons root contract bytecode (#70) --- evm_arithmetization/src/cpu/kernel/constants/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evm_arithmetization/src/cpu/kernel/constants/mod.rs b/evm_arithmetization/src/cpu/kernel/constants/mod.rs index eb11a5e66..40ec2c25e 100644 --- a/evm_arithmetization/src/cpu/kernel/constants/mod.rs +++ b/evm_arithmetization/src/cpu/kernel/constants/mod.rs @@ -308,9 +308,9 @@ pub mod cancun_constants { pub const HISTORY_BUFFER_LENGTH: (&str, u64) = ("HISTORY_BUFFER_LENGTH", 8191); - pub const BEACON_ROOTS_CONTRACT_CODE: [u8; 106] = hex!("60618060095f395ff33373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500"); + pub const BEACON_ROOTS_CONTRACT_CODE: [u8; 97] = hex!("3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500"); pub const BEACON_ROOTS_CONTRACT_CODE_HASH: [u8; 32] = - hex!("468e991a328ab315e08296896adc222230a4960692e90cb6e096006ba6ae75d5"); + hex!("f57acd40259872606d76197ef052f3d35588dadf919ee1f0e3cb9b62d3f4b02c"); pub const BEACON_ROOTS_CONTRACT_ADDRESS_HASHED: [u8; 32] = hex!("37d65eaa92c6bc4c13a5ec45527f0c18ea8932588728769ec7aecfe6d9f32e42"); From 23b7a28e638c9948d7ba33319290a9b7ca957e1f Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Fri, 8 Mar 2024 06:49:59 +0100 Subject: [PATCH 05/40] LxLy exit roots (#90) * Global exit roots * fmt * Minor * Minor * PR feedback * Minor --- .../src/cpu/kernel/aggregator.rs | 1 + .../src/cpu/kernel/asm/beacon_roots.asm | 12 +- .../src/cpu/kernel/asm/global_exit_root.asm | 81 ++++++++++++++ .../src/cpu/kernel/asm/main.asm | 2 +- .../src/cpu/kernel/constants/mod.rs | 42 ++++++- .../src/cpu/kernel/tests/add11.rs | 21 +++- evm_arithmetization/src/generation/mod.rs | 2 + .../src/generation/prover_input.rs | 7 ++ evm_arithmetization/src/generation/state.rs | 18 +++ evm_arithmetization/src/testing_utils.rs | 68 ++++++++---- evm_arithmetization/src/witness/errors.rs | 1 + evm_arithmetization/tests/add11_yml.rs | 14 ++- .../tests/basic_smart_contract.rs | 58 +++++----- evm_arithmetization/tests/empty_txn_list.rs | 26 +++-- evm_arithmetization/tests/erc20.rs | 12 +- evm_arithmetization/tests/erc721.rs | 12 +- evm_arithmetization/tests/global_exit_root.rs | 104 ++++++++++++++++++ evm_arithmetization/tests/log_opcode.rs | 12 +- .../tests/self_balance_gas_cost.rs | 14 ++- evm_arithmetization/tests/selfdestruct.rs | 13 ++- evm_arithmetization/tests/simple_transfer.rs | 46 ++++---- evm_arithmetization/tests/withdrawals.rs | 12 +- trace_decoder/src/decoding.rs | 2 + 23 files changed, 450 insertions(+), 130 deletions(-) create mode 100644 evm_arithmetization/src/cpu/kernel/asm/global_exit_root.asm create mode 100644 evm_arithmetization/tests/global_exit_root.rs diff --git a/evm_arithmetization/src/cpu/kernel/aggregator.rs b/evm_arithmetization/src/cpu/kernel/aggregator.rs index 899d30c48..3620fdea3 100644 --- a/evm_arithmetization/src/cpu/kernel/aggregator.rs +++ b/evm_arithmetization/src/cpu/kernel/aggregator.rs @@ -162,6 +162,7 @@ pub(crate) fn combined_kernel() -> Kernel { include_str!("asm/account_code.asm"), include_str!("asm/balance.asm"), include_str!("asm/bloom_filter.asm"), + include_str!("asm/global_exit_root.asm"), ]; let parsed_files = files.iter().map(|f| parse(f)).collect_vec(); diff --git a/evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm b/evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm index edcd53a3e..75962aefe 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm @@ -2,20 +2,20 @@ /// global set_beacon_root: - PUSH start_txn + PUSH set_global_exit_roots %timestamp - // stack: timestamp, start_txns + // stack: timestamp, retdest PUSH @HISTORY_BUFFER_LENGTH DUP2 - // stack: timestamp, 8191, timestamp, start_txns + // stack: timestamp, 8191, timestamp, retdest MOD - // stack: timestamp_idx, timestamp, start_txns + // stack: timestamp_idx, timestamp, retdest PUSH write_beacon_roots_to_storage %parent_beacon_block_root - // stack: calldata, write_beacon_roots_to_storage, timestamp_idx, timestamp, start_txns + // stack: calldata, write_beacon_roots_to_storage, timestamp_idx, timestamp, retdest DUP3 %add_const(@HISTORY_BUFFER_LENGTH) - // stack: root_idx, calldata, write_beacon_roots_to_storage, timestamp_idx, timestamp, start_txns + // stack: root_idx, calldata, write_beacon_roots_to_storage, timestamp_idx, timestamp, retdest write_beacon_roots_to_storage: // stack: slot, value, retdest diff --git a/evm_arithmetization/src/cpu/kernel/asm/global_exit_root.asm b/evm_arithmetization/src/cpu/kernel/asm/global_exit_root.asm new file mode 100644 index 000000000..c5cbfbdc9 --- /dev/null +++ b/evm_arithmetization/src/cpu/kernel/asm/global_exit_root.asm @@ -0,0 +1,81 @@ +/// At the top of the block, the global exit roots (if any) are written to storage. +/// Global exit roots (GER) are of the form `(timestamp, root)` and are loaded from prover inputs. +/// The timestamp is written to the storage of address `ADDRESS_GLOBAL_EXIT_ROOT_MANAGER_L2` in the slot `keccak256(abi.encodePacked(root, GLOBAL_EXIT_ROOT_STORAGE_POS))`. +/// See https://github.com/0xPolygonHermez/cdk-erigon/blob/zkevm/zk/utils/global_exit_root.go for reference. + +global set_global_exit_roots: + // stack: (empty) + PUSH start_txn + // stack: retdest + PROVER_INPUT(ger) + // stack: num_ger, retdest + PUSH 0 +ger_loop: + // stack: i, num_ger, retdest + DUP2 DUP2 EQ %jumpi(ger_loop_end) + PROVER_INPUT(ger) + // stack: timestamp, i, num_ger, retdest + PUSH @GLOBAL_EXIT_ROOT_STORAGE_POS + PROVER_INPUT(ger) + // stack: root, GLOBAL_EXIT_ROOT_STORAGE_POS, timestamp, i, num_ger, retdest + PUSH @SEGMENT_KERNEL_GENERAL + // stack: addr, root, GLOBAL_EXIT_ROOT_STORAGE_POS, timestamp, i, num_ger, retdest + MSTORE_32BYTES_32 + // stack: addr, GLOBAL_EXIT_ROOT_STORAGE_POS, timestamp, i, num_ger, retdest + MSTORE_32BYTES_32 + // stack: addr, timestamp, i, num_ger, retdest + POP + // stack: timestamp, i, num_ger, retdest + PUSH 64 PUSH @SEGMENT_KERNEL_GENERAL + // stack: addr, len, timestamp, i, num_ger, retdest + KECCAK_GENERAL + // stack: slot, timestamp, i, num_ger, retdest + +write_timestamp_to_storage: + // stack: slot, timestamp, i, num_ger, retdest + // First we write the value to MPT data, and get a pointer to it. + %get_trie_data_size + // stack: value_ptr, slot, timestamp, i, num_ger, retdest + SWAP2 + // stack: timestamp, slot, value_ptr, i, num_ger, retdest + %append_to_trie_data + // stack: slot, value_ptr, i, num_ger, retdest + + // Next, call mpt_insert on the current account's storage root. + %stack (slot, value_ptr) -> (slot, value_ptr, after_timestamp_storage_insert) + %slot_to_storage_key + // stack: storage_key, value_ptr, after_timestamp_storage_insert + PUSH 64 // storage_key has 64 nibbles + %get_storage_trie(@ADDRESS_GLOBAL_EXIT_ROOT_MANAGER_L2) + // stack: storage_root_ptr, 64, storage_key, value_ptr, after_timestamp_storage_insert + %stack (storage_root_ptr, num_nibbles, storage_key) -> (storage_root_ptr, num_nibbles, storage_key, after_read, storage_root_ptr, num_nibbles, storage_key) + %jump(mpt_read) +after_read: + // If the current value is non-zero, do nothing. + // stack: current_value_ptr, storage_root_ptr, 64, storage_key, value_ptr, after_timestamp_storage_insert + %mload_trie_data %jumpi(do_nothing) + // stack: storage_root_ptr, 64, storage_key, value_ptr, after_timestamp_storage_insert + %jump(mpt_insert) + +after_timestamp_storage_insert: + // stack: new_storage_root_ptr, i, num_ger, retdest + %get_account_data(@ADDRESS_GLOBAL_EXIT_ROOT_MANAGER_L2) + // stack: account_ptr, new_storage_root_ptr + // Update the copied account with our new storage root pointer. + %add_const(2) + // stack: account_storage_root_ptr_ptr, new_storage_root_ptr + %mstore_trie_data + + // stack: i, num_ger, retdest + %increment + %jump(ger_loop) + +ger_loop_end: + // stack: i, num_ger, retdest + %pop2 JUMP + +do_nothing: + // stack: storage_root_ptr, 64, storage_key, value_ptr, after_timestamp_storage_insert, i, num_ger, retdest + %pop7 + // stack: retdest + JUMP diff --git a/evm_arithmetization/src/cpu/kernel/asm/main.asm b/evm_arithmetization/src/cpu/kernel/asm/main.asm index 4d91a1872..78cbd6887 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/main.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/main.asm @@ -45,7 +45,7 @@ global hash_initial_tries: // stack: trie_data_full_len %mstore_global_metadata(@GLOBAL_METADATA_TRIE_DATA_SIZE) - // If txn_idx == 0, update the beacon_root. + // If txn_idx == 0, update the beacon_root and exit roots. %mload_global_metadata(@GLOBAL_METADATA_TXN_NUMBER_BEFORE) ISZERO %jumpi(set_beacon_root) diff --git a/evm_arithmetization/src/cpu/kernel/constants/mod.rs b/evm_arithmetization/src/cpu/kernel/constants/mod.rs index a1ea02e61..1129078ae 100644 --- a/evm_arithmetization/src/cpu/kernel/constants/mod.rs +++ b/evm_arithmetization/src/cpu/kernel/constants/mod.rs @@ -66,6 +66,17 @@ pub(crate) fn evm_constants() -> HashMap { cancun_constants::HISTORY_BUFFER_LENGTH.1.into(), ); + c.insert( + global_exit_root::ADDRESS_GLOBAL_EXIT_ROOT_MANAGER_L2 + .0 + .into(), + U256::from_big_endian(&global_exit_root::ADDRESS_GLOBAL_EXIT_ROOT_MANAGER_L2.1), + ); + c.insert( + global_exit_root::GLOBAL_EXIT_ROOT_STORAGE_POS.0.into(), + U256::from(global_exit_root::GLOBAL_EXIT_ROOT_STORAGE_POS.1), + ); + for segment in Segment::all() { c.insert(segment.var_name().into(), (segment as usize).into()); } @@ -328,8 +339,37 @@ pub mod cancun_constants { balance: U256::zero(), // Storage root for this account at genesis. storage_root: H256(hex!( - "f58e5f1eae7ce386de44266ff0286a88dafe7e4269c1ffa97f79dbbcf4f59e5c" + "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" )), code_hash: H256(BEACON_ROOTS_CONTRACT_CODE_HASH), }; } + +pub mod global_exit_root { + use super::*; + + /// Taken from https://github.com/0xPolygonHermez/cdk-erigon/blob/61f0b6912055c73f6879ea7e9b5bac22ea5fc85c/zk/utils/global_exit_root.go#L16. + pub const ADDRESS_GLOBAL_EXIT_ROOT_MANAGER_L2: (&str, [u8; 20]) = ( + "ADDRESS_GLOBAL_EXIT_ROOT_MANAGER_L2", + hex!("a40D5f56745a118D0906a34E69aeC8C0Db1cB8fA"), + ); + /// Taken from https://github.com/0xPolygonHermez/cdk-erigon/blob/61f0b6912055c73f6879ea7e9b5bac22ea5fc85c/zk/utils/global_exit_root.go#L17. + pub const GLOBAL_EXIT_ROOT_STORAGE_POS: (&str, u64) = ("GLOBAL_EXIT_ROOT_STORAGE_POS", 0); + + /// Taken from https://zkevm.polygonscan.com/address/0xa40D5f56745a118D0906a34E69aeC8C0Db1cB8fA#code. + pub const GLOBAL_EXIT_ROOT_CONTRACT_CODE: [u8; 2112] = hex!("60806040526004361061004e5760003560e01c80633659cfe6146100655780634f1ef286146100855780635c60da1b146100985780638f283970146100c9578063f851a440146100e95761005d565b3661005d5761005b6100fe565b005b61005b6100fe565b34801561007157600080fd5b5061005b6100803660046106ca565b610118565b61005b6100933660046106e5565b61015f565b3480156100a457600080fd5b506100ad6101d0565b6040516001600160a01b03909116815260200160405180910390f35b3480156100d557600080fd5b5061005b6100e43660046106ca565b61020b565b3480156100f557600080fd5b506100ad610235565b610106610292565b610116610111610331565b61033b565b565b61012061035f565b6001600160a01b0316336001600160a01b031614156101575761015481604051806020016040528060008152506000610392565b50565b6101546100fe565b61016761035f565b6001600160a01b0316336001600160a01b031614156101c8576101c38383838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525060019250610392915050565b505050565b6101c36100fe565b60006101da61035f565b6001600160a01b0316336001600160a01b03161415610200576101fb610331565b905090565b6102086100fe565b90565b61021361035f565b6001600160a01b0316336001600160a01b0316141561015757610154816103f1565b600061023f61035f565b6001600160a01b0316336001600160a01b03161415610200576101fb61035f565b606061028583836040518060600160405280602781526020016107e460279139610445565b9392505050565b3b151590565b61029a61035f565b6001600160a01b0316336001600160a01b031614156101165760405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b60006101fb610519565b3660008037600080366000845af43d6000803e80801561035a573d6000f35b3d6000fd5b60007fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b61039b83610541565b6040516001600160a01b038416907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a26000825111806103dc5750805b156101c3576103eb8383610260565b50505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f61041a61035f565b604080516001600160a01b03928316815291841660208301520160405180910390a1610154816105e9565b6060833b6104a45760405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b6064820152608401610328565b600080856001600160a01b0316856040516104bf9190610794565b600060405180830381855af49150503d80600081146104fa576040519150601f19603f3d011682016040523d82523d6000602084013e6104ff565b606091505b509150915061050f828286610675565b9695505050505050565b60007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc610383565b803b6105a55760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610328565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5b80546001600160a01b0319166001600160a01b039290921691909117905550565b6001600160a01b03811661064e5760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b6064820152608401610328565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61036105c8565b60608315610684575081610285565b8251156106945782518084602001fd5b8160405162461bcd60e51b815260040161032891906107b0565b80356001600160a01b03811681146106c557600080fd5b919050565b6000602082840312156106dc57600080fd5b610285826106ae565b6000806000604084860312156106fa57600080fd5b610703846106ae565b9250602084013567ffffffffffffffff8082111561072057600080fd5b818601915086601f83011261073457600080fd5b81358181111561074357600080fd5b87602082850101111561075557600080fd5b6020830194508093505050509250925092565b60005b8381101561078357818101518382015260200161076b565b838111156103eb5750506000910152565b600082516107a6818460208701610768565b9190910192915050565b60208152600082518060208401526107cf816040850160208701610768565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212204675187caf3a43285d9a2c1844a981e977bd52a85ff073e7fc649f73847d70a464736f6c63430008090033"); + pub const GLOBAL_EXIT_ROOT_CONTRACT_CODE_HASH: [u8; 32] = + hex!("6bec2bf64f7e824109f6ed55f77dd7665801d6195e461666ad6a5342a9f6daf5"); + pub const GLOBAL_EXIT_ROOT_ADDRESS_HASHED: [u8; 32] = + hex!("1d5e9c22b4b1a781d0ef63e9c1293c2a45fee966809019aa9804b5e7148b0ca9"); + + pub const GLOBAL_EXIT_ROOT_ACCOUNT: AccountRlp = AccountRlp { + nonce: U256::zero(), + balance: U256::zero(), + // Empty storage root + storage_root: H256(hex!( + "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + )), + code_hash: H256(GLOBAL_EXIT_ROOT_CONTRACT_CODE_HASH), + }; +} diff --git a/evm_arithmetization/src/cpu/kernel/tests/add11.rs b/evm_arithmetization/src/cpu/kernel/tests/add11.rs index e35c707b4..5bbddcacc 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/add11.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/add11.rs @@ -14,8 +14,9 @@ use crate::generation::mpt::{AccountRlp, LegacyReceiptRlp}; use crate::generation::TrieInputs; use crate::proof::{BlockHashes, BlockMetadata, TrieRoots}; use crate::testing_utils::{ - beacon_roots_account_nibbles, beacon_roots_contract_from_storage, - initial_state_and_storage_tries_with_beacon_roots, update_beacon_roots_account_storage, + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, ger_account_nibbles, + preinitialized_state_and_storage_tries, update_beacon_roots_account_storage, + GLOBAL_EXIT_ROOT_ACCOUNT, }; use crate::GenerationInputs; @@ -54,8 +55,7 @@ fn test_add11_yml() { ..AccountRlp::default() }; - let (mut state_trie_before, mut storage_tries) = - initial_state_and_storage_tries_with_beacon_roots(); + let (mut state_trie_before, mut storage_tries) = preinitialized_state_and_storage_tries(); let mut beacon_roots_account_storage = storage_tries[0].1.clone(); state_trie_before.insert( beneficiary_nibbles, @@ -132,6 +132,10 @@ fn test_add11_yml() { beacon_roots_account_nibbles(), rlp::encode(&beacon_roots_account).to_vec(), ); + expected_state_trie_after.insert( + ger_account_nibbles(), + rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), + ); expected_state_trie_after }; let receipt_0 = LegacyReceiptRlp { @@ -160,6 +164,7 @@ fn test_add11_yml() { let inputs = GenerationInputs { signed_txn: Some(txn.to_vec()), withdrawals: vec![], + global_exit_roots: vec![], tries: tries_before, trie_roots_after, contract_code: contract_code.clone(), @@ -220,8 +225,7 @@ fn test_add11_yml_with_exception() { ..AccountRlp::default() }; - let (mut state_trie_before, mut storage_tries) = - initial_state_and_storage_tries_with_beacon_roots(); + let (mut state_trie_before, mut storage_tries) = preinitialized_state_and_storage_tries(); let mut beacon_roots_account_storage = storage_tries[0].1.clone(); state_trie_before.insert( beneficiary_nibbles, @@ -290,6 +294,10 @@ fn test_add11_yml_with_exception() { beacon_roots_account_nibbles(), rlp::encode(&beacon_roots_account).to_vec(), ); + expected_state_trie_after.insert( + ger_account_nibbles(), + rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), + ); expected_state_trie_after }; @@ -319,6 +327,7 @@ fn test_add11_yml_with_exception() { let inputs = GenerationInputs { signed_txn: Some(txn.to_vec()), withdrawals: vec![], + global_exit_roots: vec![], tries: tries_before, trie_roots_after, contract_code: contract_code.clone(), diff --git a/evm_arithmetization/src/generation/mod.rs b/evm_arithmetization/src/generation/mod.rs index ee3e090b2..a8165dd5d 100644 --- a/evm_arithmetization/src/generation/mod.rs +++ b/evm_arithmetization/src/generation/mod.rs @@ -55,6 +55,8 @@ pub struct GenerationInputs { /// Withdrawal pairs `(addr, amount)`. At the end of the txs, `amount` is /// added to `addr`'s balance. See EIP-4895. pub withdrawals: Vec<(Address, U256)>, + /// Global exit roots pairs `(timestamp, root)`. + pub global_exit_roots: Vec<(U256, H256)>, pub tries: TrieInputs, /// Expected trie roots after the transactions are executed. pub trie_roots_after: TrieRoots, diff --git a/evm_arithmetization/src/generation/prover_input.rs b/evm_arithmetization/src/generation/prover_input.rs index ebffadc8a..13c4f5693 100644 --- a/evm_arithmetization/src/generation/prover_input.rs +++ b/evm_arithmetization/src/generation/prover_input.rs @@ -55,6 +55,7 @@ impl GenerationState { "num_bits" => self.run_num_bits(), "jumpdest_table" => self.run_jumpdest_table(input_fn), "access_lists" => self.run_access_lists(input_fn), + "ger" => self.run_global_exit_roots(), _ => Err(ProgramError::ProverInputError(InvalidFunction)), } } @@ -262,6 +263,12 @@ impl GenerationState { } } + fn run_global_exit_roots(&mut self) -> Result { + self.ger_prover_inputs + .pop() + .ok_or(ProgramError::ProverInputError(OutOfGerData)) + } + /// Returns the next used jump address. fn run_next_jumpdest_table_address(&mut self) -> Result { let context = u256_to_usize(stack_peek(self, 0)? >> CONTEXT_SCALING_FACTOR)?; diff --git a/evm_arithmetization/src/generation/state.rs b/evm_arithmetization/src/generation/state.rs index 37fca48d3..dae118f0a 100644 --- a/evm_arithmetization/src/generation/state.rs +++ b/evm_arithmetization/src/generation/state.rs @@ -277,6 +277,8 @@ pub(crate) struct GenerationState { pub(crate) withdrawal_prover_inputs: Vec, + pub(crate) ger_prover_inputs: Vec, + /// The state trie only stores state keys, which are hashes of addresses, /// but sometimes it is useful to see the actual addresses for /// debugging. Here we store the mapping for all known addresses. @@ -313,6 +315,7 @@ impl GenerationState { let rlp_prover_inputs = all_rlp_prover_inputs_reversed(inputs.clone().signed_txn.as_ref().unwrap_or(&vec![])); let withdrawal_prover_inputs = all_withdrawals_prover_inputs_reversed(&inputs.withdrawals); + let ger_prover_inputs = all_ger_prover_inputs_reversed(&inputs.global_exit_roots); let bignum_modmul_result_limbs = Vec::new(); let mut state = Self { @@ -322,6 +325,7 @@ impl GenerationState { traces: Traces::default(), rlp_prover_inputs, withdrawal_prover_inputs, + ger_prover_inputs, state_key_to_address: HashMap::new(), bignum_modmul_result_limbs, trie_root_ptrs: TrieRootPtrs { @@ -411,6 +415,7 @@ impl GenerationState { state_key_to_address: self.state_key_to_address.clone(), bignum_modmul_result_limbs: self.bignum_modmul_result_limbs.clone(), withdrawal_prover_inputs: self.withdrawal_prover_inputs.clone(), + ger_prover_inputs: self.ger_prover_inputs.clone(), trie_root_ptrs: TrieRootPtrs { state_root_ptr: 0, txn_root_ptr: 0, @@ -607,3 +612,16 @@ pub(crate) fn all_withdrawals_prover_inputs_reversed(withdrawals: &[(Address, U2 withdrawal_prover_inputs.reverse(); withdrawal_prover_inputs } + +/// Global exit roots prover input array is of the form `[N, timestamp1, +/// root1,..., timestampN, rootN]`. Returns the reversed array. +pub(crate) fn all_ger_prover_inputs_reversed(global_exit_roots: &[(U256, H256)]) -> Vec { + let mut ger_prover_inputs = vec![global_exit_roots.len().into()]; + ger_prover_inputs.extend( + global_exit_roots + .iter() + .flat_map(|ger| [ger.0, ger.1.into_uint()]), + ); + ger_prover_inputs.reverse(); + ger_prover_inputs +} diff --git a/evm_arithmetization/src/testing_utils.rs b/evm_arithmetization/src/testing_utils.rs index c7371b4b6..f70a76e4a 100644 --- a/evm_arithmetization/src/testing_utils.rs +++ b/evm_arithmetization/src/testing_utils.rs @@ -2,7 +2,7 @@ //! unit and integration tests. use env_logger::{try_init_from_env, Env, DEFAULT_FILTER_ENV}; -use ethereum_types::{H256, U256}; +use ethereum_types::{BigEndianHash, H256, U256}; use hex_literal::hex; use keccak_hash::keccak; use mpt_trie::{ @@ -11,6 +11,9 @@ use mpt_trie::{ }; pub use crate::cpu::kernel::cancun_constants::*; +pub use crate::cpu::kernel::constants::global_exit_root::{ + GLOBAL_EXIT_ROOT_ACCOUNT, GLOBAL_EXIT_ROOT_ADDRESS_HASHED, GLOBAL_EXIT_ROOT_STORAGE_POS, +}; use crate::{generation::mpt::AccountRlp, util::h2u}; pub const EMPTY_NODE_HASH: H256 = H256(hex!( @@ -85,26 +88,27 @@ pub fn beacon_roots_contract_from_storage(storage_trie: &HashedPartialTrie) -> A } } -/// Returns an initial state trie containing nothing but the beacon roots -/// contract, along with its storage trie. -pub fn initial_state_and_storage_tries_with_beacon_roots( +/// Returns an initial state trie containing the beacon roots and global exit +/// roots contracts, along with their storage tries. +pub fn preinitialized_state_and_storage_tries( ) -> (HashedPartialTrie, Vec<(H256, HashedPartialTrie)>) { - let state_trie = Node::Leaf { - nibbles: Nibbles::from_bytes_be(&BEACON_ROOTS_CONTRACT_ADDRESS_HASHED).unwrap(), - value: rlp::encode(&AccountRlp { - nonce: 0.into(), - balance: 0.into(), - storage_root: EMPTY_NODE_HASH, - code_hash: H256(BEACON_ROOTS_CONTRACT_CODE_HASH), - }) - .to_vec(), - } - .into(); - - let storage_tries = vec![( - H256(BEACON_ROOTS_CONTRACT_ADDRESS_HASHED), - Node::Empty.into(), - )]; + let mut state_trie = HashedPartialTrie::from(Node::Empty); + state_trie.insert( + beacon_roots_account_nibbles(), + rlp::encode(&BEACON_ROOTS_ACCOUNT).to_vec(), + ); + state_trie.insert( + ger_account_nibbles(), + rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), + ); + + let storage_tries = vec![ + ( + H256(BEACON_ROOTS_CONTRACT_ADDRESS_HASHED), + Node::Empty.into(), + ), + (H256(GLOBAL_EXIT_ROOT_ADDRESS_HASHED), Node::Empty.into()), + ]; (state_trie, storage_tries) } @@ -114,6 +118,30 @@ pub fn beacon_roots_account_nibbles() -> Nibbles { Nibbles::from_bytes_be(&BEACON_ROOTS_CONTRACT_ADDRESS_HASHED).unwrap() } +/// Returns the `Nibbles` corresponding to the beacon roots contract account. +pub fn ger_account_nibbles() -> Nibbles { + Nibbles::from_bytes_be(&GLOBAL_EXIT_ROOT_ADDRESS_HASHED).unwrap() +} + +pub fn update_ger_account_storage( + storage_trie: &mut HashedPartialTrie, + root: H256, + timestamp: U256, +) { + let mut arr = [0; 64]; + arr[0..32].copy_from_slice(&root.0); + U256::from(GLOBAL_EXIT_ROOT_STORAGE_POS.1).to_big_endian(&mut arr[32..64]); + let slot = keccak(arr); + insert_storage(storage_trie, slot.into_uint(), timestamp); +} + +pub fn ger_contract_from_storage(storage_trie: &HashedPartialTrie) -> AccountRlp { + AccountRlp { + storage_root: storage_trie.hash(), + ..GLOBAL_EXIT_ROOT_ACCOUNT + } +} + /// Converts an amount in `ETH` to `wei` units. pub fn eth_to_wei(eth: U256) -> U256 { // 1 ether = 10^18 wei. diff --git a/evm_arithmetization/src/witness/errors.rs b/evm_arithmetization/src/witness/errors.rs index 1b266aefd..90bdb3f8a 100644 --- a/evm_arithmetization/src/witness/errors.rs +++ b/evm_arithmetization/src/witness/errors.rs @@ -31,6 +31,7 @@ pub enum ProverInputError { OutOfMptData, OutOfRlpData, OutOfWithdrawalData, + OutOfGerData, CodeHashNotFound, InvalidMptInput, InvalidInput, diff --git a/evm_arithmetization/tests/add11_yml.rs b/evm_arithmetization/tests/add11_yml.rs index 67b313ec8..e59be51ba 100644 --- a/evm_arithmetization/tests/add11_yml.rs +++ b/evm_arithmetization/tests/add11_yml.rs @@ -8,8 +8,9 @@ use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::prove; use evm_arithmetization::testing_utils::{ - beacon_roots_account_nibbles, beacon_roots_contract_from_storage, init_logger, - initial_state_and_storage_tries_with_beacon_roots, update_beacon_roots_account_storage, + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, ger_account_nibbles, + init_logger, preinitialized_state_and_storage_tries, update_beacon_roots_account_storage, + GLOBAL_EXIT_ROOT_ACCOUNT, }; use evm_arithmetization::verifier::verify_proof; use evm_arithmetization::{AllStark, Node, StarkConfig}; @@ -62,8 +63,7 @@ fn add11_yml() -> anyhow::Result<()> { ..AccountRlp::default() }; - let (mut state_trie_before, mut storage_tries) = - initial_state_and_storage_tries_with_beacon_roots(); + let (mut state_trie_before, mut storage_tries) = preinitialized_state_and_storage_tries(); let mut beacon_roots_account_storage = storage_tries[0].1.clone(); state_trie_before.insert( beneficiary_nibbles, @@ -143,6 +143,11 @@ fn add11_yml() -> anyhow::Result<()> { beacon_roots_account_nibbles(), rlp::encode(&beacon_roots_account).to_vec(), ); + expected_state_trie_after.insert( + ger_account_nibbles(), + rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), + ); + expected_state_trie_after }; @@ -171,6 +176,7 @@ fn add11_yml() -> anyhow::Result<()> { let inputs = GenerationInputs { signed_txn: Some(txn.to_vec()), withdrawals: vec![], + global_exit_roots: vec![], tries: tries_before, trie_roots_after, contract_code, diff --git a/evm_arithmetization/tests/basic_smart_contract.rs b/evm_arithmetization/tests/basic_smart_contract.rs index 06b83d711..2c880a60f 100644 --- a/evm_arithmetization/tests/basic_smart_contract.rs +++ b/evm_arithmetization/tests/basic_smart_contract.rs @@ -9,8 +9,9 @@ use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::prove; use evm_arithmetization::testing_utils::{ - beacon_roots_account_nibbles, beacon_roots_contract_from_storage, eth_to_wei, init_logger, - initial_state_and_storage_tries_with_beacon_roots, update_beacon_roots_account_storage, + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, eth_to_wei, + ger_account_nibbles, init_logger, preinitialized_state_and_storage_tries, + update_beacon_roots_account_storage, GLOBAL_EXIT_ROOT_ACCOUNT, }; use evm_arithmetization::verifier::verify_proof; use evm_arithmetization::{AllStark, Node, StarkConfig}; @@ -68,8 +69,7 @@ fn test_basic_smart_contract() -> anyhow::Result<()> { ..AccountRlp::default() }; - let (mut state_trie_before, storage_tries) = - initial_state_and_storage_tries_with_beacon_roots(); + let (mut state_trie_before, storage_tries) = preinitialized_state_and_storage_tries(); let mut beacon_roots_account_storage = storage_tries[0].1.clone(); state_trie_before.insert( @@ -110,7 +110,8 @@ fn test_basic_smart_contract() -> anyhow::Result<()> { contract_code.insert(keccak(vec![]), vec![]); contract_code.insert(code_hash, code.to_vec()); - let expected_state_trie_after: HashedPartialTrie = { + let expected_state_trie_after = { + let mut state_trie_after = HashedPartialTrie::from(Node::Empty); update_beacon_roots_account_storage( &mut beacon_roots_account_storage, block_metadata.block_timestamp, @@ -133,34 +134,24 @@ fn test_basic_smart_contract() -> anyhow::Result<()> { ..to_account_before }; - let mut children = core::array::from_fn(|_| Node::Empty.into()); - children[beneficiary_nibbles.get_nibble(0) as usize] = Node::Leaf { - nibbles: beneficiary_nibbles.truncate_n_nibbles_front(1), - value: rlp::encode(&beneficiary_account_after).to_vec(), - } - .into(); - children[sender_nibbles.get_nibble(0) as usize] = Node::Leaf { - nibbles: sender_nibbles.truncate_n_nibbles_front(1), - value: rlp::encode(&sender_account_after).to_vec(), - } - .into(); - children[to_nibbles.get_nibble(0) as usize] = Node::Leaf { - nibbles: to_nibbles.truncate_n_nibbles_front(1), - value: rlp::encode(&to_account_after).to_vec(), - } - .into(); - children[beacon_roots_account_nibbles().get_nibble(0) as usize] = Node::Leaf { - nibbles: beacon_roots_account_nibbles().truncate_n_nibbles_front(1), - value: rlp::encode(&beacon_roots_account).to_vec(), - } - .into(); - - Node::Branch { - children, - value: vec![], - } - } - .into(); + state_trie_after.insert( + beneficiary_nibbles, + rlp::encode(&beneficiary_account_after).to_vec(), + ); + state_trie_after.insert(sender_nibbles, rlp::encode(&sender_account_after).to_vec()); + state_trie_after.insert(to_nibbles, rlp::encode(&to_account_after).to_vec()); + + state_trie_after.insert( + beacon_roots_account_nibbles(), + rlp::encode(&beacon_roots_account).to_vec(), + ); + state_trie_after.insert( + ger_account_nibbles(), + rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), + ); + + state_trie_after + }; let receipt_0 = LegacyReceiptRlp { status: true, @@ -187,6 +178,7 @@ fn test_basic_smart_contract() -> anyhow::Result<()> { let inputs = GenerationInputs { signed_txn: Some(txn.to_vec()), withdrawals: vec![], + global_exit_roots: vec![], tries: tries_before, trie_roots_after, contract_code, diff --git a/evm_arithmetization/tests/empty_txn_list.rs b/evm_arithmetization/tests/empty_txn_list.rs index e343f7e53..a2553d1d9 100644 --- a/evm_arithmetization/tests/empty_txn_list.rs +++ b/evm_arithmetization/tests/empty_txn_list.rs @@ -6,9 +6,9 @@ use ethereum_types::{BigEndianHash, H256}; use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; use evm_arithmetization::proof::{BlockHashes, BlockMetadata, PublicValues, TrieRoots}; use evm_arithmetization::testing_utils::{ - beacon_roots_account_nibbles, beacon_roots_contract_from_storage, init_logger, - initial_state_and_storage_tries_with_beacon_roots, update_beacon_roots_account_storage, - BEACON_ROOTS_CONTRACT_ADDRESS_HASHED, + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, ger_account_nibbles, + init_logger, preinitialized_state_and_storage_tries, update_beacon_roots_account_storage, + BEACON_ROOTS_CONTRACT_ADDRESS_HASHED, GLOBAL_EXIT_ROOT_ACCOUNT, }; use evm_arithmetization::{AllRecursiveCircuits, AllStark, Node, StarkConfig}; use hex_literal::hex; @@ -41,12 +41,13 @@ fn test_empty_txn_list() -> anyhow::Result<()> { ..Default::default() }; - let (state_trie, storage_tries) = initial_state_and_storage_tries_with_beacon_roots(); + let (state_trie, storage_tries) = preinitialized_state_and_storage_tries(); let mut beacon_roots_account_storage = storage_tries[0].1.clone(); let transactions_trie = HashedPartialTrie::from(Node::Empty); let receipts_trie = HashedPartialTrie::from(Node::Empty); let state_trie_after: HashedPartialTrie = { + let mut state_trie_after = HashedPartialTrie::from(Node::Empty); update_beacon_roots_account_storage( &mut beacon_roots_account_storage, block_metadata.block_timestamp, @@ -55,11 +56,16 @@ fn test_empty_txn_list() -> anyhow::Result<()> { let beacon_roots_account = beacon_roots_contract_from_storage(&beacon_roots_account_storage); - Node::Leaf { - nibbles: beacon_roots_account_nibbles(), - value: rlp::encode(&beacon_roots_account).to_vec(), - } - .into() + state_trie_after.insert( + beacon_roots_account_nibbles(), + rlp::encode(&beacon_roots_account).to_vec(), + ); + state_trie_after.insert( + ger_account_nibbles(), + rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), + ); + + state_trie_after }; let mut contract_code = HashMap::new(); @@ -76,6 +82,7 @@ fn test_empty_txn_list() -> anyhow::Result<()> { let inputs1 = GenerationInputs { signed_txn: None, withdrawals: vec![], + global_exit_roots: vec![], tries: TrieInputs { state_trie: state_trie.clone(), transactions_trie: transactions_trie.clone(), @@ -152,6 +159,7 @@ fn test_empty_txn_list() -> anyhow::Result<()> { let inputs2 = GenerationInputs { signed_txn: None, withdrawals: vec![], + global_exit_roots: vec![], tries: TrieInputs { state_trie: state_trie_after, transactions_trie, diff --git a/evm_arithmetization/tests/erc20.rs b/evm_arithmetization/tests/erc20.rs index c3235dd9b..88f9dcbdc 100644 --- a/evm_arithmetization/tests/erc20.rs +++ b/evm_arithmetization/tests/erc20.rs @@ -8,8 +8,8 @@ use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::prove; use evm_arithmetization::testing_utils::{ beacon_roots_account_nibbles, beacon_roots_contract_from_storage, create_account_storage, - init_logger, initial_state_and_storage_tries_with_beacon_roots, sd2u, - update_beacon_roots_account_storage, + ger_account_nibbles, init_logger, preinitialized_state_and_storage_tries, sd2u, + update_beacon_roots_account_storage, GLOBAL_EXIT_ROOT_ACCOUNT, }; use evm_arithmetization::verifier::verify_proof; use evm_arithmetization::{AllStark, Node, StarkConfig}; @@ -65,8 +65,7 @@ fn test_erc20() -> anyhow::Result<()> { let giver_nibbles = Nibbles::from_bytes_be(giver_state_key.as_bytes()).unwrap(); let token_nibbles = Nibbles::from_bytes_be(token_state_key.as_bytes()).unwrap(); - let (mut state_trie_before, mut storage_tries) = - initial_state_and_storage_tries_with_beacon_roots(); + let (mut state_trie_before, mut storage_tries) = preinitialized_state_and_storage_tries(); let mut beacon_roots_account_storage = storage_tries[0].1.clone(); state_trie_before.insert(sender_nibbles, rlp::encode(&sender_account()).to_vec()); state_trie_before.insert(giver_nibbles, rlp::encode(&giver_account()).to_vec()); @@ -134,6 +133,10 @@ fn test_erc20() -> anyhow::Result<()> { beacon_roots_account_nibbles(), rlp::encode(&beacon_roots_account).to_vec(), ); + state_trie_after.insert( + ger_account_nibbles(), + rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), + ); state_trie_after }; @@ -179,6 +182,7 @@ fn test_erc20() -> anyhow::Result<()> { let inputs = GenerationInputs { signed_txn: Some(txn.to_vec()), withdrawals: vec![], + global_exit_roots: vec![], tries: tries_before, trie_roots_after, contract_code, diff --git a/evm_arithmetization/tests/erc721.rs b/evm_arithmetization/tests/erc721.rs index 29db8f1a8..db560d84e 100644 --- a/evm_arithmetization/tests/erc721.rs +++ b/evm_arithmetization/tests/erc721.rs @@ -8,8 +8,8 @@ use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::prove; use evm_arithmetization::testing_utils::{ beacon_roots_account_nibbles, beacon_roots_contract_from_storage, create_account_storage, - init_logger, initial_state_and_storage_tries_with_beacon_roots, sd2u, sh2u, - update_beacon_roots_account_storage, + ger_account_nibbles, init_logger, preinitialized_state_and_storage_tries, sd2u, sh2u, + update_beacon_roots_account_storage, GLOBAL_EXIT_ROOT_ACCOUNT, }; use evm_arithmetization::verifier::verify_proof; use evm_arithmetization::{AllStark, Node, StarkConfig}; @@ -65,8 +65,7 @@ fn test_erc721() -> anyhow::Result<()> { let owner_nibbles = Nibbles::from_bytes_be(owner_state_key.as_bytes()).unwrap(); let contract_nibbles = Nibbles::from_bytes_be(contract_state_key.as_bytes()).unwrap(); - let (mut state_trie_before, mut storage_tries) = - initial_state_and_storage_tries_with_beacon_roots(); + let (mut state_trie_before, mut storage_tries) = preinitialized_state_and_storage_tries(); let mut beacon_roots_account_storage = storage_tries[0].1.clone(); state_trie_before.insert(owner_nibbles, rlp::encode(&owner_account()).to_vec()); state_trie_before.insert(contract_nibbles, rlp::encode(&contract_account()).to_vec()); @@ -156,6 +155,10 @@ fn test_erc721() -> anyhow::Result<()> { beacon_roots_account_nibbles(), rlp::encode(&beacon_roots_account).to_vec(), ); + state_trie_after.insert( + ger_account_nibbles(), + rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), + ); state_trie_after }; @@ -183,6 +186,7 @@ fn test_erc721() -> anyhow::Result<()> { let inputs = GenerationInputs { signed_txn: Some(txn.to_vec()), withdrawals: vec![], + global_exit_roots: vec![], tries: tries_before, trie_roots_after, contract_code, diff --git a/evm_arithmetization/tests/global_exit_root.rs b/evm_arithmetization/tests/global_exit_root.rs new file mode 100644 index 000000000..74b4e15c0 --- /dev/null +++ b/evm_arithmetization/tests/global_exit_root.rs @@ -0,0 +1,104 @@ +use std::collections::HashMap; +use std::time::Duration; + +use ethereum_types::{H256, U256}; +use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; +use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; +use evm_arithmetization::prover::prove; +use evm_arithmetization::testing_utils::{ + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, ger_account_nibbles, + ger_contract_from_storage, init_logger, preinitialized_state_and_storage_tries, + update_beacon_roots_account_storage, update_ger_account_storage, +}; +use evm_arithmetization::verifier::verify_proof; +use evm_arithmetization::{AllStark, Node, StarkConfig}; +use keccak_hash::keccak; +use mpt_trie::partial_trie::{HashedPartialTrie, PartialTrie}; +use plonky2::field::goldilocks_field::GoldilocksField; +use plonky2::plonk::config::PoseidonGoldilocksConfig; +use plonky2::util::timing::TimingTree; +use rand::random; + +type F = GoldilocksField; +const D: usize = 2; +type C = PoseidonGoldilocksConfig; + +/// Add a new Global Exit Root to the state trie. +#[test] +fn test_global_exit_root() -> anyhow::Result<()> { + init_logger(); + + let all_stark = AllStark::::default(); + let config = StarkConfig::standard_fast_config(); + + let block_metadata = BlockMetadata::default(); + + let (state_trie_before, storage_tries) = preinitialized_state_and_storage_tries(); + let mut beacon_roots_account_storage = storage_tries[0].1.clone(); + let mut ger_account_storage = storage_tries[1].1.clone(); + let transactions_trie = HashedPartialTrie::from(Node::Empty); + let receipts_trie = HashedPartialTrie::from(Node::Empty); + + let mut contract_code = HashMap::new(); + contract_code.insert(keccak(vec![]), vec![]); + + let global_exit_roots = vec![(U256(random()), H256(random()))]; + + let state_trie_after = { + let mut trie = HashedPartialTrie::from(Node::Empty); + update_beacon_roots_account_storage( + &mut beacon_roots_account_storage, + 0.into(), + block_metadata.parent_beacon_block_root, + ); + let beacon_roots_account = + beacon_roots_contract_from_storage(&beacon_roots_account_storage); + for &(timestamp, root) in &global_exit_roots { + update_ger_account_storage(&mut ger_account_storage, root, timestamp); + } + let ger_account = ger_contract_from_storage(&ger_account_storage); + + trie.insert( + beacon_roots_account_nibbles(), + rlp::encode(&beacon_roots_account).to_vec(), + ); + trie.insert(ger_account_nibbles(), rlp::encode(&ger_account).to_vec()); + + trie + }; + + let trie_roots_after = TrieRoots { + state_root: state_trie_after.hash(), + transactions_root: transactions_trie.hash(), + receipts_root: receipts_trie.hash(), + }; + + let inputs = GenerationInputs { + signed_txn: None, + withdrawals: vec![], + global_exit_roots, + tries: TrieInputs { + state_trie: state_trie_before, + transactions_trie, + receipts_trie, + storage_tries, + }, + trie_roots_after, + contract_code, + checkpoint_state_trie_root: HashedPartialTrie::from(Node::Empty).hash(), + block_metadata, + txn_number_before: 0.into(), + gas_used_before: 0.into(), + gas_used_after: 0.into(), + block_hashes: BlockHashes { + prev_hashes: vec![H256::default(); 256], + cur_hash: H256::default(), + }, + }; + + let mut timing = TimingTree::new("prove", log::Level::Debug); + let proof = prove::(&all_stark, &config, inputs, &mut timing, None)?; + timing.filter(Duration::from_millis(100)).print(); + + verify_proof(&all_stark, proof, &config) +} diff --git a/evm_arithmetization/tests/log_opcode.rs b/evm_arithmetization/tests/log_opcode.rs index 5d3735a6b..57c99ce8a 100644 --- a/evm_arithmetization/tests/log_opcode.rs +++ b/evm_arithmetization/tests/log_opcode.rs @@ -13,7 +13,7 @@ use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::prove; use evm_arithmetization::testing_utils::{ beacon_roots_account_nibbles, beacon_roots_contract_from_storage, init_logger, - initial_state_and_storage_tries_with_beacon_roots, update_beacon_roots_account_storage, + preinitialized_state_and_storage_tries, update_beacon_roots_account_storage, BEACON_ROOTS_CONTRACT_ADDRESS_HASHED, }; use evm_arithmetization::verifier::verify_proof; @@ -88,8 +88,7 @@ fn test_log_opcodes() -> anyhow::Result<()> { }; // Initialize the state trie with three accounts. - let (mut state_trie_before, mut storage_tries) = - initial_state_and_storage_tries_with_beacon_roots(); + let (mut state_trie_before, mut storage_tries) = preinitialized_state_and_storage_tries(); let mut beacon_roots_account_storage = storage_tries[0].1.clone(); state_trie_before.insert( beneficiary_nibbles, @@ -238,6 +237,7 @@ fn test_log_opcodes() -> anyhow::Result<()> { let inputs = GenerationInputs { signed_txn: Some(txn.to_vec()), withdrawals: vec![], + global_exit_roots: vec![], tries: tries_before, trie_roots_after, contract_code, @@ -334,8 +334,7 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { // `to_account`. let gas_price = 10; let txn_value = 0xau64; - let (mut state_trie_before, storage_tries) = - initial_state_and_storage_tries_with_beacon_roots(); + let (mut state_trie_before, storage_tries) = preinitialized_state_and_storage_tries(); let mut beacon_roots_account_storage = storage_tries[0].1.clone(); state_trie_before.insert( beneficiary_nibbles, @@ -462,6 +461,7 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { let inputs_first = GenerationInputs { signed_txn: Some(txn.to_vec()), withdrawals: vec![], + global_exit_roots: vec![], tries: tries_before, trie_roots_after: tries_after, contract_code, @@ -599,6 +599,7 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { let inputs = GenerationInputs { signed_txn: Some(txn_2.to_vec()), withdrawals: vec![], + global_exit_roots: vec![], tries: tries_before, trie_roots_after: trie_roots_after.clone(), contract_code, @@ -669,6 +670,7 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { let inputs = GenerationInputs { signed_txn: None, withdrawals: vec![], + global_exit_roots: vec![], tries: TrieInputs { state_trie: expected_state_trie_after, transactions_trie: Node::Empty.into(), diff --git a/evm_arithmetization/tests/self_balance_gas_cost.rs b/evm_arithmetization/tests/self_balance_gas_cost.rs index 292c5f95e..dcbeb6902 100644 --- a/evm_arithmetization/tests/self_balance_gas_cost.rs +++ b/evm_arithmetization/tests/self_balance_gas_cost.rs @@ -8,8 +8,9 @@ use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::prove; use evm_arithmetization::testing_utils::{ - beacon_roots_account_nibbles, beacon_roots_contract_from_storage, init_logger, - initial_state_and_storage_tries_with_beacon_roots, update_beacon_roots_account_storage, + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, ger_account_nibbles, + init_logger, preinitialized_state_and_storage_tries, update_beacon_roots_account_storage, + GLOBAL_EXIT_ROOT_ACCOUNT, }; use evm_arithmetization::verifier::verify_proof; use evm_arithmetization::{AllStark, Node, StarkConfig}; @@ -76,8 +77,7 @@ fn self_balance_gas_cost() -> anyhow::Result<()> { ..AccountRlp::default() }; - let (mut state_trie_before, mut storage_tries) = - initial_state_and_storage_tries_with_beacon_roots(); + let (mut state_trie_before, mut storage_tries) = preinitialized_state_and_storage_tries(); let mut beacon_roots_account_storage = storage_tries[0].1.clone(); state_trie_before.insert( beneficiary_nibbles, @@ -161,6 +161,11 @@ fn self_balance_gas_cost() -> anyhow::Result<()> { beacon_roots_account_nibbles(), rlp::encode(&beacon_roots_account).to_vec(), ); + expected_state_trie_after.insert( + ger_account_nibbles(), + rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), + ); + expected_state_trie_after }; @@ -189,6 +194,7 @@ fn self_balance_gas_cost() -> anyhow::Result<()> { let inputs = GenerationInputs { signed_txn: Some(txn.to_vec()), withdrawals: vec![], + global_exit_roots: vec![], tries: tries_before, trie_roots_after, contract_code, diff --git a/evm_arithmetization/tests/selfdestruct.rs b/evm_arithmetization/tests/selfdestruct.rs index 921852551..7a1e10d6e 100644 --- a/evm_arithmetization/tests/selfdestruct.rs +++ b/evm_arithmetization/tests/selfdestruct.rs @@ -7,8 +7,9 @@ use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::prove; use evm_arithmetization::testing_utils::{ - beacon_roots_account_nibbles, beacon_roots_contract_from_storage, eth_to_wei, init_logger, - initial_state_and_storage_tries_with_beacon_roots, update_beacon_roots_account_storage, + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, eth_to_wei, + ger_account_nibbles, init_logger, preinitialized_state_and_storage_tries, + update_beacon_roots_account_storage, GLOBAL_EXIT_ROOT_ACCOUNT, }; use evm_arithmetization::verifier::verify_proof; use evm_arithmetization::{AllStark, Node, StarkConfig}; @@ -59,8 +60,7 @@ fn test_selfdestruct() -> anyhow::Result<()> { code_hash: keccak(&code), }; - let (mut state_trie_before, storage_tries) = - initial_state_and_storage_tries_with_beacon_roots(); + let (mut state_trie_before, storage_tries) = preinitialized_state_and_storage_tries(); let mut beacon_roots_account_storage = storage_tries[0].1.clone(); state_trie_before.insert(sender_nibbles, rlp::encode(&sender_account_before).to_vec()); state_trie_before.insert(to_nibbles, rlp::encode(&to_account_before).to_vec()); @@ -123,6 +123,10 @@ fn test_selfdestruct() -> anyhow::Result<()> { beacon_roots_account_nibbles(), rlp::encode(&beacon_roots_account).to_vec(), ); + state_trie_after.insert( + ger_account_nibbles(), + rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), + ); state_trie_after }; @@ -152,6 +156,7 @@ fn test_selfdestruct() -> anyhow::Result<()> { let inputs = GenerationInputs { signed_txn: Some(txn.to_vec()), withdrawals: vec![], + global_exit_roots: vec![], tries: tries_before, trie_roots_after, contract_code, diff --git a/evm_arithmetization/tests/simple_transfer.rs b/evm_arithmetization/tests/simple_transfer.rs index 812677516..ce760d715 100644 --- a/evm_arithmetization/tests/simple_transfer.rs +++ b/evm_arithmetization/tests/simple_transfer.rs @@ -8,8 +8,9 @@ use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::prove; use evm_arithmetization::testing_utils::{ - beacon_roots_account_nibbles, beacon_roots_contract_from_storage, eth_to_wei, init_logger, - initial_state_and_storage_tries_with_beacon_roots, update_beacon_roots_account_storage, + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, eth_to_wei, + ger_account_nibbles, init_logger, preinitialized_state_and_storage_tries, + update_beacon_roots_account_storage, GLOBAL_EXIT_ROOT_ACCOUNT, }; use evm_arithmetization::verifier::verify_proof; use evm_arithmetization::{AllStark, Node, StarkConfig}; @@ -51,8 +52,7 @@ fn test_simple_transfer() -> anyhow::Result<()> { }; let to_account_before = AccountRlp::default(); - let (mut state_trie_before, storage_tries) = - initial_state_and_storage_tries_with_beacon_roots(); + let (mut state_trie_before, storage_tries) = preinitialized_state_and_storage_tries(); let mut beacon_roots_account_storage = storage_tries[0].1.clone(); state_trie_before.insert(sender_nibbles, rlp::encode(&sender_account_before).to_vec()); @@ -85,6 +85,8 @@ fn test_simple_transfer() -> anyhow::Result<()> { contract_code.insert(keccak(vec![]), vec![]); let expected_state_trie_after: HashedPartialTrie = { + let mut state_trie_after = HashedPartialTrie::from(Node::Empty); + let txdata_gas = 2 * 16; let gas_used = 21_000 + txdata_gas; @@ -106,28 +108,19 @@ fn test_simple_transfer() -> anyhow::Result<()> { ..to_account_before }; - let mut children = core::array::from_fn(|_| Node::Empty.into()); - children[sender_nibbles.get_nibble(0) as usize] = Node::Leaf { - nibbles: sender_nibbles.truncate_n_nibbles_front(1), - value: rlp::encode(&sender_account_after).to_vec(), - } - .into(); - children[to_nibbles.get_nibble(0) as usize] = Node::Leaf { - nibbles: to_nibbles.truncate_n_nibbles_front(1), - value: rlp::encode(&to_account_after).to_vec(), - } - .into(); - children[beacon_roots_account_nibbles().get_nibble(0) as usize] = Node::Leaf { - nibbles: beacon_roots_account_nibbles().truncate_n_nibbles_front(1), - value: rlp::encode(&beacon_roots_account).to_vec(), - } - .into(); - - Node::Branch { - children, - value: vec![], - } - .into() + state_trie_after.insert(sender_nibbles, rlp::encode(&sender_account_after).to_vec()); + state_trie_after.insert(to_nibbles, rlp::encode(&to_account_after).to_vec()); + + state_trie_after.insert( + beacon_roots_account_nibbles(), + rlp::encode(&beacon_roots_account).to_vec(), + ); + state_trie_after.insert( + ger_account_nibbles(), + rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), + ); + + state_trie_after }; let receipt_0 = LegacyReceiptRlp { @@ -155,6 +148,7 @@ fn test_simple_transfer() -> anyhow::Result<()> { let inputs = GenerationInputs { signed_txn: Some(txn.to_vec()), withdrawals: vec![], + global_exit_roots: vec![], tries: tries_before, trie_roots_after, contract_code, diff --git a/evm_arithmetization/tests/withdrawals.rs b/evm_arithmetization/tests/withdrawals.rs index 56742be03..88d83374e 100644 --- a/evm_arithmetization/tests/withdrawals.rs +++ b/evm_arithmetization/tests/withdrawals.rs @@ -7,8 +7,9 @@ use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::prove; use evm_arithmetization::testing_utils::{ - beacon_roots_account_nibbles, beacon_roots_contract_from_storage, init_logger, - initial_state_and_storage_tries_with_beacon_roots, update_beacon_roots_account_storage, + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, ger_account_nibbles, + init_logger, preinitialized_state_and_storage_tries, update_beacon_roots_account_storage, + GLOBAL_EXIT_ROOT_ACCOUNT, }; use evm_arithmetization::verifier::verify_proof; use evm_arithmetization::{AllStark, Node, StarkConfig}; @@ -34,7 +35,7 @@ fn test_withdrawals() -> anyhow::Result<()> { let block_metadata = BlockMetadata::default(); - let (state_trie_before, storage_tries) = initial_state_and_storage_tries_with_beacon_roots(); + let (state_trie_before, storage_tries) = preinitialized_state_and_storage_tries(); let mut beacon_roots_account_storage = storage_tries[0].1.clone(); let transactions_trie = HashedPartialTrie::from(Node::Empty); let receipts_trie = HashedPartialTrie::from(Node::Empty); @@ -66,6 +67,10 @@ fn test_withdrawals() -> anyhow::Result<()> { beacon_roots_account_nibbles(), rlp::encode(&beacon_roots_account).to_vec(), ); + trie.insert( + ger_account_nibbles(), + rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), + ); trie }; @@ -79,6 +84,7 @@ fn test_withdrawals() -> anyhow::Result<()> { let inputs = GenerationInputs { signed_txn: None, withdrawals, + global_exit_roots: vec![], tries: TrieInputs { state_trie: state_trie_before, transactions_trie, diff --git a/trace_decoder/src/decoding.rs b/trace_decoder/src/decoding.rs index 35900b193..ea9c4ab8e 100644 --- a/trace_decoder/src/decoding.rs +++ b/trace_decoder/src/decoding.rs @@ -150,6 +150,7 @@ impl ProcessedBlockTrace { withdrawals: Vec::default(), /* Only ever set in a dummy txn at the end of * the block (see `[add_withdrawals_to_txns]` * for more info). */ + global_exit_roots: Vec::default(), // TODO tries, trie_roots_after, checkpoint_state_trie_root: extra_data.checkpoint_state_trie_root, @@ -573,6 +574,7 @@ fn create_dummy_gen_input_common( gas_used_after: extra_data.gas_used_after, contract_code: HashMap::default(), withdrawals: vec![], // this is set after creating dummy payloads + global_exit_roots: vec![], } } From e6b137cbc6b791e1ad5c3490218e77d84b7c7774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alonso=20Gonz=C3=A1lez?= Date: Sat, 9 Mar 2024 10:46:01 +0100 Subject: [PATCH 06/40] Eip 1153 (TLOAD/TSTORE) (#59) * Add transient storage * Add basic tests and fix bugs * Add more tests fix more bugs * [WIP] Add revert test * [WIP] expanding macro below global label * Fix revert test bugs * Fix revert test bugs * Add missing files * Apply suggestions from code review Co-authored-by: Robin Salen <30937548+Nashtare@users.noreply.github.com> * Address reviews * chore: fix broken tests after merge * Apply suggestions from code review Co-authored-by: Robin Salen <30937548+Nashtare@users.noreply.github.com> * Add kernel_mode false to exit kernel in tests * Fix problem with modified kernel * Clippy --------- Co-authored-by: Robin Salen <30937548+Nashtare@users.noreply.github.com> Co-authored-by: David --- .../src/cpu/kernel/aggregator.rs | 320 +++++++++-------- .../src/cpu/kernel/asm/core/exception.asm | 6 +- .../src/cpu/kernel/asm/core/syscall.asm | 6 +- .../src/cpu/kernel/asm/journal/journal.asm | 1 - .../src/cpu/kernel/asm/journal/revert.asm | 23 +- .../asm/journal/transient_storage_change.asm | 21 ++ .../kernel/asm/memory/transient_storage.asm | 158 ++++++++ .../src/cpu/kernel/constants/exc_bitfields.rs | 2 +- .../cpu/kernel/constants/global_metadata.rs | 7 +- .../src/cpu/kernel/constants/journal_entry.rs | 5 +- evm_arithmetization/src/cpu/kernel/opcodes.rs | 2 + .../src/cpu/kernel/stack/permutations.rs | 2 +- .../src/cpu/kernel/tests/checkpoint_label.asm | 5 + .../src/cpu/kernel/tests/mod.rs | 1 + .../src/cpu/kernel/tests/transient_storage.rs | 339 ++++++++++++++++++ evm_arithmetization/src/memory/segments.rs | 11 +- evm_arithmetization/src/witness/transition.rs | 2 + 17 files changed, 730 insertions(+), 181 deletions(-) create mode 100644 evm_arithmetization/src/cpu/kernel/asm/journal/transient_storage_change.asm create mode 100644 evm_arithmetization/src/cpu/kernel/asm/memory/transient_storage.asm create mode 100644 evm_arithmetization/src/cpu/kernel/tests/checkpoint_label.asm create mode 100644 evm_arithmetization/src/cpu/kernel/tests/transient_storage.rs diff --git a/evm_arithmetization/src/cpu/kernel/aggregator.rs b/evm_arithmetization/src/cpu/kernel/aggregator.rs index 3620fdea3..9b2b0a85f 100644 --- a/evm_arithmetization/src/cpu/kernel/aggregator.rs +++ b/evm_arithmetization/src/cpu/kernel/aggregator.rs @@ -7,168 +7,176 @@ use super::assembler::{assemble, Kernel}; use crate::cpu::kernel::constants::evm_constants; use crate::cpu::kernel::parser::parse; -pub static KERNEL: Lazy = Lazy::new(combined_kernel); +pub const NUMBER_KERNEL_FILES: usize = 152; -pub(crate) fn combined_kernel() -> Kernel { - let files = vec![ - "global jumped_to_0: PANIC", - "global jumped_to_1: PANIC", - include_str!("asm/beacon_roots.asm"), - include_str!("asm/bignum/add.asm"), - include_str!("asm/bignum/addmul.asm"), - include_str!("asm/bignum/cmp.asm"), - include_str!("asm/bignum/isone.asm"), - include_str!("asm/bignum/iszero.asm"), - include_str!("asm/bignum/modexp.asm"), - include_str!("asm/bignum/modmul.asm"), - include_str!("asm/bignum/mul.asm"), - include_str!("asm/bignum/shr.asm"), - include_str!("asm/bignum/util.asm"), - include_str!("asm/core/call.asm"), - include_str!("asm/core/call_gas.asm"), - include_str!("asm/core/create.asm"), - include_str!("asm/core/create_addresses.asm"), - include_str!("asm/core/create_contract_account.asm"), - include_str!("asm/core/exception.asm"), - include_str!("asm/core/create_receipt.asm"), - include_str!("asm/core/gas.asm"), - include_str!("asm/core/intrinsic_gas.asm"), - include_str!("asm/core/jumpdest_analysis.asm"), - include_str!("asm/core/nonce.asm"), - include_str!("asm/core/process_txn.asm"), - include_str!("asm/core/syscall.asm"), - include_str!("asm/core/terminate.asm"), - include_str!("asm/core/transfer.asm"), - include_str!("asm/core/util.asm"), - include_str!("asm/core/access_lists.asm"), - include_str!("asm/core/log.asm"), - include_str!("asm/core/selfdestruct_list.asm"), - include_str!("asm/core/touched_addresses.asm"), - include_str!("asm/core/withdrawals.asm"), - include_str!("asm/core/precompiles/main.asm"), - include_str!("asm/core/precompiles/ecrec.asm"), - include_str!("asm/core/precompiles/sha256.asm"), - include_str!("asm/core/precompiles/rip160.asm"), - include_str!("asm/core/precompiles/id.asm"), - include_str!("asm/core/precompiles/expmod.asm"), - include_str!("asm/core/precompiles/bn_add.asm"), - include_str!("asm/core/precompiles/bn_mul.asm"), - include_str!("asm/core/precompiles/snarkv.asm"), - include_str!("asm/core/precompiles/blake2_f.asm"), - include_str!("asm/curve/bls381/util.asm"), - include_str!("asm/curve/bn254/curve_arithmetic/constants.asm"), - include_str!("asm/curve/bn254/curve_arithmetic/curve_add.asm"), - include_str!("asm/curve/bn254/curve_arithmetic/curve_mul.asm"), - include_str!("asm/curve/bn254/curve_arithmetic/final_exponent.asm"), - include_str!("asm/curve/bn254/curve_arithmetic/glv.asm"), - include_str!("asm/curve/bn254/curve_arithmetic/miller_loop.asm"), - include_str!("asm/curve/bn254/curve_arithmetic/msm.asm"), - include_str!("asm/curve/bn254/curve_arithmetic/pairing.asm"), - include_str!("asm/curve/bn254/curve_arithmetic/precomputation.asm"), - include_str!("asm/curve/bn254/curve_arithmetic/twisted_curve.asm"), - include_str!("asm/curve/bn254/field_arithmetic/degree_6_mul.asm"), - include_str!("asm/curve/bn254/field_arithmetic/degree_12_mul.asm"), - include_str!("asm/curve/bn254/field_arithmetic/frobenius.asm"), - include_str!("asm/curve/bn254/field_arithmetic/inverse.asm"), - include_str!("asm/curve/bn254/field_arithmetic/util.asm"), - include_str!("asm/curve/common.asm"), - include_str!("asm/curve/secp256k1/curve_add.asm"), - include_str!("asm/curve/secp256k1/ecrecover.asm"), - include_str!("asm/curve/secp256k1/inverse_scalar.asm"), - include_str!("asm/curve/secp256k1/lift_x.asm"), - include_str!("asm/curve/secp256k1/moddiv.asm"), - include_str!("asm/curve/secp256k1/glv.asm"), - include_str!("asm/curve/secp256k1/precomputation.asm"), - include_str!("asm/curve/wnaf.asm"), - include_str!("asm/exp.asm"), - include_str!("asm/halt.asm"), - include_str!("asm/hash/blake2/addresses.asm"), - include_str!("asm/hash/blake2/blake2_f.asm"), - // include_str!("asm/hash/blake2/blake2b.asm"), - // include_str!("asm/hash/blake2/compression.asm"), - include_str!("asm/hash/blake2/g_functions.asm"), - include_str!("asm/hash/blake2/hash.asm"), - include_str!("asm/hash/blake2/iv.asm"), - include_str!("asm/hash/blake2/ops.asm"), - include_str!("asm/hash/blake2/permutations.asm"), - include_str!("asm/hash/ripemd/box.asm"), - include_str!("asm/hash/ripemd/compression.asm"), - include_str!("asm/hash/ripemd/constants.asm"), - include_str!("asm/hash/ripemd/functions.asm"), - include_str!("asm/hash/ripemd/main.asm"), - include_str!("asm/hash/ripemd/update.asm"), - include_str!("asm/hash/sha2/compression.asm"), - include_str!("asm/hash/sha2/constants.asm"), - include_str!("asm/hash/sha2/main.asm"), - include_str!("asm/hash/sha2/message_schedule.asm"), - include_str!("asm/hash/sha2/ops.asm"), - include_str!("asm/hash/sha2/temp_words.asm"), - include_str!("asm/hash/sha2/write_length.asm"), - include_str!("asm/main.asm"), - include_str!("asm/memory/core.asm"), - include_str!("asm/memory/memcpy.asm"), - include_str!("asm/memory/memset.asm"), - include_str!("asm/memory/metadata.asm"), - include_str!("asm/memory/packing.asm"), - include_str!("asm/memory/syscalls.asm"), - include_str!("asm/memory/txn_fields.asm"), - include_str!("asm/mpt/accounts.asm"), - include_str!("asm/mpt/delete/delete.asm"), - include_str!("asm/mpt/delete/delete_branch.asm"), - include_str!("asm/mpt/delete/delete_extension.asm"), - include_str!("asm/mpt/hash/hash.asm"), - include_str!("asm/mpt/hash/hash_trie_specific.asm"), - include_str!("asm/mpt/hex_prefix.asm"), - include_str!("asm/mpt/insert/insert.asm"), - include_str!("asm/mpt/insert/insert_extension.asm"), - include_str!("asm/mpt/insert/insert_leaf.asm"), - include_str!("asm/mpt/insert/insert_trie_specific.asm"), - include_str!("asm/mpt/read.asm"), - include_str!("asm/mpt/storage/storage_read.asm"), - include_str!("asm/mpt/storage/storage_write.asm"), - include_str!("asm/mpt/util.asm"), - include_str!("asm/rlp/decode.asm"), - include_str!("asm/rlp/encode.asm"), - include_str!("asm/rlp/encode_rlp_scalar.asm"), - include_str!("asm/rlp/encode_rlp_string.asm"), - include_str!("asm/rlp/increment_bounded_rlp.asm"), - include_str!("asm/rlp/num_bytes.asm"), - include_str!("asm/rlp/read_to_memory.asm"), - include_str!("asm/shift.asm"), - include_str!("asm/signed.asm"), - include_str!("asm/journal/journal.asm"), - include_str!("asm/journal/account_loaded.asm"), - include_str!("asm/journal/account_destroyed.asm"), - include_str!("asm/journal/account_touched.asm"), - include_str!("asm/journal/balance_transfer.asm"), - include_str!("asm/journal/nonce_change.asm"), - include_str!("asm/journal/storage_change.asm"), - include_str!("asm/journal/storage_loaded.asm"), - include_str!("asm/journal/code_change.asm"), - include_str!("asm/journal/refund.asm"), - include_str!("asm/journal/account_created.asm"), - include_str!("asm/journal/revert.asm"), - include_str!("asm/journal/log.asm"), - include_str!("asm/transactions/common_decoding.asm"), - include_str!("asm/transactions/router.asm"), - include_str!("asm/transactions/type_0.asm"), - include_str!("asm/transactions/type_1.asm"), - include_str!("asm/transactions/type_2.asm"), - include_str!("asm/transactions/type_3.asm"), - include_str!("asm/util/assertions.asm"), - include_str!("asm/util/basic_macros.asm"), - include_str!("asm/util/keccak.asm"), - include_str!("asm/util/math.asm"), - include_str!("asm/account_code.asm"), - include_str!("asm/balance.asm"), - include_str!("asm/bloom_filter.asm"), - include_str!("asm/global_exit_root.asm"), - ]; +pub static KERNEL_FILES: [&str; NUMBER_KERNEL_FILES] = [ + "global jumped_to_0: PANIC", + "global jumped_to_1: PANIC", + include_str!("asm/beacon_roots.asm"), + include_str!("asm/bignum/add.asm"), + include_str!("asm/bignum/addmul.asm"), + include_str!("asm/bignum/cmp.asm"), + include_str!("asm/bignum/isone.asm"), + include_str!("asm/bignum/iszero.asm"), + include_str!("asm/bignum/modexp.asm"), + include_str!("asm/bignum/modmul.asm"), + include_str!("asm/bignum/mul.asm"), + include_str!("asm/bignum/shr.asm"), + include_str!("asm/bignum/util.asm"), + include_str!("asm/core/call.asm"), + include_str!("asm/core/call_gas.asm"), + include_str!("asm/core/create.asm"), + include_str!("asm/core/create_addresses.asm"), + include_str!("asm/core/create_contract_account.asm"), + include_str!("asm/core/exception.asm"), + include_str!("asm/core/create_receipt.asm"), + include_str!("asm/core/gas.asm"), + include_str!("asm/core/intrinsic_gas.asm"), + include_str!("asm/core/jumpdest_analysis.asm"), + include_str!("asm/core/nonce.asm"), + include_str!("asm/core/process_txn.asm"), + include_str!("asm/core/syscall.asm"), + include_str!("asm/core/terminate.asm"), + include_str!("asm/core/transfer.asm"), + include_str!("asm/core/util.asm"), + include_str!("asm/core/access_lists.asm"), + include_str!("asm/core/log.asm"), + include_str!("asm/core/selfdestruct_list.asm"), + include_str!("asm/core/touched_addresses.asm"), + include_str!("asm/core/withdrawals.asm"), + include_str!("asm/core/precompiles/main.asm"), + include_str!("asm/core/precompiles/ecrec.asm"), + include_str!("asm/core/precompiles/sha256.asm"), + include_str!("asm/core/precompiles/rip160.asm"), + include_str!("asm/core/precompiles/id.asm"), + include_str!("asm/core/precompiles/expmod.asm"), + include_str!("asm/core/precompiles/bn_add.asm"), + include_str!("asm/core/precompiles/bn_mul.asm"), + include_str!("asm/core/precompiles/snarkv.asm"), + include_str!("asm/core/precompiles/blake2_f.asm"), + include_str!("asm/curve/bls381/util.asm"), + include_str!("asm/curve/bn254/curve_arithmetic/constants.asm"), + include_str!("asm/curve/bn254/curve_arithmetic/curve_add.asm"), + include_str!("asm/curve/bn254/curve_arithmetic/curve_mul.asm"), + include_str!("asm/curve/bn254/curve_arithmetic/final_exponent.asm"), + include_str!("asm/curve/bn254/curve_arithmetic/glv.asm"), + include_str!("asm/curve/bn254/curve_arithmetic/miller_loop.asm"), + include_str!("asm/curve/bn254/curve_arithmetic/msm.asm"), + include_str!("asm/curve/bn254/curve_arithmetic/pairing.asm"), + include_str!("asm/curve/bn254/curve_arithmetic/precomputation.asm"), + include_str!("asm/curve/bn254/curve_arithmetic/twisted_curve.asm"), + include_str!("asm/curve/bn254/field_arithmetic/degree_6_mul.asm"), + include_str!("asm/curve/bn254/field_arithmetic/degree_12_mul.asm"), + include_str!("asm/curve/bn254/field_arithmetic/frobenius.asm"), + include_str!("asm/curve/bn254/field_arithmetic/inverse.asm"), + include_str!("asm/curve/bn254/field_arithmetic/util.asm"), + include_str!("asm/curve/common.asm"), + include_str!("asm/curve/secp256k1/curve_add.asm"), + include_str!("asm/curve/secp256k1/ecrecover.asm"), + include_str!("asm/curve/secp256k1/inverse_scalar.asm"), + include_str!("asm/curve/secp256k1/lift_x.asm"), + include_str!("asm/curve/secp256k1/moddiv.asm"), + include_str!("asm/curve/secp256k1/glv.asm"), + include_str!("asm/curve/secp256k1/precomputation.asm"), + include_str!("asm/curve/wnaf.asm"), + include_str!("asm/exp.asm"), + include_str!("asm/halt.asm"), + include_str!("asm/hash/blake2/addresses.asm"), + include_str!("asm/hash/blake2/blake2_f.asm"), + // include_str!("asm/hash/blake2/blake2b.asm"), + // include_str!("asm/hash/blake2/compression.asm"), + include_str!("asm/hash/blake2/g_functions.asm"), + include_str!("asm/hash/blake2/hash.asm"), + include_str!("asm/hash/blake2/iv.asm"), + include_str!("asm/hash/blake2/ops.asm"), + include_str!("asm/hash/blake2/permutations.asm"), + include_str!("asm/hash/ripemd/box.asm"), + include_str!("asm/hash/ripemd/compression.asm"), + include_str!("asm/hash/ripemd/constants.asm"), + include_str!("asm/hash/ripemd/functions.asm"), + include_str!("asm/hash/ripemd/main.asm"), + include_str!("asm/hash/ripemd/update.asm"), + include_str!("asm/hash/sha2/compression.asm"), + include_str!("asm/hash/sha2/constants.asm"), + include_str!("asm/hash/sha2/main.asm"), + include_str!("asm/hash/sha2/message_schedule.asm"), + include_str!("asm/hash/sha2/ops.asm"), + include_str!("asm/hash/sha2/temp_words.asm"), + include_str!("asm/hash/sha2/write_length.asm"), + include_str!("asm/main.asm"), + include_str!("asm/memory/core.asm"), + include_str!("asm/memory/memcpy.asm"), + include_str!("asm/memory/memset.asm"), + include_str!("asm/memory/metadata.asm"), + include_str!("asm/memory/packing.asm"), + include_str!("asm/memory/syscalls.asm"), + include_str!("asm/memory/txn_fields.asm"), + include_str!("asm/memory/transient_storage.asm"), + include_str!("asm/mpt/accounts.asm"), + include_str!("asm/mpt/delete/delete.asm"), + include_str!("asm/mpt/delete/delete_branch.asm"), + include_str!("asm/mpt/delete/delete_extension.asm"), + include_str!("asm/mpt/hash/hash.asm"), + include_str!("asm/mpt/hash/hash_trie_specific.asm"), + include_str!("asm/mpt/hex_prefix.asm"), + include_str!("asm/mpt/insert/insert.asm"), + include_str!("asm/mpt/insert/insert_extension.asm"), + include_str!("asm/mpt/insert/insert_leaf.asm"), + include_str!("asm/mpt/insert/insert_trie_specific.asm"), + include_str!("asm/mpt/read.asm"), + include_str!("asm/mpt/storage/storage_read.asm"), + include_str!("asm/mpt/storage/storage_write.asm"), + include_str!("asm/mpt/util.asm"), + include_str!("asm/rlp/decode.asm"), + include_str!("asm/rlp/encode.asm"), + include_str!("asm/rlp/encode_rlp_scalar.asm"), + include_str!("asm/rlp/encode_rlp_string.asm"), + include_str!("asm/rlp/increment_bounded_rlp.asm"), + include_str!("asm/rlp/num_bytes.asm"), + include_str!("asm/rlp/read_to_memory.asm"), + include_str!("asm/shift.asm"), + include_str!("asm/signed.asm"), + include_str!("asm/journal/journal.asm"), + include_str!("asm/journal/account_loaded.asm"), + include_str!("asm/journal/account_destroyed.asm"), + include_str!("asm/journal/account_touched.asm"), + include_str!("asm/journal/balance_transfer.asm"), + include_str!("asm/journal/nonce_change.asm"), + include_str!("asm/journal/storage_change.asm"), + include_str!("asm/journal/storage_loaded.asm"), + include_str!("asm/journal/code_change.asm"), + include_str!("asm/journal/refund.asm"), + include_str!("asm/journal/account_created.asm"), + include_str!("asm/journal/revert.asm"), + include_str!("asm/journal/log.asm"), + include_str!("asm/journal/transient_storage_change.asm"), + include_str!("asm/transactions/common_decoding.asm"), + include_str!("asm/transactions/router.asm"), + include_str!("asm/transactions/type_0.asm"), + include_str!("asm/transactions/type_1.asm"), + include_str!("asm/transactions/type_2.asm"), + include_str!("asm/transactions/type_3.asm"), + include_str!("asm/util/assertions.asm"), + include_str!("asm/util/basic_macros.asm"), + include_str!("asm/util/keccak.asm"), + include_str!("asm/util/math.asm"), + include_str!("asm/account_code.asm"), + include_str!("asm/balance.asm"), + include_str!("asm/bloom_filter.asm"), + include_str!("asm/global_exit_root.asm"), +]; + +pub static KERNEL: Lazy = Lazy::new(combined_kernel); +pub(crate) fn combined_kernel_from_files(files: [&str; N]) -> Kernel { let parsed_files = files.iter().map(|f| parse(f)).collect_vec(); assemble(parsed_files, evm_constants(), true) } +pub(crate) fn combined_kernel() -> Kernel { + combined_kernel_from_files(KERNEL_FILES) +} + #[cfg(test)] mod tests { use env_logger::{try_init_from_env, Env, DEFAULT_FILTER_ENV}; diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/exception.asm b/evm_arithmetization/src/cpu/kernel/asm/core/exception.asm index 54500ec3e..fb260a20e 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/exception.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/exception.asm @@ -393,9 +393,9 @@ gas_cost_for_opcode: BYTES 0 // 0x59, MSIZE BYTES 0 // 0x5a, GAS BYTES @GAS_JUMPDEST // 0x5b, JUMPDEST - %rep 3 // 0x5c-0x5e, invalid - BYTES 0 - %endrep + BYTES 0 // 0x5c, TLOAD + BYTES 0 // 0x5d, TSTORE + BYTES 0 // 0x5e, invalid BYTES @GAS_BASE // 0x5f, PUSH0 %rep 32 // 0x60-0x7f, PUSH1-PUSH32 diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/syscall.asm b/evm_arithmetization/src/cpu/kernel/asm/core/syscall.asm index 6a42c5070..f74b0d529 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/syscall.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/syscall.asm @@ -88,9 +88,9 @@ global syscall_jumptable: JUMPTABLE sys_msize JUMPTABLE sys_gas JUMPTABLE panic // jumpdest is implemented natively - JUMPTABLE panic // 0x5c is an invalid opcode - JUMPTABLE panic // 0x5d is an invalid opcode - JUMPTABLE sys_mcopy + JUMPTABLE sys_tload + JUMPTABLE sys_tstore + JUMPTABLE panic // 0x5e is an invalid opcode JUMPTABLE panic // 0x5f is an invalid opcode // 0x60-0x6f diff --git a/evm_arithmetization/src/cpu/kernel/asm/journal/journal.asm b/evm_arithmetization/src/cpu/kernel/asm/journal/journal.asm index 39b6c9f1b..96fa8aae8 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/journal/journal.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/journal/journal.asm @@ -182,7 +182,6 @@ %mload_global_metadata(@GLOBAL_METADATA_CURRENT_CHECKPOINT) %endmacro - %macro checkpoint // stack: (empty) %current_checkpoint diff --git a/evm_arithmetization/src/cpu/kernel/asm/journal/revert.asm b/evm_arithmetization/src/cpu/kernel/asm/journal/revert.asm index 857bf612b..fd9770bbc 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/journal/revert.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/journal/revert.asm @@ -6,17 +6,18 @@ // stack: ptr, %%after, journal_size-1 DUP1 %mload_journal_data // stack: entry_type, ptr, %%after, journal_size-1 - DUP1 %eq_const(@JOURNAL_ENTRY_ACCOUNT_LOADED) %jumpi(revert_account_loaded) - DUP1 %eq_const(@JOURNAL_ENTRY_ACCOUNT_DESTROYED) %jumpi(revert_account_destroyed) - DUP1 %eq_const(@JOURNAL_ENTRY_ACCOUNT_TOUCHED) %jumpi(revert_account_touched) - DUP1 %eq_const(@JOURNAL_ENTRY_BALANCE_TRANSFER) %jumpi(revert_balance_transfer) - DUP1 %eq_const(@JOURNAL_ENTRY_NONCE_CHANGE) %jumpi(revert_nonce_change) - DUP1 %eq_const(@JOURNAL_ENTRY_STORAGE_CHANGE) %jumpi(revert_storage_change) - DUP1 %eq_const(@JOURNAL_ENTRY_STORAGE_LOADED) %jumpi(revert_storage_loaded) - DUP1 %eq_const(@JOURNAL_ENTRY_CODE_CHANGE) %jumpi(revert_code_change) - DUP1 %eq_const(@JOURNAL_ENTRY_REFUND) %jumpi(revert_refund) - DUP1 %eq_const(@JOURNAL_ENTRY_ACCOUNT_CREATED) %jumpi(revert_account_created) - DUP1 %eq_const(@JOURNAL_ENTRY_LOG) %jumpi(revert_log) + DUP1 %eq_const(@JOURNAL_ENTRY_ACCOUNT_LOADED) %jumpi(revert_account_loaded) + DUP1 %eq_const(@JOURNAL_ENTRY_ACCOUNT_DESTROYED) %jumpi(revert_account_destroyed) + DUP1 %eq_const(@JOURNAL_ENTRY_ACCOUNT_TOUCHED) %jumpi(revert_account_touched) + DUP1 %eq_const(@JOURNAL_ENTRY_BALANCE_TRANSFER) %jumpi(revert_balance_transfer) + DUP1 %eq_const(@JOURNAL_ENTRY_NONCE_CHANGE) %jumpi(revert_nonce_change) + DUP1 %eq_const(@JOURNAL_ENTRY_STORAGE_CHANGE) %jumpi(revert_storage_change) + DUP1 %eq_const(@JOURNAL_ENTRY_STORAGE_LOADED) %jumpi(revert_storage_loaded) + DUP1 %eq_const(@JOURNAL_ENTRY_CODE_CHANGE) %jumpi(revert_code_change) + DUP1 %eq_const(@JOURNAL_ENTRY_REFUND) %jumpi(revert_refund) + DUP1 %eq_const(@JOURNAL_ENTRY_ACCOUNT_CREATED) %jumpi(revert_account_created) + DUP1 %eq_const(@JOURNAL_ENTRY_LOG) %jumpi(revert_log) + DUP1 %eq_const(@JOURNAL_ENTRY_TRANSIENT_STORAGE_CHANGE) %jumpi(revert_transient_storage_change) PANIC // This should never happen. %%after: // stack: journal_size-1 diff --git a/evm_arithmetization/src/cpu/kernel/asm/journal/transient_storage_change.asm b/evm_arithmetization/src/cpu/kernel/asm/journal/transient_storage_change.asm new file mode 100644 index 000000000..dcd43e456 --- /dev/null +++ b/evm_arithmetization/src/cpu/kernel/asm/journal/transient_storage_change.asm @@ -0,0 +1,21 @@ +// struct StorageChange { address, slot, prev_value } + +%macro journal_add_transient_storage_change + %journal_add_3(@JOURNAL_ENTRY_TRANSIENT_STORAGE_CHANGE) +%endmacro + +global revert_transient_storage_change: + // stack: entry_type, ptr, retdest + POP + %journal_load_3 + // We will always write 0 for deletions as it makes no difference. + // stack: address, slot, prev_value, retdest + %search_transient_storage + // The value must have been stored + %assert_nonzero + // stack: pos, addr, value, key, prev_value, retdest + %add_const(2) + DUP5 + MSTORE_GENERAL + %pop4 + JUMP \ No newline at end of file diff --git a/evm_arithmetization/src/cpu/kernel/asm/memory/transient_storage.asm b/evm_arithmetization/src/cpu/kernel/asm/memory/transient_storage.asm new file mode 100644 index 000000000..6c7b3929c --- /dev/null +++ b/evm_arithmetization/src/cpu/kernel/asm/memory/transient_storage.asm @@ -0,0 +1,158 @@ +// Transient data storage + + +/// The transient storage is stored in an array. The length of the array is stored in the global metadata. +/// For storage keys, the address and key are stored as two consecutive elements. +/// The array is stored in the SEGMENT_TRANSIENT_STORAGE segment in the kernel memory (context=0). +/// Searching and inserting is done by doing a linear search through the array. +/// If the key isn't found in the array, it is inserted at the end. +/// TODO: Look into using a more efficient data structure. + +%macro search_transient_storage + %stack (addr, key) -> (addr, key, %%after) + %jump(search_transient_storage) +%%after: + // stack: (is_present, pos, addr, key, val) +%endmacro + +/// Looks for an address, key pair into the transient storage. Returns 1 and the position in @SEGMENT_TRANSIENT_STORAGE +/// if present or 0 and @GLOBAL_METADATA_TRANSIENT_STORAGE_LEN if not. +global search_transient_storage: + // stack: addr, key, retdest + %mload_global_metadata(@GLOBAL_METADATA_TRANSIENT_STORAGE_LEN) + // stack: len, addr, key, retdest + PUSH @SEGMENT_TRANSIENT_STORAGE ADD + PUSH @SEGMENT_TRANSIENT_STORAGE +search_transient_storage_loop: + // `i` and `len` are both scaled by SEGMENT_TRANSIENT_STORAGE + %stack (i, len, addr, key, retdest) -> (i, len, i, len, addr, key, retdest) + EQ %jumpi(search_transient_storage_not_found) + // stack: i, len, addr, key, retdest + DUP1 + MLOAD_GENERAL + // stack: loaded_addr, i, len, addr, key, retdest + DUP4 + // stack: addr, loaded_addr, i, len, addr, key, retdest + SUB // functions as NEQ + // stack: addr != loaded_addr, i, len, addr, key, retdest + %jumpi(increment_and_loop) + + // Addresses match, but we need to check for keys as well + DUP1 + %increment + MLOAD_GENERAL + // stack: loaded_key, i, len, addr, key, retdest + DUP5 + // stack: key, loaded_key, i, len, addr, key, retdest + EQ + %jumpi(search_transient_storage_found) +increment_and_loop: + // stack: i, len, addr, key, retdest + %increment + %jump(search_transient_storage_loop) + +search_transient_storage_not_found: + %stack (i, len, addr, key, retdest) -> (retdest, 0, i, addr, 0, key) // Return 0 to indicate that the address, key was not found. + JUMP + +search_transient_storage_found: + DUP1 %add_const(2) + MLOAD_GENERAL + %stack (val, i, len, addr, key, retdest) -> (retdest, 1, i, addr, val, key) // Return 1 to indicate that the address was already present. + JUMP + +%macro tload_current + %stack (slot) -> (slot, %%after) + %jump(tload_current) +%%after: +%endmacro + +global tload_current: + %address + // stack: addr, slot, retdest + %search_transient_storage + // stack: found, pos, addr, val, slot, retdest + %jumpi(tload_found) + // The value is not in memory so we return 0 + %stack (pos, addr, val, slot, retdest) -> (retdest, 0) + JUMP +tload_found: + // stack: pos, addr, val, slot, retdest + %stack (pos, addr, val, slot, retdest) -> (retdest, val) + JUMP + +// Read a word from the current account's transient storage list +// +// Pre stack: kexit_info, slot +// Post stack: value +global sys_tload: + // stack: kexit_info, slot + SWAP1 + // stack: slot, kexit_info + %tload_current + SWAP1 + + %charge_gas_const(@GAS_WARMACCESS) + // stack: kexit_info, value + EXIT_KERNEL + +// Write a word to the current account's transient storage. +// +// Pre stack: kexit_info, slot, value +// Post stack: (empty) + +global sys_tstore: + %check_static + %charge_gas_const(@GAS_WARMACCESS) + %stack (kexit_info, slot, value) -> (slot, value, kexit_info) + %address + %search_transient_storage + // stack: found, pos, addr, original_value, slot, value, kexit_info + POP + // If the address and slot pair was not present pos will be pointing to the end of the array. + DUP1 DUP3 + // stack: addr, pos, pos, addr, original_value, slot, value, kexit_info + MSTORE_GENERAL + %increment DUP1 + DUP5 + // stack: slot, pos', pos', addr, original_value, slot, value, kexit_info + MSTORE_GENERAL + %increment DUP1 + DUP6 + MSTORE_GENERAL + // stack: pos'', addr, original_value, slot, value, kexit_info + // If pos'' > @GLOBAL_METADATA_TRANSIENT_STORAGE_LEN we need to also store the new @GLOBAL_METADATA_TRANSIENT_STORAGE_LEN + %mload_global_metadata(@GLOBAL_METADATA_TRANSIENT_STORAGE_LEN) + DUP2 + GT + %jumpi(new_transient_storage_len) + POP +sys_tstore_charge_gas: + // stack: addr, original_value, slot, value, kexit_info + // Check if `value` is equal to `current_value`, and if so exit the kernel early. + %stack + (addr, original_value, slot, value, kexit_info) -> + (value, original_value, addr, slot, original_value, kexit_info) + EQ %jumpi(sstore_noop) + + // stack: addr, slot, original_value, kexit_info +global debug_journal: + %journal_add_transient_storage_change + + // stack: kexit_info + EXIT_KERNEL + +new_transient_storage_len: + // Store the new (unscaled) length. + // stack: addr, original_value, slot, value, kexit_info + PUSH @SEGMENT_TRANSIENT_STORAGE + PUSH 1 + SUB // 1 - seg + ADD // new_len = (addr - seg) + 1 + %mstore_global_metadata(@GLOBAL_METADATA_TRANSIENT_STORAGE_LEN) + %jump(sys_tstore_charge_gas) + +sstore_noop: + // stack: current_value, slot, value, kexit_info + %pop3 + EXIT_KERNEL diff --git a/evm_arithmetization/src/cpu/kernel/constants/exc_bitfields.rs b/evm_arithmetization/src/cpu/kernel/constants/exc_bitfields.rs index cb5bb539f..9ffc626f5 100644 --- a/evm_arithmetization/src/cpu/kernel/constants/exc_bitfields.rs +++ b/evm_arithmetization/src/cpu/kernel/constants/exc_bitfields.rs @@ -47,7 +47,7 @@ pub(crate) const INVALID_OPCODES_USER: U256 = u256_from_set_index_ranges(&[ 0x1e..=0x1f, 0x21..=0x2f, 0x4a..=0x4f, - 0x5c..=0x5e, + 0x5e..=0x5e, 0xa5..=0xef, 0xf6..=0xf9, 0xfb..=0xfc, diff --git a/evm_arithmetization/src/cpu/kernel/constants/global_metadata.rs b/evm_arithmetization/src/cpu/kernel/constants/global_metadata.rs index c0c84d3bd..43182bc1d 100644 --- a/evm_arithmetization/src/cpu/kernel/constants/global_metadata.rs +++ b/evm_arithmetization/src/cpu/kernel/constants/global_metadata.rs @@ -103,6 +103,9 @@ pub(crate) enum GlobalMetadata { KernelHash, KernelLen, + /// The length of the transient storage segment. + TransientStorageLen, + // Start of the blob versioned hashes in the RLP for type-3 txns. BlobVersionedHashesRlpStart, // Length of the blob versioned hashes in the RLP for type-3 txns. @@ -112,7 +115,7 @@ pub(crate) enum GlobalMetadata { } impl GlobalMetadata { - pub(crate) const COUNT: usize = 55; + pub(crate) const COUNT: usize = 56; /// Unscales this virtual offset by their respective `Segment` value. pub(crate) const fn unscale(&self) -> usize { @@ -173,6 +176,7 @@ impl GlobalMetadata { Self::CreatedContractsLen, Self::KernelHash, Self::KernelLen, + Self::TransientStorageLen, Self::BlobVersionedHashesRlpStart, Self::BlobVersionedHashesRlpLen, Self::BlobVersionedHashesLen, @@ -234,6 +238,7 @@ impl GlobalMetadata { Self::CreatedContractsLen => "GLOBAL_METADATA_CREATED_CONTRACTS_LEN", Self::KernelHash => "GLOBAL_METADATA_KERNEL_HASH", Self::KernelLen => "GLOBAL_METADATA_KERNEL_LEN", + Self::TransientStorageLen => "GLOBAL_METADATA_TRANSIENT_STORAGE_LEN", Self::BlobVersionedHashesRlpStart => "GLOBAL_METADATA_BLOB_VERSIONED_HASHES_RLP_START", Self::BlobVersionedHashesRlpLen => "GLOBAL_METADATA_BLOB_VERSIONED_HASHES_RLP_LEN", Self::BlobVersionedHashesLen => "GLOBAL_METADATA_BLOB_VERSIONED_HASHES_LEN", diff --git a/evm_arithmetization/src/cpu/kernel/constants/journal_entry.rs b/evm_arithmetization/src/cpu/kernel/constants/journal_entry.rs index d84f2ade8..7a21fd646 100644 --- a/evm_arithmetization/src/cpu/kernel/constants/journal_entry.rs +++ b/evm_arithmetization/src/cpu/kernel/constants/journal_entry.rs @@ -11,10 +11,11 @@ pub(crate) enum JournalEntry { Refund = 8, AccountCreated = 9, Log = 10, + TransientStorageChange = 11, } impl JournalEntry { - pub(crate) const COUNT: usize = 11; + pub(crate) const COUNT: usize = 12; pub(crate) const fn all() -> [Self; Self::COUNT] { [ @@ -29,6 +30,7 @@ impl JournalEntry { Self::Refund, Self::AccountCreated, Self::Log, + Self::TransientStorageChange, ] } @@ -46,6 +48,7 @@ impl JournalEntry { Self::Refund => "JOURNAL_ENTRY_REFUND", Self::AccountCreated => "JOURNAL_ENTRY_ACCOUNT_CREATED", Self::Log => "JOURNAL_ENTRY_LOG", + Self::TransientStorageChange => "JOURNAL_ENTRY_TRANSIENT_STORAGE_CHANGE", } } } diff --git a/evm_arithmetization/src/cpu/kernel/opcodes.rs b/evm_arithmetization/src/cpu/kernel/opcodes.rs index cfa44e01f..6491003f1 100644 --- a/evm_arithmetization/src/cpu/kernel/opcodes.rs +++ b/evm_arithmetization/src/cpu/kernel/opcodes.rs @@ -76,6 +76,8 @@ pub fn get_opcode(mnemonic: &str) -> u8 { "MSIZE" => 0x59, "GAS" => 0x5a, "JUMPDEST" => 0x5b, + "TLOAD" => 0x5c, + "TSTORE" => 0x5d, "MCOPY" => 0x5e, "DUP1" => 0x80, "DUP2" => 0x81, diff --git a/evm_arithmetization/src/cpu/kernel/stack/permutations.rs b/evm_arithmetization/src/cpu/kernel/stack/permutations.rs index 77a3a7725..72bcf42a7 100644 --- a/evm_arithmetization/src/cpu/kernel/stack/permutations.rs +++ b/evm_arithmetization/src/cpu/kernel/stack/permutations.rs @@ -157,7 +157,7 @@ fn combine_cycles(mut perm: Vec>, lst_a: &[T]) if cycl.contains(term) { if joinedperm.is_empty() { // This is the first cycle we have found including an element of positions. - joinedperm = cycl.clone(); + joinedperm.clone_from(&cycl); pos = cycl.iter().position(|x| x == term).unwrap(); } else { // Need to merge 2 cycles. If A_i = A_j then the permutations diff --git a/evm_arithmetization/src/cpu/kernel/tests/checkpoint_label.asm b/evm_arithmetization/src/cpu/kernel/tests/checkpoint_label.asm new file mode 100644 index 000000000..22406f426 --- /dev/null +++ b/evm_arithmetization/src/cpu/kernel/tests/checkpoint_label.asm @@ -0,0 +1,5 @@ +global checkpoint: + // stack: (empty) + %checkpoint + // stack: (empty) + JUMP \ No newline at end of file diff --git a/evm_arithmetization/src/cpu/kernel/tests/mod.rs b/evm_arithmetization/src/cpu/kernel/tests/mod.rs index bd61c897e..e738e55d2 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/mod.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/mod.rs @@ -19,6 +19,7 @@ mod receipt; mod rlp; mod signed_syscalls; mod transaction_parsing; +mod transient_storage; use std::str::FromStr; diff --git a/evm_arithmetization/src/cpu/kernel/tests/transient_storage.rs b/evm_arithmetization/src/cpu/kernel/tests/transient_storage.rs new file mode 100644 index 000000000..9eb377d84 --- /dev/null +++ b/evm_arithmetization/src/cpu/kernel/tests/transient_storage.rs @@ -0,0 +1,339 @@ +use std::array; + +use anyhow::Result; +use ethereum_types::{Address, U256}; +use once_cell::sync::Lazy; +use pest::error::Error; +use plonky2::field::goldilocks_field::GoldilocksField as F; +use rand::{thread_rng, Rng}; + +use crate::cpu::kernel::aggregator::{ + combined_kernel_from_files, KERNEL_FILES, NUMBER_KERNEL_FILES, +}; +use crate::cpu::kernel::assembler::Kernel; +use crate::cpu::kernel::constants::context_metadata::ContextMetadata; +use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; +use crate::cpu::kernel::interpreter::{self, Interpreter}; +use crate::generation::state::GenerationState; +use crate::memory::segments::Segment; +use crate::witness::errors::ProgramError; +use crate::witness::memory::MemoryAddress; +use crate::GenerationInputs; + +#[test] +fn test_tstore() -> Result<()> { + let sys_tstore = crate::cpu::kernel::aggregator::KERNEL.global_labels["sys_tstore"]; + + let kexit_info = U256::from(0xdeadbeefu32) + (U256::from(u64::from(true)) << 32); + + let initial_stack = vec![ + 1.into(), // val + 2.into(), // slot + kexit_info, + ]; + + let mut interpreter: Interpreter = Interpreter::new(sys_tstore, initial_stack); + let gas_limit_address = MemoryAddress { + context: 0, + segment: Segment::ContextMetadata.unscale(), + virt: ContextMetadata::GasLimit.unscale(), + }; + let addr_addr = MemoryAddress { + context: 0, + segment: Segment::ContextMetadata.unscale(), + virt: ContextMetadata::Address.unscale(), + }; + interpreter + .generation_state + .memory + .set(gas_limit_address, 100.into()); + interpreter.generation_state.memory.set(addr_addr, 3.into()); + + interpreter.run()?; + + assert_eq!(interpreter.generation_state.registers.gas_used, 100); + + let stored_addr = MemoryAddress::new(0, Segment::TransientStorage, 0); + let stored_slot = MemoryAddress::new(0, Segment::TransientStorage, 1); + let stored_val = MemoryAddress::new(0, Segment::TransientStorage, 2); + assert_eq!( + interpreter + .generation_state + .memory + .get(stored_addr) + .unwrap(), + 3.into(), + ); + assert_eq!( + interpreter + .generation_state + .memory + .get(stored_slot) + .unwrap(), + 2.into(), + ); + assert_eq!( + interpreter.generation_state.memory.get(stored_val).unwrap(), + 1.into(), + ); + + Ok(()) +} + +#[test] +fn test_tstore_tload() -> Result<()> { + let sys_tstore = crate::cpu::kernel::aggregator::KERNEL.global_labels["sys_tstore"]; + + let kexit_info = U256::from(0xdeadbeefu32) + (U256::from(u64::from(true)) << 32); + + let initial_stack = vec![ + 1.into(), // val + 2.into(), // slot + kexit_info, + ]; + + let mut interpreter: Interpreter = Interpreter::new(sys_tstore, initial_stack); + let gas_limit_address = MemoryAddress { + context: 0, + segment: Segment::ContextMetadata.unscale(), + virt: ContextMetadata::GasLimit.unscale(), + }; + let addr_addr = MemoryAddress { + context: 0, + segment: Segment::ContextMetadata.unscale(), + virt: ContextMetadata::Address.unscale(), + }; + interpreter + .generation_state + .memory + .set(gas_limit_address, 200.into()); + interpreter.generation_state.memory.set(addr_addr, 3.into()); + + interpreter.run()?; + println!("MEjor aca"); + + assert_eq!(interpreter.generation_state.registers.gas_used, 100); + + let sys_tload = crate::cpu::kernel::aggregator::KERNEL.global_labels["sys_tload"]; + let kexit_info = U256::from(0xdeadbeefu32) + + (U256::from(u64::from(true)) << 32) + + (U256::from(interpreter.generation_state.registers.gas_used) << 192); + interpreter.generation_state.registers.program_counter = sys_tload; + interpreter.generation_state.registers.is_kernel = true; + interpreter.push(2.into()); + interpreter.push(kexit_info); + + interpreter.run()?; + println!("2"); + + assert_eq!(interpreter.generation_state.registers.gas_used, 200); + + let val = interpreter.pop().unwrap(); + + assert_eq!(val, 1.into()); + + // Load non-existing slot + interpreter.generation_state.registers.program_counter = sys_tload; + interpreter.generation_state.registers.is_kernel = true; + let slot: U256 = 4.into(); + interpreter.push(slot); + interpreter.push(kexit_info); + + interpreter.run()?; + + let val = interpreter.stack()[0]; + + assert_eq!(U256::zero(), val); + Ok(()) +} + +#[test] +fn test_many_tstore_many_tload() -> Result<()> { + let kexit_info = U256::from(0xdeadbeefu32) + (U256::from(u64::from(true)) << 32); + + let initial_stack = vec![ + 1.into(), // val + 2.into(), // slot + kexit_info, + ]; + + let mut interpreter: Interpreter = Interpreter::new(0, initial_stack); + let gas_limit_address = MemoryAddress { + context: 0, + segment: Segment::ContextMetadata.unscale(), + virt: ContextMetadata::GasLimit.unscale(), + }; + let addr_addr = MemoryAddress { + context: 0, + segment: Segment::ContextMetadata.unscale(), + virt: ContextMetadata::Address.unscale(), + }; + + interpreter + .generation_state + .memory + .set(gas_limit_address, (10 * 200).into()); + interpreter.generation_state.memory.set(addr_addr, 3.into()); + + let sys_tstore = crate::cpu::kernel::aggregator::KERNEL.global_labels["sys_tstore"]; + + for i in (0..10) { + interpreter.generation_state.registers.program_counter = sys_tstore; + interpreter.generation_state.registers.is_kernel = true; + let kexit_info = U256::from(0xdeadbeefu32) + + (U256::from(u64::from(true)) << 32) + + (U256::from(interpreter.generation_state.registers.gas_used) << 192); + let val: U256 = i.into(); + let slot: U256 = i.into(); + interpreter.push(val); + interpreter.push(slot); + interpreter.push(kexit_info); + + interpreter.run()?; + assert_eq!( + interpreter.generation_state.registers.gas_used, + 100 * (i + 1) + ); + } + + let sys_tload = crate::cpu::kernel::aggregator::KERNEL.global_labels["sys_tload"]; + + for i in (0..10) { + interpreter.generation_state.registers.program_counter = sys_tload; + interpreter.generation_state.registers.is_kernel = true; + let kexit_info = U256::from(0xdeadbeefu32) + + (U256::from(u64::from(true)) << 32) + + (U256::from(interpreter.generation_state.registers.gas_used) << 192); + let slot: U256 = i.into(); + interpreter.push(slot); + interpreter.push(kexit_info); + + interpreter.run()?; + assert_eq!( + interpreter.generation_state.registers.gas_used, + 100 * (i + 10 + 1) + ); + + assert_eq!(U256::from(i), interpreter.pop().unwrap()); + } + + Ok(()) +} + +#[test] +fn test_revert() -> Result<()> { + // We use a modified kernel with an extra file defining a label + // where the `checkpoint` macro from file cpu/kernel/asm/journal/journal.asm + // is expanded. + const NUMBER_FILES: usize = NUMBER_KERNEL_FILES + 1; + static KERNEL: Lazy = Lazy::new(|| { + combined_kernel_from_files(std::array::from_fn::<_, NUMBER_FILES, _>(|i| { + if i < NUMBER_KERNEL_FILES { + KERNEL_FILES[i] + } else { + include_str!("checkpoint_label.asm") + } + })) + }); + + let sys_tstore = KERNEL.global_labels["sys_tstore"]; + let mut interpreter = Interpreter::::new(sys_tstore, vec![]); + interpreter.generation_state = + GenerationState::::new(GenerationInputs::default(), &KERNEL.code).unwrap(); + + let gas_limit_address = MemoryAddress { + context: 0, + segment: Segment::ContextMetadata.unscale(), + virt: ContextMetadata::GasLimit.unscale(), + }; + let addr_addr = MemoryAddress { + context: 0, + segment: Segment::ContextMetadata.unscale(), + virt: ContextMetadata::Address.unscale(), + }; + + interpreter + .generation_state + .memory + .set(gas_limit_address, (20 * 100).into()); + interpreter.generation_state.memory.set(addr_addr, 3.into()); + + // Store different values at slot 1 + for i in (0..10) { + interpreter.generation_state.registers.program_counter = sys_tstore; + interpreter.generation_state.registers.is_kernel = true; + let kexit_info = U256::from(0xdeadbeefu32) + + (U256::from(u64::from(true)) << 32) + + (U256::from(interpreter.generation_state.registers.gas_used) << 192); + let val: U256 = i.into(); + let slot: U256 = 1.into(); + interpreter.push(val); + interpreter.push(slot); + interpreter.push(kexit_info); + + interpreter.run()?; + assert_eq!( + interpreter.generation_state.registers.gas_used, + 100 * (i + 1) + ); + } + + let gas_before_checkpoint = interpreter.generation_state.registers.gas_used; + + // We will revert to the point where `val` was 9 + let checkpoint = KERNEL.global_labels["checkpoint"]; + interpreter.generation_state.registers.program_counter = checkpoint; + interpreter.generation_state.registers.is_kernel = true; + interpreter.push(0xdeadbeefu32.into()); + interpreter.run()?; + assert!(interpreter.stack().is_empty()); + + // Don't charge gas for the checkpoint + interpreter.generation_state.registers.gas_used = gas_before_checkpoint; + + // Now we change `val` 10 more times + for i in (10..20) { + interpreter.generation_state.registers.program_counter = sys_tstore; + interpreter.generation_state.registers.is_kernel = true; + let kexit_info = U256::from(0xdeadbeefu32) + + (U256::from(u64::from(true)) << 32) + + (U256::from(interpreter.generation_state.registers.gas_used) << 192); + let val: U256 = i.into(); + let slot: U256 = 1.into(); + interpreter.push(val); + interpreter.push(slot); + interpreter.push(kexit_info); + + interpreter.run()?; + assert_eq!( + interpreter.generation_state.registers.gas_used, + 100 * (i + 1) + ); + } + + // The interpreter will run out of gas and revert to the checkpoint + interpreter.generation_state.registers.program_counter = sys_tstore; + interpreter.generation_state.registers.is_kernel = true; + let kexit_info = U256::from(0xdeadbeefu32) + + (U256::from(u64::from(true)) << 32) + + (U256::from(interpreter.generation_state.registers.gas_used) << 192); + interpreter.push(3.into()); // val + interpreter.push(2.into()); // slot + interpreter.push(kexit_info); + assert!(interpreter.run().is_err()); + + // Now we should load the value before the revert + let sys_tload = KERNEL.global_labels["sys_tload"]; + interpreter.generation_state.registers.program_counter = sys_tload; + interpreter.generation_state.registers.gas_used = 0; + let kexit_info = U256::from(0xdeadbeefu32) + (U256::from(u64::from(true)) << 32); + interpreter.generation_state.registers.is_kernel = true; + interpreter.push(1.into()); + interpreter.push(kexit_info); + + interpreter.run()?; + + assert_eq!(interpreter.pop().unwrap(), 9.into()); + + Ok(()) +} diff --git a/evm_arithmetization/src/memory/segments.rs b/evm_arithmetization/src/memory/segments.rs index fafba7985..67aa93a6d 100644 --- a/evm_arithmetization/src/memory/segments.rs +++ b/evm_arithmetization/src/memory/segments.rs @@ -73,15 +73,17 @@ pub(crate) enum Segment { ContextCheckpoints = 32 << SEGMENT_SCALING_FACTOR, /// List of 256 previous block hashes. BlockHashes = 33 << SEGMENT_SCALING_FACTOR, + // The transient storage of the current transaction. + TransientStorage = 34 << SEGMENT_SCALING_FACTOR, /// List of contracts which have been created during the current /// transaction. - CreatedContracts = 34 << SEGMENT_SCALING_FACTOR, + CreatedContracts = 35 << SEGMENT_SCALING_FACTOR, /// Blob versioned hashes specified in a type-3 transaction. - TxnBlobVersionedHashes = 35 << SEGMENT_SCALING_FACTOR, + TxnBlobVersionedHashes = 36 << SEGMENT_SCALING_FACTOR, } impl Segment { - pub(crate) const COUNT: usize = 36; + pub(crate) const COUNT: usize = 37; /// Unscales this segment by `SEGMENT_SCALING_FACTOR`. pub(crate) const fn unscale(&self) -> usize { @@ -124,6 +126,7 @@ impl Segment { Self::TouchedAddresses, Self::ContextCheckpoints, Self::BlockHashes, + Self::TransientStorage, Self::CreatedContracts, Self::TxnBlobVersionedHashes, ] @@ -166,6 +169,7 @@ impl Segment { Segment::TouchedAddresses => "SEGMENT_TOUCHED_ADDRESSES", Segment::ContextCheckpoints => "SEGMENT_CONTEXT_CHECKPOINTS", Segment::BlockHashes => "SEGMENT_BLOCK_HASHES", + Segment::TransientStorage => "SEGMENT_TRANSIENT_STORAGE", Segment::CreatedContracts => "SEGMENT_CREATED_CONTRACTS", Segment::TxnBlobVersionedHashes => "SEGMENT_TXN_BLOB_VERSIONED_HASHES", } @@ -207,6 +211,7 @@ impl Segment { Segment::TouchedAddresses => 256, Segment::ContextCheckpoints => 256, Segment::BlockHashes => 256, + Segment::TransientStorage => 256, Segment::CreatedContracts => 256, Segment::TxnBlobVersionedHashes => 256, } diff --git a/evm_arithmetization/src/witness/transition.rs b/evm_arithmetization/src/witness/transition.rs index 458fc11b3..0a82f54a3 100644 --- a/evm_arithmetization/src/witness/transition.rs +++ b/evm_arithmetization/src/witness/transition.rs @@ -127,6 +127,8 @@ pub(crate) fn decode(registers: RegistersState, opcode: u8) -> Result Ok(Operation::Syscall(opcode, 0, true)), // MSIZE (0x5a, _) => Ok(Operation::Syscall(opcode, 0, true)), // GAS (0x5b, _) => Ok(Operation::Jumpdest), + (0x5c, _) => Ok(Operation::Syscall(opcode, 1, false)), // TLOAD + (0x5d, _) => Ok(Operation::Syscall(opcode, 2, false)), // TSTORE (0x5e, _) => Ok(Operation::Syscall(opcode, 3, false)), // MCOPY (0x5f..=0x7f, _) => Ok(Operation::Push(opcode - 0x5f)), (0x80..=0x8f, _) => Ok(Operation::Dup(opcode & 0xf)), From 6eca575a58b409ca571c3277323800291c971c2b Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Mon, 11 Mar 2024 15:59:13 +0900 Subject: [PATCH 07/40] Remove blobbasefee from block header (#100) --- .../src/cpu/kernel/asm/memory/metadata.asm | 6 +--- .../cpu/kernel/constants/global_metadata.rs | 5 +--- .../src/cpu/kernel/constants/mod.rs | 7 ++++- .../src/cpu/kernel/interpreter.rs | 4 --- evm_arithmetization/src/generation/mod.rs | 4 --- .../src/generation/prover_input.rs | 28 +++++++++++++++++++ evm_arithmetization/src/recursive_verifier.rs | 10 +------ evm_arithmetization/src/verifier.rs | 8 ------ 8 files changed, 37 insertions(+), 35 deletions(-) diff --git a/evm_arithmetization/src/cpu/kernel/asm/memory/metadata.asm b/evm_arithmetization/src/cpu/kernel/asm/memory/metadata.asm index cded168d3..a00c57028 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/memory/metadata.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/memory/metadata.asm @@ -268,10 +268,6 @@ global sys_chainid: %mload_global_metadata(@GLOBAL_METADATA_BLOCK_BASE_FEE) %endmacro -%macro blobbasefee - %mload_global_metadata(@GLOBAL_METADATA_BLOCK_BLOB_BASE_FEE) -%endmacro - global sys_basefee: // stack: kexit_info %charge_gas_const(@GAS_BASE) @@ -318,7 +314,7 @@ global sys_blobbasefee: // stack: kexit_info %charge_gas_const(@GAS_BASE) // stack: kexit_info - %blobbasefee + PROVER_INPUT(blobbasefee) // stack: blobbasefee, kexit_info SWAP1 EXIT_KERNEL diff --git a/evm_arithmetization/src/cpu/kernel/constants/global_metadata.rs b/evm_arithmetization/src/cpu/kernel/constants/global_metadata.rs index 43182bc1d..8f18ec60a 100644 --- a/evm_arithmetization/src/cpu/kernel/constants/global_metadata.rs +++ b/evm_arithmetization/src/cpu/kernel/constants/global_metadata.rs @@ -49,7 +49,6 @@ pub(crate) enum GlobalMetadata { BlockGasLimit, BlockChainId, BlockBaseFee, - BlockBlobBaseFee, BlockBlobGasUsed, BlockExcessBlobGas, BlockGasUsed, @@ -115,7 +114,7 @@ pub(crate) enum GlobalMetadata { } impl GlobalMetadata { - pub(crate) const COUNT: usize = 56; + pub(crate) const COUNT: usize = 55; /// Unscales this virtual offset by their respective `Segment` value. pub(crate) const fn unscale(&self) -> usize { @@ -146,7 +145,6 @@ impl GlobalMetadata { Self::BlockChainId, Self::BlockBaseFee, Self::BlockGasUsed, - Self::BlockBlobBaseFee, Self::BlockBlobGasUsed, Self::BlockExcessBlobGas, Self::BlockGasUsedBefore, @@ -207,7 +205,6 @@ impl GlobalMetadata { Self::BlockGasLimit => "GLOBAL_METADATA_BLOCK_GAS_LIMIT", Self::BlockChainId => "GLOBAL_METADATA_BLOCK_CHAIN_ID", Self::BlockBaseFee => "GLOBAL_METADATA_BLOCK_BASE_FEE", - Self::BlockBlobBaseFee => "GLOBAL_METADATA_BLOCK_BLOB_BASE_FEE", Self::BlockBlobGasUsed => "GLOBAL_METADATA_BLOCK_BLOB_GAS_USED", Self::BlockExcessBlobGas => "GLOBAL_METADATA_BLOCK_EXCESS_BLOB_GAS", Self::BlockGasUsed => "GLOBAL_METADATA_BLOCK_GAS_USED", diff --git a/evm_arithmetization/src/cpu/kernel/constants/mod.rs b/evm_arithmetization/src/cpu/kernel/constants/mod.rs index 1129078ae..892feab15 100644 --- a/evm_arithmetization/src/cpu/kernel/constants/mod.rs +++ b/evm_arithmetization/src/cpu/kernel/constants/mod.rs @@ -316,10 +316,15 @@ const MAX_NONCE: (&str, u64) = ("MAX_NONCE", 0xffffffffffffffff); const CALL_STACK_LIMIT: (&str, u64) = ("CALL_STACK_LIMIT", 1024); /// Cancun-related constants -/// See . +/// See and +/// . pub mod cancun_constants { use super::*; + pub const BLOB_BASE_FEE_UPDATE_FRACTION: U256 = U256([0x32f0ed, 0, 0, 0]); + + pub const MIN_BLOB_BASE_FEE: U256 = U256::one(); + pub const BEACON_ROOTS_ADDRESS: (&str, [u8; 20]) = ( "BEACON_ROOTS_ADDRESS", hex!("000F3df6D732807Ef1319fB7B8bB8522d0Beac02"), diff --git a/evm_arithmetization/src/cpu/kernel/interpreter.rs b/evm_arithmetization/src/cpu/kernel/interpreter.rs index 8a31a8d91..c40d77459 100644 --- a/evm_arithmetization/src/cpu/kernel/interpreter.rs +++ b/evm_arithmetization/src/cpu/kernel/interpreter.rs @@ -243,10 +243,6 @@ impl Interpreter { (GlobalMetadata::BlockGasLimit, metadata.block_gaslimit), (GlobalMetadata::BlockChainId, metadata.block_chain_id), (GlobalMetadata::BlockBaseFee, metadata.block_base_fee), - ( - GlobalMetadata::BlockBlobBaseFee, - metadata.block_blob_base_fee, - ), ( GlobalMetadata::BlockCurrentHash, h2u(inputs.block_hashes.cur_hash), diff --git a/evm_arithmetization/src/generation/mod.rs b/evm_arithmetization/src/generation/mod.rs index 20c805e02..69f821c49 100644 --- a/evm_arithmetization/src/generation/mod.rs +++ b/evm_arithmetization/src/generation/mod.rs @@ -130,10 +130,6 @@ fn apply_metadata_and_tries_memops, const D: usize> h2u(inputs.block_hashes.cur_hash), ), (GlobalMetadata::BlockGasUsed, metadata.block_gas_used), - ( - GlobalMetadata::BlockBlobBaseFee, - metadata.block_blob_base_fee, - ), ( GlobalMetadata::BlockBlobGasUsed, metadata.block_blob_gas_used, diff --git a/evm_arithmetization/src/generation/prover_input.rs b/evm_arithmetization/src/generation/prover_input.rs index 13c4f5693..bd8ab932e 100644 --- a/evm_arithmetization/src/generation/prover_input.rs +++ b/evm_arithmetization/src/generation/prover_input.rs @@ -1,4 +1,5 @@ use core::mem::transmute; +use std::cmp::min; use std::collections::{BTreeSet, HashMap}; use std::str::FromStr; @@ -9,6 +10,9 @@ use num_bigint::BigUint; use plonky2::field::types::Field; use serde::{Deserialize, Serialize}; +use crate::cpu::kernel::constants::cancun_constants::{ + BLOB_BASE_FEE_UPDATE_FRACTION, MIN_BLOB_BASE_FEE, +}; use crate::cpu::kernel::constants::context_metadata::ContextMetadata; use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; use crate::cpu::kernel::interpreter::simulate_cpu_and_get_user_jumps; @@ -48,6 +52,7 @@ impl GenerationState { "sf" => self.run_sf(input_fn), "ffe" => self.run_ffe(input_fn), "rlp" => self.run_rlp(), + "blobbasefee" => self.run_blobbasefee(), "current_hash" => self.run_current_hash(), "account_code" => self.run_account_code(), "bignum_modmul" => self.run_bignum_modmul(), @@ -136,6 +141,15 @@ impl GenerationState { .ok_or(ProgramError::ProverInputError(OutOfRlpData)) } + fn run_blobbasefee(&mut self) -> Result { + let excess_blob_gas = self.inputs.block_metadata.block_excess_blob_gas; + Ok(fake_exponential( + MIN_BLOB_BASE_FEE, + excess_blob_gas, + BLOB_BASE_FEE_UPDATE_FRACTION, + )) + } + fn run_current_hash(&mut self) -> Result { Ok(U256::from_big_endian(&self.inputs.block_hashes.cur_hash.0)) } @@ -829,3 +843,17 @@ fn modexp(x: U256, e: U256, n: U256) -> Result { Ok(product) } + +/// See EIP-4844: . +fn fake_exponential(factor: U256, numerator: U256, denominator: U256) -> U256 { + let mut i = 1; + let mut output = U256::zero(); + let mut numerator_accum = factor * denominator; + while !numerator_accum.is_zero() { + output += numerator_accum; + numerator_accum *= numerator; // (denominator * i) + i += 1; + } + + output // denominator +} diff --git a/evm_arithmetization/src/recursive_verifier.rs b/evm_arithmetization/src/recursive_verifier.rs index 34245c24b..eafffc9f9 100644 --- a/evm_arithmetization/src/recursive_verifier.rs +++ b/evm_arithmetization/src/recursive_verifier.rs @@ -379,7 +379,7 @@ pub(crate) fn get_memory_extra_looking_sum_circuit, // This contains the `block_beneficiary`, `block_random`, `block_base_fee`, // `block_blob_base_fee`, `block_blob_gas_used`, `block_excess_blob_gas`, // `parent_beacon_block_root` as well as `cur_hash`. - let block_fields_arrays: [(GlobalMetadata, &[Target]); 8] = [ + let block_fields_arrays: [(GlobalMetadata, &[Target]); 7] = [ ( GlobalMetadata::BlockBeneficiary, &public_values.block_metadata.block_beneficiary, @@ -392,10 +392,6 @@ pub(crate) fn get_memory_extra_looking_sum_circuit, GlobalMetadata::BlockBaseFee, &public_values.block_metadata.block_base_fee, ), - ( - GlobalMetadata::BlockBlobBaseFee, - &public_values.block_metadata.block_blob_base_fee, - ), ( GlobalMetadata::BlockBlobGasUsed, &public_values.block_metadata.block_blob_gas_used, @@ -800,10 +796,6 @@ where block_metadata_target.block_gas_used, u256_to_u32(block_metadata.block_gas_used)?, ); - // BlobBaseFee fits in 2 limbs - let blob_basefee = u256_to_u64(block_metadata.block_blob_base_fee)?; - witness.set_target(block_metadata_target.block_blob_base_fee[0], blob_basefee.0); - witness.set_target(block_metadata_target.block_blob_base_fee[1], blob_basefee.1); // BlobGasUsed fits in 2 limbs let blob_gas_used = u256_to_u64(block_metadata.block_blob_gas_used)?; witness.set_target( diff --git a/evm_arithmetization/src/verifier.rs b/evm_arithmetization/src/verifier.rs index e92d150af..4de9b1b17 100644 --- a/evm_arithmetization/src/verifier.rs +++ b/evm_arithmetization/src/verifier.rs @@ -194,10 +194,6 @@ where GlobalMetadata::BlockGasUsed, public_values.block_metadata.block_gas_used, ), - ( - GlobalMetadata::BlockBlobBaseFee, - public_values.block_metadata.block_blob_base_fee, - ), ( GlobalMetadata::BlockBlobGasUsed, public_values.block_metadata.block_blob_gas_used, @@ -351,10 +347,6 @@ pub(crate) mod debug_utils { GlobalMetadata::BlockGasUsed, public_values.block_metadata.block_gas_used, ), - ( - GlobalMetadata::BlockBlobBaseFee, - public_values.block_metadata.block_blob_base_fee, - ), ( GlobalMetadata::BlockBlobGasUsed, public_values.block_metadata.block_blob_gas_used, From deb4f1cb6ec2aef1a10473f634cdebf58a98c246 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Mon, 11 Mar 2024 16:12:38 +0900 Subject: [PATCH 08/40] Remove blobbasefee from block header --- .../src/cpu/kernel/tests/add11.rs | 2 -- evm_arithmetization/src/get_challenges.rs | 4 --- evm_arithmetization/src/proof.rs | 36 ++++--------------- evm_arithmetization/src/recursive_verifier.rs | 6 ++-- evm_arithmetization/tests/add11_yml.rs | 1 - .../tests/basic_smart_contract.rs | 1 - evm_arithmetization/tests/erc20.rs | 1 - evm_arithmetization/tests/erc721.rs | 1 - evm_arithmetization/tests/log_opcode.rs | 2 -- .../tests/self_balance_gas_cost.rs | 1 - evm_arithmetization/tests/selfdestruct.rs | 1 - evm_arithmetization/tests/simple_transfer.rs | 1 - 12 files changed, 9 insertions(+), 48 deletions(-) diff --git a/evm_arithmetization/src/cpu/kernel/tests/add11.rs b/evm_arithmetization/src/cpu/kernel/tests/add11.rs index 5bbddcacc..c3e833c92 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/add11.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/add11.rs @@ -87,7 +87,6 @@ fn test_add11_yml() { block_chain_id: 1.into(), block_base_fee: 0xa.into(), block_gas_used: gas_used, - block_blob_base_fee: 0x2.into(), ..Default::default() }; @@ -257,7 +256,6 @@ fn test_add11_yml_with_exception() { block_chain_id: 1.into(), block_base_fee: 0xa.into(), block_gas_used: txn_gas_limit.into(), - block_blob_base_fee: 0x2.into(), ..Default::default() }; diff --git a/evm_arithmetization/src/get_challenges.rs b/evm_arithmetization/src/get_challenges.rs index 31742c114..1fb80af6d 100644 --- a/evm_arithmetization/src/get_challenges.rs +++ b/evm_arithmetization/src/get_challenges.rs @@ -65,9 +65,6 @@ fn observe_block_metadata< challenger.observe_element(basefee.0); challenger.observe_element(basefee.1); challenger.observe_element(u256_to_u32(block_metadata.block_gas_used)?); - let blob_basefee = u256_to_u64(block_metadata.block_blob_base_fee)?; - challenger.observe_element(blob_basefee.0); - challenger.observe_element(blob_basefee.1); let blob_gas_used = u256_to_u64(block_metadata.block_blob_gas_used)?; challenger.observe_element(blob_gas_used.0); challenger.observe_element(blob_gas_used.1); @@ -101,7 +98,6 @@ fn observe_block_metadata_target< challenger.observe_element(block_metadata.block_chain_id); challenger.observe_elements(&block_metadata.block_base_fee); challenger.observe_element(block_metadata.block_gas_used); - challenger.observe_elements(&block_metadata.block_blob_base_fee); challenger.observe_elements(&block_metadata.block_blob_gas_used); challenger.observe_elements(&block_metadata.block_excess_blob_gas); challenger.observe_elements(&block_metadata.parent_beacon_block_root); diff --git a/evm_arithmetization/src/proof.rs b/evm_arithmetization/src/proof.rs index 5d01cc2ef..56ff90dc1 100644 --- a/evm_arithmetization/src/proof.rs +++ b/evm_arithmetization/src/proof.rs @@ -185,11 +185,9 @@ pub struct BlockMetadata { pub block_base_fee: U256, /// The total gas used in this block. It must fit in a `u32`. pub block_gas_used: U256, - /// The blob base fee. It must fit in a `u64`. - pub block_blob_base_fee: U256, - /// The blob base fee. It must fit in a `u64`. + /// The blob gas used. It must fit in a `u64`. pub block_blob_gas_used: U256, - /// The blob base fee. It must fit in a `u64`. + /// The excess blob base. It must fit in a `u64`. pub block_excess_blob_gas: U256, /// The hash tree root of the parent beacon block. pub parent_beacon_block_root: H256, @@ -212,8 +210,6 @@ impl BlockMetadata { let block_base_fee = (pis[18].to_canonical_u64() + (pis[19].to_canonical_u64() << 32)).into(); let block_gas_used = pis[20].to_canonical_u64().into(); - let block_blob_base_fee = - (pis[21].to_canonical_u64() + (pis[22].to_canonical_u64() << 32)).into(); let block_blob_gas_used = (pis[23].to_canonical_u64() + (pis[24].to_canonical_u64() << 32)).into(); let block_excess_blob_gas = @@ -232,7 +228,6 @@ impl BlockMetadata { block_chain_id, block_base_fee, block_gas_used, - block_blob_base_fee, block_blob_gas_used, block_excess_blob_gas, parent_beacon_block_root, @@ -331,7 +326,6 @@ impl PublicValuesTarget { block_chain_id, block_base_fee, block_gas_used, - block_blob_base_fee, block_blob_gas_used, block_excess_blob_gas, parent_beacon_block_root, @@ -347,7 +341,6 @@ impl PublicValuesTarget { buffer.write_target(block_chain_id)?; buffer.write_target_array(&block_base_fee)?; buffer.write_target(block_gas_used)?; - buffer.write_target_array(&block_blob_base_fee)?; buffer.write_target_array(&block_blob_gas_used)?; buffer.write_target_array(&block_excess_blob_gas)?; buffer.write_target_array(&parent_beacon_block_root)?; @@ -400,7 +393,6 @@ impl PublicValuesTarget { block_chain_id: buffer.read_target()?, block_base_fee: buffer.read_target_array()?, block_gas_used: buffer.read_target()?, - block_blob_base_fee: buffer.read_target_array()?, block_blob_gas_used: buffer.read_target_array()?, block_excess_blob_gas: buffer.read_target_array()?, parent_beacon_block_root: buffer.read_target_array()?, @@ -604,8 +596,6 @@ pub struct BlockMetadataTarget { pub(crate) block_base_fee: [Target; 2], /// `Target` for the gas used of this block. pub(crate) block_gas_used: Target, - /// `Target`s for the blob base fee of this block. - pub(crate) block_blob_base_fee: [Target; 2], /// `Target`s for the total blob gas used of this block. pub(crate) block_blob_gas_used: [Target; 2], /// `Target`s for the excess blob gas of this block. @@ -618,7 +608,7 @@ pub struct BlockMetadataTarget { impl BlockMetadataTarget { /// Number of `Target`s required for the block metadata. - pub(crate) const SIZE: usize = 99; + pub(crate) const SIZE: usize = 97; /// Extracts block metadata `Target`s from the provided public input /// `Target`s. The provided `pis` should start with the block metadata. @@ -632,11 +622,10 @@ impl BlockMetadataTarget { let block_chain_id = pis[17]; let block_base_fee = pis[18..20].try_into().unwrap(); let block_gas_used = pis[20]; - let block_blob_base_fee = pis[21..23].try_into().unwrap(); - let block_blob_gas_used = pis[23..25].try_into().unwrap(); - let block_excess_blob_gas = pis[25..27].try_into().unwrap(); - let parent_beacon_block_root = pis[27..35].try_into().unwrap(); - let block_bloom = pis[35..99].try_into().unwrap(); + let block_blob_gas_used = pis[21..23].try_into().unwrap(); + let block_excess_blob_gas = pis[23..25].try_into().unwrap(); + let parent_beacon_block_root = pis[25..33].try_into().unwrap(); + let block_bloom = pis[33..97].try_into().unwrap(); Self { block_beneficiary, @@ -648,7 +637,6 @@ impl BlockMetadataTarget { block_chain_id, block_base_fee, block_gas_used, - block_blob_base_fee, block_blob_gas_used, block_excess_blob_gas, parent_beacon_block_root, @@ -684,13 +672,6 @@ impl BlockMetadataTarget { builder.select(condition, bm0.block_base_fee[i], bm1.block_base_fee[i]) }), block_gas_used: builder.select(condition, bm0.block_gas_used, bm1.block_gas_used), - block_blob_base_fee: core::array::from_fn(|i| { - builder.select( - condition, - bm0.block_blob_base_fee[i], - bm1.block_blob_base_fee[i], - ) - }), block_blob_gas_used: core::array::from_fn(|i| { builder.select( condition, @@ -739,9 +720,6 @@ impl BlockMetadataTarget { builder.connect(bm0.block_base_fee[i], bm1.block_base_fee[i]) } builder.connect(bm0.block_gas_used, bm1.block_gas_used); - for i in 0..2 { - builder.connect(bm0.block_blob_base_fee[i], bm1.block_blob_base_fee[i]) - } for i in 0..2 { builder.connect(bm0.block_blob_gas_used[i], bm1.block_blob_gas_used[i]) } diff --git a/evm_arithmetization/src/recursive_verifier.rs b/evm_arithmetization/src/recursive_verifier.rs index eafffc9f9..cd5da697e 100644 --- a/evm_arithmetization/src/recursive_verifier.rs +++ b/evm_arithmetization/src/recursive_verifier.rs @@ -377,8 +377,8 @@ pub(crate) fn get_memory_extra_looking_sum_circuit, ]; // This contains the `block_beneficiary`, `block_random`, `block_base_fee`, - // `block_blob_base_fee`, `block_blob_gas_used`, `block_excess_blob_gas`, - // `parent_beacon_block_root` as well as `cur_hash`. + // `block_blob_gas_used`, `block_excess_blob_gas`, `parent_beacon_block_root` + // as well as `cur_hash`. let block_fields_arrays: [(GlobalMetadata, &[Target]); 7] = [ ( GlobalMetadata::BlockBeneficiary, @@ -608,7 +608,6 @@ pub(crate) fn add_virtual_block_metadata, const D: let block_chain_id = builder.add_virtual_public_input(); let block_base_fee = builder.add_virtual_public_input_arr(); let block_gas_used = builder.add_virtual_public_input(); - let block_blob_base_fee = builder.add_virtual_public_input_arr(); let block_blob_gas_used = builder.add_virtual_public_input_arr(); let block_excess_blob_gas = builder.add_virtual_public_input_arr(); let parent_beacon_block_root = builder.add_virtual_public_input_arr(); @@ -623,7 +622,6 @@ pub(crate) fn add_virtual_block_metadata, const D: block_chain_id, block_base_fee, block_gas_used, - block_blob_base_fee, block_blob_gas_used, block_excess_blob_gas, parent_beacon_block_root, diff --git a/evm_arithmetization/tests/add11_yml.rs b/evm_arithmetization/tests/add11_yml.rs index e59be51ba..683dbcaa5 100644 --- a/evm_arithmetization/tests/add11_yml.rs +++ b/evm_arithmetization/tests/add11_yml.rs @@ -93,7 +93,6 @@ fn add11_yml() -> anyhow::Result<()> { block_chain_id: 1.into(), block_base_fee: 0xa.into(), block_gas_used: 0xa868u64.into(), - block_blob_base_fee: 0x2.into(), ..Default::default() }; diff --git a/evm_arithmetization/tests/basic_smart_contract.rs b/evm_arithmetization/tests/basic_smart_contract.rs index 2c880a60f..0b6c49bcb 100644 --- a/evm_arithmetization/tests/basic_smart_contract.rs +++ b/evm_arithmetization/tests/basic_smart_contract.rs @@ -102,7 +102,6 @@ fn test_basic_smart_contract() -> anyhow::Result<()> { block_gaslimit: 0xff112233u32.into(), block_gas_used: gas_used.into(), block_base_fee: 0xa.into(), - block_blob_base_fee: 0x2.into(), ..Default::default() }; diff --git a/evm_arithmetization/tests/erc20.rs b/evm_arithmetization/tests/erc20.rs index 88f9dcbdc..0e07783be 100644 --- a/evm_arithmetization/tests/erc20.rs +++ b/evm_arithmetization/tests/erc20.rs @@ -97,7 +97,6 @@ fn test_erc20() -> anyhow::Result<()> { block_chain_id: 1.into(), block_base_fee: 0xa.into(), block_gas_used: gas_used, - block_blob_base_fee: 0x2.into(), block_bloom: bloom, ..Default::default() }; diff --git a/evm_arithmetization/tests/erc721.rs b/evm_arithmetization/tests/erc721.rs index db560d84e..10571f3de 100644 --- a/evm_arithmetization/tests/erc721.rs +++ b/evm_arithmetization/tests/erc721.rs @@ -120,7 +120,6 @@ fn test_erc721() -> anyhow::Result<()> { block_chain_id: 1.into(), block_base_fee: 0xa.into(), block_gas_used: gas_used, - block_blob_base_fee: 0x2.into(), block_bloom: bloom.try_into().unwrap(), ..Default::default() }; diff --git a/evm_arithmetization/tests/log_opcode.rs b/evm_arithmetization/tests/log_opcode.rs index 57c99ce8a..90d1d5404 100644 --- a/evm_arithmetization/tests/log_opcode.rs +++ b/evm_arithmetization/tests/log_opcode.rs @@ -148,7 +148,6 @@ fn test_log_opcodes() -> anyhow::Result<()> { block_gaslimit: 0xffffffffu32.into(), block_chain_id: 1.into(), block_base_fee: 0xa.into(), - block_blob_base_fee: 0x2.into(), ..Default::default() }; @@ -366,7 +365,6 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { block_chain_id: 1.into(), block_base_fee: 0xa.into(), block_gas_used: (22570 + 21000).into(), - block_blob_base_fee: 0x2.into(), block_bloom: [ 0.into(), 0.into(), diff --git a/evm_arithmetization/tests/self_balance_gas_cost.rs b/evm_arithmetization/tests/self_balance_gas_cost.rs index dcbeb6902..6d7e2c8ab 100644 --- a/evm_arithmetization/tests/self_balance_gas_cost.rs +++ b/evm_arithmetization/tests/self_balance_gas_cost.rs @@ -108,7 +108,6 @@ fn self_balance_gas_cost() -> anyhow::Result<()> { block_gaslimit: 0xff112233u32.into(), block_gas_used: gas_used.into(), block_base_fee: 0xa.into(), - block_blob_base_fee: 0x2.into(), ..Default::default() }; diff --git a/evm_arithmetization/tests/selfdestruct.rs b/evm_arithmetization/tests/selfdestruct.rs index 7a1e10d6e..b4620b1a8 100644 --- a/evm_arithmetization/tests/selfdestruct.rs +++ b/evm_arithmetization/tests/selfdestruct.rs @@ -85,7 +85,6 @@ fn test_selfdestruct() -> anyhow::Result<()> { block_chain_id: 1.into(), block_base_fee: 0xa.into(), block_gas_used: 26002.into(), - block_blob_base_fee: 0x2.into(), ..Default::default() }; diff --git a/evm_arithmetization/tests/simple_transfer.rs b/evm_arithmetization/tests/simple_transfer.rs index ce760d715..89cd79ea5 100644 --- a/evm_arithmetization/tests/simple_transfer.rs +++ b/evm_arithmetization/tests/simple_transfer.rs @@ -77,7 +77,6 @@ fn test_simple_transfer() -> anyhow::Result<()> { block_chain_id: 1.into(), block_base_fee: 0xa.into(), block_gas_used: 21032.into(), - block_blob_base_fee: 0x2.into(), ..Default::default() }; From 8c7ccb0c3dab6d4775d418fadd9cb867493a14a1 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Mon, 11 Mar 2024 17:51:28 +0900 Subject: [PATCH 09/40] Remove prints --- evm_arithmetization/src/cpu/kernel/tests/transient_storage.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/evm_arithmetization/src/cpu/kernel/tests/transient_storage.rs b/evm_arithmetization/src/cpu/kernel/tests/transient_storage.rs index 9eb377d84..d1332c586 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/transient_storage.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/transient_storage.rs @@ -110,7 +110,6 @@ fn test_tstore_tload() -> Result<()> { interpreter.generation_state.memory.set(addr_addr, 3.into()); interpreter.run()?; - println!("MEjor aca"); assert_eq!(interpreter.generation_state.registers.gas_used, 100); @@ -124,7 +123,6 @@ fn test_tstore_tload() -> Result<()> { interpreter.push(kexit_info); interpreter.run()?; - println!("2"); assert_eq!(interpreter.generation_state.registers.gas_used, 200); From 366e0a243c7bcf02683f2f83766894f9fdfdf50a Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Mon, 11 Mar 2024 22:51:41 +0900 Subject: [PATCH 10/40] Fix MCOPY from rebasing (#103) --- evm_arithmetization/src/cpu/kernel/asm/core/exception.asm | 2 +- evm_arithmetization/src/cpu/kernel/asm/core/syscall.asm | 2 +- evm_arithmetization/src/cpu/kernel/constants/exc_bitfields.rs | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/exception.asm b/evm_arithmetization/src/cpu/kernel/asm/core/exception.asm index fb260a20e..9aae5983b 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/exception.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/exception.asm @@ -395,7 +395,7 @@ gas_cost_for_opcode: BYTES @GAS_JUMPDEST // 0x5b, JUMPDEST BYTES 0 // 0x5c, TLOAD BYTES 0 // 0x5d, TSTORE - BYTES 0 // 0x5e, invalid + BYTES 0 // 0x5e, MCOPY BYTES @GAS_BASE // 0x5f, PUSH0 %rep 32 // 0x60-0x7f, PUSH1-PUSH32 diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/syscall.asm b/evm_arithmetization/src/cpu/kernel/asm/core/syscall.asm index f74b0d529..87c01dc7f 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/syscall.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/syscall.asm @@ -90,7 +90,7 @@ global syscall_jumptable: JUMPTABLE panic // jumpdest is implemented natively JUMPTABLE sys_tload JUMPTABLE sys_tstore - JUMPTABLE panic // 0x5e is an invalid opcode + JUMPTABLE sys_mcopy JUMPTABLE panic // 0x5f is an invalid opcode // 0x60-0x6f diff --git a/evm_arithmetization/src/cpu/kernel/constants/exc_bitfields.rs b/evm_arithmetization/src/cpu/kernel/constants/exc_bitfields.rs index 9ffc626f5..0abc84e70 100644 --- a/evm_arithmetization/src/cpu/kernel/constants/exc_bitfields.rs +++ b/evm_arithmetization/src/cpu/kernel/constants/exc_bitfields.rs @@ -47,7 +47,6 @@ pub(crate) const INVALID_OPCODES_USER: U256 = u256_from_set_index_ranges(&[ 0x1e..=0x1f, 0x21..=0x2f, 0x4a..=0x4f, - 0x5e..=0x5e, 0xa5..=0xef, 0xf6..=0xf9, 0xfb..=0xfc, From 05fd0c1bf48cbc5081c8ec4e21831ec407bd1b34 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Tue, 12 Mar 2024 19:22:48 +0900 Subject: [PATCH 11/40] Fix storage write for beacons root contract (#102) * Fix storage write for beacons root contract * Fix test * Add comment about panicking for invalid timestamps * Add trie read check for missing key --- .../src/cpu/kernel/asm/beacon_roots.asm | 49 ++++++++++++++++++- .../src/cpu/kernel/asm/global_exit_root.asm | 2 + evm_arithmetization/src/testing_utils.rs | 12 +++-- evm_arithmetization/tests/empty_txn_list.rs | 1 + evm_arithmetization/tests/global_exit_root.rs | 7 ++- evm_arithmetization/tests/withdrawals.rs | 7 ++- 6 files changed, 68 insertions(+), 10 deletions(-) diff --git a/evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm b/evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm index 75962aefe..3b9db19e7 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm @@ -1,5 +1,7 @@ /// EIP-4788: Beacon block root in the EVM /// +/// +/// *NOTE*: This will panic if one of the provided timestamps is zero. global set_beacon_root: PUSH set_global_exit_roots @@ -17,9 +19,12 @@ global set_beacon_root: %add_const(@HISTORY_BUFFER_LENGTH) // stack: root_idx, calldata, write_beacon_roots_to_storage, timestamp_idx, timestamp, retdest + // If the calldata is zero, delete the slot from the storage trie. + DUP2 ISZERO %jumpi(delete_root_idx_slot) + write_beacon_roots_to_storage: // stack: slot, value, retdest - // First we write the value to MPT data, and get a pointer to it. + // First we write the value to MPT data, and get a pointer to it. %get_trie_data_size // stack: value_ptr, slot, value, retdest SWAP2 @@ -46,3 +51,45 @@ after_beacon_roots_storage_insert: // stack: account_storage_root_ptr_ptr, new_storage_root_ptr, retdest %mstore_trie_data JUMP + +delete_root_idx_slot: + // stack: root_idx, 0, write_beacon_roots_to_storage, timestamp_idx, timestamp, retdest + PUSH after_root_idx_slot_delete + SWAP2 POP + // stack: root_idx, after_root_idx_slot_delete, write_beacon_roots_to_storage, timestamp_idx, timestamp, retdest + %slot_to_storage_key + // stack: storage_key, after_root_idx_slot_delete, write_beacon_roots_to_storage, timestamp_idx, timestamp, retdest + PUSH 64 // storage_key has 64 nibbles + %get_storage_trie(@BEACON_ROOTS_ADDRESS) + // stack: storage_root_ptr, 64, storage_key, after_root_idx_slot_delete, write_beacon_roots_to_storage, timestamp_idx, timestamp, retdest + + // If the slot is empty (i.e. ptr defaulting to 0), skip the deletion. + DUP1 ISZERO %jumpi(skip_empty_slot) + + // stack: storage_root_ptr, 64, storage_key, after_root_idx_slot_delete, write_beacon_roots_to_storage, timestamp_idx, timestamp, retdest + %stack (storage_root_ptr, nibbles, storage_key) -> (storage_root_ptr, nibbles, storage_key, checkpoint_delete_root_idx, storage_root_ptr, nibbles, storage_key) + %jump(mpt_read) +checkpoint_delete_root_idx: + // stack: value_ptr, storage_root_ptr, 64, storage_key, after_root_idx_slot_delete, write_beacon_roots_to_storage, timestamp_idx, timestamp, retdest + // If the the storage key is not found (i.e. ptr defaulting to 0), skip the deletion. + ISZERO %jumpi(skip_empty_slot) + + // stack: storage_root_ptr, 64, storage_key, after_root_idx_slot_delete, write_beacon_roots_to_storage, timestamp_idx, timestamp, retdest + %jump(mpt_delete) + +after_root_idx_slot_delete: + // stack: new_storage_root_ptr, write_beacon_roots_to_storage, timestamp_idx, timestamp, retdest + %get_account_data(@BEACON_ROOTS_ADDRESS) + // stack: account_ptr, new_storage_root_ptr, write_beacon_roots_to_storage, timestamp_idx, timestamp, retdest + + // Update the copied account with our new storage root pointer. + %add_const(2) + // stack: account_storage_root_ptr_ptr, new_storage_root_ptr, write_beacon_roots_to_storage, timestamp_idx, timestamp, retdest + %mstore_trie_data + // stack: write_beacon_roots_to_storage, timestamp_idx, timestamp, retdest + JUMP + +skip_empty_slot: + // stack: 0, 64, storage_key, after_root_idx_slot_delete, write_beacon_roots_to_storage, timestamp_idx, timestamp, retdest + %pop4 + JUMP diff --git a/evm_arithmetization/src/cpu/kernel/asm/global_exit_root.asm b/evm_arithmetization/src/cpu/kernel/asm/global_exit_root.asm index c5cbfbdc9..7e45af890 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/global_exit_root.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/global_exit_root.asm @@ -2,6 +2,8 @@ /// Global exit roots (GER) are of the form `(timestamp, root)` and are loaded from prover inputs. /// The timestamp is written to the storage of address `ADDRESS_GLOBAL_EXIT_ROOT_MANAGER_L2` in the slot `keccak256(abi.encodePacked(root, GLOBAL_EXIT_ROOT_STORAGE_POS))`. /// See https://github.com/0xPolygonHermez/cdk-erigon/blob/zkevm/zk/utils/global_exit_root.go for reference. +/// +/// *NOTE*: This will panic if one of the provided timestamps is zero. global set_global_exit_roots: // stack: (empty) diff --git a/evm_arithmetization/src/testing_utils.rs b/evm_arithmetization/src/testing_utils.rs index f70a76e4a..779b389d0 100644 --- a/evm_arithmetization/src/testing_utils.rs +++ b/evm_arithmetization/src/testing_utils.rs @@ -40,8 +40,12 @@ fn insert_storage(trie: &mut HashedPartialTrie, slot: U256, value: U256) { slot.to_big_endian(&mut bytes); let key = keccak(bytes); let nibbles = Nibbles::from_bytes_be(key.as_bytes()).unwrap(); - let r = rlp::encode(&value); - trie.insert(nibbles, r.freeze().to_vec()); + if value.is_zero() { + trie.delete(nibbles); + } else { + let r = rlp::encode(&value); + trie.insert(nibbles, r.freeze().to_vec()); + } } /// Creates a storage trie for an account, given a list of `(slot, value)` @@ -81,10 +85,8 @@ pub fn update_beacon_roots_account_storage( /// Returns the beacon roots contract account from its provided storage trie. pub fn beacon_roots_contract_from_storage(storage_trie: &HashedPartialTrie) -> AccountRlp { AccountRlp { - nonce: 0.into(), - balance: 0.into(), storage_root: storage_trie.hash(), - code_hash: H256(BEACON_ROOTS_CONTRACT_CODE_HASH), + ..BEACON_ROOTS_ACCOUNT } } diff --git a/evm_arithmetization/tests/empty_txn_list.rs b/evm_arithmetization/tests/empty_txn_list.rs index a2553d1d9..b9e9b21e6 100644 --- a/evm_arithmetization/tests/empty_txn_list.rs +++ b/evm_arithmetization/tests/empty_txn_list.rs @@ -35,6 +35,7 @@ fn test_empty_txn_list() -> anyhow::Result<()> { let block_metadata = BlockMetadata { block_number: 1.into(), + block_timestamp: 1.into(), parent_beacon_block_root: H256(hex!( "44e2566c06c03b132e0ede3e90af477ebca74393b89dd6cb29f9c79cbcb6e963" )), diff --git a/evm_arithmetization/tests/global_exit_root.rs b/evm_arithmetization/tests/global_exit_root.rs index 74b4e15c0..fe15c85cb 100644 --- a/evm_arithmetization/tests/global_exit_root.rs +++ b/evm_arithmetization/tests/global_exit_root.rs @@ -31,7 +31,10 @@ fn test_global_exit_root() -> anyhow::Result<()> { let all_stark = AllStark::::default(); let config = StarkConfig::standard_fast_config(); - let block_metadata = BlockMetadata::default(); + let block_metadata = BlockMetadata { + block_timestamp: 1.into(), + ..BlockMetadata::default() + }; let (state_trie_before, storage_tries) = preinitialized_state_and_storage_tries(); let mut beacon_roots_account_storage = storage_tries[0].1.clone(); @@ -48,7 +51,7 @@ fn test_global_exit_root() -> anyhow::Result<()> { let mut trie = HashedPartialTrie::from(Node::Empty); update_beacon_roots_account_storage( &mut beacon_roots_account_storage, - 0.into(), + block_metadata.block_timestamp, block_metadata.parent_beacon_block_root, ); let beacon_roots_account = diff --git a/evm_arithmetization/tests/withdrawals.rs b/evm_arithmetization/tests/withdrawals.rs index 88d83374e..b5155bd75 100644 --- a/evm_arithmetization/tests/withdrawals.rs +++ b/evm_arithmetization/tests/withdrawals.rs @@ -33,7 +33,10 @@ fn test_withdrawals() -> anyhow::Result<()> { let all_stark = AllStark::::default(); let config = StarkConfig::standard_fast_config(); - let block_metadata = BlockMetadata::default(); + let block_metadata = BlockMetadata { + block_timestamp: 1.into(), + ..BlockMetadata::default() + }; let (state_trie_before, storage_tries) = preinitialized_state_and_storage_tries(); let mut beacon_roots_account_storage = storage_tries[0].1.clone(); @@ -50,7 +53,7 @@ fn test_withdrawals() -> anyhow::Result<()> { let mut trie = HashedPartialTrie::from(Node::Empty); update_beacon_roots_account_storage( &mut beacon_roots_account_storage, - 0.into(), + block_metadata.block_timestamp, block_metadata.parent_beacon_block_root, ); let beacon_roots_account = From 4f3e858a3bb7a146cd40131d2e57be9858d5bb0f Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Fri, 5 Apr 2024 08:25:02 +0900 Subject: [PATCH 12/40] EIP-4844 part 2: Point evaluation precompile (#133) * WIP * Complex test not passing * Misc * Fix exponentiation * PRints * Fix * Cleanup * Replace by zkcrypto lib * Fix * Cleanup * Post merge * Cleanup * TODO * Update return value * Add trusted setup * Fix * Now pass test * Add more tests * comment * Pacify clippy * Fix jump * Abort run in case of failure * Clippy * Add suggestions * Comments --- .../src/cpu/kernel/aggregator.rs | 3 +- .../kernel/asm/core/precompiles/blake2_f.asm | 2 +- .../kernel/asm/core/precompiles/bn_add.asm | 2 +- .../kernel/asm/core/precompiles/bn_mul.asm | 2 +- .../cpu/kernel/asm/core/precompiles/ecrec.asm | 2 +- .../kernel/asm/core/precompiles/expmod.asm | 2 +- .../cpu/kernel/asm/core/precompiles/id.asm | 2 +- .../kernel/asm/core/precompiles/kzg_peval.asm | 82 ++ .../cpu/kernel/asm/core/precompiles/main.asm | 19 +- .../kernel/asm/core/precompiles/rip160.asm | 2 +- .../kernel/asm/core/precompiles/sha256.asm | 2 +- .../kernel/asm/core/precompiles/snarkv.asm | 2 +- .../src/cpu/kernel/asm/core/process_txn.asm | 1 + .../src/cpu/kernel/asm/core/util.asm | 4 +- .../src/cpu/kernel/constants/mod.rs | 31 +- .../src/cpu/kernel/interpreter.rs | 8 + .../src/cpu/kernel/tests/bls381.rs | 140 +- .../src/cpu/kernel/tests/bn254.rs | 24 +- evm_arithmetization/src/curve_pairings.rs | 1286 ++++++++++++----- evm_arithmetization/src/extension_tower.rs | 1136 ++++++++++++++- .../src/generation/prover_input.rs | 195 ++- evm_arithmetization/src/witness/errors.rs | 2 + 22 files changed, 2528 insertions(+), 421 deletions(-) create mode 100644 evm_arithmetization/src/cpu/kernel/asm/core/precompiles/kzg_peval.asm diff --git a/evm_arithmetization/src/cpu/kernel/aggregator.rs b/evm_arithmetization/src/cpu/kernel/aggregator.rs index 9b2b0a85f..47a378b33 100644 --- a/evm_arithmetization/src/cpu/kernel/aggregator.rs +++ b/evm_arithmetization/src/cpu/kernel/aggregator.rs @@ -7,7 +7,7 @@ use super::assembler::{assemble, Kernel}; use crate::cpu::kernel::constants::evm_constants; use crate::cpu::kernel::parser::parse; -pub const NUMBER_KERNEL_FILES: usize = 152; +pub const NUMBER_KERNEL_FILES: usize = 153; pub static KERNEL_FILES: [&str; NUMBER_KERNEL_FILES] = [ "global jumped_to_0: PANIC", @@ -54,6 +54,7 @@ pub static KERNEL_FILES: [&str; NUMBER_KERNEL_FILES] = [ include_str!("asm/core/precompiles/bn_mul.asm"), include_str!("asm/core/precompiles/snarkv.asm"), include_str!("asm/core/precompiles/blake2_f.asm"), + include_str!("asm/core/precompiles/kzg_peval.asm"), include_str!("asm/curve/bls381/util.asm"), include_str!("asm/curve/bn254/curve_arithmetic/constants.asm"), include_str!("asm/curve/bn254/curve_arithmetic/curve_add.asm"), diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/blake2_f.asm b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/blake2_f.asm index 3a7498482..80557493c 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/blake2_f.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/blake2_f.asm @@ -35,7 +35,7 @@ global precompile_blake2_f: // stack: flag, flag, flag_addr, blake2_f_contd %gt_const(1) %jumpi(fault_exception) // Check flag < 2 (flag = 0 or flag = 1) - PUSH 0x100000000 // = 2^32 (is_kernel = true) + PUSH @IS_KERNEL // true // stack: kexit_info, flag, flag_addr, blake2_f_contd %stack () -> (@SEGMENT_CALLDATA, 4) diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/bn_add.asm b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/bn_add.asm index 9554044ef..43414e859 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/bn_add.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/bn_add.asm @@ -9,7 +9,7 @@ global precompile_bn_add: %checkpoint // Checkpoint %increment_call_depth // stack: (empty) - PUSH 0x100000000 // = 2^32 (is_kernel = true) + PUSH @IS_KERNEL // true // stack: kexit_info %charge_gas_const(@BN_ADD_GAS) diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/bn_mul.asm b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/bn_mul.asm index 5872e17f2..c29080166 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/bn_mul.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/bn_mul.asm @@ -9,7 +9,7 @@ global precompile_bn_mul: %checkpoint // Checkpoint %increment_call_depth // stack: (empty) - PUSH 0x100000000 // = 2^32 (is_kernel = true) + PUSH @IS_KERNEL // true // stack: kexit_info %charge_gas_const(@BN_MUL_GAS) diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/ecrec.asm b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/ecrec.asm index 6c141aabc..4a27ca75b 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/ecrec.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/ecrec.asm @@ -9,7 +9,7 @@ global precompile_ecrec: %checkpoint // Checkpoint %increment_call_depth // stack: (empty) - PUSH 0x100000000 // = 2^32 (is_kernel = true) + PUSH @IS_KERNEL // true // stack: kexit_info %charge_gas_const(@ECREC_GAS) diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/expmod.asm b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/expmod.asm index 6bff54ea4..1dc7841b5 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/expmod.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/expmod.asm @@ -163,7 +163,7 @@ global precompile_expmod: %checkpoint // Checkpoint %increment_call_depth // stack: (empty) - PUSH 0x100000000 // = 2^32 (is_kernel = true) + PUSH @IS_KERNEL // true // stack: kexit_info // Load l_B from i[0..32]. diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/id.asm b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/id.asm index a606ef4a8..4e238822b 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/id.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/id.asm @@ -9,7 +9,7 @@ global precompile_id: %checkpoint // Checkpoint %increment_call_depth // stack: (empty) - PUSH 0x100000000 // = 2^32 (is_kernel = true) + PUSH @IS_KERNEL // true // stack: kexit_info %calldatasize diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/kzg_peval.asm b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/kzg_peval.asm new file mode 100644 index 000000000..dba8766d2 --- /dev/null +++ b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/kzg_peval.asm @@ -0,0 +1,82 @@ +global precompile_kzg_peval: + // stack: address, retdest, new_ctx, (old stack) + %pop2 + // stack: new_ctx, (old stack) + %set_new_ctx_parent_pc(after_precompile) + // stack: new_ctx, (old stack) + DUP1 + SET_CONTEXT + %checkpoint // Checkpoint + %increment_call_depth + // stack: (empty) + PUSH @IS_KERNEL // true + // stack: kexit_info + + %charge_gas_const(@KZG_PEVAL_GAS) + + // Load `versioned_hash | z | y | commitment | proof` from the call data using `MLOAD_32BYTES`. + // Note that `z` and `y` are padded 32 byte big endian values, and `commitment` and `proof` are + // both 48 bytes big-endian encoded values. + // stack: kexit_info + PUSH @SEGMENT_CALLDATA + GET_CONTEXT + %build_address_no_offset + // stack: base_addr, kexit_info + PUSH 16 + DUP2 %add_const(176) + MLOAD_32BYTES + // stack: proof_lo, base_addr, kexit_info + PUSH 32 + DUP3 %add_const(144) + MLOAD_32BYTES + // stack: proof_hi, proof_lo, base_addr, kexit_info + PUSH 16 + DUP4 %add_const(128) + MLOAD_32BYTES + // stack: comm_lo, proof_hi, proof_lo, base_addr, kexit_info + PUSH 32 + DUP5 %add_const(96) + MLOAD_32BYTES + // stack: comm_hi, comm_lo, proof_hi, proof_lo, base_addr, kexit_info + PUSH 32 + DUP6 %add_const(64) + MLOAD_32BYTES + // stack: y, comm_hi, comm_lo, proof_hi, proof_lo, base_addr, kexit_info + PUSH 32 + DUP7 %add_const(32) + MLOAD_32BYTES + // stack: z, y, comm_hi, comm_lo, proof_hi, proof_lo, base_addr, kexit_info + PUSH 32 + DUP8 // no offset + MLOAD_32BYTES + +global verify_kzg_proof: + // stack: versioned_hash, z, y, comm_hi, comm_lo, proof_hi, proof_lo, base_addr, kexit_info + PROVER_INPUT(kzg_point_eval) + PROVER_INPUT(kzg_point_eval_2) + // stack: res_lo, res_hi, versioned_hash, z, y, comm_hi, comm_lo, proof_hi, proof_lo, base_addr, kexit_info + %stack (res_lo, res_hi, versioned_hash, z, y, comm_hi, comm_lo, proof_hi, proof_lo, base_addr, kexit_info) -> + (res_lo, res_hi, kexit_info) + +global store_kzg_verification: + // Store the result to the parent's return data using `mstore_unpacking`. + %mstore_parent_context_metadata(@CTX_METADATA_RETURNDATA_SIZE, 64) + %mload_context_metadata(@CTX_METADATA_PARENT_CONTEXT) + // stack: parent_ctx, res_lo, res_hi, kexit_info + PUSH @SEGMENT_RETURNDATA + %build_address_no_offset + // stack: addr, res_lo, res_hi, kexit_info + DUP1 %add_const(32) + %stack (addr_hi, addr_lo, res_lo, res_hi) + -> (addr_lo, res_lo, addr_hi, res_hi) + MSTORE_32BYTES_32 + MSTORE_32BYTES_32 + // stack: kexit_info + + POP + %leftover_gas + // stack: leftover_gas + PUSH 1 // success + %jump(terminate_common) + + SWAP1 diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/main.asm b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/main.asm index b7c916e9c..39f4e5734 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/main.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/main.asm @@ -10,15 +10,16 @@ global handle_precompiles: // stack: address, retdest, new_ctx, (old stack) - DUP1 %eq_const(@ECREC) %jumpi(precompile_ecrec) - DUP1 %eq_const(@SHA256) %jumpi(precompile_sha256) - DUP1 %eq_const(@RIP160) %jumpi(precompile_rip160) - DUP1 %eq_const(@ID) %jumpi(precompile_id) - DUP1 %eq_const(@EXPMOD) %jumpi(precompile_expmod) - DUP1 %eq_const(@BN_ADD) %jumpi(precompile_bn_add) - DUP1 %eq_const(@BN_MUL) %jumpi(precompile_bn_mul) - DUP1 %eq_const(@SNARKV) %jumpi(precompile_snarkv) - %eq_const(@BLAKE2_F) %jumpi(precompile_blake2_f) + DUP1 %eq_const(@ECREC) %jumpi(precompile_ecrec) + DUP1 %eq_const(@SHA256) %jumpi(precompile_sha256) + DUP1 %eq_const(@RIP160) %jumpi(precompile_rip160) + DUP1 %eq_const(@ID) %jumpi(precompile_id) + DUP1 %eq_const(@EXPMOD) %jumpi(precompile_expmod) + DUP1 %eq_const(@BN_ADD) %jumpi(precompile_bn_add) + DUP1 %eq_const(@BN_MUL) %jumpi(precompile_bn_mul) + DUP1 %eq_const(@SNARKV) %jumpi(precompile_snarkv) + DUP1 %eq_const(@BLAKE2_F) %jumpi(precompile_blake2_f) + %eq_const(@KZG_PEVAL) %jumpi(precompile_kzg_peval) // stack: retdest JUMP diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/rip160.asm b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/rip160.asm index e57504961..7dfdb1f9b 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/rip160.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/rip160.asm @@ -9,7 +9,7 @@ global precompile_rip160: %checkpoint // Checkpoint %increment_call_depth // stack: (empty) - PUSH 0x100000000 // = 2^32 (is_kernel = true) + PUSH @IS_KERNEL // true // stack: kexit_info %calldatasize diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/sha256.asm b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/sha256.asm index 3c926f0bb..44ce08159 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/sha256.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/sha256.asm @@ -9,7 +9,7 @@ global precompile_sha256: %checkpoint // Checkpoint %increment_call_depth // stack: (empty) - PUSH 0x100000000 // = 2^32 (is_kernel = true) + PUSH @IS_KERNEL // true // stack: kexit_info %calldatasize diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/snarkv.asm b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/snarkv.asm index 23ad9eb17..295acd029 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/snarkv.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/snarkv.asm @@ -9,7 +9,7 @@ global precompile_snarkv: %checkpoint // Checkpoint %increment_call_depth // stack: (empty) - PUSH 0x100000000 // = 2^32 (is_kernel = true) + PUSH @IS_KERNEL // true // stack: kexit_info PUSH 192 %calldatasize DUP2 DUP2 diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/process_txn.asm b/evm_arithmetization/src/cpu/kernel/asm/core/process_txn.asm index c70287a6f..5d60258dd 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/process_txn.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/process_txn.asm @@ -97,6 +97,7 @@ global warm_precompiles: PUSH @BN_MUL %insert_accessed_addresses_no_return PUSH @SNARKV %insert_accessed_addresses_no_return PUSH @BLAKE2_F %insert_accessed_addresses_no_return + PUSH @KZG_PEVAL %insert_accessed_addresses_no_return // EIP-3651 global warm_coinbase: diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/util.asm b/evm_arithmetization/src/cpu/kernel/asm/core/util.asm index a77329bd8..6b48428ef 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/util.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/util.asm @@ -26,8 +26,8 @@ %macro is_precompile // stack: addr - DUP1 %ge_const(@ECREC) SWAP1 %le_const(@BLAKE2_F) - // stack: addr>=1, addr<=9 + DUP1 %ge_const(@ECREC) SWAP1 %le_const(@KZG_PEVAL) + // stack: addr>=1, addr<=10 MUL // Cheaper than AND %endmacro diff --git a/evm_arithmetization/src/cpu/kernel/constants/mod.rs b/evm_arithmetization/src/cpu/kernel/constants/mod.rs index 892feab15..bf4bca84f 100644 --- a/evm_arithmetization/src/cpu/kernel/constants/mod.rs +++ b/evm_arithmetization/src/cpu/kernel/constants/mod.rs @@ -109,7 +109,7 @@ pub(crate) fn evm_constants() -> HashMap { c } -const MISC_CONSTANTS: [(&str, [u8; 32]); 4] = [ +const MISC_CONSTANTS: [(&str, [u8; 32]); 5] = [ // Base for limbs used in bignum arithmetic. ( "BIGNUM_LIMB_BASE", @@ -134,6 +134,12 @@ const MISC_CONSTANTS: [(&str, [u8; 32]); 4] = [ "INITIAL_TXN_RLP_ADDR", hex!("0000000000000000000000000000000000000000000000000000000c00000001"), ), + // Scaled boolean value indicating that we are in kernel mode, to be used within `kexit_info`. + // It is equal to 2^32. + ( + "IS_KERNEL", + hex!("0000000000000000000000000000000000000000000000000000000100000000"), + ), ]; const HASH_CONSTANTS: [(&str, [u8; 32]); 2] = [ @@ -276,7 +282,7 @@ const GAS_CONSTANTS: [(&str, u16); 37] = [ const REFUND_CONSTANTS: [(&str, u16); 2] = [("REFUND_SCLEAR", 4_800), ("MAX_REFUND_QUOTIENT", 5)]; -const PRECOMPILES: [(&str, u16); 9] = [ +const PRECOMPILES: [(&str, u16); 10] = [ ("ECREC", 1), ("SHA256", 2), ("RIP160", 3), @@ -286,9 +292,10 @@ const PRECOMPILES: [(&str, u16); 9] = [ ("BN_MUL", 7), ("SNARKV", 8), ("BLAKE2_F", 9), + ("KZG_PEVAL", 10), ]; -const PRECOMPILES_GAS: [(&str, u16); 13] = [ +const PRECOMPILES_GAS: [(&str, u16); 14] = [ ("ECREC_GAS", 3_000), ("SHA256_STATIC_GAS", 60), ("SHA256_DYNAMIC_GAS", 12), @@ -302,6 +309,7 @@ const PRECOMPILES_GAS: [(&str, u16); 13] = [ ("SNARKV_STATIC_GAS", 45_000), ("SNARKV_DYNAMIC_GAS", 34_000), ("BLAKE2_F__GAS", 1), + ("KZG_PEVAL_GAS", 50_000), ]; const SNARKV_POINTERS: [(&str, u64); 2] = [("SNARKV_INP", 112), ("SNARKV_OUT", 100)]; @@ -325,6 +333,23 @@ pub mod cancun_constants { pub const MIN_BLOB_BASE_FEE: U256 = U256::one(); + pub const KZG_VERSIONED_HASH: u8 = 0x01; + + pub const POINT_EVALUATION_PRECOMPILE_RETURN_VALUE: [[u8; 32]; 2] = [ + // U256(FIELD_ELEMENTS_PER_BLOB).to_be_bytes() + hex!("0000000000000000000000000000000000000000000000000000000000001000"), + // BLS_MODULUS.to_bytes32() + hex!("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"), + ]; + + // Taken from . + pub const G2_TRUSTED_SETUP_POINT: [[u8; 64]; 4] = [ + hex!("00000000000000000000000000000000185cbfee53492714734429b7b38608e23926c911cceceac9a36851477ba4c60b087041de621000edc98edada20c1def2"), // x_re + hex!("0000000000000000000000000000000015bfd7dd8cdeb128843bc287230af38926187075cbfbefa81009a2ce615ac53d2914e5870cb452d2afaaab24f3499f72"), // x_im + hex!("00000000000000000000000000000000014353bdb96b626dd7d5ee8599d1fca2131569490e28de18e82451a496a9c9794ce26d105941f383ee689bfbbb832a99"), // y_re + hex!("000000000000000000000000000000001666c54b0a32529503432fcae0181b4bef79de09fc63671fda5ed1ba9bfa07899495346f3d7ac9cd23048ef30d0a154f"), // y_im + ]; + pub const BEACON_ROOTS_ADDRESS: (&str, [u8; 20]) = ( "BEACON_ROOTS_ADDRESS", hex!("000F3df6D732807Ef1319fB7B8bB8522d0Beac02"), diff --git a/evm_arithmetization/src/cpu/kernel/interpreter.rs b/evm_arithmetization/src/cpu/kernel/interpreter.rs index c40d77459..2d6873f7c 100644 --- a/evm_arithmetization/src/cpu/kernel/interpreter.rs +++ b/evm_arithmetization/src/cpu/kernel/interpreter.rs @@ -737,6 +737,14 @@ impl Interpreter { if context == 0 { assert!(self.is_kernel()); } + + while self.generation_state.memory.contexts.len() <= context { + self.generation_state + .memory + .contexts + .push(MemoryContextState::default()); + } + self.generation_state.registers.context = context; } diff --git a/evm_arithmetization/src/cpu/kernel/tests/bls381.rs b/evm_arithmetization/src/cpu/kernel/tests/bls381.rs index 1ffa71150..2bf56c831 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/bls381.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/bls381.rs @@ -1,13 +1,20 @@ use anyhow::Result; use ethereum_types::U256; +use hex_literal::hex; +use keccak_hash::keccak; use plonky2::field::goldilocks_field::GoldilocksField as F; use rand::Rng; +use crate::cpu::kernel::aggregator::KERNEL; +use crate::cpu::kernel::cancun_constants::POINT_EVALUATION_PRECOMPILE_RETURN_VALUE; +use crate::cpu::kernel::constants::cancun_constants::KZG_VERSIONED_HASH; +use crate::cpu::kernel::constants::context_metadata::ContextMetadata; use crate::cpu::kernel::interpreter::{ - run_interpreter_with_memory, InterpreterMemoryInitialization, + self, run_interpreter_with_memory, Interpreter, InterpreterMemoryInitialization, }; use crate::extension_tower::{Fp2, Stack, BLS381}; -use crate::memory::segments::Segment::KernelGeneral; +use crate::memory::segments::Segment::{self, KernelGeneral}; +use crate::witness::errors::ProgramError; #[test] fn test_bls_fp2_mul() -> Result<()> { @@ -31,3 +38,132 @@ fn test_bls_fp2_mul() -> Result<()> { assert_eq!(output, x * y); Ok(()) } + +/// A KZG point evaluation precompile payload consists in: +/// - a G1 compressed point commitment (48 bytes) +/// - a Scalar element z (32 bytes) +/// - a Scalar element y (32 bytes) +/// - a G1 compressed point proof (48 bytes) +type KzgPayload = ([u8; 48], [u8; 32], [u8; 32], [u8; 48]); +/// Contains a KZG payload and the expected result, i.e. success or failure. +type TestSequence = (KzgPayload, bool); + +/// Test cases taken from . +const KZG_PRECOMPILE_TEST_SEQUENCES: [TestSequence; 10] = [ + // verify_kzg_proof_case_correct_proof_02e696ada7d4631d/data.yaml + ((hex!("c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + hex!("0000000000000000000000000000000000000000000000000000000000000002"), + hex!("0000000000000000000000000000000000000000000000000000000000000000"), + hex!("c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")), true), + + // verify_kzg_proof_case_correct_proof_0cf79b17cb5f4ea2/data.yaml + ((hex!("c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + hex!("5eb7004fe57383e6c88b99d839937fddf3f99279353aaf8d5c9a75f91ce33c62"), + hex!("0000000000000000000000000000000000000000000000000000000000000000"), + hex!("c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")), true), + + // verify_kzg_proof_case_correct_proof_9b24f8997145435c/data.yaml + ((hex!("93efc82d2017e9c57834a1246463e64774e56183bb247c8fc9dd98c56817e878d97b05f5c8d900acf1fbbbca6f146556"), + hex!("0000000000000000000000000000000000000000000000000000000000000001"), + hex!("0000000000000000000000000000000000000000000000000000000000000000"), + hex!("b9241c6816af6388d1014cd4d7dd21662a6e3d47f96c0257bce642b70e8e375839a880864638669c6a709b414ab8bffc")), true), + + // verify_kzg_proof_case_correct_proof_31ebd010e6098750/data.yaml + ((hex!("8f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7"), + hex!("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000"), + hex!("1522a4a7f34e1ea350ae07c29c96c7e79655aa926122e95fe69fcbd932ca49e9"), + hex!("a62ad71d14c5719385c0686f1871430475bf3a00f0aa3f7b8dd99a9abc2160744faf0070725e00b60ad9a026a15b1a8c")), true), + + + + // verify_kzg_proof_case_incorrect_proof_05c1f3685f3393f0/data.yaml + ((hex!("a572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e"), + hex!("564c0a11a0f704f4fc3e8acfe0f8245f0ad1347b378fbf96e206da11a5d36306"), + hex!("0000000000000000000000000000000000000000000000000000000000000002"), + hex!("97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb")), false), + + // verify_kzg_proof_case_incorrect_proof_d736268229bd87ec/data.yaml + ((hex!("93efc82d2017e9c57834a1246463e64774e56183bb247c8fc9dd98c56817e878d97b05f5c8d900acf1fbbbca6f146556"), + hex!("5eb7004fe57383e6c88b99d839937fddf3f99279353aaf8d5c9a75f91ce33c62"), + hex!("5fd58150b731b4facfcdd89c0e393ff842f5f2071303eff99b51e103161cd233"), + hex!("84c349506215a2d55f9d06f475b8229c6dedc08fd467f41fabae6bb042c2d0dbdbcd5f7532c475e479588eec5820fd37")), false), + + // verify_kzg_proof_case_incorrect_proof_point_at_infinity_83e53423a2dd93fe/data.yaml + ((hex!("a421e229565952cfff4ef3517100a97da1d4fe57956fa50a442f92af03b1bf37adacc8ad4ed209b31287ea5bb94d9d06"), + hex!("0000000000000000000000000000000000000000000000000000000000000001"), + hex!("1824b159acc5056f998c4fefecbc4ff55884b7fa0003480200000001fffffffe"), + hex!("c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")), false), + + // verify_kzg_proof_case_invalid_commitment_e9d3e9ec16fbc15f/data.yaml + ((hex!("8123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde0"), + hex!("0000000000000000000000000000000000000000000000000000000000000001"), + hex!("1824b159acc5056f998c4fefecbc4ff55884b7fa0003480200000001fffffffe"), + hex!("b0c829a8d2d3405304fecbea193e6c67f7c3912a6adc7c3737ad3f8a3b750425c1531a7426f03033a3994bc82a10609f")), false), + + // verify_kzg_proof_case_invalid_y_64b9ff2b8f7dddee/data.yaml + ((hex!("8f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7"), + hex!("0000000000000000000000000000000000000000000000000000000000000001"), + hex!("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000002"), + hex!("b30b3d1e4faccc380557792c9a0374d58fa286f5f75fea48870585393f890909cd3c53cfe4897e799fb211b4be531e43")), false), + + // verify_kzg_proof_case_invalid_z_64b9ff2b8f7dddee/data.yaml + ((hex!("8f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7"), + hex!("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000002"), + hex!("60f840641ec0d0c0d2b77b2d5a393b329442721fad05ab78c7b98f2aa3c20ec9"), + hex!("b30b3d1e4faccc380557792c9a0374d58fa286f5f75fea48870585393f890909cd3c53cfe4897e799fb211b4be531e43")), false), +]; + +#[test] +fn test_kzg_peval_precompile() -> Result<()> { + for (bytes, is_correct) in KZG_PRECOMPILE_TEST_SEQUENCES.iter() { + let commitment_bytes = bytes.0; + let comm_hi = U256::from_big_endian(&commitment_bytes[0..16]); + let comm_lo = U256::from_big_endian(&commitment_bytes[16..48]); + let mut versioned_hash = keccak(commitment_bytes).0; + versioned_hash[0] = KZG_VERSIONED_HASH; + let versioned_hash = U256::from_big_endian(&versioned_hash); + let z = U256::from_big_endian(&bytes.1); + let y = U256::from_big_endian(&bytes.2); + let proof_bytes = bytes.3; + let proof_hi = U256::from_big_endian(&proof_bytes[0..16]); + let proof_lo = U256::from_big_endian(&proof_bytes[16..48]); + + let mut stack = vec![ + versioned_hash, + z, + y, + comm_hi, + comm_lo, + proof_hi, + proof_lo, + U256::from(0xdeadbeefu32), + ]; + stack.reverse(); + + let verify_kzg_proof = KERNEL.global_labels["verify_kzg_proof"]; + let mut interpreter: Interpreter = Interpreter::new(verify_kzg_proof, stack); + interpreter.halt_offsets = vec![KERNEL.global_labels["store_kzg_verification"]]; + if *is_correct { + interpreter.run().unwrap(); + + let mut post_stack = interpreter.stack(); + post_stack.reverse(); + + assert_eq!( + post_stack[0], + U256::from_big_endian(&POINT_EVALUATION_PRECOMPILE_RETURN_VALUE[0]) + ); + assert_eq!( + post_stack[1], + U256::from_big_endian(&POINT_EVALUATION_PRECOMPILE_RETURN_VALUE[1]) + ); + } else { + assert!(interpreter.run().is_err()); + + let err_msg = interpreter.run().unwrap_err(); + assert!(err_msg.to_string().contains("KzgEvalFailure")); + } + } + + Ok(()) +} diff --git a/evm_arithmetization/src/cpu/kernel/tests/bn254.rs b/evm_arithmetization/src/cpu/kernel/tests/bn254.rs index efe2ed9f1..95994d9a9 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/bn254.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/bn254.rs @@ -6,8 +6,10 @@ use rand::Rng; use crate::cpu::kernel::interpreter::{ run_interpreter_with_memory, Interpreter, InterpreterMemoryInitialization, }; +use crate::curve_pairings::CurveAff; use crate::curve_pairings::{ - bn_final_exponent, bn_miller_loop, gen_bn_fp12_sparse, Curve, CyclicGroup, + bn254::{final_exponent, miller_loop}, + gen_fp12_sparse, CyclicGroup, }; use crate::extension_tower::{FieldExt, Fp12, Fp2, Fp6, Stack, BN254}; use crate::memory::segments::Segment::BnPairing; @@ -74,7 +76,7 @@ fn test_bn_mul_fp12() -> Result<()> { let mut rng = rand::thread_rng(); let f: Fp12 = rng.gen::>(); let g: Fp12 = rng.gen::>(); - let h: Fp12 = gen_bn_fp12_sparse(&mut rng); + let h: Fp12 = gen_fp12_sparse(&mut rng); let output_normal = run_bn_mul_fp12(f, g, "mul_fp254_12"); let output_sparse = run_bn_mul_fp12(f, h, "mul_fp254_12_sparse"); @@ -124,7 +126,7 @@ fn run_bn_frob_fp12(f: Fp12, n: usize) -> Fp12 { } #[test] -fn test_frob_fp12() -> Result<()> { +fn test_bn_frob_fp12() -> Result<()> { let mut rng = rand::thread_rng(); let f: Fp12 = rng.gen::>(); @@ -178,7 +180,7 @@ fn test_bn_final_exponent() -> Result<()> { let interpreter: Interpreter = run_interpreter_with_memory(setup).unwrap(); let output: Vec = interpreter.extract_kernel_memory(BnPairing, ptr..ptr + 12); - let expected: Vec = bn_final_exponent(f).to_stack(); + let expected: Vec = final_exponent(f).to_stack(); assert_eq!(output, expected); @@ -191,8 +193,8 @@ fn test_bn_miller() -> Result<()> { let out: usize = 106; let mut rng = rand::thread_rng(); - let p: Curve = rng.gen::>(); - let q: Curve> = rng.gen::>>(); + let p: CurveAff = rng.gen::>(); + let q: CurveAff> = rng.gen::>>(); let mut input = p.to_stack(); input.extend(q.to_stack()); @@ -205,7 +207,7 @@ fn test_bn_miller() -> Result<()> { }; let interpreter = run_interpreter_with_memory::(setup).unwrap(); let output: Vec = interpreter.extract_kernel_memory(BnPairing, out..out + 12); - let expected = bn_miller_loop(p, q).to_stack(); + let expected = miller_loop(p, q).to_stack(); assert_eq!(output, expected); @@ -226,13 +228,13 @@ fn test_bn_pairing() -> Result<()> { let n: i32 = rng.gen_range(-8..8); acc -= m * n; - let p: Curve = Curve::::int(m); - let q: Curve> = Curve::>::int(n); + let p: CurveAff = CurveAff::::int(m); + let q: CurveAff> = CurveAff::>::int(n); input.extend(p.to_stack()); input.extend(q.to_stack()); } - let p: Curve = Curve::::int(acc); - let q: Curve> = Curve::>::GENERATOR; + let p: CurveAff = CurveAff::::int(acc); + let q: CurveAff> = CurveAff::>::GENERATOR; input.extend(p.to_stack()); input.extend(q.to_stack()); diff --git a/evm_arithmetization/src/curve_pairings.rs b/evm_arithmetization/src/curve_pairings.rs index 673b3db03..2d9f8248d 100644 --- a/evm_arithmetization/src/curve_pairings.rs +++ b/evm_arithmetization/src/curve_pairings.rs @@ -1,14 +1,14 @@ use core::ops::{Add, Mul, Neg}; -use ethereum_types::U256; +use ethereum_types::{U256, U512}; use rand::distributions::Standard; use rand::prelude::Distribution; use rand::Rng; -use crate::extension_tower::{FieldExt, Fp12, Fp2, Fp6, Stack, BN254}; +use crate::extension_tower::{Adj, FieldExt, Fp12, Fp2, Fp6, Stack, BLS381, BN254}; #[derive(Debug, Copy, Clone, PartialEq)] -pub(crate) struct Curve +pub(crate) struct CurveAff where T: FieldExt, { @@ -16,16 +16,16 @@ where pub y: T, } -impl Curve { +impl CurveAff { pub(crate) const fn unit() -> Self { - Curve { + CurveAff { x: T::ZERO, y: T::ZERO, } } } -impl Stack for Curve { +impl Stack for CurveAff { const SIZE: usize = 2 * T::SIZE; fn to_stack(&self) -> Vec { @@ -35,47 +35,47 @@ impl Stack for Curve { } fn from_stack(stack: &[U256]) -> Self { - Curve { + CurveAff { x: T::from_stack(&stack[0..T::SIZE]), y: T::from_stack(&stack[T::SIZE..2 * T::SIZE]), } } } -impl Curve +impl CurveAff where T: FieldExt, - Curve: CyclicGroup, + CurveAff: CyclicGroup, { pub(crate) fn int(z: i32) -> Self { - Curve::::GENERATOR * z + CurveAff::::GENERATOR * z } } -impl Distribution> for Standard +impl Distribution> for Standard where T: FieldExt, - Curve: CyclicGroup, + CurveAff: CyclicGroup, { - fn sample(&self, rng: &mut R) -> Curve { - Curve::::GENERATOR * rng.gen::() + fn sample(&self, rng: &mut R) -> CurveAff { + CurveAff::::GENERATOR * rng.gen::() } } /// Standard addition formula for elliptic curves, restricted to the cases /// -impl Add for Curve { +impl Add for CurveAff { type Output = Self; fn add(self, other: Self) -> Self { - if self == Curve::::unit() { + if self == CurveAff::::unit() { return other; } - if other == Curve::::unit() { + if other == CurveAff::::unit() { return self; } if self == -other { - return Curve::::unit(); + return CurveAff::::unit(); } let m = if self == other { T::new(3) * self.x * self.x / (T::new(2) * self.y) @@ -83,18 +83,18 @@ impl Add for Curve { (other.y - self.y) / (other.x - self.x) }; let x = m * m - (self.x + other.x); - Curve { + CurveAff { x, y: m * (self.x - x) - self.y, } } } -impl Neg for Curve { - type Output = Curve; +impl Neg for CurveAff { + type Output = CurveAff; fn neg(self) -> Self { - Curve { + CurveAff { x: self.x, y: -self.y, } @@ -105,38 +105,29 @@ pub trait CyclicGroup { const GENERATOR: Self; } -/// The BN curve consists of pairs -/// (x, y): (BN254, BN254) | y^2 = x^3 + 2 -// with generator given by (1, 2) -impl CyclicGroup for Curve { - const GENERATOR: Curve = Curve { - x: BN254 { val: U256::one() }, - y: BN254 { - val: U256([2, 0, 0, 0]), - }, - }; -} - -impl Mul for Curve +impl Mul for CurveAff where T: FieldExt, - Curve: CyclicGroup, + CurveAff: CyclicGroup, { - type Output = Curve; + type Output = CurveAff; fn mul(self, other: i32) -> Self { if other == 0 { - return Curve::::unit(); + return CurveAff::::unit(); } - if self == Curve::::unit() { - return Curve::::unit(); + if self == CurveAff::::unit() { + return CurveAff::::unit(); + } + if other == 1 { + return self; } - let mut x: Curve = self; + let mut x: CurveAff = self; if other.is_negative() { x = -x; } - let mut result = Curve::::unit(); + let mut result = CurveAff::::unit(); let mut exp = other.unsigned_abs() as usize; while exp > 0 { @@ -150,366 +141,937 @@ where } } -/// The twisted curve consists of pairs -/// (x, y): (Fp2, Fp2) | y^2 = x^3 + 3/(9 + i) -/// with generator given as follows -impl CyclicGroup for Curve> { - const GENERATOR: Curve> = Curve { - x: Fp2 { - re: BN254 { - val: U256([ - 0x46debd5cd992f6ed, - 0x674322d4f75edadd, - 0x426a00665e5c4479, - 0x1800deef121f1e76, - ]), - }, - im: BN254 { - val: U256([ - 0x97e485b7aef312c2, - 0xf1aa493335a9e712, - 0x7260bfb731fb5d25, - 0x198e9393920d483a, - ]), - }, - }, - y: Fp2 { - re: BN254 { - val: U256([ - 0x4ce6cc0166fa7daa, - 0xe3d1e7690c43d37b, - 0x4aab71808dcb408f, - 0x12c85ea5db8c6deb, - ]), - }, - im: BN254 { - val: U256([ - 0x55acdadcd122975b, - 0xbc4b313370b38ef3, - 0xec9e99ad690c3395, - 0x090689d0585ff075, - ]), - }, - }, - }; -} - -// The tate pairing takes a point each from the curve and its twist and outputs -// an Fp12 element -pub(crate) fn bn_tate(p: Curve, q: Curve>) -> Fp12 { - let miller_output = bn_miller_loop(p, q); - bn_final_exponent(miller_output) +#[derive(Debug, Copy, Clone, PartialEq)] +pub(crate) struct CurveProj +where + T: FieldExt, +{ + pub x: T, + pub y: T, + pub z: T, } -/// Standard code for miller loop, can be found on page 99 at this url: -/// -/// where BN_EXP is a hardcoding of the array of Booleans that the loop -/// traverses -pub(crate) fn bn_miller_loop(p: Curve, q: Curve>) -> Fp12 { - let mut r = p; - let mut acc: Fp12 = Fp12::::UNIT; - let mut line: Fp12; - - for i in BN_EXP { - line = bn_tangent(r, q); - r = r + r; - acc = line * acc * acc; - if i { - line = bn_cord(p, r, q); - r = r + p; - acc = line * acc; +impl CurveProj { + pub(crate) const fn unit() -> Self { + CurveProj { + x: T::ZERO, + y: T::ZERO, + z: T::ZERO, } } - acc } -/// The sloped line function for doubling a point -pub(crate) fn bn_tangent(p: Curve, q: Curve>) -> Fp12 { - let cx = -BN254::new(3) * p.x * p.x; - let cy = BN254::new(2) * p.y; - bn_sparse_embed(p.y * p.y - BN254::new(9), q.x * cx, q.y * cy) -} +impl Stack for CurveProj { + const SIZE: usize = 3 * T::SIZE; + + fn to_stack(&self) -> Vec { + let mut stack = self.x.to_stack(); + stack.extend(self.y.to_stack()); + stack.extend(self.z.to_stack()); + stack + } -/// The sloped line function for adding two points -pub(crate) fn bn_cord(p1: Curve, p2: Curve, q: Curve>) -> Fp12 { - let cx = p2.y - p1.y; - let cy = p1.x - p2.x; - bn_sparse_embed(p1.y * p2.x - p2.y * p1.x, q.x * cx, q.y * cy) + fn from_stack(stack: &[U256]) -> Self { + CurveProj { + x: T::from_stack(&stack[0..T::SIZE]), + y: T::from_stack(&stack[T::SIZE..2 * T::SIZE]), + z: T::from_stack(&stack[2 * T::SIZE..3 * T::SIZE]), + } + } } -/// The tangent and cord functions output sparse Fp12 elements. +/// The tangent and chord functions output sparse Fp12 elements. /// This map embeds the nonzero coefficients into an Fp12. -pub(crate) const fn bn_sparse_embed(g000: BN254, g01: Fp2, g11: Fp2) -> Fp12 { +pub(crate) const fn sparse_embed(g000: F, g01: Fp2, g11: Fp2) -> Fp12 +where + F: FieldExt, + Fp2: Adj, +{ let g0 = Fp6 { t0: Fp2 { re: g000, - im: BN254::ZERO, + im: F::ZERO, }, t1: g01, - t2: Fp2::::ZERO, + t2: Fp2::::ZERO, }; let g1 = Fp6 { - t0: Fp2::::ZERO, + t0: Fp2::::ZERO, t1: g11, - t2: Fp2::::ZERO, + t2: Fp2::::ZERO, }; Fp12 { z0: g0, z1: g1 } } -pub(crate) fn gen_bn_fp12_sparse(rng: &mut R) -> Fp12 { - bn_sparse_embed( - rng.gen::(), - rng.gen::>(), - rng.gen::>(), - ) +pub(crate) fn check_curve_eq_aff(p: CurveAff, b_coeff: T) -> bool +where + T: FieldExt, +{ + p.y * p.y == p.x * p.x * p.x + b_coeff } -/// The output y of the miller loop is not an invariant, -/// but one gets an invariant by raising y to the power -/// (p^12 - 1)/N = (p^6 - 1)(p^2 + 1)(p^4 - p^2 + 1)/N -/// where N is the cyclic group order of the curve. -/// To achieve this, we first exponentiate y by p^6 - 1 via -/// y = y_6 / y -/// and then exponentiate the result by p^2 + 1 via -/// y = y_2 * y -/// We then note that (p^4 - p^2 + 1)/N can be rewritten as -/// (p^4 - p^2 + 1)/N = p^3 + (a2)p^2 - (a1)p - a0 -/// where 0 < a0, a1, a2 < p. Then the final power is given by -/// y = y_3 * (y^a2)_2 * (y^-a1)_1 * (y^-a0) -pub(crate) fn bn_final_exponent(f: Fp12) -> Fp12 { - let mut y = f.frob(6) / f; - y = y.frob(2) * y; - let (y_a2, y_a1, y_a0) = get_bn_custom_powers(y); - y.frob(3) * y_a2.frob(2) * y_a1.frob(1) * y_a0 +pub(crate) fn check_curve_eq_proj(p: CurveProj, b_coeff: T) -> bool +where + T: FieldExt, +{ + p.y * p.y == p.x * p.x * p.x + b_coeff } -/// We first together (so as to avoid repeated steps) compute -/// y^a4, y^a2, y^a0 -/// where a1 is given by -/// a1 = a4 + 2a2 - a0 -/// we then invert y^a0 and return -/// y^a2, y^a1 = y^a4 * y^a2 * y^a2 * y^(-a0), y^(-a0) -/// -/// Representing a4, a2, a0 in *little endian* binary, define -/// BN_EXPS4 = [(a4[i], a2[i], a0[i]) for i in 0..len(a4)] -/// BN_EXPS2 = [ (a2[i], a0[i]) for i in len(a4)..len(a2)] -/// BN_EXPS0 = [ a0[i] for i in len(a2)..len(a0)] -fn get_bn_custom_powers(f: Fp12) -> (Fp12, Fp12, Fp12) { - let mut sq: Fp12 = f; - let mut y0: Fp12 = Fp12::::UNIT; - let mut y2: Fp12 = Fp12::::UNIT; - let mut y4: Fp12 = Fp12::::UNIT; - - // proceed via standard squaring algorithm for exponentiation - - // must keep multiplying all three values: a4, a2, a0 - for (a, b, c) in BN_EXPS4 { - if a { - y4 = y4 * sq; +/// Generates a sparse, random Fp12 element. +pub(crate) fn gen_fp12_sparse(rng: &mut R) -> Fp12 +where + F: FieldExt, + Fp2: Adj, + Standard: Distribution, +{ + sparse_embed::(rng.gen::(), rng.gen::>(), rng.gen::>()) +} + +pub mod bn254 { + use super::*; + + /// The BN curve consists of pairs + /// (x, y): (BN254, BN254) | y^2 = x^3 + 3 + // with generator given by (1, 2). + impl CyclicGroup for CurveAff { + const GENERATOR: CurveAff = CurveAff { + x: BN254 { val: U256::one() }, + y: BN254 { + val: U256([2, 0, 0, 0]), + }, + }; + } + + /// The twisted curve consists of pairs + /// (x, y): (Fp2, Fp2) | y^2 = x^3 + 3/(9 + i) + /// with generator given as follows: + impl CyclicGroup for CurveAff> { + const GENERATOR: CurveAff> = CurveAff { + x: Fp2 { + re: BN254 { + val: U256([ + 0x46debd5cd992f6ed, + 0x674322d4f75edadd, + 0x426a00665e5c4479, + 0x1800deef121f1e76, + ]), + }, + im: BN254 { + val: U256([ + 0x97e485b7aef312c2, + 0xf1aa493335a9e712, + 0x7260bfb731fb5d25, + 0x198e9393920d483a, + ]), + }, + }, + y: Fp2 { + re: BN254 { + val: U256([ + 0x4ce6cc0166fa7daa, + 0xe3d1e7690c43d37b, + 0x4aab71808dcb408f, + 0x12c85ea5db8c6deb, + ]), + }, + im: BN254 { + val: U256([ + 0x55acdadcd122975b, + 0xbc4b313370b38ef3, + 0xec9e99ad690c3395, + 0x090689d0585ff075, + ]), + }, + }, + }; + } + + /// The sloped line function for doubling a point. + pub(crate) fn tangent(p: CurveAff, q: CurveAff>) -> Fp12 { + let cx = -BN254::new(3) * p.x * p.x; + let cy = BN254::new(2) * p.y; + sparse_embed::(p.y * p.y - BN254::new(9), q.x * cx, q.y * cy) + } + + /// The sloped line function for adding two points. + pub(crate) fn chord( + p1: CurveAff, + p2: CurveAff, + q: CurveAff>, + ) -> Fp12 { + let cx = p2.y - p1.y; + let cy = p1.x - p2.x; + sparse_embed::(p1.y * p2.x - p2.y * p1.x, q.x * cx, q.y * cy) + } + + // The tate pairing takes points from the curve and its twist and outputs + // an Fp12 element. + pub(crate) fn tate(p: CurveAff, q: CurveAff>) -> Fp12 { + let miller_output = miller_loop(p, q); + final_exponent(miller_output) + } + + /// Standard code for miller loop, can be found on page 99 at this url: + /// + /// where BN_EXP is a hardcoding of the array of Booleans that the loop + /// traverses. + pub(crate) fn miller_loop(p: CurveAff, q: CurveAff>) -> Fp12 { + let mut r = p; + let mut acc: Fp12 = Fp12::::UNIT; + let mut line: Fp12; + + for i in BN_EXP { + line = tangent(r, q); + r = r + r; + acc = line * acc * acc; + if i { + line = chord(p, r, q); + r = r + p; + acc = line * acc; + } + } + acc + } + + /// The output y of the miller loop is not an invariant, + /// but one gets an invariant by raising y to the power + /// (p^12 - 1)/N = (p^6 - 1)(p^2 + 1)(p^4 - p^2 + 1)/N + /// where N is the cyclic group order of the curve. + /// To achieve this, we first exponentiate y by p^6 - 1 via + /// y = y_6 / y + /// and then exponentiate the result by p^2 + 1 via + /// y = y_2 * y + /// We then note that (p^4 - p^2 + 1)/N can be rewritten as + /// (p^4 - p^2 + 1)/N = p^3 + (a2)p^2 + (a1)p - a0 + /// where 0 < a0, a1, a2 < p. Then the final power is given by + /// y = y_3 * (y^a2)_2 * (y^a1)_1 * (y^-a0). + pub(crate) fn final_exponent(f: Fp12) -> Fp12 { + let mut y = f.frob(6) / f; + y = y.frob(2) * y; + let (y_a2, y_a1, y_a0) = get_custom_powers(y); + y.frob(3) * y_a2.frob(2) * y_a1.frob(1) * y_a0 + } + + /// We first together (so as to avoid repeated steps) compute + /// y^a4, y^a2, y^a0 + /// where a1 is given by + /// a1 = a4 + 2a2 - a0 + /// we then invert y^a0 and return + /// y^a2, y^a1 = y^a4 * y^a2 * y^a2 * y^(-a0), y^(-a0) + /// + /// Representing a4, a2, a0 in *little endian* binary, define + /// BN_EXPS4 = [(a4[i], a2[i], a0[i]) for i in 0..len(a4)] + /// BN_EXPS2 = [ (a2[i], a0[i]) for i in len(a4)..len(a2)] + /// BN_EXPS0 = [ a0[i] for i in len(a2)..len(a0)] + fn get_custom_powers(f: Fp12) -> (Fp12, Fp12, Fp12) { + let mut sq: Fp12 = f; + let mut y0: Fp12 = Fp12::::UNIT; + let mut y2: Fp12 = Fp12::::UNIT; + let mut y4: Fp12 = Fp12::::UNIT; + + // proceed via standard squaring algorithm for exponentiation + + // must keep multiplying all three values: a4, a2, a0 + for (a, b, c) in BN_EXPS4 { + if a { + y4 = y4 * sq; + } + if b { + y2 = y2 * sq; + } + if c { + y0 = y0 * sq; + } + sq = sq * sq; } - if b { - y2 = y2 * sq; + // leading term of a4 is always 1 + y4 = y4 * sq; + + // must keep multiplying remaining two values: a2, a0 + for (a, b) in BN_EXPS2 { + if a { + y2 = y2 * sq; + } + if b { + y0 = y0 * sq; + } + sq = sq * sq; } - if c { - y0 = y0 * sq; + // leading term of a2 is always 1 + y2 = y2 * sq; + + // must keep multiplying final remaining value: a0 + for a in BN_EXPS0 { + if a { + y0 = y0 * sq; + } + sq = sq * sq; } - sq = sq * sq; + // leading term of a0 is always 1 + y0 = y0 * sq; + + // invert y0 to compute y^(-a0) + let y0_inv = y0.inv(); + + // return y^a2 = y2, y^a1 = y4 * y2^2 * y^(-a0), y^(-a0) + (y2, y4 * y2 * y2 * y0_inv, y0_inv) + } + + const BN_EXP: [bool; 253] = [ + true, false, false, false, false, false, true, true, false, false, true, false, false, + false, true, false, false, true, true, true, false, false, true, true, true, false, false, + true, false, true, true, true, false, false, false, false, true, false, false, true, true, + false, false, false, true, true, false, true, false, false, false, false, false, false, + false, true, false, true, false, false, true, true, false, true, true, true, false, false, + false, false, true, false, true, false, false, false, false, false, true, false, false, + false, true, false, true, true, false, true, true, false, true, true, false, true, false, + false, false, false, false, false, true, true, false, false, false, false, false, false, + true, false, true, false, true, true, false, false, false, false, true, false, true, true, + true, false, true, false, false, true, false, true, false, false, false, false, false, + true, true, false, false, true, true, true, true, true, false, true, false, false, false, + false, true, false, false, true, false, false, false, false, true, true, true, true, false, + false, true, true, false, true, true, true, false, false, true, false, true, true, true, + false, false, false, false, true, false, false, true, false, false, false, true, false, + true, false, false, false, false, true, true, true, true, true, false, false, false, false, + true, true, true, true, true, false, true, false, true, true, false, false, true, false, + false, true, true, true, true, true, true, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + ]; + + // The following constants are defined above get_custom_powers. + const BN_EXPS4: [(bool, bool, bool); 64] = [ + (true, true, false), + (true, true, true), + (true, true, true), + (false, false, false), + (false, false, true), + (true, false, true), + (false, true, false), + (true, false, true), + (true, true, false), + (true, false, true), + (false, true, false), + (true, true, false), + (true, true, false), + (true, true, false), + (false, true, false), + (false, true, false), + (false, false, true), + (true, false, true), + (true, true, false), + (false, true, false), + (true, true, false), + (true, true, false), + (true, true, false), + (false, false, true), + (false, false, true), + (true, false, true), + (true, false, true), + (true, true, false), + (true, false, false), + (true, true, false), + (false, true, false), + (true, true, false), + (true, false, false), + (false, true, false), + (false, false, false), + (true, false, false), + (true, false, false), + (true, false, true), + (false, false, true), + (false, true, true), + (false, false, true), + (false, true, true), + (false, true, true), + (false, false, false), + (true, true, true), + (true, false, true), + (true, false, true), + (false, true, true), + (true, false, true), + (false, true, true), + (false, true, true), + (true, true, false), + (true, true, false), + (true, true, false), + (true, false, false), + (false, false, true), + (true, false, false), + (false, false, true), + (true, false, true), + (true, true, false), + (true, true, true), + (false, true, true), + (false, true, false), + (true, true, true), + ]; + + const BN_EXPS2: [(bool, bool); 62] = [ + (true, false), + (true, true), + (false, false), + (true, false), + (true, false), + (true, true), + (true, false), + (true, true), + (true, false), + (false, true), + (false, true), + (true, true), + (true, true), + (false, false), + (true, true), + (false, false), + (false, false), + (false, true), + (false, true), + (true, true), + (true, true), + (true, true), + (false, true), + (true, true), + (false, false), + (true, true), + (true, false), + (true, true), + (false, false), + (true, true), + (true, true), + (true, false), + (false, false), + (false, true), + (false, false), + (true, true), + (false, true), + (false, false), + (true, false), + (false, true), + (false, true), + (true, false), + (false, true), + (false, false), + (false, false), + (false, false), + (false, true), + (true, false), + (true, true), + (false, true), + (true, true), + (true, false), + (false, true), + (false, false), + (true, false), + (false, true), + (true, false), + (true, true), + (true, false), + (true, true), + (false, true), + (true, true), + ]; + + const BN_EXPS0: [bool; 65] = [ + false, false, true, false, false, true, true, false, true, false, true, true, true, false, + true, false, false, false, true, false, false, true, false, true, false, true, true, false, + false, false, false, false, true, false, true, false, true, true, true, false, false, true, + true, true, true, false, true, false, true, true, false, false, true, false, false, false, + true, true, true, true, false, false, true, true, false, + ]; +} + +// The optimal Ate pairing implementation for BLS12-381 has been taken from +// . +pub mod bls381 { + use anyhow::{anyhow, Result}; + + use super::*; + use crate::extension_tower::BLS_BASE; + + const B_G1: BLS381 = BLS381 { + val: U512([4, 0, 0, 0, 0, 0, 0, 0]), + }; + + /// The BLS curve consists of pairs + /// (x, y): (BLS381, BLS381) | y^2 = x^3 + 4 + // with generator given by + // x = 3685416753713387016781088315183077757961620795782546409894578378688607592378376318836054947676345821548104185464507 + // y = 1339506544944476473020471379941921221584933875938349620426543736416511423956333506472724655353366534992391756441569 + impl CyclicGroup for CurveAff { + const GENERATOR: CurveAff = CurveAff { + x: BLS381 { + val: U512([ + 0xfb3af00adb22c6bb, + 0x6c55e83ff97a1aef, + 0xa14e3a3f171bac58, + 0xc3688c4f9774b905, + 0x2695638c4fa9ac0f, + 0x17f1d3a73197d794, + 0, + 0, + ]), + }, + y: BLS381 { + val: U512([ + 0x0caa232946c5e7e1, + 0xd03cc744a2888ae4, + 0x00db18cb2c04b3ed, + 0xfcf5e095d5d00af6, + 0xa09e30ed741d8ae4, + 0x08b3f481e3aaa0f1, + 0, + 0, + ]), + }, + }; } - // leading term of a4 is always 1 - y4 = y4 * sq; - // must keep multiplying remaining two values: a2, a0 - for (a, b) in BN_EXPS2 { - if a { - y2 = y2 * sq; + /// The twisted curve consists of pairs + /// (x, y): (Fp2, Fp2) | y^2 = x^3 + 4*(i + 1) + /// with generator given by + // x = 352701069587466618187139116011060144890029952792775240219908644239793785735715026873347600343865175952761926303160 + // + 3059144344244213709971259814753781636986470325476647558659373206291635324768958432433509563104347017837885763365758 * i + // y = 1985150602287291935568054521177171638300868978215655730859378665066344726373823718423869104263333984641494340347905 + // + 927553665492332455747201965776037880757740193453592970025027978793976877002675564980949289727957565575433344219582 * i + impl CyclicGroup for CurveAff> { + const GENERATOR: CurveAff> = CurveAff { + x: Fp2 { + re: BLS381 { + val: U512([ + 0xd48056c8c121bdb8, + 0x0bac0326a805bbef, + 0xb4510b647ae3d177, + 0xc6e47ad4fa403b02, + 0x260805272dc51051, + 0x024aa2b2f08f0a91, + 0, + 0, + ]), + }, + im: BLS381 { + val: U512([ + 0xe5ac7d055d042b7e, + 0x334cf11213945d57, + 0xb5da61bbdc7f5049, + 0x596bd0d09920b61a, + 0x7dacd3a088274f65, + 0x13e02b6052719f60, + 0, + 0, + ]), + }, + }, + y: Fp2 { + re: BLS381 { + val: U512([ + 0xe193548608b82801, + 0x923ac9cc3baca289, + 0x6d429a695160d12c, + 0xadfd9baa8cbdd3a7, + 0x8cc9cdc6da2e351a, + 0x0ce5d527727d6e11, + 0, + 0, + ]), + }, + im: BLS381 { + val: U512([ + 0xaaa9075ff05f79be, + 0x3f370d275cec1da1, + 0x267492ab572e99ab, + 0xcb3e287e85a763af, + 0x32acd2b02bc28b99, + 0x0606c4a02ea734cc, + 0, + 0, + ]), + }, + }, + }; + } + + /// Deserializes a sequence of bytes into a BLS12-381 G1 element in affine + /// coordinates. Follows the procedure defined in `octets_to_point` of + /// , + /// based on zkcrypto/bls12_381 serialization design notes available at + /// . + pub(crate) fn g1_from_bytes(bytes: &[u8; 48]) -> Result> { + // Obtain the three flags from the start of the byte sequence + let compression_flag_set = ((bytes[0] >> 7) & 1) != 0; + let infinity_flag_set = ((bytes[0] >> 6) & 1) != 0; + let sort_flag_set = ((bytes[0] >> 5) & 1) != 0; + + // Attempt to obtain the x-coordinate + let x = { + let mut tmp = [0; 48]; + tmp.copy_from_slice(&bytes[0..48]); + + // Mask away the flag bits + tmp[0] &= 0b0001_1111; + + BLS381 { + val: U512::from_big_endian(&tmp), + } + }; + + if x.val > BLS_BASE { + return Err(anyhow!("X coordinate is larger than modulus.")); } - if b { - y0 = y0 * sq; + + if infinity_flag_set { + if !( + compression_flag_set & // Compression flag should be set + (!sort_flag_set) & // Sort flag should not be set + x.val.is_zero() + // The x-coordinate should be zero + ) { + return Err(anyhow!("Byte flags are contradictory")); + } + + return Ok(CurveAff::::unit()); + } + + // Recover a y-coordinate given x with y = sqrt(x^3 + 4). + if let Ok(mut y) = ((x * x * x) + B_G1).sqrt() { + // Switch to the correct y-coordinate if necessary. + + if y.lexicographically_largest() ^ sort_flag_set { + y = -y; + } + + if infinity_flag_set | !compression_flag_set { + return Err(anyhow!("Byte flags are contradictory")); + } + + Ok(CurveAff:: { x, y }) + } else { + Err(anyhow!("This point is not on the curve.")) } - sq = sq * sq; } - // leading term of a2 is always 1 - y2 = y2 * sq; - // must keep multiplying final remaining value: a0 - for a in BN_EXPS0 { - if a { - y0 = y0 * sq; + // The optimal Ate pairing takes a point each from the curve and its twist and + // outputs an Fp12 element. + pub(crate) fn ate_optim(p: CurveAff, q: CurveAff>) -> Fp12 { + let miller_output = miller_loop(p, q); + final_exponent(miller_output) + } + + /// Miller loop for the optimal Ate pairing, which computes $f_{u,Q}(P)$ + /// with the accumulator as a point on the twist, before exponentiating + /// by $(p^12 - 1)/r$ with $r$ the order of the multiplicative target group. + pub(crate) fn miller_loop(p: CurveAff, q: CurveAff>) -> Fp12 { + let mut r = CurveProj::> { + x: q.x, + y: q.y, + z: Fp2::::UNIT, + }; + let mut acc: Fp12 = Fp12::::UNIT; + let mut line: Fp12; + + let mut found_one = false; + for i in (0..64).rev().map(|b| (((X_GENERATOR >> 1) >> b) & 1) == 1) { + if !found_one { + found_one = i; + continue; + } + let coeffs = doubling_step(&mut r); + acc = ell(acc, &coeffs, &p); + + if i { + let coeffs = addition_step(&mut r, &q); + acc = ell(acc, &coeffs, &p); + } + + acc = acc * acc; } - sq = sq * sq; + + let coeffs = doubling_step(&mut r); + acc = ell(acc, &coeffs, &p); + + acc.conj() // X_GENERATOR is negative + } + + fn ell( + f: Fp12, + coeffs: &(Fp2, Fp2, Fp2), + p: &CurveAff, + ) -> Fp12 { + let mut c0 = coeffs.0; + let mut c1 = coeffs.1; + + c0.re = c0.re * p.y; + c0.im = c0.im * p.y; + + c1.re = c1.re * p.x; + c1.im = c1.im * p.x; + + f.mul_by_014(coeffs.2, c1, c0) } - // leading term of a0 is always 1 - y0 = y0 * sq; - // invert y0 to compute y^(-a0) - let y0_inv = y0.inv(); + fn doubling_step(r: &mut CurveProj>) -> (Fp2, Fp2, Fp2) { + // Adaptation of Algorithm 26, https://eprint.iacr.org/2010/354.pdf + let tmp0 = r.x * r.x; + let tmp1 = r.y * r.y; + let tmp2 = tmp1 * tmp1; + let tmp3 = (tmp1 + r.x) * (tmp1 + r.x) - tmp0 - tmp2; + let tmp3 = tmp3 + tmp3; + let tmp4 = tmp0 + tmp0 + tmp0; + let tmp6 = r.x + tmp4; + let tmp5 = tmp4 * tmp4; + let z_sq = r.z * r.z; + r.x = tmp5 - tmp3 - tmp3; + r.z = (r.z + r.y) * (r.z + r.y) - tmp1 - z_sq; + r.y = (tmp3 - r.x) * tmp4; + let tmp2 = tmp2 + tmp2; + let tmp2 = tmp2 + tmp2; + let tmp2 = tmp2 + tmp2; + r.y = r.y - tmp2; + let tmp3 = tmp4 * z_sq; + let tmp3 = tmp3 + tmp3; + let tmp3 = -tmp3; + let tmp6 = tmp6 * tmp6 - tmp0 - tmp5; + let tmp1 = tmp1 + tmp1; + let tmp1 = tmp1 + tmp1; + let tmp6 = tmp6 - tmp1; + let tmp0 = r.z * z_sq; + let tmp0 = tmp0 + tmp0; - // return y^a2 = y2, y^a1 = y4 * y2^2 * y^(-a0), y^(-a0) - (y2, y4 * y2 * y2 * y0_inv, y0_inv) + (tmp0, tmp3, tmp6) + } + + fn addition_step( + r: &mut CurveProj>, + q: &CurveAff>, + ) -> (Fp2, Fp2, Fp2) { + // Adaptation of Algorithm 27, https://eprint.iacr.org/2010/354.pdf + let z_sq = r.z * r.z; + let y_sq = q.y * q.y; + let t0 = z_sq * q.x; + let t1 = ((q.y + r.z) * (q.y + r.z) - y_sq - z_sq) * z_sq; + let t2 = t0 - r.x; + let t3 = t2 * t2; + let t4 = t3 + t3; + let t4 = t4 + t4; + let t5 = t4 * t2; + let t6 = t1 - r.y - r.y; + let t9 = t6 * q.x; + let t7 = t4 * r.x; + r.x = t6 * t6 - t5 - t7 - t7; + r.z = (r.z + t2) * (r.z + t2) - z_sq - t3; + let t10 = q.y + r.z; + let t8 = (t7 - r.x) * t6; + let t0 = r.y * t5; + let t0 = t0 + t0; + r.y = t8 - t0; + let t10 = t10 * t10 - y_sq; + let zt_sq = r.z * r.z; + let t10 = t10 - zt_sq; + let t9 = t9 + t9 - t10; + let t10 = r.z + r.z; + let t6 = -t6; + let t1 = t6 + t6; + + (t10, t1, t9) + } + + /// The output y of the miller loop is not an invariant, + /// but one gets an invariant by raising y to the power + /// (p^12 - 1)/N = (p^6 - 1)(p^2 + 1)(p^4 - p^2 + 1)/N + /// where N is the cyclic group order of the curve. + /// + /// See section 5 of . + pub(crate) fn final_exponent(f: Fp12) -> Fp12 { + let mut t0 = f.frob(6); + let mut t1 = f.inv(); + let mut t2 = t0 * t1; + t1 = t2; + t2 = t2.frob(2); + t2 = t2 * t1; + t1 = cyclotomic_square(t2).conj(); + let mut t3 = cyclotomic_exp(t2); + let mut t4 = cyclotomic_square(t3); + let mut t5 = t1 * t3; + t1 = cyclotomic_exp(t5); + t0 = cyclotomic_exp(t1); + let mut t6 = cyclotomic_exp(t0); + t6 = t6 * t4; + t4 = cyclotomic_exp(t6); + t5 = t5.conj(); + t4 = t4 * t5; + t4 = t4 * t2; + t5 = t2.conj(); + t1 = t1 * t2; + t1 = t1.frob(3); + t6 = t6 * t5; + t6 = t6.frob(1); + t3 = t3 * t0; + t3 = t3.frob(2); + t3 = t3 * t1; + t3 = t3 * t6; + t3 * t4 + } + + fn fp4_square(a: Fp2, b: Fp2) -> (Fp2, Fp2) { + let t0 = a * a; + let t1 = b * b; + let mut t2 = t1.mul_adj(); + let c0 = t2 + t0; + t2 = a + b; + t2 = t2 * t2 - t0; + let c1 = t2 - t1; + + (c0, c1) + } + + // Adaptation of Algorithm 5.5.4, Guide to Pairing-Based Cryptography + // Faster Squaring in the Cyclotomic Subgroup of Sixth Degree Extensions + // . + fn cyclotomic_square(f: Fp12) -> Fp12 { + let mut z0 = f.z0.t0; + let mut z4 = f.z0.t1; + let mut z3 = f.z0.t2; + let mut z2 = f.z1.t0; + let mut z1 = f.z1.t1; + let mut z5 = f.z1.t2; + + let (t0, t1) = fp4_square(z0, z1); + + // For A + z0 = t0 - z0; + z0 = z0 + z0 + t0; + + z1 = t1 + z1; + z1 = z1 + z1 + t1; + + let (mut t0, t1) = fp4_square(z2, z3); + let (t2, t3) = fp4_square(z4, z5); + + // For C + z4 = t0 - z4; + z4 = z4 + z4 + t0; + + z5 = t1 + z5; + z5 = z5 + z5 + t1; + + // For B + t0 = t3.mul_adj(); + z2 = t0 + z2; + z2 = z2 + z2 + t0; + + z3 = t2 - z3; + z3 = z3 + z3 + t2; + + Fp12:: { + z0: Fp6:: { + t0: z0, + t1: z4, + t2: z3, + }, + z1: Fp6:: { + t0: z2, + t1: z1, + t2: z5, + }, + } + } + + fn cyclotomic_exp(f: Fp12) -> Fp12 { + let mut tmp = Fp12::::UNIT; + + let mut found_one = false; + for i in (0..64).rev().map(|b| ((X_GENERATOR >> b) & 1) == 1) { + if found_one { + tmp = cyclotomic_square(tmp) + } else { + found_one = i; + } + + if i { + tmp = tmp * f; + } + } + + tmp.conj() + } + + /// The value used to generate both scalar and base fields of BLS12-381. + /// Note that `x` is negative, and the Miller loop hence require a final + /// conjugation in Fp12. + const X_GENERATOR: u64 = 0xd201000000010000; } -const BN_EXP: [bool; 253] = [ - true, false, false, false, false, false, true, true, false, false, true, false, false, false, - true, false, false, true, true, true, false, false, true, true, true, false, false, true, - false, true, true, true, false, false, false, false, true, false, false, true, true, false, - false, false, true, true, false, true, false, false, false, false, false, false, false, true, - false, true, false, false, true, true, false, true, true, true, false, false, false, false, - true, false, true, false, false, false, false, false, true, false, false, false, true, false, - true, true, false, true, true, false, true, true, false, true, false, false, false, false, - false, false, true, true, false, false, false, false, false, false, true, false, true, false, - true, true, false, false, false, false, true, false, true, true, true, false, true, false, - false, true, false, true, false, false, false, false, false, true, true, false, false, true, - true, true, true, true, false, true, false, false, false, false, true, false, false, true, - false, false, false, false, true, true, true, true, false, false, true, true, false, true, - true, true, false, false, true, false, true, true, true, false, false, false, false, true, - false, false, true, false, false, false, true, false, true, false, false, false, false, true, - true, true, true, true, false, false, false, false, true, true, true, true, true, false, true, - false, true, true, false, false, true, false, false, true, true, true, true, true, true, false, - false, false, false, false, false, false, false, false, false, false, false, false, false, - false, false, false, false, false, false, false, false, false, false, false, false, false, - false, -]; - -// The following constants are defined above get_custom_powers - -const BN_EXPS4: [(bool, bool, bool); 64] = [ - (true, true, false), - (true, true, true), - (true, true, true), - (false, false, false), - (false, false, true), - (true, false, true), - (false, true, false), - (true, false, true), - (true, true, false), - (true, false, true), - (false, true, false), - (true, true, false), - (true, true, false), - (true, true, false), - (false, true, false), - (false, true, false), - (false, false, true), - (true, false, true), - (true, true, false), - (false, true, false), - (true, true, false), - (true, true, false), - (true, true, false), - (false, false, true), - (false, false, true), - (true, false, true), - (true, false, true), - (true, true, false), - (true, false, false), - (true, true, false), - (false, true, false), - (true, true, false), - (true, false, false), - (false, true, false), - (false, false, false), - (true, false, false), - (true, false, false), - (true, false, true), - (false, false, true), - (false, true, true), - (false, false, true), - (false, true, true), - (false, true, true), - (false, false, false), - (true, true, true), - (true, false, true), - (true, false, true), - (false, true, true), - (true, false, true), - (false, true, true), - (false, true, true), - (true, true, false), - (true, true, false), - (true, true, false), - (true, false, false), - (false, false, true), - (true, false, false), - (false, false, true), - (true, false, true), - (true, true, false), - (true, true, true), - (false, true, true), - (false, true, false), - (true, true, true), -]; - -const BN_EXPS2: [(bool, bool); 62] = [ - (true, false), - (true, true), - (false, false), - (true, false), - (true, false), - (true, true), - (true, false), - (true, true), - (true, false), - (false, true), - (false, true), - (true, true), - (true, true), - (false, false), - (true, true), - (false, false), - (false, false), - (false, true), - (false, true), - (true, true), - (true, true), - (true, true), - (false, true), - (true, true), - (false, false), - (true, true), - (true, false), - (true, true), - (false, false), - (true, true), - (true, true), - (true, false), - (false, false), - (false, true), - (false, false), - (true, true), - (false, true), - (false, false), - (true, false), - (false, true), - (false, true), - (true, false), - (false, true), - (false, false), - (false, false), - (false, false), - (false, true), - (true, false), - (true, true), - (false, true), - (true, true), - (true, false), - (false, true), - (false, false), - (true, false), - (false, true), - (true, false), - (true, true), - (true, false), - (true, true), - (false, true), - (true, true), -]; - -const BN_EXPS0: [bool; 65] = [ - false, false, true, false, false, true, true, false, true, false, true, true, true, false, - true, false, false, false, true, false, false, true, false, true, false, true, true, false, - false, false, false, false, true, false, true, false, true, true, true, false, false, true, - true, true, true, false, true, false, true, true, false, false, true, false, false, false, - true, true, true, true, false, false, true, true, false, -]; +#[cfg(test)] +mod tests { + use rand::thread_rng; + + use super::*; + + #[test] + fn test_bls_pairing() { + let mut rng = rand::thread_rng(); + let mut acc = 0_i32; + let mut running_product = Fp12::::UNIT; + for _ in 0..5 { + let m = rng.gen_range(-8..8); + let n = rng.gen_range(-8..8); + if m * n == 0 { + continue; + } + acc -= m * n; + + let p = CurveAff::::int(m); + let q = CurveAff::>::int(n); + running_product = running_product * bls381::ate_optim(p, q); + } + + // Finally, multiply by the accumulated inverse and check this matches the + // expected value. + let p = CurveAff::::int(acc); + let q = CurveAff::>::GENERATOR; + running_product = running_product * bls381::ate_optim(p, q); + + let expected = if acc == 0 { + Fp12::::ZERO + } else { + Fp12::::UNIT + }; + + assert_eq!(running_product, expected); + } + + #[test] + fn test_bn_pairing() { + let mut rng = rand::thread_rng(); + let mut acc = 0_i32; + let mut running_product = Fp12::::UNIT; + for _ in 0..5 { + let m = rng.gen_range(-8..8); + let n = rng.gen_range(-8..8); + if m * n == 0 { + continue; + } + acc -= m * n; + + let p = CurveAff::::int(m); + let q = CurveAff::>::int(n); + running_product = running_product * bn254::tate(p, q); + } + + // Finally, multiply by the accumulated inverse and check this matches the + // expected value. + let p = CurveAff::::int(acc); + let q = CurveAff::>::GENERATOR; + running_product = running_product * bn254::tate(p, q); + + let expected = if acc == 0 { + Fp12::::ZERO + } else { + Fp12::::UNIT + }; + + assert_eq!(running_product, expected); + } +} diff --git a/evm_arithmetization/src/extension_tower.rs b/evm_arithmetization/src/extension_tower.rs index 3f51235f9..686be2dde 100644 --- a/evm_arithmetization/src/extension_tower.rs +++ b/evm_arithmetization/src/extension_tower.rs @@ -1,6 +1,7 @@ use core::fmt::Debug; use core::ops::{Add, Div, Mul, Neg, Sub}; +use anyhow::{anyhow, Result}; use ethereum_types::{U256, U512}; use rand::distributions::{Distribution, Standard}; use rand::Rng; @@ -125,6 +126,13 @@ pub(crate) const BLS_BASE: U512 = U512([ 0x0, ]); +pub(crate) const BLS_SCALAR: U256 = U256([ + 0xffffffff00000001, + 0x53bda402fffe5bfe, + 0x3339d80809a1d805, + 0x73eda753299d7d48, +]); + #[derive(Debug, Copy, Clone, PartialEq)] pub(crate) struct BLS381 { pub val: U512, @@ -179,6 +187,9 @@ impl Sub for BLS381 { } } +// The square root implementation for BLS12-381 as well as the +// `lexicographically_largest` method have been taken from +// . impl BLS381 { fn lsh_128(self) -> BLS381 { let b128: U512 = U512([0, 0, 1, 0, 0, 0, 0, 0]); @@ -195,6 +206,83 @@ impl BLS381 { fn lsh_512(self) -> BLS381 { self.lsh_256().lsh_256() } + + pub fn sqrt(&self) -> Result { + // Uses Shank's method, as p = 3 (mod 4). This means + // we only need to exponentiate by (p+1)/4. This only + // works for elements that are actually quadratic residue, + // so we check that we got the correct result at the end. + + const MODULUS_MINUS_ONE_DIV_FOUR: [bool; 379] = [ + true, true, false, true, false, true, false, true, false, true, false, true, false, + true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, false, true, true, true, true, true, true, true, true, + false, false, true, true, true, false, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, false, false, true, + false, true, false, true, false, false, false, true, true, false, true, false, true, + true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, false, true, false, true, false, true, false, true, true, true, true, + false, false, false, false, false, true, false, false, true, false, false, false, true, + true, false, true, true, true, true, false, false, false, false, true, true, false, + true, false, true, true, false, true, true, true, true, false, false, false, false, + false, true, false, true, false, true, false, false, true, false, true, true, false, + false, false, false, true, true, false, false, true, true, true, false, false, true, + true, false, true, true, true, true, true, true, false, true, false, true, false, + false, true, false, false, false, true, false, true, false, false, false, false, true, + true, true, false, false, true, true, true, true, false, false, true, false, false, + false, false, true, true, true, false, true, false, false, true, false, true, true, + true, false, true, true, true, false, false, false, true, false, false, true, true, + false, true, true, true, false, true, false, true, true, false, false, true, true, + false, true, false, true, true, true, false, true, false, false, true, false, true, + true, false, false, false, false, true, false, false, true, true, false, true, true, + false, true, true, true, true, false, false, true, false, true, true, true, false, + true, true, false, false, false, true, true, false, true, false, false, true, false, + false, true, false, true, true, false, false, true, false, true, true, false, false, + true, true, true, true, true, true, true, true, true, true, false, true, false, false, + true, true, true, false, false, false, true, false, true, false, true, true, true, + true, false, false, false, true, false, false, false, true, false, false, false, false, + false, false, false, false, true, false, true, true, + ]; + + let mut root = BLS381::UNIT; + let mut sq = *self; + for bit in MODULUS_MINUS_ONE_DIV_FOUR { + if bit { + root = root * sq; + } + sq = sq * sq; + } + + // Check that the element is a quadratic residue. + if root * root != *self { + return Err(anyhow!( + "The element does not have a square root in this field." + )); + } + + Ok(root) + } + + /// Returns whether or not this element is strictly lexicographically + /// larger than its negation. + pub fn lexicographically_largest(&self) -> bool { + // This can be determined by checking if the element is larger than + // (p - 1) / 2. + + const MODULUS_MINUS_ONE_DIV_TWO: U512 = U512([ + 0xdcff7fffffffd556, + 0x0f55ffff58a9ffff, + 0xb39869507b587b12, + 0xb23ba5c279c2895f, + 0x258dd3db21a5d66b, + 0x0d0088f51cbff34d, + 0x0000000000000000, + 0x0000000000000000, + ]); + + self.val > MODULUS_MINUS_ONE_DIV_TWO + } } #[allow(clippy::suspicious_arithmetic_impl)] @@ -238,7 +326,7 @@ impl FieldExt for BLS381 { let mut current = self; let mut product = BLS381 { val: U512::one() }; - for j in 0..512 { + for j in 0..384 { if exp.bit(j) { product = product * current; } @@ -393,7 +481,7 @@ impl Div for Fp2 { /// adjoin in the subsequent cubic extension. /// For BN254 this is 9+i, and for BLS381 it is 1+i. /// It also defines the relevant FROB constants, -/// given by t^(p^n) and t^(p^2n) for various n, +/// given by t^(p^n) and (t^2)^(p^n) for various n, /// used to compute the frobenius operations. pub trait Adj: Sized { fn mul_adj(self) -> Self; @@ -805,8 +893,442 @@ impl Adj for Fp2 { im: self.re + self.im, } } - const FROB_T: [[Fp2; 6]; 2] = [[Fp2::::ZERO; 6]; 2]; - const FROB_Z: [Fp2; 12] = [Fp2::::ZERO; 12]; + + const FROB_T: [[Fp2; 6]; 2] = [ + [ + Fp2 { + re: BLS381 { val: U512::one() }, + im: BLS381 { val: U512::zero() }, + }, + Fp2 { + re: BLS381 { val: U512::zero() }, + im: BLS381 { + val: U512([ + 0x8bfd00000000aaac, + 0x409427eb4f49fffd, + 0x897d29650fb85f9b, + 0xaa0d857d89759ad4, + 0xec02408663d4de85, + 0x1a0111ea397fe699, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + }, + Fp2 { + re: BLS381 { + val: U512([ + 0x2e01fffffffefffe, + 0xde17d813620a0002, + 0xddb3a93be6f89688, + 0xba69c6076a0f77ea, + 0x5f19672fdf76ce51, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + im: BLS381 { val: U512::zero() }, + }, + Fp2 { + re: BLS381 { val: U512::zero() }, + im: BLS381 { val: U512::one() }, + }, + Fp2 { + re: BLS381 { + val: U512([ + 0x8bfd00000000aaac, + 0x409427eb4f49fffd, + 0x897d29650fb85f9b, + 0xaa0d857d89759ad4, + 0xec02408663d4de85, + 0x1a0111ea397fe699, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + im: BLS381 { val: U512::zero() }, + }, + Fp2 { + re: BLS381 { val: U512::zero() }, + im: BLS381 { + val: U512([ + 0x2e01fffffffefffe, + 0xde17d813620a0002, + 0xddb3a93be6f89688, + 0xba69c6076a0f77ea, + 0x5f19672fdf76ce51, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + }, + ], + [ + Fp2 { + re: BLS381 { val: U512::one() }, + im: BLS381 { val: U512::zero() }, + }, + Fp2 { + re: { + BLS381 { + val: U512([ + 0x8bfd00000000aaad, + 0x409427eb4f49fffd, + 0x897d29650fb85f9b, + 0xaa0d857d89759ad4, + 0xec02408663d4de85, + 0x1a0111ea397fe699, + 0x0000000000000000, + 0x0000000000000000, + ]), + } + }, + im: { BLS381 { val: U512::zero() } }, + }, + Fp2 { + re: { + BLS381 { + val: U512([ + 0x8bfd00000000aaac, + 0x409427eb4f49fffd, + 0x897d29650fb85f9b, + 0xaa0d857d89759ad4, + 0xec02408663d4de85, + 0x1a0111ea397fe699, + 0x0000000000000000, + 0x0000000000000000, + ]), + } + }, + im: { BLS381 { val: U512::zero() } }, + }, + Fp2 { + re: { + BLS381 { + val: U512([ + 0xb9feffffffffaaaa, + 0x1eabfffeb153ffff, + 0x6730d2a0f6b0f624, + 0x64774b84f38512bf, + 0x4b1ba7b6434bacd7, + 0x1a0111ea397fe69a, + 0x0000000000000000, + 0x0000000000000000, + ]), + } + }, + im: { BLS381 { val: U512::zero() } }, + }, + Fp2 { + re: { + BLS381 { + val: U512([ + 0x2e01fffffffefffe, + 0xde17d813620a0002, + 0xddb3a93be6f89688, + 0xba69c6076a0f77ea, + 0x5f19672fdf76ce51, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + } + }, + im: { BLS381 { val: U512::zero() } }, + }, + Fp2 { + re: { + BLS381 { + val: U512([ + 0x2e01fffffffeffff, + 0xde17d813620a0002, + 0xddb3a93be6f89688, + 0xba69c6076a0f77ea, + 0x5f19672fdf76ce51, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + } + }, + im: { BLS381 { val: U512::zero() } }, + }, + ], + ]; + + const FROB_Z: [Fp2; 12] = [ + Fp2 { + re: { BLS381 { val: U512::one() } }, + im: { BLS381 { val: U512::zero() } }, + }, + Fp2 { + re: { + BLS381 { + val: U512([ + 0x8d0775ed92235fb8, + 0xf67ea53d63e7813d, + 0x7b2443d784bab9c4, + 0xfd603fd3cbd5f4f, + 0xc231beb4202c0d1f, + 0x1904d3bf02bb0667, + 0x0000000000000000, + 0x0000000000000000, + ]), + } + }, + im: { + BLS381 { + val: U512([ + 0x2cf78a126ddc4af3, + 0x282d5ac14d6c7ec2, + 0xec0c8ec971f63c5f, + 0x54a14787b6c7b36f, + 0x88e9e902231f9fb8, + 0xfc3e2b36c4e032, + 0x0000000000000000, + 0x0000000000000000, + ]), + } + }, + }, + Fp2 { + re: { + BLS381 { + val: U512([ + 0x2e01fffffffeffff, + 0xde17d813620a0002, + 0xddb3a93be6f89688, + 0xba69c6076a0f77ea, + 0x5f19672fdf76ce51, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + } + }, + im: { BLS381 { val: U512::zero() } }, + }, + Fp2 { + re: { + BLS381 { + val: U512([ + 0xf1ee7b04121bdea2, + 0x304466cf3e67fa0a, + 0xef396489f61eb45e, + 0x1c3dedd930b1cf60, + 0xe2e9c448d77a2cd9, + 0x135203e60180a68e, + 0x0000000000000000, + 0x0000000000000000, + ]), + } + }, + im: { + BLS381 { + val: U512([ + 0xc81084fbede3cc09, + 0xee67992f72ec05f4, + 0x77f76e17009241c5, + 0x48395dabc2d3435e, + 0x6831e36d6bd17ffe, + 0x6af0e0437ff400b, + 0x0000000000000000, + 0x0000000000000000, + ]), + } + }, + }, + Fp2 { + re: { + BLS381 { + val: U512([ + 0x2e01fffffffefffe, + 0xde17d813620a0002, + 0xddb3a93be6f89688, + 0xba69c6076a0f77ea, + 0x5f19672fdf76ce51, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + } + }, + im: { BLS381 { val: U512::zero() } }, + }, + Fp2 { + re: { + BLS381 { + val: U512([ + 0x1ee605167ff82995, + 0x5871c1908bd478cd, + 0xdb45f3536814f0bd, + 0x70df3560e77982d0, + 0x6bd3ad4afa99cc91, + 0x144e4211384586c1, + 0x0000000000000000, + 0x0000000000000000, + ]), + } + }, + im: { + BLS381 { + val: U512([ + 0x9b18fae980078116, + 0xc63a3e6e257f8732, + 0x8beadf4d8e9c0566, + 0xf39816240c0b8fee, + 0xdf47fa6b48b1e045, + 0x5b2cfd9013a5fd8, + 0x0000000000000000, + 0x0000000000000000, + ]), + } + }, + }, + Fp2 { + re: { + BLS381 { + val: U512([ + 0xb9feffffffffaaaa, + 0x1eabfffeb153ffff, + 0x6730d2a0f6b0f624, + 0x64774b84f38512bf, + 0x4b1ba7b6434bacd7, + 0x1a0111ea397fe69a, + 0x0000000000000000, + 0x0000000000000000, + ]), + } + }, + im: { BLS381 { val: U512::zero() } }, + }, + Fp2 { + re: { + BLS381 { + val: U512([ + 0x2cf78a126ddc4af3, + 0x282d5ac14d6c7ec2, + 0xec0c8ec971f63c5f, + 0x54a14787b6c7b36f, + 0x88e9e902231f9fb8, + 0xfc3e2b36c4e032, + 0x0000000000000000, + 0x0000000000000000, + ]), + } + }, + im: { + BLS381 { + val: U512([ + 0x8d0775ed92235fb8, + 0xf67ea53d63e7813d, + 0x7b2443d784bab9c4, + 0xfd603fd3cbd5f4f, + 0xc231beb4202c0d1f, + 0x1904d3bf02bb0667, + 0x0000000000000000, + 0x0000000000000000, + ]), + } + }, + }, + Fp2 { + re: { + BLS381 { + val: U512([ + 0x8bfd00000000aaac, + 0x409427eb4f49fffd, + 0x897d29650fb85f9b, + 0xaa0d857d89759ad4, + 0xec02408663d4de85, + 0x1a0111ea397fe699, + 0x0000000000000000, + 0x0000000000000000, + ]), + } + }, + im: { BLS381 { val: U512::zero() } }, + }, + Fp2 { + re: { + BLS381 { + val: U512([ + 0xc81084fbede3cc09, + 0xee67992f72ec05f4, + 0x77f76e17009241c5, + 0x48395dabc2d3435e, + 0x6831e36d6bd17ffe, + 0x6af0e0437ff400b, + 0x0000000000000000, + 0x0000000000000000, + ]), + } + }, + im: { + BLS381 { + val: U512([ + 0xf1ee7b04121bdea2, + 0x304466cf3e67fa0a, + 0xef396489f61eb45e, + 0x1c3dedd930b1cf60, + 0xe2e9c448d77a2cd9, + 0x135203e60180a68e, + 0x0000000000000000, + 0x0000000000000000, + ]), + } + }, + }, + Fp2 { + re: { + BLS381 { + val: U512([ + 0x8bfd00000000aaad, + 0x409427eb4f49fffd, + 0x897d29650fb85f9b, + 0xaa0d857d89759ad4, + 0xec02408663d4de85, + 0x1a0111ea397fe699, + 0x0000000000000000, + 0x0000000000000000, + ]), + } + }, + im: { BLS381 { val: U512::zero() } }, + }, + Fp2 { + re: { + BLS381 { + val: U512([ + 0x9b18fae980078116, + 0xc63a3e6e257f8732, + 0x8beadf4d8e9c0566, + 0xf39816240c0b8fee, + 0xdf47fa6b48b1e045, + 0x5b2cfd9013a5fd8, + 0x0000000000000000, + 0x0000000000000000, + ]), + } + }, + im: { + BLS381 { + val: U512([ + 0x1ee605167ff82995, + 0x5871c1908bd478cd, + 0xdb45f3536814f0bd, + 0x70df3560e77982d0, + 0x6bd3ad4afa99cc91, + 0x144e4211384586c1, + 0x0000000000000000, + 0x0000000000000000, + ]), + } + }, + }, + ]; } /// The degree 3 field extension Fp6 over Fp2 is given by adjoining t, where t^3 @@ -921,7 +1443,7 @@ where Fp2: Adj, { /// This function multiplies an Fp6 element by t, and hence shifts the - /// bases, where the t^2 coefficient picks up a factor of 1+i as the 1 + /// bases, where the t^2 coefficient picks up a factor of t^3 as the 1 /// coefficient of the output fn sh(self) -> Fp6 { Fp6 { @@ -930,6 +1452,27 @@ where t2: self.t1, } } + + pub fn mul_by_1(&self, c1: Fp2) -> Fp6 { + let t = Fp6:: { + t0: self.t0 * c1, + t1: self.t1 * c1, + t2: self.t2 * c1, + }; + + t.sh() + } + + pub fn mul_by_01(&self, c0: Fp2, c1: Fp2) -> Fp6 { + let a_a = self.t0 * c0; + let b_b = self.t1 * c1; + + let t0 = (self.t2 * c1).mul_adj() + a_a; + let t1 = (c0 + c1) * (self.t0 + self.t1) - a_a - b_b; + let t2 = self.t2 * c0 + b_b; + + Fp6:: { t0, t1, t2 } + } } impl Fp6 @@ -940,10 +1483,10 @@ where /// The nth frobenius endomorphism of a p^q field is given by mapping /// x to x^(p^n) /// which sends a + bt + ct^2: Fp6 to - /// a^(p^n) + b^(p^n) * t^(p^n) + c^(p^n) * t^(2p^n) + /// a^(p^n) + b^(p^n) * t^(p^n) + c^(p^n) * (t^2)^(p^n) /// The Fp2 coefficients are determined by the comment in the conj method, /// while the values of - /// t^(p^n) and t^(2p^n) + /// t^(p^n) and (t^2)^(p^n) /// are precomputed in the constant arrays FROB_T1 and FROB_T2 pub(crate) fn frob(self, n: usize) -> Fp6 { let n = n % 6; @@ -1183,7 +1726,7 @@ where T: FieldExt, Fp2: Adj, { - fn conj(self) -> Fp12 { + pub fn conj(self) -> Fp12 { Fp12 { z0: self.z0, z1: -self.z1, @@ -1196,6 +1739,20 @@ where T: FieldExt, Fp2: Adj, { + pub fn mul_by_014(&self, c0: Fp2, c1: Fp2, c4: Fp2) -> Fp12 { + let aa = self.z0.mul_by_01(c0, c1); + let bb = self.z1.mul_by_1(c4); + let o = c1 + c4; + let z1 = self.z1 + self.z0; + let z1 = z1.mul_by_01(c0, o); + let z1 = z1 - aa - bb; + let z0 = bb; + let z0 = z0.sh(); + let z0 = z0 + aa; + + Fp12:: { z0, z1 } + } + /// The nth frobenius endomorphism of a p^q field is given by mapping /// x to x^(p^n) /// which sends a + bz: Fp12 to @@ -1320,3 +1877,566 @@ where Fp12 { z0, z1 } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_bls_fp6_frobenius() { + // f == 2 + let f = Fp6:: { + t0: Fp2::::UNIT + Fp2::::UNIT, + t1: Fp2::::ZERO, + t2: Fp2::::ZERO, + }; + + for i in 0..6 { + assert_eq!(f, f.frob(i)); + } + + // f == t + let f = Fp6:: { + t0: Fp2::::ZERO, + t1: Fp2::::UNIT, + t2: Fp2::::ZERO, + }; + + let expected = [ + f, + Fp6:: { + t0: Fp2::::ZERO, + t1: Fp2:: { + re: BLS381 { val: U512::zero() }, + im: BLS381 { + val: U512([ + 0x8bfd00000000aaac, + 0x409427eb4f49fffd, + 0x897d29650fb85f9b, + 0xaa0d857d89759ad4, + 0xec02408663d4de85, + 0x1a0111ea397fe699, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + }, + t2: Fp2::::ZERO, + }, + Fp6:: { + t0: Fp2::::ZERO, + t1: Fp2:: { + re: BLS381 { + val: U512([ + 0x2e01fffffffefffe, + 0xde17d813620a0002, + 0xddb3a93be6f89688, + 0xba69c6076a0f77ea, + 0x5f19672fdf76ce51, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + im: BLS381 { val: U512::zero() }, + }, + t2: Fp2::::ZERO, + }, + Fp6:: { + t0: Fp2::::ZERO, + t1: Fp2:: { + re: BLS381::ZERO, + im: BLS381::UNIT, + }, + t2: Fp2::::ZERO, + }, + Fp6:: { + t0: Fp2::::ZERO, + t1: Fp2:: { + re: BLS381 { + val: U512([ + 0x8bfd00000000aaac, + 0x409427eb4f49fffd, + 0x897d29650fb85f9b, + 0xaa0d857d89759ad4, + 0xec02408663d4de85, + 0x1a0111ea397fe699, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + im: BLS381 { val: U512::zero() }, + }, + t2: Fp2::::ZERO, + }, + Fp6:: { + t0: Fp2::::ZERO, + t1: Fp2:: { + re: BLS381 { val: U512::zero() }, + im: BLS381 { + val: U512([ + 0x2e01fffffffefffe, + 0xde17d813620a0002, + 0xddb3a93be6f89688, + 0xba69c6076a0f77ea, + 0x5f19672fdf76ce51, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + }, + t2: Fp2::::ZERO, + }, + ]; + + for i in 0..6 { + assert_eq!(expected[i], f.frob(i)); + } + + // f == t^2 + let f = Fp6:: { + t0: Fp2::::ZERO, + t1: Fp2::::ZERO, + t2: Fp2::::UNIT, + }; + + let expected = [ + f, + Fp6:: { + t0: Fp2::::ZERO, + t1: Fp2::::ZERO, + t2: Fp2:: { + re: BLS381 { + val: U512([ + 0x8bfd00000000aaad, + 0x409427eb4f49fffd, + 0x897d29650fb85f9b, + 0xaa0d857d89759ad4, + 0xec02408663d4de85, + 0x1a0111ea397fe699, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + im: BLS381 { val: U512::zero() }, + }, + }, + Fp6:: { + t0: Fp2::::ZERO, + t1: Fp2::::ZERO, + t2: Fp2:: { + re: BLS381 { + val: U512([ + 0x8bfd00000000aaac, + 0x409427eb4f49fffd, + 0x897d29650fb85f9b, + 0xaa0d857d89759ad4, + 0xec02408663d4de85, + 0x1a0111ea397fe699, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + im: BLS381 { val: U512::zero() }, + }, + }, + Fp6:: { + t0: Fp2::::ZERO, + t1: Fp2::::ZERO, + t2: Fp2:: { + re: BLS381 { + val: U512([ + 0xb9feffffffffaaaa, + 0x1eabfffeb153ffff, + 0x6730d2a0f6b0f624, + 0x64774b84f38512bf, + 0x4b1ba7b6434bacd7, + 0x1a0111ea397fe69a, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + im: BLS381 { val: U512::zero() }, + }, + }, + Fp6:: { + t0: Fp2::::ZERO, + t1: Fp2::::ZERO, + t2: Fp2:: { + re: BLS381 { + val: U512([ + 0x2e01fffffffefffe, + 0xde17d813620a0002, + 0xddb3a93be6f89688, + 0xba69c6076a0f77ea, + 0x5f19672fdf76ce51, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + im: BLS381 { val: U512::zero() }, + }, + }, + Fp6:: { + t0: Fp2::::ZERO, + t1: Fp2::::ZERO, + t2: Fp2:: { + re: BLS381 { + val: U512([ + 0x2e01fffffffeffff, + 0xde17d813620a0002, + 0xddb3a93be6f89688, + 0xba69c6076a0f77ea, + 0x5f19672fdf76ce51, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + im: BLS381 { val: U512::zero() }, + }, + }, + ]; + + for i in 0..6 { + assert_eq!(expected[i], f.frob(i)); + } + } + + #[test] + fn test_bls_fp12_frobenius() { + // f == 2 + let f = Fp12:: { + z0: Fp6::::UNIT + Fp6::::UNIT, + z1: Fp6::::ZERO, + }; + + for i in 0..6 { + assert_eq!(f, f.frob(i)); + } + + // f == z + let f = Fp12:: { + z0: Fp6::::ZERO, + z1: Fp6::::UNIT, + }; + + let expected = [ + f, + Fp12:: { + z0: Fp6::::ZERO, + z1: Fp6:: { + t0: Fp2:: { + re: BLS381 { + val: U512([ + 0x8d0775ed92235fb8, + 0xf67ea53d63e7813d, + 0x7b2443d784bab9c4, + 0xfd603fd3cbd5f4f, + 0xc231beb4202c0d1f, + 0x1904d3bf02bb0667, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + im: BLS381 { + val: U512([ + 0x2cf78a126ddc4af3, + 0x282d5ac14d6c7ec2, + 0xec0c8ec971f63c5f, + 0x54a14787b6c7b36f, + 0x88e9e902231f9fb8, + 0xfc3e2b36c4e032, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + }, + t1: Fp2::::ZERO, + t2: Fp2::::ZERO, + }, + }, + Fp12:: { + z0: Fp6::::ZERO, + z1: Fp6:: { + t0: Fp2:: { + re: BLS381 { + val: U512([ + 0x2e01fffffffeffff, + 0xde17d813620a0002, + 0xddb3a93be6f89688, + 0xba69c6076a0f77ea, + 0x5f19672fdf76ce51, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + im: BLS381 { val: U512::zero() }, + }, + t1: Fp2::::ZERO, + t2: Fp2::::ZERO, + }, + }, + Fp12:: { + z0: Fp6::::ZERO, + z1: Fp6:: { + t0: Fp2:: { + re: BLS381 { + val: U512([ + 0xf1ee7b04121bdea2, + 0x304466cf3e67fa0a, + 0xef396489f61eb45e, + 0x1c3dedd930b1cf60, + 0xe2e9c448d77a2cd9, + 0x135203e60180a68e, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + im: BLS381 { + val: U512([ + 0xc81084fbede3cc09, + 0xee67992f72ec05f4, + 0x77f76e17009241c5, + 0x48395dabc2d3435e, + 0x6831e36d6bd17ffe, + 0x6af0e0437ff400b, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + }, + t1: Fp2::::ZERO, + t2: Fp2::::ZERO, + }, + }, + Fp12:: { + z0: Fp6::::ZERO, + z1: Fp6:: { + t0: Fp2:: { + re: BLS381 { + val: U512([ + 0x2e01fffffffefffe, + 0xde17d813620a0002, + 0xddb3a93be6f89688, + 0xba69c6076a0f77ea, + 0x5f19672fdf76ce51, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + im: BLS381 { val: U512::zero() }, + }, + t1: Fp2::::ZERO, + t2: Fp2::::ZERO, + }, + }, + Fp12:: { + z0: Fp6::::ZERO, + z1: Fp6:: { + t0: Fp2:: { + re: BLS381 { + val: U512([ + 0x1ee605167ff82995, + 0x5871c1908bd478cd, + 0xdb45f3536814f0bd, + 0x70df3560e77982d0, + 0x6bd3ad4afa99cc91, + 0x144e4211384586c1, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + im: BLS381 { + val: U512([ + 0x9b18fae980078116, + 0xc63a3e6e257f8732, + 0x8beadf4d8e9c0566, + 0xf39816240c0b8fee, + 0xdf47fa6b48b1e045, + 0x5b2cfd9013a5fd8, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + }, + t1: Fp2::::ZERO, + t2: Fp2::::ZERO, + }, + }, + Fp12:: { + z0: Fp6::::ZERO, + z1: Fp6:: { + t0: Fp2:: { + re: BLS381 { + val: U512([ + 0xb9feffffffffaaaa, + 0x1eabfffeb153ffff, + 0x6730d2a0f6b0f624, + 0x64774b84f38512bf, + 0x4b1ba7b6434bacd7, + 0x1a0111ea397fe69a, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + im: BLS381 { val: U512::zero() }, + }, + t1: Fp2::::ZERO, + t2: Fp2::::ZERO, + }, + }, + Fp12:: { + z0: Fp6::::ZERO, + z1: Fp6:: { + t0: Fp2:: { + re: BLS381 { + val: U512([ + 0x2cf78a126ddc4af3, + 0x282d5ac14d6c7ec2, + 0xec0c8ec971f63c5f, + 0x54a14787b6c7b36f, + 0x88e9e902231f9fb8, + 0xfc3e2b36c4e032, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + im: BLS381 { + val: U512([ + 0x8d0775ed92235fb8, + 0xf67ea53d63e7813d, + 0x7b2443d784bab9c4, + 0xfd603fd3cbd5f4f, + 0xc231beb4202c0d1f, + 0x1904d3bf02bb0667, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + }, + t1: Fp2::::ZERO, + t2: Fp2::::ZERO, + }, + }, + Fp12:: { + z0: Fp6::::ZERO, + z1: Fp6:: { + t0: Fp2:: { + re: BLS381 { + val: U512([ + 0x8bfd00000000aaac, + 0x409427eb4f49fffd, + 0x897d29650fb85f9b, + 0xaa0d857d89759ad4, + 0xec02408663d4de85, + 0x1a0111ea397fe699, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + im: BLS381 { val: U512::zero() }, + }, + t1: Fp2::::ZERO, + t2: Fp2::::ZERO, + }, + }, + Fp12:: { + z0: Fp6::::ZERO, + z1: Fp6:: { + t0: Fp2:: { + re: BLS381 { + val: U512([ + 0xc81084fbede3cc09, + 0xee67992f72ec05f4, + 0x77f76e17009241c5, + 0x48395dabc2d3435e, + 0x6831e36d6bd17ffe, + 0x6af0e0437ff400b, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + im: BLS381 { + val: U512([ + 0xf1ee7b04121bdea2, + 0x304466cf3e67fa0a, + 0xef396489f61eb45e, + 0x1c3dedd930b1cf60, + 0xe2e9c448d77a2cd9, + 0x135203e60180a68e, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + }, + t1: Fp2::::ZERO, + t2: Fp2::::ZERO, + }, + }, + Fp12:: { + z0: Fp6::::ZERO, + z1: Fp6:: { + t0: Fp2:: { + re: BLS381 { + val: U512([ + 0x8bfd00000000aaad, + 0x409427eb4f49fffd, + 0x897d29650fb85f9b, + 0xaa0d857d89759ad4, + 0xec02408663d4de85, + 0x1a0111ea397fe699, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + im: BLS381 { val: U512::zero() }, + }, + t1: Fp2::::ZERO, + t2: Fp2::::ZERO, + }, + }, + Fp12:: { + z0: Fp6::::ZERO, + z1: Fp6:: { + t0: Fp2:: { + re: BLS381 { + val: U512([ + 0x9b18fae980078116, + 0xc63a3e6e257f8732, + 0x8beadf4d8e9c0566, + 0xf39816240c0b8fee, + 0xdf47fa6b48b1e045, + 0x5b2cfd9013a5fd8, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + im: BLS381 { + val: U512([ + 0x1ee605167ff82995, + 0x5871c1908bd478cd, + 0xdb45f3536814f0bd, + 0x70df3560e77982d0, + 0x6bd3ad4afa99cc91, + 0x144e4211384586c1, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + }, + t1: Fp2::::ZERO, + t2: Fp2::::ZERO, + }, + }, + ]; + + for i in 0..12 { + assert_eq!(expected[i], f.frob(i)); + } + } +} diff --git a/evm_arithmetization/src/generation/prover_input.rs b/evm_arithmetization/src/generation/prover_input.rs index bd8ab932e..edb08a935 100644 --- a/evm_arithmetization/src/generation/prover_input.rs +++ b/evm_arithmetization/src/generation/prover_input.rs @@ -1,22 +1,27 @@ +use core::cmp::min; use core::mem::transmute; -use std::cmp::min; +use core::ops::Neg; use std::collections::{BTreeSet, HashMap}; use std::str::FromStr; -use anyhow::{bail, Error}; +use anyhow::{bail, Error, Result}; use ethereum_types::{BigEndianHash, H256, U256, U512}; use itertools::Itertools; +use keccak_hash::keccak; use num_bigint::BigUint; use plonky2::field::types::Field; use serde::{Deserialize, Serialize}; +use crate::cpu::kernel::cancun_constants::KZG_VERSIONED_HASH; use crate::cpu::kernel::constants::cancun_constants::{ - BLOB_BASE_FEE_UPDATE_FRACTION, MIN_BLOB_BASE_FEE, + BLOB_BASE_FEE_UPDATE_FRACTION, G2_TRUSTED_SETUP_POINT, MIN_BLOB_BASE_FEE, + POINT_EVALUATION_PRECOMPILE_RETURN_VALUE, }; use crate::cpu::kernel::constants::context_metadata::ContextMetadata; use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; use crate::cpu::kernel::interpreter::simulate_cpu_and_get_user_jumps; -use crate::extension_tower::{FieldExt, Fp12, BLS381, BN254}; +use crate::curve_pairings::{bls381, CurveAff, CyclicGroup}; +use crate::extension_tower::{FieldExt, Fp12, Fp2, BLS381, BLS_BASE, BLS_SCALAR, BN254, BN_BASE}; use crate::generation::prover_input::EvmField::{ Bls381Base, Bls381Scalar, Bn254Base, Bn254Scalar, Secp256k1Base, Secp256k1Scalar, }; @@ -24,7 +29,7 @@ use crate::generation::prover_input::FieldOp::{Inverse, Sqrt}; use crate::generation::state::GenerationState; use crate::memory::segments::Segment; use crate::memory::segments::Segment::BnPairing; -use crate::util::{biguint_to_mem_vec, mem_vec_to_biguint, u256_to_u8, u256_to_usize}; +use crate::util::{biguint_to_mem_vec, h2u, mem_vec_to_biguint, u256_to_u8, u256_to_usize}; use crate::witness::errors::ProverInputError::*; use crate::witness::errors::{ProgramError, ProverInputError}; use crate::witness::memory::MemoryAddress; @@ -61,6 +66,8 @@ impl GenerationState { "jumpdest_table" => self.run_jumpdest_table(input_fn), "access_lists" => self.run_access_lists(input_fn), "ger" => self.run_global_exit_roots(), + "kzg_point_eval" => self.run_kzg_point_eval(), + "kzg_point_eval_2" => self.run_kzg_point_eval_2(), _ => Err(ProgramError::ProverInputError(InvalidFunction)), } } @@ -410,6 +417,165 @@ impl GenerationState { } Ok((Segment::AccessedStorageKeys as usize).into()) } + + /// Returns the first part of the KZG precompile output. + fn run_kzg_point_eval(&mut self) -> Result { + let versioned_hash = stack_peek(self, 0)?; + let z = stack_peek(self, 1)?; + let y = stack_peek(self, 2)?; + let comm_hi = stack_peek(self, 3)?; + let comm_lo = stack_peek(self, 4)?; + let proof_hi = stack_peek(self, 5)?; + let proof_lo = stack_peek(self, 6)?; + + // Validate scalars + if z > BLS_SCALAR { + return Err(ProgramError::ProverInputError( + ProverInputError::KzgEvalFailure("z is not canonical.".to_string()), + )); + } + if y > BLS_SCALAR { + return Err(ProgramError::ProverInputError( + ProverInputError::KzgEvalFailure("y is not canonical.".to_string()), + )); + } + + let mut comm_bytes = [0u8; 64]; + comm_hi.to_big_endian(&mut comm_bytes[0..32]); + comm_lo.to_big_endian(&mut comm_bytes[32..64]); // only actually 16 bits + + let mut proof_bytes = [0u8; 64]; + proof_hi.to_big_endian(&mut proof_bytes[0..32]); + proof_lo.to_big_endian(&mut proof_bytes[32..64]); // only actually 16 bits + + let mut expected_versioned_hash = keccak(&comm_bytes[16..64]).0; + expected_versioned_hash[0] = KZG_VERSIONED_HASH; + + if versioned_hash != U256::from_big_endian(&expected_versioned_hash) { + return Err(ProgramError::ProverInputError( + ProverInputError::KzgEvalFailure( + "Versioned hash does not match expected value.".to_string(), + ), + )); + } + + self.verify_kzg_proof(&comm_bytes, z, y, &proof_bytes) + } + + /// Returns the second part of the KZG precompile output. + /// The POINT_EVALUATION_PRECOMPILE returns a 64-byte value. Because EVM + /// words only fit in 32 bytes, we read the previously pushed value and + /// then accordingly push the following word. + fn run_kzg_point_eval_2(&mut self) -> Result { + let prev_value = stack_peek(self, 0)?; + + if prev_value == U256::from_big_endian(&POINT_EVALUATION_PRECOMPILE_RETURN_VALUE[1]) { + Ok(U256::from_big_endian( + &POINT_EVALUATION_PRECOMPILE_RETURN_VALUE[0], + )) + } else { + Err(ProgramError::ProverInputError( + ProverInputError::KzgEvalFailure( + "run_kzg_point_eval_1 should have output the expected return value or errored" + .to_string(), + ), + )) + } + } + + /// Verifies a KZG proof, i.e. that the commitment opens to y at z. + fn verify_kzg_proof( + &self, + comm_bytes: &[u8; 64], + z: U256, + y: U256, + proof_bytes: &[u8; 64], + ) -> Result { + if comm_bytes[0..16] != [0; 16] || proof_bytes[0..16] != [0; 16] { + // Proofs and commitments must fit in 48 bytes to be deserializable. + return Err(ProgramError::ProverInputError( + ProverInputError::KzgEvalFailure( + "Proof or commitment do not fit in 48 bytes.".to_string(), + ), + )); + } + + let comm = if let Ok(c) = bls381::g1_from_bytes(comm_bytes[16..64].try_into().unwrap()) { + c + } else { + return Err(ProgramError::ProverInputError( + ProverInputError::KzgEvalFailure( + "Commitment did not deserialize into a valid G1 point.".to_string(), + ), + )); + }; + + let proof = if let Ok(p) = bls381::g1_from_bytes(proof_bytes[16..64].try_into().unwrap()) { + p + } else { + return Err(ProgramError::ProverInputError( + ProverInputError::KzgEvalFailure( + "Proof did not deserialize into a valid G1 point.".to_string(), + ), + )); + }; + + // TODO: use some WNAF method if performance becomes critical + let mut z_bytes = [0u8; 32]; + z.to_big_endian(&mut z_bytes); + let mut acc = CurveAff::>::unit(); + for (i, &byte) in z_bytes.iter().enumerate() { + acc = acc * 256_i32; + acc = acc + (CurveAff::>::GENERATOR * byte as i32); + } + let minus_z_g2 = -acc; + + let mut y_bytes = [0u8; 32]; + y.to_big_endian(&mut y_bytes); + let mut acc = CurveAff::::unit(); + for byte in y_bytes { + acc = acc * 256_i32; + acc = acc + (CurveAff::::GENERATOR * byte as i32); + } + let comm_minus_y = comm + (acc.neg()); + + let x = CurveAff::> { + x: Fp2:: { + re: BLS381 { + val: U512::from_big_endian(&G2_TRUSTED_SETUP_POINT[0]), + }, + im: BLS381 { + val: U512::from_big_endian(&G2_TRUSTED_SETUP_POINT[1]), + }, + }, + y: Fp2:: { + re: BLS381 { + val: U512::from_big_endian(&G2_TRUSTED_SETUP_POINT[2]), + }, + im: BLS381 { + val: U512::from_big_endian(&G2_TRUSTED_SETUP_POINT[3]), + }, + }, + }; + let x_minus_z = x + minus_z_g2; + + // TODO: If this ends up being implemented in the Kernel directly, we should + // really not have to go through the final exponentiation twice. + if bls381::ate_optim(comm_minus_y, -CurveAff::>::GENERATOR) + * bls381::ate_optim(proof, x_minus_z) + != Fp12::::UNIT + { + Err(ProgramError::ProverInputError( + ProverInputError::KzgEvalFailure( + "Final pairing check did not succeed.".to_string(), + ), + )) + } else { + Ok(U256::from_big_endian( + &POINT_EVALUATION_PRECOMPILE_RETURN_VALUE[1], + )) + } + } } impl GenerationState { @@ -723,22 +889,21 @@ impl FromStr for FieldOp { } impl EvmField { - fn order(&self) -> U256 { + fn order(&self) -> U512 { match self { - EvmField::Bls381Base => todo!(), - EvmField::Bls381Scalar => todo!(), - EvmField::Bn254Base => { - U256::from_str("0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47") - .unwrap() - } + EvmField::Bls381Base => BLS_BASE, + EvmField::Bls381Scalar => BLS_SCALAR.into(), + EvmField::Bn254Base => BN_BASE.into(), EvmField::Bn254Scalar => todo!(), EvmField::Secp256k1Base => { U256::from_str("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f") .unwrap() + .into() } EvmField::Secp256k1Scalar => { U256::from_str("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141") .unwrap() + .into() } } } @@ -751,7 +916,8 @@ impl EvmField { } fn inverse(&self, x: U256) -> Result { - let n = self.order(); + let n = U256::try_from(self.order()) + .map_err(|_| ProgramError::ProverInputError(Unimplemented))?; if x >= n { return Err(ProgramError::ProverInputError(InvalidInput)); }; @@ -759,7 +925,8 @@ impl EvmField { } fn sqrt(&self, x: U256) -> Result { - let n = self.order(); + let n = U256::try_from(self.order()) + .map_err(|_| ProgramError::ProverInputError(Unimplemented))?; if x >= n { return Err(ProgramError::ProverInputError(InvalidInput)); }; diff --git a/evm_arithmetization/src/witness/errors.rs b/evm_arithmetization/src/witness/errors.rs index 90bdb3f8a..ef1e9f73b 100644 --- a/evm_arithmetization/src/witness/errors.rs +++ b/evm_arithmetization/src/witness/errors.rs @@ -39,4 +39,6 @@ pub enum ProverInputError { NumBitsError, InvalidJumpDestination, InvalidJumpdestSimulation, + KzgEvalFailure(String), + Unimplemented, } From bfc622a11cc22e4dc95c05e285b7305b7fa0dd56 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Thu, 25 Apr 2024 01:26:45 +0900 Subject: [PATCH 13/40] Some fixes to Cancun (#187) * Add a couple of fixes to Cancun implem Co-authored-by: 4l0n50 --------- Co-authored-by: 4l0n50 --- .../src/cpu/kernel/asm/beacon_roots.asm | 8 ++++---- .../src/cpu/kernel/asm/global_exit_root.asm | 6 +++--- evm_arithmetization/src/cpu/kernel/asm/main.asm | 2 +- .../src/cpu/kernel/asm/mpt/accounts.asm | 4 ++-- .../src/cpu/kernel/asm/mpt/read.asm | 8 ++++++++ .../src/cpu/kernel/constants/mod.rs | 16 ++++++++-------- .../src/cpu/kernel/interpreter.rs | 4 ++++ evm_arithmetization/src/generation/mpt.rs | 1 + 8 files changed, 31 insertions(+), 18 deletions(-) diff --git a/evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm b/evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm index 3b9db19e7..125c9d58b 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/beacon_roots.asm @@ -37,13 +37,13 @@ write_beacon_roots_to_storage: %slot_to_storage_key // stack: storage_key, value_ptr, after_beacon_roots_storage_insert, retdest PUSH 64 // storage_key has 64 nibbles - %get_storage_trie(@BEACON_ROOTS_ADDRESS) + %get_storage_trie(@BEACON_ROOTS_CONTRACT_STATE_KEY) // stack: storage_root_ptr, 64, storage_key, value_ptr, after_beacon_roots_storage_insert, retdest %jump(mpt_insert) after_beacon_roots_storage_insert: // stack: new_storage_root_ptr, retdest - %get_account_data(@BEACON_ROOTS_ADDRESS) + %get_account_data(@BEACON_ROOTS_CONTRACT_STATE_KEY) // stack: account_ptr, new_storage_root_ptr, retdest // Update the copied account with our new storage root pointer. @@ -60,7 +60,7 @@ delete_root_idx_slot: %slot_to_storage_key // stack: storage_key, after_root_idx_slot_delete, write_beacon_roots_to_storage, timestamp_idx, timestamp, retdest PUSH 64 // storage_key has 64 nibbles - %get_storage_trie(@BEACON_ROOTS_ADDRESS) + %get_storage_trie(@BEACON_ROOTS_CONTRACT_STATE_KEY) // stack: storage_root_ptr, 64, storage_key, after_root_idx_slot_delete, write_beacon_roots_to_storage, timestamp_idx, timestamp, retdest // If the slot is empty (i.e. ptr defaulting to 0), skip the deletion. @@ -79,7 +79,7 @@ checkpoint_delete_root_idx: after_root_idx_slot_delete: // stack: new_storage_root_ptr, write_beacon_roots_to_storage, timestamp_idx, timestamp, retdest - %get_account_data(@BEACON_ROOTS_ADDRESS) + %get_account_data(@BEACON_ROOTS_CONTRACT_STATE_KEY) // stack: account_ptr, new_storage_root_ptr, write_beacon_roots_to_storage, timestamp_idx, timestamp, retdest // Update the copied account with our new storage root pointer. diff --git a/evm_arithmetization/src/cpu/kernel/asm/global_exit_root.asm b/evm_arithmetization/src/cpu/kernel/asm/global_exit_root.asm index 7e45af890..ffcc377a5 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/global_exit_root.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/global_exit_root.asm @@ -1,6 +1,6 @@ /// At the top of the block, the global exit roots (if any) are written to storage. /// Global exit roots (GER) are of the form `(timestamp, root)` and are loaded from prover inputs. -/// The timestamp is written to the storage of address `ADDRESS_GLOBAL_EXIT_ROOT_MANAGER_L2` in the slot `keccak256(abi.encodePacked(root, GLOBAL_EXIT_ROOT_STORAGE_POS))`. +/// The timestamp is written to the storage of address `GLOBAL_EXIT_ROOT_MANAGER_L2_STATE_KEY` in the slot `keccak256(abi.encodePacked(root, GLOBAL_EXIT_ROOT_STORAGE_POS))`. /// See https://github.com/0xPolygonHermez/cdk-erigon/blob/zkevm/zk/utils/global_exit_root.go for reference. /// /// *NOTE*: This will panic if one of the provided timestamps is zero. @@ -48,7 +48,7 @@ write_timestamp_to_storage: %slot_to_storage_key // stack: storage_key, value_ptr, after_timestamp_storage_insert PUSH 64 // storage_key has 64 nibbles - %get_storage_trie(@ADDRESS_GLOBAL_EXIT_ROOT_MANAGER_L2) + %get_storage_trie(@GLOBAL_EXIT_ROOT_MANAGER_L2_STATE_KEY) // stack: storage_root_ptr, 64, storage_key, value_ptr, after_timestamp_storage_insert %stack (storage_root_ptr, num_nibbles, storage_key) -> (storage_root_ptr, num_nibbles, storage_key, after_read, storage_root_ptr, num_nibbles, storage_key) %jump(mpt_read) @@ -61,7 +61,7 @@ after_read: after_timestamp_storage_insert: // stack: new_storage_root_ptr, i, num_ger, retdest - %get_account_data(@ADDRESS_GLOBAL_EXIT_ROOT_MANAGER_L2) + %get_account_data(@GLOBAL_EXIT_ROOT_MANAGER_L2_STATE_KEY) // stack: account_ptr, new_storage_root_ptr // Update the copied account with our new storage root pointer. %add_const(2) diff --git a/evm_arithmetization/src/cpu/kernel/asm/main.asm b/evm_arithmetization/src/cpu/kernel/asm/main.asm index 5187fe3f7..234643da2 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/main.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/main.asm @@ -37,7 +37,7 @@ global hash_initial_tries: // We initialize the segment length with 1 because the segment contains // the null pointer `0` when the tries are empty. PUSH 1 - %mpt_hash_state_trie %mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_DIGEST_BEFORE) %assert_eq + %mpt_hash_state_trie %mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_DIGEST_BEFORE) %assert_eq // stack: trie_data_len %mpt_hash_txn_trie %mload_global_metadata(@GLOBAL_METADATA_TXN_TRIE_DIGEST_BEFORE) %assert_eq // stack: trie_data_len diff --git a/evm_arithmetization/src/cpu/kernel/asm/mpt/accounts.asm b/evm_arithmetization/src/cpu/kernel/asm/mpt/accounts.asm index cb8eede70..1f60a3f75 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/mpt/accounts.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/mpt/accounts.asm @@ -32,9 +32,9 @@ %endmacro // Returns a pointer to the root of the storage trie associated with the provided account. -%macro get_storage_trie(addr) +%macro get_storage_trie(key) // stack: (empty) - %get_account_data($addr) + %get_account_data($key) // stack: account_ptr %add_const(2) // stack: storage_root_ptr_ptr diff --git a/evm_arithmetization/src/cpu/kernel/asm/mpt/read.asm b/evm_arithmetization/src/cpu/kernel/asm/mpt/read.asm index 86926d130..3741049fe 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/mpt/read.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/mpt/read.asm @@ -4,6 +4,7 @@ global mpt_read_state_trie: // stack: addr, retdest %addr_to_state_key +global mpt_read_state_trie_from_key: // stack: key, retdest PUSH 64 // num_nibbles %mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) // node_ptr @@ -17,6 +18,13 @@ global mpt_read_state_trie: %%after: %endmacro +// Convenience macro to call mpt_read_state_trie_from_key and return where we left off. +%macro mpt_read_state_trie_from_key + %stack (key) -> (key, %%after) + %jump(mpt_read_state_trie_from_key) +%%after: +%endmacro + // Read a value from a MPT. // // Arguments: diff --git a/evm_arithmetization/src/cpu/kernel/constants/mod.rs b/evm_arithmetization/src/cpu/kernel/constants/mod.rs index bf4bca84f..58a8f6eb8 100644 --- a/evm_arithmetization/src/cpu/kernel/constants/mod.rs +++ b/evm_arithmetization/src/cpu/kernel/constants/mod.rs @@ -58,8 +58,8 @@ pub(crate) fn evm_constants() -> HashMap { c.insert(MAX_NONCE.0.into(), U256::from(MAX_NONCE.1)); c.insert(CALL_STACK_LIMIT.0.into(), U256::from(CALL_STACK_LIMIT.1)); c.insert( - cancun_constants::BEACON_ROOTS_ADDRESS.0.into(), - U256::from_big_endian(&cancun_constants::BEACON_ROOTS_ADDRESS.1), + cancun_constants::BEACON_ROOTS_CONTRACT_STATE_KEY.0.into(), + U256::from_big_endian(&cancun_constants::BEACON_ROOTS_CONTRACT_STATE_KEY.1), ); c.insert( cancun_constants::HISTORY_BUFFER_LENGTH.0.into(), @@ -67,10 +67,10 @@ pub(crate) fn evm_constants() -> HashMap { ); c.insert( - global_exit_root::ADDRESS_GLOBAL_EXIT_ROOT_MANAGER_L2 + global_exit_root::GLOBAL_EXIT_ROOT_MANAGER_L2_STATE_KEY .0 .into(), - U256::from_big_endian(&global_exit_root::ADDRESS_GLOBAL_EXIT_ROOT_MANAGER_L2.1), + U256::from_big_endian(&global_exit_root::GLOBAL_EXIT_ROOT_MANAGER_L2_STATE_KEY.1), ); c.insert( global_exit_root::GLOBAL_EXIT_ROOT_STORAGE_POS.0.into(), @@ -350,8 +350,8 @@ pub mod cancun_constants { hex!("000000000000000000000000000000001666c54b0a32529503432fcae0181b4bef79de09fc63671fda5ed1ba9bfa07899495346f3d7ac9cd23048ef30d0a154f"), // y_im ]; - pub const BEACON_ROOTS_ADDRESS: (&str, [u8; 20]) = ( - "BEACON_ROOTS_ADDRESS", + pub const BEACON_ROOTS_CONTRACT_STATE_KEY: (&str, [u8; 20]) = ( + "BEACON_ROOTS_CONTRACT_STATE_KEY", hex!("000F3df6D732807Ef1319fB7B8bB8522d0Beac02"), ); @@ -379,8 +379,8 @@ pub mod global_exit_root { use super::*; /// Taken from https://github.com/0xPolygonHermez/cdk-erigon/blob/61f0b6912055c73f6879ea7e9b5bac22ea5fc85c/zk/utils/global_exit_root.go#L16. - pub const ADDRESS_GLOBAL_EXIT_ROOT_MANAGER_L2: (&str, [u8; 20]) = ( - "ADDRESS_GLOBAL_EXIT_ROOT_MANAGER_L2", + pub const GLOBAL_EXIT_ROOT_MANAGER_L2_STATE_KEY: (&str, [u8; 20]) = ( + "GLOBAL_EXIT_ROOT_MANAGER_L2_STATE_KEY", hex!("a40D5f56745a118D0906a34E69aeC8C0Db1cB8fA"), ); /// Taken from https://github.com/0xPolygonHermez/cdk-erigon/blob/61f0b6912055c73f6879ea7e9b5bac22ea5fc85c/zk/utils/global_exit_root.go#L17. diff --git a/evm_arithmetization/src/cpu/kernel/interpreter.rs b/evm_arithmetization/src/cpu/kernel/interpreter.rs index 83cac0957..b43523f01 100644 --- a/evm_arithmetization/src/cpu/kernel/interpreter.rs +++ b/evm_arithmetization/src/cpu/kernel/interpreter.rs @@ -256,6 +256,10 @@ impl Interpreter { GlobalMetadata::BlockExcessBlobGas, metadata.block_excess_blob_gas, ), + ( + GlobalMetadata::ParentBeaconBlockRoot, + h2u(metadata.parent_beacon_block_root), + ), (GlobalMetadata::BlockGasUsedBefore, inputs.gas_used_before), (GlobalMetadata::BlockGasUsedAfter, inputs.gas_used_after), (GlobalMetadata::TxnNumberBefore, inputs.txn_number_before), diff --git a/evm_arithmetization/src/generation/mpt.rs b/evm_arithmetization/src/generation/mpt.rs index a9450b027..7319eb5bf 100644 --- a/evm_arithmetization/src/generation/mpt.rs +++ b/evm_arithmetization/src/generation/mpt.rs @@ -71,6 +71,7 @@ pub(crate) fn parse_receipts(rlp: &[u8]) -> Result, ProgramError> { let txn_type = match rlp.first().ok_or(ProgramError::InvalidRlp)? { 1 => 1, 2 => 2, + 3 => 3, _ => 0, }; From b1d04166ba749f96d55047967c990b0ad09c916d Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Fri, 3 May 2024 00:45:36 +0900 Subject: [PATCH 14/40] Insert blob versioned hashes in signature payload for hashing (#209) --- evm_arithmetization/src/cpu/kernel/asm/transactions/type_3.asm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/evm_arithmetization/src/cpu/kernel/asm/transactions/type_3.asm b/evm_arithmetization/src/cpu/kernel/asm/transactions/type_3.asm index ba01368d6..536801dc7 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/transactions/type_3.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/transactions/type_3.asm @@ -119,6 +119,9 @@ after_serializing_access_list: rlp_addr, rlp_start, retdest) %jump(memcpy_bytes) after_serializing_blob_versioned_hashes: + // stack: rlp_addr, rlp_start, retdest + %mload_global_metadata(@GLOBAL_METADATA_BLOB_VERSIONED_HASHES_RLP_LEN) ADD + // stack: rlp_addr, rlp_start, retdest %prepend_rlp_list_prefix // stack: prefix_start_pos, rlp_len, retdest From 57a62a42595bc18b0f6ad91fe335e94f56a14255 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Fri, 3 May 2024 00:45:51 +0900 Subject: [PATCH 15/40] Fix KZG precompile context setup (#210) --- .../src/cpu/kernel/asm/core/precompiles/blake2_f.asm | 4 ++-- .../src/cpu/kernel/asm/core/precompiles/kzg_peval.asm | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/blake2_f.asm b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/blake2_f.asm index 80557493c..8868be406 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/blake2_f.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/blake2_f.asm @@ -1,6 +1,6 @@ global precompile_blake2_f: - // stack: retdest, new_ctx, (old stack) - POP + // stack: address, retdest, new_ctx, (old stack) + %pop2 // stack: new_ctx, (old stack) %set_new_ctx_parent_pc(after_precompile) // stack: new_ctx, (old stack) diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/kzg_peval.asm b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/kzg_peval.asm index dba8766d2..adf87534d 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/kzg_peval.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/kzg_peval.asm @@ -1,6 +1,6 @@ global precompile_kzg_peval: - // stack: address, retdest, new_ctx, (old stack) - %pop2 + // stack: retdest, new_ctx, (old stack) + POP // stack: new_ctx, (old stack) %set_new_ctx_parent_pc(after_precompile) // stack: new_ctx, (old stack) From f0022eb82ceff397bc0c672bd1ac90edb546569a Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Fri, 3 May 2024 19:47:45 +0900 Subject: [PATCH 16/40] Fix txn type encoding for receipts (#214) --- evm_arithmetization/src/cpu/kernel/asm/core/create_receipt.asm | 1 + .../src/cpu/kernel/asm/mpt/hash/hash_trie_specific.asm | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/create_receipt.asm b/evm_arithmetization/src/cpu/kernel/asm/core/create_receipt.asm index bc6959483..fc3632cc6 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/create_receipt.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/create_receipt.asm @@ -60,6 +60,7 @@ process_receipt_after_bloom: // stack: first_txn_byte, receipt_ptr, payload_len, status, new_cum_gas, txn_nb, new_cum_gas, txn_nb, num_nibbles, retdest DUP1 %eq_const(1) %jumpi(receipt_nonzero_type) DUP1 %eq_const(2) %jumpi(receipt_nonzero_type) + DUP1 %eq_const(3) %jumpi(receipt_nonzero_type) // If we are here, we are dealing with a legacy transaction, and we do not need to write the type. POP diff --git a/evm_arithmetization/src/cpu/kernel/asm/mpt/hash/hash_trie_specific.asm b/evm_arithmetization/src/cpu/kernel/asm/mpt/hash/hash_trie_specific.asm index cd07c01fd..8d8c7a419 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/mpt/hash/hash_trie_specific.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/mpt/hash/hash_trie_specific.asm @@ -169,7 +169,7 @@ global encode_receipt: // stack: first_value, rlp_addr, value_ptr, cur_len, retdest // The first value is either the transaction type or the payload length. // Since the receipt contains at least the 256-bytes long bloom filter, payload_len > 3. - DUP1 %lt_const(3) %jumpi(encode_nonzero_receipt_type) + DUP1 %lt_const(4) %jumpi(encode_nonzero_receipt_type) // If we are here, then the first byte is the payload length. %rlp_list_len // stack: rlp_receipt_len, rlp_addr, value_ptr, cur_len, retdest From f225a5d7ddf1815023dfe218005c835f496c5e5f Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Sat, 4 May 2024 02:58:53 +0900 Subject: [PATCH 17/40] Add blob gas fee burn for type-3 txns (#219) * Add blob gas fee burn for type-3 txns * Fix fake_exponential * Remove comment * Newline --- .../cpu/kernel/asm/core/precompiles/main.asm | 2 +- .../src/cpu/kernel/asm/core/process_txn.asm | 8 +++--- .../cpu/kernel/asm/transactions/type_3.asm | 26 +++++++++++++++++++ .../src/cpu/kernel/constants/mod.rs | 5 ++-- .../src/generation/prover_input.rs | 12 ++++++--- 5 files changed, 42 insertions(+), 11 deletions(-) diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/main.asm b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/main.asm index 39f4e5734..0a1883491 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/main.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/main.asm @@ -52,7 +52,7 @@ global handle_precompiles_from_eoa: // stack: addr, retdest %create_context // stack: new_ctx, addr, retdest - %non_intrinisic_gas %set_new_ctx_gas_limit + %non_intrinsic_gas %set_new_ctx_gas_limit // stack: new_ctx, addr, retdest // Set calldatasize and copy txn data to calldata. diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/process_txn.asm b/evm_arithmetization/src/cpu/kernel/asm/core/process_txn.asm index 391860799..c6d10eb40 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/process_txn.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/process_txn.asm @@ -170,7 +170,7 @@ global process_contract_creation_txn_after_code_loaded: %mload_txn_field(@TXN_FIELD_VALUE) %set_new_ctx_value %set_new_ctx_parent_ctx %set_new_ctx_parent_pc(process_contract_creation_txn_after_constructor) - %non_intrinisic_gas %set_new_ctx_gas_limit + %non_intrinsic_gas %set_new_ctx_gas_limit // stack: new_ctx, address, retdest %enter_new_ctx @@ -258,7 +258,7 @@ global process_message_txn_insufficient_balance: global process_message_txn_return: // stack: retdest // Since no code was executed, the leftover gas is the non-intrinsic gas. - %non_intrinisic_gas + %non_intrinsic_gas DUP1 // stack: leftover_gas, leftover_gas, retdest %pay_coinbase_and_refund_sender @@ -283,7 +283,7 @@ global process_message_txn_code_loaded: %mload_txn_field(@TXN_FIELD_VALUE) %set_new_ctx_value %set_new_ctx_parent_ctx %set_new_ctx_parent_pc(process_message_txn_after_call) - %non_intrinisic_gas %set_new_ctx_gas_limit + %non_intrinsic_gas %set_new_ctx_gas_limit // stack: new_ctx, retdest // Set calldatasize and copy txn data to calldata. @@ -387,7 +387,7 @@ process_message_txn_fail: // stack: (empty) %endmacro -%macro non_intrinisic_gas +%macro non_intrinsic_gas // stack: (empty) %mload_txn_field(@TXN_FIELD_INTRINSIC_GAS) %mload_txn_field(@TXN_FIELD_GAS_LIMIT) diff --git a/evm_arithmetization/src/cpu/kernel/asm/transactions/type_3.asm b/evm_arithmetization/src/cpu/kernel/asm/transactions/type_3.asm index 536801dc7..286ff6b06 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/transactions/type_3.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/transactions/type_3.asm @@ -154,7 +154,33 @@ store_origin: %eq_const(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) %jumpi(panic) + // stack: address, retdest + + // EIP-4844: Deduct blob_gas_fee from the sender and burn it + %compute_blob_gas_fee + DUP2 + // stack: address, blob_gas_fee, address, retdest + %deduct_eth + // stack: deduct_eth_status, address, retdest + %jumpi(transfer_eth_failure) + // stack: address, retdest %mstore_txn_field(@TXN_FIELD_ORIGIN) // stack: retdest %jump(process_normalized_txn) + +%macro compute_blob_gas_fee + PUSH @GAS_PER_BLOB + %get_blob_versioned_hashes_list_length + MUL + PROVER_INPUT(blobbasefee) + MUL +%endmacro + +%macro get_blob_versioned_hashes_list_length + // stack: (empty) + PUSH 33 // encoded length of each blob versioned hash + %mload_global_metadata(@GLOBAL_METADATA_BLOB_VERSIONED_HASHES_LEN) + DIV + // stack: len +%endmacro diff --git a/evm_arithmetization/src/cpu/kernel/constants/mod.rs b/evm_arithmetization/src/cpu/kernel/constants/mod.rs index 58a8f6eb8..2b709d469 100644 --- a/evm_arithmetization/src/cpu/kernel/constants/mod.rs +++ b/evm_arithmetization/src/cpu/kernel/constants/mod.rs @@ -240,7 +240,7 @@ const EC_CONSTANTS: [(&str, [u8; 32]); 20] = [ ), ]; -const GAS_CONSTANTS: [(&str, u16); 37] = [ +const GAS_CONSTANTS: [(&str, u32); 38] = [ ("GAS_ZERO", 0), ("GAS_JUMPDEST", 1), ("GAS_BASE", 2), @@ -278,6 +278,7 @@ const GAS_CONSTANTS: [(&str, u16); 37] = [ ("GAS_COPY", 3), ("GAS_BLOCKHASH", 20), ("GAS_HASH_OPCODE", 3), + ("GAS_PER_BLOB", 131_072), ]; const REFUND_CONSTANTS: [(&str, u16); 2] = [("REFUND_SCLEAR", 4_800), ("MAX_REFUND_QUOTIENT", 5)]; @@ -331,7 +332,7 @@ pub mod cancun_constants { pub const BLOB_BASE_FEE_UPDATE_FRACTION: U256 = U256([0x32f0ed, 0, 0, 0]); - pub const MIN_BLOB_BASE_FEE: U256 = U256::one(); + pub const MIN_BASE_FEE_PER_BLOB_GAS: U256 = U256::one(); pub const KZG_VERSIONED_HASH: u8 = 0x01; diff --git a/evm_arithmetization/src/generation/prover_input.rs b/evm_arithmetization/src/generation/prover_input.rs index 8e1a31886..774d493b7 100644 --- a/evm_arithmetization/src/generation/prover_input.rs +++ b/evm_arithmetization/src/generation/prover_input.rs @@ -14,7 +14,7 @@ use serde::{Deserialize, Serialize}; use crate::cpu::kernel::cancun_constants::KZG_VERSIONED_HASH; use crate::cpu::kernel::constants::cancun_constants::{ - BLOB_BASE_FEE_UPDATE_FRACTION, G2_TRUSTED_SETUP_POINT, MIN_BLOB_BASE_FEE, + BLOB_BASE_FEE_UPDATE_FRACTION, G2_TRUSTED_SETUP_POINT, MIN_BASE_FEE_PER_BLOB_GAS, POINT_EVALUATION_PRECOMPILE_RETURN_VALUE, }; use crate::cpu::kernel::constants::context_metadata::ContextMetadata; @@ -151,7 +151,7 @@ impl GenerationState { fn run_blobbasefee(&mut self) -> Result { let excess_blob_gas = self.inputs.block_metadata.block_excess_blob_gas; Ok(fake_exponential( - MIN_BLOB_BASE_FEE, + MIN_BASE_FEE_PER_BLOB_GAS, excess_blob_gas, BLOB_BASE_FEE_UPDATE_FRACTION, )) @@ -998,14 +998,18 @@ fn modexp(x: U256, e: U256, n: U256) -> Result { /// See EIP-4844: . fn fake_exponential(factor: U256, numerator: U256, denominator: U256) -> U256 { + if factor.is_zero() || numerator.is_zero() { + return factor; + } + let mut i = 1; let mut output = U256::zero(); let mut numerator_accum = factor * denominator; while !numerator_accum.is_zero() { output += numerator_accum; - numerator_accum *= numerator; // (denominator * i) + numerator_accum *= numerator / (denominator * i); i += 1; } - output // denominator + output / denominator } From 052e4765f46a8c2839bdf25ad0e947000331c2ae Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Sun, 5 May 2024 00:05:46 +0900 Subject: [PATCH 18/40] Update decoder processing for cancun (#207) * Add beacon root update in the decoder * Update padding logic * Apply some comments * Nit --- trace_decoder/src/decoding.rs | 304 ++++++++++----------- trace_decoder/src/processed_block_trace.rs | 8 +- 2 files changed, 154 insertions(+), 158 deletions(-) diff --git a/trace_decoder/src/decoding.rs b/trace_decoder/src/decoding.rs index 50550684d..69b2192d7 100644 --- a/trace_decoder/src/decoding.rs +++ b/trace_decoder/src/decoding.rs @@ -1,13 +1,14 @@ use std::{ collections::HashMap, fmt::{self, Display, Formatter}, - iter::{self, empty, once}, + iter::{self, once}, }; -use ethereum_types::{Address, H256, U256, U512}; +use ethereum_types::{Address, BigEndianHash, H256, U256, U512}; use evm_arithmetization::{ generation::{mpt::AccountRlp, GenerationInputs, TrieInputs}, - proof::{ExtraBlockData, TrieRoots}, + proof::{BlockMetadata, ExtraBlockData, TrieRoots}, + testing_utils::{BEACON_ROOTS_CONTRACT_ADDRESS_HASHED, HISTORY_BUFFER_LENGTH}, }; use log::trace; use mpt_trie::{ @@ -240,13 +241,6 @@ impl ProcessedBlockTrace { ..Default::default() }; - // This is just a copy of `curr_block_tries`. - let initial_tries_for_dummies = PartialTrieState { - state: self.tries.state, - storage: self.tries.storage, - ..Default::default() - }; - let mut extra_data = ExtraBlockData { checkpoint_state_trie_root: other_data.checkpoint_state_trie_root, txn_number_before: U256::zero(), @@ -255,16 +249,24 @@ impl ProcessedBlockTrace { gas_used_after: U256::zero(), }; - // A copy of the initial extra_data possibly needed during padding. - let extra_data_for_dummies = extra_data.clone(); + // Dummy payloads do not increment this accumulator. + // For actual transactions, it will match their position in the block. + let mut txn_idx = 0; let mut txn_gen_inputs = self .txn_info .into_iter() .enumerate() - .map(|(txn_idx, txn_info)| { + .map(|(idx, txn_info)| { + let current_idx = txn_idx; + if !txn_info.meta.is_dummy() { + txn_idx += 1; + } + let is_initial_payload = idx == 0; + Self::process_txn_info( - txn_idx, + current_idx, + is_initial_payload, txn_info, &mut curr_block_tries, &mut extra_data, @@ -282,15 +284,6 @@ impl ProcessedBlockTrace { e })?; - Self::pad_gen_inputs_with_dummy_inputs_if_needed( - &mut txn_gen_inputs, - &other_data, - &extra_data, - &extra_data_for_dummies, - &initial_tries_for_dummies, - &curr_block_tries, - ); - if !self.withdrawals.is_empty() { Self::add_withdrawals_to_txns( &mut txn_gen_inputs, @@ -302,11 +295,120 @@ impl ProcessedBlockTrace { Ok(txn_gen_inputs) } + /// Cancun HF specific: At the start of a block, prior txn execution, we + /// need to update the storage of the beacon block root contract. + // See . + fn update_beacon_block_root_contract_storage( + trie_state: &mut PartialTrieState, + delta_out: &mut TrieDeltaApplicationOutput, + nodes_used: &mut NodesUsedByTxn, + block_data: &BlockMetadata, + ) -> TraceParsingResult<()> { + const HISTORY_BUFFER_LENGTH_MOD: U256 = U256([HISTORY_BUFFER_LENGTH.1, 0, 0, 0]); + const ADDRESS: H256 = H256(BEACON_ROOTS_CONTRACT_ADDRESS_HASHED); + + let timestamp_idx = block_data.block_timestamp % HISTORY_BUFFER_LENGTH_MOD; + let timestamp = rlp::encode(&block_data.block_timestamp).to_vec(); + + let root_idx = timestamp_idx + HISTORY_BUFFER_LENGTH_MOD; + let calldata = rlp::encode(&block_data.parent_beacon_block_root).to_vec(); + + let storage_trie = trie_state + .storage + .get_mut(&ADDRESS) + .ok_or(TraceParsingError::new( + TraceParsingErrorReason::MissingAccountStorageTrie(ADDRESS), + ))?; + + let mut slots_nibbles = vec![]; + + for (slot, val) in [(timestamp_idx, timestamp), (root_idx, calldata)] + .iter() + .map(|(k, v)| { + ( + Nibbles::from_h256_be(hash( + &Nibbles::from_h256_be(H256::from_uint(k)).bytes_be(), + )), + v, + ) + }) + { + slots_nibbles.push(slot); + + // If we are writing a zero, then we actually need to perform a delete. + match val == &ZERO_STORAGE_SLOT_VAL_RLPED { + false => { + storage_trie.insert(slot, val.clone()).map_err(|err| { + let mut e = + TraceParsingError::new(TraceParsingErrorReason::TrieOpError(err)); + e.slot(U512::from_big_endian(slot.bytes_be().as_slice())); + e.slot_value(U512::from_big_endian(val.as_slice())); + e + })?; + + delta_out + .additional_storage_trie_paths_to_not_hash + .entry(ADDRESS) + .or_default() + .push(slot); + } + true => { + if let Ok(Some(remaining_slot_key)) = + Self::delete_node_and_report_remaining_key_if_branch_collapsed( + storage_trie, + &slot, + ) + { + delta_out + .additional_storage_trie_paths_to_not_hash + .entry(ADDRESS) + .or_default() + .push(remaining_slot_key); + } + } + }; + } + + nodes_used.storage_accesses.push((ADDRESS, slots_nibbles)); + + let addr_nibbles = Nibbles::from_h256_be(ADDRESS); + delta_out + .additional_state_trie_paths_to_not_hash + .push(addr_nibbles); + let addr_bytes = trie_state.state.get(addr_nibbles).ok_or_else(|| { + TraceParsingError::new(TraceParsingErrorReason::NonExistentTrieEntry( + TrieType::State, + addr_nibbles, + trie_state.state.hash(), + )) + })?; + let mut account = account_from_rlped_bytes(addr_bytes)?; + + account.storage_root = storage_trie.hash(); + + let updated_account_bytes = rlp::encode(&account); + trie_state + .state + .insert(addr_nibbles, updated_account_bytes.to_vec()) + .map_err(|err| { + let mut e = TraceParsingError::new(TraceParsingErrorReason::TrieOpError(err)); + e.slot(U512::from_big_endian(addr_nibbles.bytes_be().as_slice())); + e + })?; + + Ok(()) + } + fn update_txn_and_receipt_tries( trie_state: &mut PartialTrieState, meta: &TxnMetaState, txn_idx: TxnIdx, ) -> TrieOpResult<()> { + if meta.is_dummy() { + // This is a dummy payload, that does not mutate these tries. + return Ok(()); + } + let txn_k = Nibbles::from_bytes_be(&rlp::encode(&txn_idx)).unwrap(); trie_state.txn.insert(txn_k, meta.txn_bytes())?; @@ -522,43 +624,6 @@ impl ProcessedBlockTrace { branch_collapse_occurred.then(|| new_path.iter().into_key()) } - /// Pads a generated IR vec with additional "dummy" entries if needed. - /// We need to ensure that generated IR always has at least `2` elements, - /// and if there are only `0` or `1` elements, then we need to pad so - /// that we have two entries in total. These dummy entries serve only to - /// allow the proof generation process to finish. Specifically, we need - /// at least two entries to generate an agg proof, and we need an agg - /// proof to generate a block proof. These entries do not mutate state. - fn pad_gen_inputs_with_dummy_inputs_if_needed( - gen_inputs: &mut Vec, - other_data: &OtherBlockData, - final_extra_data: &ExtraBlockData, - initial_extra_data: &ExtraBlockData, - initial_tries: &PartialTrieState, - final_tries: &PartialTrieState, - ) { - match gen_inputs.len() { - 0 => { - debug_assert!(initial_tries.state == final_tries.state); - debug_assert!(initial_extra_data == final_extra_data); - // We need to pad with two dummy entries. - gen_inputs.extend(create_dummy_txn_pair_for_empty_block( - other_data, - final_extra_data, - initial_tries, - )); - } - 1 => { - // We just need one dummy entry. - // The dummy proof will be prepended to the actual txn. - let dummy_txn = - create_dummy_gen_input(other_data, initial_extra_data, initial_tries); - gen_inputs.insert(0, dummy_txn) - } - _ => (), - } - } - /// The withdrawals are always in the final ir payload. fn add_withdrawals_to_txns( txn_ir: &mut [GenerationInputs], @@ -630,6 +695,7 @@ impl ProcessedBlockTrace { /// Processes a single transaction in the trace. fn process_txn_info( txn_idx: usize, + is_initial_payload: bool, txn_info: ProcessedTxnInfo, curr_block_tries: &mut PartialTrieState, extra_data: &mut ExtraBlockData, @@ -661,12 +727,26 @@ impl ProcessedBlockTrace { Self::update_txn_and_receipt_tries(curr_block_tries, &txn_info.meta, txn_idx) .map_err(TraceParsingError::from)?; - let delta_out = + let mut delta_out = Self::apply_deltas_to_trie_state(curr_block_tries, &txn_info.nodes_used_by_txn)?; + let nodes_used_by_txn = if is_initial_payload { + let mut nodes_used = txn_info.nodes_used_by_txn; + Self::update_beacon_block_root_contract_storage( + curr_block_tries, + &mut delta_out, + &mut nodes_used, + &other_data.b_data.b_meta, + )?; + + nodes_used + } else { + txn_info.nodes_used_by_txn + }; + let tries = Self::create_minimal_partial_tries_needed_by_txn( &tries_at_start_of_txn, - &txn_info.nodes_used_by_txn, + &nodes_used_by_txn, txn_idx, delta_out, &other_data.b_data.b_meta.block_beneficiary, @@ -739,100 +819,6 @@ fn calculate_trie_input_hashes(t_inputs: &PartialTrieState) -> TrieRoots { } } -// We really want to get a trie with just a hash node here, and this is an easy -// way to do it. -fn create_fully_hashed_out_sub_partial_trie(trie: &HashedPartialTrie) -> HashedPartialTrie { - // Impossible to actually fail with an empty iter. - create_trie_subset(trie, empty::()).unwrap() -} - -fn create_dummy_txn_pair_for_empty_block( - other_data: &OtherBlockData, - extra_data: &ExtraBlockData, - final_tries: &PartialTrieState, -) -> [GenerationInputs; 2] { - [ - create_dummy_gen_input(other_data, extra_data, final_tries), - create_dummy_gen_input(other_data, extra_data, final_tries), - ] -} - -fn create_dummy_gen_input( - other_data: &OtherBlockData, - extra_data: &ExtraBlockData, - final_tries: &PartialTrieState, -) -> GenerationInputs { - let sub_tries = create_dummy_proof_trie_inputs( - final_tries, - create_fully_hashed_out_sub_partial_trie(&final_tries.state), - ); - create_dummy_gen_input_common(other_data, extra_data, sub_tries) -} - -fn create_dummy_gen_input_common( - other_data: &OtherBlockData, - extra_data: &ExtraBlockData, - sub_tries: TrieInputs, -) -> GenerationInputs { - let trie_roots_after = TrieRoots { - state_root: sub_tries.state_trie.hash(), - transactions_root: sub_tries.transactions_trie.hash(), - receipts_root: sub_tries.receipts_trie.hash(), - }; - - // Sanity checks - assert_eq!( - extra_data.txn_number_before, extra_data.txn_number_after, - "Txn numbers before/after differ in a dummy payload with no txn!" - ); - assert_eq!( - extra_data.gas_used_before, extra_data.gas_used_after, - "Gas used before/after differ in a dummy payload with no txn!" - ); - - GenerationInputs { - signed_txn: None, - tries: sub_tries, - trie_roots_after, - checkpoint_state_trie_root: extra_data.checkpoint_state_trie_root, - block_metadata: other_data.b_data.b_meta.clone(), - block_hashes: other_data.b_data.b_hashes.clone(), - txn_number_before: extra_data.txn_number_before, - gas_used_before: extra_data.gas_used_before, - gas_used_after: extra_data.gas_used_after, - contract_code: HashMap::default(), - withdrawals: vec![], // this is set after creating dummy payloads - global_exit_roots: vec![], - } -} - -fn create_dummy_proof_trie_inputs( - final_tries_at_end_of_block: &PartialTrieState, - state_trie: HashedPartialTrie, -) -> TrieInputs { - let partial_sub_storage_tries: Vec<_> = final_tries_at_end_of_block - .storage - .iter() - .map(|(hashed_acc_addr, s_trie)| { - ( - *hashed_acc_addr, - create_fully_hashed_out_sub_partial_trie(s_trie), - ) - }) - .collect(); - - TrieInputs { - state_trie, - transactions_trie: create_fully_hashed_out_sub_partial_trie( - &final_tries_at_end_of_block.txn, - ), - receipts_trie: create_fully_hashed_out_sub_partial_trie( - &final_tries_at_end_of_block.receipt, - ), - storage_tries: partial_sub_storage_tries, - } -} - fn create_minimal_state_partial_trie( state_trie: &HashedPartialTrie, state_accesses: impl Iterator, @@ -904,6 +890,12 @@ fn account_from_rlped_bytes(bytes: &[u8]) -> TraceParsingResult { } impl TxnMetaState { + /// Outputs a boolean indicating whether this `TxnMetaState` + /// represents a dummy payload or an actual transaction. + const fn is_dummy(&self) -> bool { + self.txn_bytes.is_none() + } + fn txn_bytes(&self) -> Vec { match self.txn_bytes.as_ref() { Some(v) => v.clone(), diff --git a/trace_decoder/src/processed_block_trace.rs b/trace_decoder/src/processed_block_trace.rs index 9da370a4e..7b697403c 100644 --- a/trace_decoder/src/processed_block_trace.rs +++ b/trace_decoder/src/processed_block_trace.rs @@ -87,7 +87,7 @@ impl BlockTrace { let last_tx_idx = self.txn_info.len().saturating_sub(1); - let txn_info = self + let mut txn_info = self .txn_info .into_iter() .enumerate() @@ -111,6 +111,10 @@ impl BlockTrace { }) .collect::>(); + while txn_info.len() < 2 { + txn_info.insert(0, ProcessedTxnInfo::default()); + } + Ok(ProcessedBlockTrace { tries: pre_image_data.tries, txn_info, @@ -232,7 +236,7 @@ where } } -#[derive(Debug)] +#[derive(Debug, Default)] pub(crate) struct ProcessedTxnInfo { pub(crate) nodes_used_by_txn: NodesUsedByTxn, pub(crate) contract_code_accessed: HashMap>, From 06e45bfa1fa7a4a2f93eff686f726c8d64ba65de Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Mon, 6 May 2024 22:07:35 +0900 Subject: [PATCH 19/40] cancun: Add a full block test (#223) * Add a test for testing decoder + evm_arithmetization * Imports --- trace_decoder/Cargo.toml | 2 + trace_decoder/tests/b19807080_trace.json | 1023 ++++++++++++++++++++++ trace_decoder/tests/test_b19807080.rs | 66 ++ 3 files changed, 1091 insertions(+) create mode 100644 trace_decoder/tests/b19807080_trace.json create mode 100644 trace_decoder/tests/test_b19807080.rs diff --git a/trace_decoder/Cargo.toml b/trace_decoder/Cargo.toml index 08ebdcf48..7020aec04 100644 --- a/trace_decoder/Cargo.toml +++ b/trace_decoder/Cargo.toml @@ -31,4 +31,6 @@ mpt_trie = { version = "0.2.1", path = "../mpt_trie" } evm_arithmetization = { version = "0.1.3", path = "../evm_arithmetization" } [dev-dependencies] +plonky2 = { workspace = true } pretty_env_logger = "0.5.0" +serde_json = { workspace = true } diff --git a/trace_decoder/tests/b19807080_trace.json b/trace_decoder/tests/b19807080_trace.json new file mode 100644 index 000000000..539ea49af --- /dev/null +++ b/trace_decoder/tests/b19807080_trace.json @@ -0,0 +1,1023 @@ +{ + "block_trace": { + "trie_pre_images": { + "combined": { + "compact": "0x01033779ac18716333eaa8b1ee23faedf89eaeb26376210080ea0b6c433ce26cb792037574323ce6b3c85015c8634e605f2bacbf96585b00c9025895db7dccaeb25c20031f0277ea255926ec80976c3e0c6cff328b4a62975ef75615863a6d9a8bfd7c130382e784b1da96aae9e2b3caad0bd92c48222ebf7839320089918d98d273e65a2903d3d0866eafbfb46411b41fca6e7c1a7c3a1f44811db62bc91ee93b17fbcb58f903e7fd542c7765c5510c6de407df112d9346c3052bd3a9a3f0833c10806e926f67036484f5d244034b7db0866222fce3efb5d3cd4a9a33cfd04990f083d653bdf98c038f9965353925cc629d7ab66d9ba3be03455c55f95aed5c04fa48dffff53a58f203cdf44c07525e702674e29efa63ff73aebe88d24953b032bd1f46439fddeef250034370b17e98177b64553036bcc9ebe6ca2fa04734d35f13fef50f29586884145603f824149ac38976ee93444b078acd3dd902ef9786b3021c0fe1bdce84794f00ee0323d249d3bcd11058160ca35a34d4a14e397ea67921af26d2d953d63b16281aff03f5054df6157a58891c4f4ba4c7051f366f6f0015b57b09b2657f0150bcf0b8800343d15dcdc3d5efac93795c05c6913f0b432dbe2f92b3d806e244a51d3464755b03966389c702b0bbdea6b282ebb6c06da03f30ef74cbb4e487fdffe04095bc8330038c072e44eb5d38675e81d7c7623f99c0df3f18326a47316c48ff10db723700770315cf6bad672c89529b200568b8102ac1fe541fc57e13b5374576275b7b927f58038a887dbe5a4535228bb4d1dafdb8d525939c3d9344bb4099160e8ce05a68d6920316130e3944bd82f5f54401b06685da78738459bebf019ba3646cf5e1f2f5213c03ee3a842ddd1243db9a564664b85d829006a1134c449f2d5eca7e9d20acf9d63403e1b69cbba0289fb3e370ff8d74ffd4dc1d85db400a299870c09135d3e81ec071033c09ed77be3902649b5290ad18353daf5a4079c65d1786029f7798bd64fb6a01038d019f3435b0e54f223d69e39fe824e75200074d91a30246cfb34a3783c4463703c02b838cd1d3db81a706700e30b4e53728205aa68a3a849592afc5105c201c2403c3020c3e60fc86465eec22d520e147ca83068e17842f542f4bc3682699d4555003154fce6043787886b3efd46ce95281528132709ed624c382db1345b57f7a74c50315f236830317921dfdaabeb920a6f308e16ff1c06ab19a03f223e65fef24bf9d03a5b7f77f93a8111cba1b112864315f4b44412563c68650c9596e5c2427c5f4030307ca7dbe52955c286b792595fb42cb1233e1d53da1760c543e08be13268a8ac703a6f5ede0875bb7ea0387e36389509a0e2d178d5dbf6e5341f2ec16d34774072e037136a6c56b17ec61ba43b8168fa4828e6da00a570ac196efc3ad0f1103869bcf03562d59a51820d47f520c975e0b2bcffac644a509749a3161f481f57b6e826d210605581e03084b81013b6587e6696fe7b719a0725c12b952ec24385e6e61292f819007011bffffffffffffffff05581e03a91df29e0b231fed9f835ccce160a54bfcd07d62ef0d072dc5a25a2f20040205581e03e485f8fa3ecd23ef16485af319ee03a7abd373a3eab3e24a2d671ba0e00c0547adbdfbbb33772c05581d02ba92575482222cc6c6f2ce3a509e5575c0916abc26f8e6abc20e7bd50847af1e725a56355005581d02804bb3cec0269ac8b062c4cf2e0f525b8475d86816e5ac95f70d161b0c19014f4905aecb9e914af729fb0219802003a6cbf61f05a10a3503e7bbd2daf98492e120eb1fd3f31e56f8b48bcde38f091b03f1b574431f3838d9cdff6e701afd5a058652dab5ae5523288a83d5fad769613903576d247b864a9c9c193245be5262a86aceeb51240a250d7b4354c293d83a678d05581e03ab49bd0a6b152d309d49ddbf8a3cc062892cdf65b012d545ae5f08dfc007011bffffffffffffffff05581e03e1125504e79dd63cc52b693a4ba421c6d38bad7c748db6744acbe40b500c064701d6362429ae0005581e034a81699f33d820054e21486be616f2b85bb7ba689b731c302b510d17200c014701e32940b051a005581e03088dc52df1c821d602aea4b9943d4b4cc7cb1196800ff10c7f86ce315004010219a7b9036d65aa2c676a637aa985cb3d802520f895945c67174283f8c5bfa93ae95762a803ce4bd618fe6ab413cfaf48e72f832ec7a0aa585f19b457ae53cccec66cbd0eaf03d3a1afe00fb4b4e7400888b75c939e55c20e727c2be54b51d28eb70739ce949403366c6d43540c31a7a47103bcbd722eb849cd7b09c62ac9f7cbe4df25b3a62d2003d8fd4c90f8f39e168662557028a400e8b3f15c4d530767d43a42550a82ce9d83039f2b339675a4ca1b4e665e10c8744f53ffaa3142b4f36c183cc7df6f9ff416a303bcef9c2acc4cdbc54f26c7dce6f1a0300d3bd105233a67e9e8c0c5983cb37770039ccc5386d9fae80059b3563315468a416b4f433576d050e7c7d191cd10dcb7fd0347902918c6df4e5fa13e2ac68a44cc489ec913f80769e2a072c6f3f57dfa60fb034cf8a2f7c9d765d7ae76fb7fd93dbe58e02be9ef77243dca012fc2c111a66f3c0219ffff038a87e71935a6c03fc655e37d2084542a110bcab781d81219a42d2e306ad10a0603d4d30973bfaff35861879d23bb71c42725bbabade2b9040ac316bb1dd59de35903068325651b8e9af1bd5c24160390b29a0c08f1fccd3268098a6664a6e57179370318dfe6a2a838248d8ac46b0451c67ff807dbe83ffa91ea46b8b75d8a5a6e8d4c03cc085ac9c77c645ab1a40132fe16d9235b45c78b586af5b414f48dc7c2b9ac9303fb79045fb5143671222e8a6161093c5770a4eca03e97140314df8ff2681c71c70385fe99fdf3e1bb8a92eaebf1b34fe9d660bb233d6d9cb0bda1464b793975ff1903d4052bfde7f1a30f7440f67cdd7888d19a9b1df7de37a21c21dc38e116c892870357cc88a05e470e52a1bb4676b7b74daacfa3f2a92879ab3bdc69b93cee0b524e03aa2b65f9b49f4cf77c5cc175b840aa504d6707e2512f85188cf2438b842e857603ce301c228dd726f20c40c0b0f996b91e3000e2128bee8508fbdb8b40e2155714036893ee48254ab45772282751648ada94fe08372e84e1a8a74cda93de1c5064330219ffff036bc65e4f8f9c4db613e4f645916134eb50a304b1234f5704752caa9bcf8a4b4c0312ed86326ad3fd17284e0cdc9d89091b8ed48520dd6c1cdd8c81a7dede60c1e603be907c5d58601daa203e6fdd42648f058eded9594c59c64ad5f03c32b3c8ac8703678483977ecdbd9344d9b2f69f416c85c6388801778696d8a0be51f164b515c60314a6b5a25b62f39e2d9d7cf4e8872b59d753c2851c4b92485eb495f5c302e79e034cfb9ef48fbde023d36423b0f49f033498152fc8503c61132f81bfe0f5c26e4b03106403b51c750a735d1bddaaa349a10026932ca8ae81ea2b81eb1f8a29bff12b0219ffff03ea3db328a4986acceaa5ffa34bf166bf8d881b4ca8997fb61b0f35296a83acd8034a30bce55dfe93b665912c3e5615b64ebd1e0af1a6fb2a226eb8ee24cabe22a80219ffff03673fa84aea0b0f8628d895697bb08c09903a0cb92db83a2178e6a441ab9510b803006f4006055dde70dc4c2b87106be96f079a15abee5dcb60aa5290785ccec50303305c223d7c65d90cafc1c5830f27cf58e26fd0755791a4d35d9c0b5827649cae03642a97b9d56cf442f8082fdf83d1518930c26a04a08efaf10a5d94338ed4594903d7428c54eaeecbcd89277ef5dcbede101b2ac47701b877a2c79856fd0693717603b974990a02b358f377db44a1d11510515ac24e2e7c1d459389986b633a3d5cc603ec29e57dbc7e1129e75093fb04263f443b35374da59e0a7f0c927052d52ef6d3035326307f35e9b6e255605cc5901a11224a938f62d63f835278cc37641da250ef039ab4641c650ceb1a60704b98b5e2ba0b83290e35b20afb1bfe1908ff8705f6f303588ff352d7528fa0cfc6e6de63fa2524a7c4e7bfc8143bc1cb98b4da1f261ace035c7bf11a00d5750b8921627801e5bbb6518dc31b17ea529a1d9d93510d4aad9103666d74a52d44aab3ab4f9e28f48dcc592054d1bd1d13dca508bbc097e79628370321789df6e00ce42c74c3f015dc84e9a915cffddf24b4a08fd6be4a605c031667030463b307a2b6b552ee059829fdbe0a1ea37a5faa78bfa8586bfcf3520374c0d9031f8e6a3427ef6ef844ba16da319406f23e6cf14ef9df5639f87fb37d3479377803b268d6ba72c73f7f1cb163c74466ee1a5ed0dd0cfa2fe3f538699fcacaf8f783033dc408e1316c7fb267d2800307da7bee9d586dbcac8db366be4550de02972899030fb7bbd1977d6a5af1621e97701e3263eb41f498c0a2750bf106e5321575046203bf80bd77ecf6565b46d1154c36ff145eccb12e2e906755f44e5d5d08e37d834803934a0f18fe0401d10cd9078ebe9523349c2a8787c8f7d102c6ccc3c9adfcf7fe03f5b11eb47d78118e03e374fa2aa010742f33f6f60e78799849bbda689975dbff03d4ec39a2a6f526978d0520c221edb1b552e1647d6ea988121e14885c1f36614003909c700e36b36b646029de7eb8c28933eed667ab03c80f6521808b175d481c33037b3b3abc93d2e5d47d14875c12fe53a4d1ac82f3967bd1ed34378441f2acf2e805581e03580a26e0f75462fdd7aa448d121fc649e46fc04a182e29cec4e8546c9008471400668dce880005581e036cb5135f76e47ab0ceb25810c334b735e4bcc472003720f904ac837820084831444faa80e0200005581e03b421c7951915ff8b0f31869caa956d7f48f5c282aa771b2b63f6058c10040205581e030bfab676abff632c74d8327002ab236cc0fc98fc76f27305bfa6c47be00401031f9dd64ecce4e8deb8daaf5ebc66fc0c4e0f88f77092c41968f588a247fbd4df05581e0344d2efb90a365cd28896d14e3f60c86d9b7c0e006e433db674d3d7a2c0040603f856fd0ac73cd3d02b9907b9db4ebf7cea663ccae0598922600e02e5154669f10353af315a0fcaa9dff012916a9a129f20c3b74bae1a8bb08a9a316de5f8e60f0e05581e032640cabedae07e7b2cf2f87380f1c03d6915bfc8ece216a02ae2f248500c190a18480936e2106de2f64703e9c928a377a0a5c361cdf0424feab2e041dbaa42e8be97c67b8cdd32b61193660219ed8d0359c8e5cd5398897f8ef29cea5ac0a112c084e0c4bf1d09dd5bc0df1f453d05110388a34842733985b3a2ad89951f47c279207ce4032188c01d59d5ce7398017208039301ee60769a5cc1e1b93db3f8ceaca862d1b6230cf3dd894d795e9bc3c653b703706e7308c91c196fa759e9a744ca9b43ed50402e3f21bff848e3a92ef1fb9298039ac17e7f0739eb47bcb4b59bad175fb16879e316d937ae265e52fc1665dd179e03f5d1c4bdafba9c9f85e12c76b60b321aad40ed2dbd4c115c82f64f53c83f2ae503a707123a8de89a86642af5d17a5f46a405002c99f9fd9636df48acb117ffa6e203aa0fb7b0887e5cb555f44f9d7929051fa9657c39b09e32086c569ccb6e6c64f60219ffff036c5a1f0b13a2612419148584c832446b97df1f9c67bea20032c475945b4dc62c035504938b4588e9f1c328c0f5226c65bdc483f463acde1ed92560075f96ed6f33035e150a796ed8cf2905fbb3da7531ade6a8d6c63d04f5c92afaba93a76071a91d03d0cc3d19e35ab6988464ab4a30834b7998161c82e5ce6eab15b19b6c12c7fef803b9c04de2fb64d6e60e9be70249151c84aed39118ef3a52f9c027749de938f8f003a5b52af42d4ce1ec784a3a642e8b2f23f438e782cfa8844733cf0522f967aedd036529e10404bacdc1bf05787b9b90448fe39661fd6227e75e0d03098ad579966f031dd2dad2608d11e93b1e8b3efe63c3157cfcf2aa5fba24b3a78494cb303a3b1403120d3a8b7f9f8aaffdb4dde5a0095937a6a16f14a3c6fa2e560ea79171018a240310b5b51c5754134f5f36b7318e6188e4053ede2e7fa4071e8260fc421beb6e66033a20fbe50aa26dd941bbde42353ed1db1218ef553664db1e476b1294c3900c060219ffff037d41bbf4b13fa70838409c4730370f6d758580cad57a84a2e70a3b293959805b033dbfdb3f8c910f657e3842c1572004a8c27645de87d67a4546bc9d627ccd079603b73b9136efd9b214d7273738ce8e47ff73490ebc8f5e781b0d2c799c3a0f64c9033ceae6fb2fb39225d3be1f6171efb911f4598725efb952ab08d304dea121e14003cd9f727d584242a4a67dd2bd3368f9f4181855c2cd227475fc10bff01948356503ab77af421ad2d237715766947479e19bfaaf0734a6c64f68dd4846c7d3cfa64d034e68c1099de2910c199ea73b57042f601287b66ec29a68d556edee14601c32a303c2fb15dac72b853eba1ac2afd03d7de99db6097600f264ef8cb42bb87064c7bf0219ffff03ae72ee11061fd18790a03a54f046e273bf861dad63d9a8a72341f09b2fcfe1750326c892150c6bcc75c040f7772b6c6b5335411a530516b2e3a4bc556dc6ee0fa9030ea23e6b71bae4ab262177837ed3d9d54f8d111c14587ff8a4cc38e8631ffc37038acf9a4c95ae79f42775f2dd362f0a51f640b40e92f3402e7c162b3c20c01241038016d5786f51cfd02ca203f36389f83f1e9c551a36e6cd2bf2caf9f7e71bf937038f4567a77cff99626d05f8793482ee0d973c7f8d213420244710b4e70638bc7d031d669fd05a028d7b686956dd23115a125e7f3f7eeba3750f1bf309fe9515c19303ec69863dfc84d9a61ee5ce2040e078b46096927815d9b26d6a8c79df062c74a503ad65c2085d87c1de63c1b67b9fc9f67d7bd5e46bd94c388fe8dc85d00bff57c00377eedb3bdb67415acd542cde19c9d40094635be6ab36140adeab42e96b92cfb80219ffff03d1dca78a8a141a285a6bdf05d94f21e002557d73e68b191e11f7cd311f3ede2a031acac9d38ca38ff98da8d0f9a2a8789f968c4e379ad3ff7cd8bb5b5f9da385650387ecff14def0afa74183e00da5efda82df545c98b7e859481aef8f24a2cdc8220343625f41f7af9f1037d70e983a3b8d0db1adf239660b16c3d73e730a4f634d0203f6a481afa4faab91d104cdec398b537be074a99936e5a3491542f42bb141e10803cb3437e61f8ec2cfd51f006df4f9f2fce562b234ace6a91cf2b9835ff172be850352e759d1c87ae5e9391fefc457ed50d3814da8e96d60998afbebe028b8aba2e403bc4e03f127d3dbeca9755bf0b0e5314d9bde3db19a70fceb0debe574cd5dec2503cb08fc461bd03abb1fe6df856afb879627316c3007f98dc20e8f12f5d3122cd003b233519fbb55d2567ec4d592e2ffe047ac13f38b495ca383c356d5edbb8063690356f8eb2f637eb2b6da519d6ea20a5b3183005fa5d08c0b7572a9425fa6d78845038cf30f717edd9258c60cb2ed43e6c814649e63819111d0263f14722a4f871bc5030b98e0d643c86f511c800e28512adb9766b8b7ec83a1eb9f05063de4bae18019037e3b21f100b7d02806a88460dce0a02a1aede13252b273bdb2521a4e898200b203f2ee256b3e1dc4a7c7596e54ebf4a0eb8c2a85feab0603a2577db8d3abf074c3036443af6e39f39a0d6baf98a77c15bc9d2cad412dcc8ef690a32fb3aea772bed9034b026b8214597b35919c7f9bd9780d32fd2daa2d558fbc69adf027c74c9fd068033e0cbcc44291723f9a212e32fa81f7a09908d83c20eefd3c58aa4edeb84658d8033269463fca63ebe488bf9a87a1db205958c54cfbf43c24d9503311ee0fb53d4b0392890d961f6c0ca76151b99a3aa4bed0ac198ccfe387380daed62bc854cc0ed103cae85bceefac339e981320952d8e1854f73a931df8837c8b3e1865923e936532032a5ea55e51c8ff67c6a3f13c521ad8d267f4dd0cab13ab2789ef223df2a4d55005581d0248c6c547ec55378e2c0a61680666e229104d5132fdf9ee31729e64020c014705b19fa46bd50005581d022a37715299ed85873b9ceaa8a2578309efe1ff9d3d4474247d96e7e2084801ff973cafa800000219024003d6f4261e3eb772dbc22561849021fbc514fa158bdcf3294d8ccfd5e00bcf520503f9073dc0c6318b7a3d4139205bcf2dfc19e52d92f786eb0b7daf5150a74dd725021922990353d71e82ce260a30018c76a6c9a39a8e6c5c37c78dbbe246c4af86c5bf9b6ce6033383bc0b2d03bcb5906a7f396ee41a2a5c36507139118801a24359015558cafb03b8a5aa6fd604059e03516b4e1e0cc6f4fc2240893e74d3a8966043bd8961a164031bb390335989c555bfc3182e0476da76d59a5175fba90e61f4d2bdf614c8a6e703c11220ecf272dbd7ed4459cb7674ce12265841898b5fbe2016a8457cb34c326c03c5cc4dec75d39dfc41755fe91a224265d9e5a22d75f99eeea6a8e9c85468e86903c26a8ee1eeb84c898a479372826a7643d2da4dbda3aec9641465611d590039a203e7753a5102330e4bd9c397090d2cba9105d869ac7ca7362de91bfd8250ceb14103d743b3c0c2aac773abe7e6ba4d1e3f4cab1cfe7db51d8e1e3272c57071f4304703b4adc0b2abdcbab1ed7259159d8dba7404bb90b1ec7167f6f2d4e43507314419036efa33540d8af22ada129bd6e19c8566075088a3aa6e613a4ed39378eaf842e403a791e80119ee3297e129e171432558f5cd5dce6cdb079d123646a012d7aa7b59037d3c36e13194d7e8e3242ae072796a3092458f296a90f3424dc469f932c3b92c03293059d7f335b0215c61193acb55440962ccfc8a44178ea82c4ba8fcc7c082b80219ffff03215151cadcfa8461c6ffb721016f5e1841b26bd20093c2285667ee1a2c835a7003e2f94aa7aada4eac688ab1468a0e76028ebd508f0c5ab7f7fa208c1900b9ccc70308c5a4259db46efdf4f4c5e5881fc5f183d1375aa744815dd56e66675d84133803e7134df08bdf9dae09e63915e3e7a3829c48ddf5868e2aff91249b14e66c5374039685124197604d3b54ac35482805264b69dc564bffa18c9ba6ea1c78d4019dd80319f4c967aca3a82efad1f8b56b53e108ff25374eae7600738e9c037464c8b316037356b0e8a7a3026197d2d03f11ee324f39d2eb3d4f2b3927fedc7b74293bef3f038f9ec361d5b1a6e87ceebd85118772733e207c8f012c8d73bc979acbf587dba603983d177bab22d5da014ab752200b7143ec636c96a92c26db7c51c3f0e1d29c6b03196af95e9d505162163089eecfb5f79da1d3afb9b70ea9ec66e2febe4b037feb032c3ff131a66d8aaae1b63e9222c53f51b1463f004bd1a13c730f889aaff8c964038e0cb7d3461eecacadb4fdaafc6adc39aa323dfde36eb857db249943990467b5036ae293a1b476ad4710c1e3b322ba6fa130111f8292a7578372c2d0895b8962c803970b99093937dbdb65b1bbfcec734f02993474eda5944f37ffd3c17f7f66e6be0219ffff03c416d48e7f3140778c6721437cff0075abcb18241c7a39e2c2c8e48290b9ca920379c2eb811d777888ec28c959be06d36f3865c4d2911b2b7fb3f7489d0c0698790362e0c01a7c9ac342ef5452e5ffb086067b26b0c1f26bbf8c4a827e94632f64c9036a57017047137b45f8170bcbd25ec0c37a62eda63e203342c36277d15a07fa6003268cc63bc3aa8f9ae41f088a286dde6bad38b9fd4355730c48dd0af45f905e24034928871f92cf6bcbf7a57728558549b2c21b79c6108d7961b1e9845e6a3c8f4003f6c0a1eec7ccd3df5cf1d836c60c8bd98271796a5385f1eed73822b2fca6a9140361d754ebd451cb85f21c738fb583c375b2e973568464354c846f82b342a8d8dc030356de2f217e8d3f5eb624cac231b69f64780159d74cc15ed19deaab698078c403ef9f6af60f28590fd0d268dbc470a085ee5918851f43a6184fce09a1b77921e70219ffff03b1d87303aaed9bb71bceb48c31cd23010095a95c4911a68fec063ae654c781d60307d0ee4fe250030e124f0b2e0b445daca1b533cfc64f7df3af8cdfb515d9712803d5bf153ad911fc391f4603d4d1cb07cc7f4eb94a9c355c1be5b41e25112e76de0381b723b8407bd8f11bdee313379a9affc5abbd4bd797bfe2100f96e098558bb1035b8509dd3007587a15cb92ae6cadfe994c3788b7b715b71a717cb6afe26bac3503c9170c41846c880e6b215223c6c88eb214df88c646c5b46cbf1981e2f481a4ea03a879d359b8c140ada36ae63352f961b9a70656e89cca7b1e6b5521ee2f5351710219ffff039fd6b523cb251d7a170bd581ad2e9e258e3e62160ca3badacfab6434ee18798f03466346b08dc08b429818c54672c4323d1c9d157a4f519b76b6d0f955eb08c86b037a22764d35894484876ba73ca1e6c2d03bb86ca89b34397e227cd6a3a8798fe2038cb78be7dd175e1ca67c2ba8e025f660e1e99ecb1b7be03fa1aef4523fe9c9dc03656e6cb353651309affab0995d8dfec147e346d0cf13d7abb9391aeec38b6307033ed40d526fec01ca7bd213ccd7d8a62055831a04770726711e8dddfb880d86d803f2b8056eef9243343e36c8d82f36b27646da99817f5d5e82ff31d58b9bf0be350219ffff03df3592210cdaad452cc40f0e243d920afbb033fcd9490f2f6cc926dea05cf14b037816658c639149ab3eebb38f6ac22cf38752b7e4c6ddde32a6c3246707d80fd903f5ac862b401525c3cfdad2a29ce5294bbddd3ed338b6626fc8f9c44fb1cf8d90034be77e83d32bf9f4fe2880b66ad8908983dfc1dec078d9e31d227d786b8cf148039f0e8639747e8ab71adc53e8b07f01206b5900c0272eae6594d37c750768abea03c390f1e47a610cebad6c27e089e33465b8fccbf3ad3b3b7e2a07457d8ba632c303cdd9b0d1509c100dbb97d416ec662e3077cddbd5c47aef42ba982f7c0cd4769703c4243f4af1fbfd6a87252ccd6ffc33385970802e52996d723483078a268f13a803a1ecedf8165b6b65ed1b9994f5d3c514620fb494dc86171e6194e33465eb87cc03088ec9d7bf047917fdc5049955ba7851e13907e685f43c2df16e24591c9dde5a03ed094da69c124333f8345d6a2d61fd3009f13d18af68fafe7b821221dc796e5c0337a03266a9bdc253aaeaba6b66a4a347f18829c6a23b248db62eb21389bae960039fb3a610d2830303b2fe3efc4c420bb5d26dfb8f99f1ed9ab0df274e2ce53a5e038b0e0c639fcf93b1ef48bc870cf1c2db6dc710a364386b61e55e29ccda4d584503e41f647481abd0f1ddcf9362df2eaa21515cc62a98184a3e7e165189b69b9d3d034ca3128be96a17ef0e09f3d1a4df42be6cd707c193b33d55133225873d9426840355ff618203c69889c9bac81fe7c29259b911a8bfab94d550893576bd8eba8a97032cf9f0cf6d3a8d5299973d08bc171ff6c5374b9549019a33b69f1ba8224fb9cd03b0dedf0a2c83674687d5d90a1f5f176104ff7036cd01ff5c0125d954c047f974035ae9ad796ba4067225e90374539826e4e4388c926dedf59477cc5eed7dc593490316ced7b33a98ee1c651fa2fcbe1a0374604f7c909b84f062b589aec7f44f11d703f2867144ee588e4d55ea4761c23e21c783ee2cc00c68975aefd48df592f2b8da03f9164129351a46f77d025bc0509050e9eea8cd308710920658ca1f3063b1cf6803052017d82fb6f8a4d85a9cdd48f3f0fed849d1cd1aeba0fc2884557428a4ede403ee4b6adbf3362ff64d07641f516d8ec746b044f977bb448911c4e0af555365e503cff85de233b523d9d1457c82daa68ba239ab77f439892acd0a2068bf97ebdbff0385d0525a0a0034c5bd08935cc25dd3f51edca9ebb6fccd04f02db7502705cdbc05581e03e46afb6f2a29b471fb70f204efc85a293e3aafe620b6d7184495207d900401037e5088d9117486b2f086a8ece3ba2d74100ca7b7abf188cb800645b99b0b36de03546360b87e0fdc3a10341394d32f120453ae1f8f3d21372cca6361cef94f473305581e03be7b9938054f9dbc0e1be10d1cb4775560fc4652f94cec4c823790c2100402038bc989e7c0e91f054a8fb35ab7646b78f1dc692e67dee5cbf05a5e390a5b96eb031355d7d9562a49fdb90376af866d3927f7b97c55954ccf4909fe488a99284aef05581e03ea58b11a2d9b9076492a1cf0a1f28ec2abc3ad5597945d915728e2485007011bffffffffffffffff05581e0352bc53f1d7c8c3f1070c02f6f0313e53ab53b786b0dfc5fa833a09db20040105581e03ad08a72a553101e869eaa555d3ee8dc641c102d9db971a2c88791db4e00c05470178bdc1aac140045917fc608060405260043610610113575f3560e01c8063751039fc1161009d578063a9059cbb11610062578063a9059cbb146102ee578063bf474bed1461030d578063c876d0b914610322578063c9567bf91461033b578063dd62ed3e1461034f575f80fd5b8063751039fc1461025e5780637d1db4a5146102725780638da5cb5b146102875780638f9a55c0146102ad57806395d89b41146102c2575f80fd5b806323b872dd116100e357806323b872dd146101c6578063313ce567146101e557806351bc3c851461020057806370a0823114610216578063715018a61461024a575f80fd5b806306fdde031461011e578063095ea7b3146101605780630faee56f1461018f57806318160ddd146101b2575f80fd5b3661011a57005b5f80fd5b348015610129575f80fd5b506040805180820190915260088152675a7964696f20414960c01b60208201525b60405161015791906113ef565b60405180910390f35b34801561016b575f80fd5b5061017f61017a366004611451565b610393565b6040519015158152602001610157565b34801561019a575f80fd5b506101a460125481565b604051908152602001610157565b3480156101bd575f80fd5b506101a46103a9565b3480156101d1575f80fd5b5061017f6101e036600461147b565b6103c9565b3480156101f0575f80fd5b5060405160098152602001610157565b34801561020b575f80fd5b50610214610430565b005b348015610221575f80fd5b506101a46102303660046114b9565b6001600160a01b03165f9081526001602052604090205490565b348015610255575f80fd5b50610214610486565b348015610269575f80fd5b50610214610500565b34801561027d575f80fd5b506101a4600f5481565b348015610292575f80fd5b505f546040516001600160a01b039091168152602001610157565b3480156102b8575f80fd5b506101a460105481565b3480156102cd575f80fd5b506040805180820190915260048152635a44414960e01b602082015261014a565b3480156102f9575f80fd5b5061017f610308366004611451565b6105bb565b348015610318575f80fd5b506101a460115481565b34801561032d575f80fd5b5060065461017f9060ff1681565b348015610346575f80fd5b506102146105c7565b34801561035a575f80fd5b506101a46103693660046114d4565b6001600160a01b039182165f90815260026020908152604080832093909416825291909152205490565b5f61039f338484610970565b5060015b92915050565b5f6103b66009600a6115ff565b6103c4906305f5e10061160d565b905090565b5f6103d5848484610a93565b61042684336104218560405180606001604052806028815260200161179f602891396001600160a01b038a165f9081526002602090815260408083203384529091529020549190611067565b610970565b5060019392505050565b60065461010090046001600160a01b0316336001600160a01b031614610454575f80fd5b305f908152600160205260409020548015610472576104728161109f565b478015610482576104828161120f565b5050565b5f546001600160a01b031633146104b85760405162461bcd60e51b81526004016104af90611624565b60405180910390fd5b5f80546040516001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a35f80546001600160a01b0319169055565b5f546001600160a01b031633146105295760405162461bcd60e51b81526004016104af90611624565b6105356009600a6115ff565b610543906305f5e10061160d565b600f556105526009600a6115ff565b610560906305f5e10061160d565b6010556006805460ff191690557f947f344d56e1e8c70dc492fb94c4ddddd490c016aab685f5e7e47b2e85cb44cf61059a6009600a6115ff565b6105a8906305f5e10061160d565b60405190815260200160405180910390a1565b5f61039f338484610a93565b5f546001600160a01b031633146105f05760405162461bcd60e51b81526004016104af90611624565b601454600160a01b900460ff161561064a5760405162461bcd60e51b815260206004820152601760248201527f74726164696e6720697320616c7265616479206f70656e00000000000000000060448201526064016104af565b601380546001600160a01b031916737a250d5630b4cf539739df2c5dacb4c659f2488d9081179091556106939030906106856009600a6115ff565b610421906305f5e10061160d565b60135f9054906101000a90046001600160a01b03166001600160a01b031663c45a01556040518163ffffffff1660e01b8152600401602060405180830381865afa1580156106e3573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107079190611659565b6001600160a01b031663c9c653963060135f9054906101000a90046001600160a01b03166001600160a01b031663ad5c46486040518163ffffffff1660e01b8152600401602060405180830381865afa158015610766573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061078a9190611659565b6040516001600160e01b031960e085901b1681526001600160a01b039283166004820152911660248201526044016020604051808303815f875af11580156107d4573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107f89190611659565b601480546001600160a01b039283166001600160a01b03199091161790556013541663f305d719473061083f816001600160a01b03165f9081526001602052604090205490565b5f806108525f546001600160a01b031690565b60405160e088901b6001600160e01b03191681526001600160a01b03958616600482015260248101949094526044840192909252606483015290911660848201524260a482015260c40160606040518083038185885af11580156108b8573d5f803e3d5ffd5b50505050506040513d601f19601f820116820180604052508101906108dd9190611674565b505060145460135460405163095ea7b360e01b81526001600160a01b0391821660048201525f1960248201529116915063095ea7b3906044016020604051808303815f875af1158015610932573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610956919061169f565b506014805462ff00ff60a01b19166201000160a01b179055565b6001600160a01b0383166109d25760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b60648201526084016104af565b6001600160a01b038216610a335760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b60648201526084016104af565b6001600160a01b038381165f8181526002602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b6001600160a01b038316610af75760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b60648201526084016104af565b6001600160a01b038216610b595760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b60648201526084016104af565b5f8111610bba5760405162461bcd60e51b815260206004820152602960248201527f5472616e7366657220616d6f756e74206d7573742062652067726561746572206044820152687468616e207a65726f60b81b60648201526084016104af565b5f80546001600160a01b03858116911614801590610be557505f546001600160a01b03848116911614155b15610f2a57610c166064610c10600b54600e5411610c0557600754610c09565b6009545b859061124a565b906112cf565b60065490915060ff1615610cfc576013546001600160a01b03848116911614801590610c5057506014546001600160a01b03848116911614155b15610cfc57325f908152600560205260409020544311610cea5760405162461bcd60e51b815260206004820152604960248201527f5f7472616e736665723a3a205472616e736665722044656c617920656e61626c60448201527f65642e20204f6e6c79206f6e652070757263686173652070657220626c6f636b6064820152681030b63637bbb2b21760b91b608482015260a4016104af565b325f9081526005602052604090204390555b6014546001600160a01b038581169116148015610d2757506013546001600160a01b03848116911614155b8015610d4b57506001600160a01b0383165f9081526003602052604090205460ff16155b15610e3157600f54821115610da25760405162461bcd60e51b815260206004820152601960248201527f4578636565647320746865205f6d61785478416d6f756e742e0000000000000060448201526064016104af565b60105482610dc4856001600160a01b03165f9081526001602052604090205490565b610dce91906116be565b1115610e1c5760405162461bcd60e51b815260206004820152601a60248201527f4578636565647320746865206d617857616c6c657453697a652e00000000000060448201526064016104af565b600e8054905f610e2b836116d1565b91905055505b6014546001600160a01b038481169116148015610e5757506001600160a01b0384163014155b15610e8457610e816064610c10600c54600e5411610e7757600854610c09565b600a54859061124a565b90505b305f90815260016020526040902054601454600160a81b900460ff16158015610eba57506014546001600160a01b038581169116145b8015610ecf5750601454600160b01b900460ff165b8015610edc575060115481115b8015610eeb5750600d54600e54115b15610f2857610f0d610f0884610f0384601254611310565b611310565b61109f565b4766b1a2bc2ec50000811115610f2657610f264761120f565b505b505b8015610fa257305f90815260016020526040902054610f499082611324565b305f81815260016020526040908190209290925590516001600160a01b038616907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90610f999085815260200190565b60405180910390a35b6001600160a01b0384165f90815260016020526040902054610fc49083611382565b6001600160a01b0385165f90815260016020526040902055611007610fe98383611382565b6001600160a01b0385165f9081526001602052604090205490611324565b6001600160a01b038085165f8181526001602052604090209290925585167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef6110508585611382565b60405190815260200160405180910390a350505050565b5f818484111561108a5760405162461bcd60e51b81526004016104af91906113ef565b505f61109684866116e9565b95945050505050565b6014805460ff60a81b1916600160a81b1790556040805160028082526060820183525f9260208301908036833701905050905030815f815181106110e5576110e56116fc565b6001600160a01b03928316602091820292909201810191909152601354604080516315ab88c960e31b81529051919093169263ad5c46489260048083019391928290030181865afa15801561113c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111609190611659565b81600181518110611173576111736116fc565b6001600160a01b0392831660209182029290920101526013546111999130911684610970565b60135460405163791ac94760e01b81526001600160a01b039091169063791ac947906111d19085905f90869030904290600401611710565b5f604051808303815f87803b1580156111e8575f80fd5b505af11580156111fa573d5f803e3d5ffd5b50506014805460ff60a81b1916905550505050565b6006546040516101009091046001600160a01b0316906108fc8315029083905f818181858888f19350505050158015610482573d5f803e3d5ffd5b5f825f0361125957505f6103a3565b5f611264838561160d565b905082611271858361177f565b146112c85760405162461bcd60e51b815260206004820152602160248201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6044820152607760f81b60648201526084016104af565b9392505050565b5f6112c883836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f0000000000008152506113c3565b5f81831161131e57826112c8565b50919050565b5f8061133083856116be565b9050838110156112c85760405162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f77000000000060448201526064016104af565b5f6112c883836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250611067565b5f81836113e35760405162461bcd60e51b81526004016104af91906113ef565b505f611096848661177f565b5f6020808352835180828501525f5b8181101561141a578581018301518582016040015282016113fe565b505f604082860101526040601f19601f8301168501019250505092915050565b6001600160a01b038116811461144e575f80fd5b50565b5f8060408385031215611462575f80fd5b823561146d8161143a565b946020939093013593505050565b5f805f6060848603121561148d575f80fd5b83356114988161143a565b925060208401356114a88161143a565b929592945050506040919091013590565b5f602082840312156114c9575f80fd5b81356112c88161143a565b5f80604083850312156114e5575f80fd5b82356114f08161143a565b915060208301356115008161143a565b809150509250929050565b634e487b7160e01b5f52601160045260245ffd5b600181815b8085111561155957815f190482111561153f5761153f61150b565b8085161561154c57918102915b93841c9390800290611524565b509250929050565b5f8261156f575060016103a3565b8161157b57505f6103a3565b8160018114611591576002811461159b576115b7565b60019150506103a3565b60ff8411156115ac576115ac61150b565b50506001821b6103a3565b5060208310610133831016604e8410600b84101617156115da575081810a6103a3565b6115e4838361151f565b805f19048211156115f7576115f761150b565b029392505050565b5f6112c860ff841683611561565b80820281158282048414176103a3576103a361150b565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b5f60208284031215611669575f80fd5b81516112c88161143a565b5f805f60608486031215611686575f80fd5b8351925060208401519150604084015190509250925092565b5f602082840312156116af575f80fd5b815180151581146112c8575f80fd5b808201808211156103a3576103a361150b565b5f600182016116e2576116e261150b565b5060010190565b818103818111156103a3576103a361150b565b634e487b7160e01b5f52603260045260245ffd5b5f60a082018783526020878185015260a0604085015281875180845260c08601915082890193505f5b8181101561175e5784516001600160a01b031683529383019391830191600101611739565b50506001600160a01b03969096166060850152505050608001529392505050565b5f8261179957634e487b7160e01b5f52601260045260245ffd5b50049056fe45524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e6365a2646970667358221220fabb245980ce7b674f8c8938e6a9547420e1985f631d17befd4e60b6ad4ef03964736f6c63430008140033032e9a1bdb7719f32ebe6e8748056096c302e0117ebc7d8a695d3c69ec190c94a4005820033deaaae292505f99da7730d679d66dd76ee315a572765c6b28477acbef84205820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581f02581cebdbe182d4ece6688321f0b6da020dff6b0c685984e2b533880f40895820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581f02b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db94123021830005820037296de936eb2802bae692c3ad184fb80ac4733c4763fb05c067aa104af4a104701a0ab99c148670219018400582003643924d70380e8c3b27dbb7ab991599d58393812896fce01b82ea17b0358505820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00582003b584627eef1edb923dd3a5730dcce1f8a65d4b0a3f0f498764366473bd3dd0460bf1d763426a00582003dc1e32d4b868a9e50e514d63a1a3eca30cca8aca615c5fcb6e76aff3941b104701c0467370689b02190121032eb736af20c66853b0524fa3afed1b5087d4db0a1f8dadeafaa35a66527298ce03012b76fc2abd9f42db1bfc99f3b77279886f78830f4f07189ff0f2e6b7b64509035df46df06f8f5bff1a2799ec89f3d27c5046d439c4571b046701e0ce90f8e52e03a5d1e37bc4c238dbddf3583b32bb2bcedca6da92d63419e564218330c41a9d8503136b9f41d0a75818fce9eee2f181fa25f7637677ad1b6f3607f2e0e70fe728f703dc5492c96e23328e3c7c6f01c8df680731771f94e16cc90f5a2526a9010bd241005820020ff2aeb056ea80f584f0f596a56da0db1c533b5770e5f58b5d85a81872d3214711c37937e08000034ba899bcb365ee327c8ff95b40db4c5b0554d304e76cc3f0cfeb8171f0acc15103b56094dc76eebc806678d38a7d72e95c1a316b34199cb574a00e003b29d39f0d005820033a29332cc87cee6bb85ec6c4d48fa62a05e0e689d38915df8e8711fec6f6d04701d57eaddeea7903e9d858e542f01f0cc9a78fe3c7d155ba2eb1ab3bdfa228febe0cbf35f9d236000058200345f7402f34e0165cc412d391d9a6f3650cc8a9ecca726e8325318ffc9b4a205820fffffffffffffffffffffffffffffffffffffffffffffffffffdde4d9d227fff005820033a52a2e3bf74152f03427aaa45125fa9d276601ae28f16b4f256e23e4411f05820fffffffffffffffffffffffffffffffffffffffffffffffffffdaed140fed6c900582003008b69b457c61a29303e67f4eb817a578dfb867f0eb8e306bce681219d22405820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005820030c54fed9cbfaf5d6f13c3cd9aadd836c032fae0b7297b466d50befccb91aa05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0219447103a6d4faca4f3fa2cb2c42ba6b209e87b2dcce1ede3b2020f8ec09364e73bf092003c5302a75aea5ed51be684447bafaf15e1bbd6b68e84e4eeb939c45c5da08e6550219bfff03fcc803681c88970cb11e9491d7e4704b0d7d7aa4f30edd837018edb9e8b7974a00582002cb45a65ac413176a5293c5dcc90cf019a7e925c7b4dfea6fa65bd0ffd552a2464b6d0797f9fd037ab0c4b1c17c34977d9af54350635a3f1a3fa0ff837d6d211d24a7ab6120e2d303d881e51d2a3eb57f1d64063521cd848ef9090eb55d20f6031751ffcf26dfd9f5005820021ba19dc1dd14b9e06c8781534275493317db4006d593ec78d3893979430ad35820ffffffffffffffffffffffffffffffffffffffffffffffffffffc914e0f9a02d033f4987be9456d56871914d690843fb7e31e165290b44ef1737612c9f5e63c0e603f25d24915b46dfa25f6cd54056caf222533d999c5e1103feb1fc60ee6ac5af1603fff9818874c16d20b922dc8e9d94bfd5e4f0ec644571582a901c30f5b1d812b5035708bcff448dba53f838fc064ffe210269d0db44b8e57acdcd7d31afad4a664e035cfccc1947fe9ca4d21a85d8aa92601d7478432dbce2f2623097d5692dbb235d00582003dd580b09fc78b641fed62f71d95358bfc448b769be26f97308f55ac78f0d705820fffffffffffffffffffffffffffffffffffffffffffffffffffe82f2c4fc936600582003a449eb1a2da34c7e8b4afc3e53beabbf30b220bbd1ba3783194d707ce7c3b044012e343702188803fa06a150af961eb306023fe56cebfb3cbdd83faa0dfa374bc07b895dc4f8b93603d063c607b5ca7772092eccb711cbecb999f6280ed84dd14d31b1fe84e5d9ff590359ecf023d1f4253e2a66415f209d7c6a67c6327d03490806aff85ee070d1968d030663d512bb073f73fa945cb7ed99c268e607c2a83afed42cfaaa60793fbf197803bdc8374b941fc71ad48960781eb2ff806ec903c0b307fc21290a269e65c88e0403eb57fbd9e4894fe8eee12f15e7e6a7dd7a4b11cbac681aa07428762a641b6cc90219ffff03adb353379cf9f856bf0cdd9a8444c5559651e9a9568ea91de2607fb1b841972600582003b2a13ac4575f81a191570ca0fd326662aa0c579a23ef07ae9ad92d5a14f2305820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00582003cc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c68046b5e620f4800002194040031621d5683860ce7af2b9a8f0db55ae1004926a3ddeac35f8179b9cb08b4b5ad500582002fdc970a2c954cbd9cac4b3dee87d3d5e28dfc9aa628581ce61b29abd6a13535820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00582002a7ba5548113732fc6857f152ceb343dff7a56b40eb54168639003b931e6aee5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005820020afa2884108caacaa604f3a4b1c026c537454cd211d5e6e196134963a61a48465343947ae07603f3669d5a9877b0f0e2f6f472f8fed2a84efbf6b50be27a8089a0ee9d533ee417037c23c104e325371f5295886d89d86ff5537636b1a384b903c735b5421da912930058200214845ccd09051265cabfa666bbb0983955c377677c980b566c05854ebda2b546d08b1108e3c20396d127107e2d18df9282865aee758bce86dfedc6bbfc2e404466f36ff8eb4ebe033bcdc1e6636d1e90ce729e75fbc776c1296bfcb6de57e755fe58167dcb5da67203a4af2c85e40dd23e183a0fd1b906db0bbbf698eed40aa98f0da677426e01ad3203060640284caa2af263844bddc8d8bae0c7da607530e33883345c2ee2b26cd6a903f8aea964a15ce2ff22be121be942df099b810503fb7e906d3d912c873e38048200582002250a8d3e0beca5a210e28513b383dfdaba031209252ba1abb8c2f45982e37344012e3435038507f95e571f1c5a21e5eb4ad1ffba56544b6e854b304c0b603f58830685110b0219ffff03a9fe5356b92c4f2f75402dc5efdb22611131143ca8c0ed55b7fec4f3d65f286603bc768027381098e1aa78bd68ced7a5a19901967411e1f37861337cd1989dde930308860eb562c7d32cd98b4806d4a838c8fb728ecb760cee5c4e38396f08ca9da7037c09ec3fdbca73eba19660211c5e4f054c0855631c485c2cce9335cd3e272acd03900e5f5c1bbb1a833fdf50a4cdb694a76bfd9336a7e9f4b35523022c762e41ed0328c84670fbca73ea7a4a082e89242cb4a572c7ebb817170074df470fe56f619703a80c916a5497f6e34928087211b925696d3929e8a3d5b1c4344d1a85e0b69e8e032198b566d6dc30d6376c408249bdcfd9637d3486cca59986434bbf66378f082600582002de8ffda797e3de9c05e8fc57b3bf0ec28a930d40b0d285d93c06501cf6a090547a250d5630b4cf539739df2c5dacb4c659f2488d032f9b96255409c98217aa93e564f16b8826f00ecdfbeeb646d7bb75d1ad339e8603b3031686b66e62da21f1ae8918b9c6d56ebe9867730fe92e3ebd2c8a7f9e0cc6005820025905e5d91b29ae58ab1639725131b91ac3292e8ea575a2748b4812b4e41f6147018bf0371c2c5903e15aa9b31f0606afb3d8156a855e5677e54a94d43da4fecabb7815d6a0633d60036bb3a6401d2b8110a788fde6505ba6309f1bdf1629f1324799e32d627d616e6c014102035076d3d2724e2a05c0b79cb5d2b587f062af5dc4853edec5845cbd23ab3fa7c800582003206663ab66597cb8c43f3d5842f783638bf03eefb1da8e601574d37e747830470581b77f66e00000582003e3fdade540c47b00ad7d42b0eb73b082bbd06c915f74e4b0afa5d223837f3047011e4b76c252fa00582003865b01e3623aef98ed3b8e168ecd858969fe6677fa05fda65f424b5129d71046581d0f000e4100582003672453513140c71c3ec7e580e55beeef49b4fcd93598d330b62e50783af180472abfbcea99faf900582003209fce85f2387e995f88614873e4912fe3614ed7d6edafe01b3193a18b8370460c1c55657d43021985c0005820021540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af410503f7fbe063205d32427c30579f83642876def3afd2b894377ab22e23683488afca0219ffff03fdb2f8dbe707222b891f6ba7647c81ba2de38018a8ef28ecaf0787d39d111d3403aefd71484378f910465fe3052755e494e480812dd3580f67ee4c509bcf3e4c170058200219031e838b32c426f108a641482e188d703f0f5bbbf0a7688b21794bea4cc25820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03366914fe7402daec0c4d451e3ee126bd192fc7c3e3b751244325b1e60fa466070058200239cd439f121dac378d210574545cc78c432dc31550c7ccde2748533d40ff6344265e2262031dd7ff23121c7d58df21c4046c6a49c2b18bd6908adc24e06b541ce5082c9a8f03fb3b03daf6042e4b3f9f650298559b349f3f6ed6438b1b585660a128f6a1dc3200582002496e4d7297e0f840d838f0719c8eb46740450f6270da99726357c7a43a983c5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0387cd7a86e266db7bb2067f637b78b5f2aa32deccd17bc05e936a2ef6cb889b57033b7c4063ba7591ee0a39e17bf281c700e924bfefd9596f44b9f4cee75901c8b6005820024fbdc3ac2e3df30aee772dbed6092f873eb66696ee517bf1bcecb2fbb4ff3c5820ffffffffffffffffffffffffffffffffffffffffffffffffffff2530ef3c9f0b033892562f1ffc0e79c782f4c6a5eb12cb15f70872add76048eda5eb5df08db0720308b601d9cdaee90591f15e8f0b7eeb33a9f96010c85c6d05fa307b33265f1a970314f4f0fd94243f77bf37bc15d478ade52f50a74fcd6a277e733782a9b5a96fe2005820039e4ced25866b3c5e41c122849ae9bf3471bf5be80e27d1877e54a8e39e6a105820fffffffffffffffffffffffffffffffffffffffffffffffffffb655998199eb700582003d398d6239c8afd00fb18e63dd471952b090293e8aceb6b98de1874c7f1a6b0462fdd6181d2f40058200372b8338b562eca1dd7279d5f07142e395ab0720d40d6e241d72afae4c9b650468af2abc28b160218d00058200273001a5bee02924ce048678a9d4b655de5969942503dd9a34a040b3f644ed14703d530ec238af7037d1c4eb68a2e90cfef5490eaf572d4c4fbe4b3e206bed26a27ed7cd906ae51960219ffdf03257455b0469086c626d6f0400d29480f8c9f365a4e83c08d2195e797148eb6910392829198c5e270a756c8ba4498d4b90cff5a43120fdfd9743858830cb2fae0d00303d45eff9f42682de3668969d3d58310153b9cb93349cbc388c1e522b65bdc91034121557e2af943782bb05c7f667cfc5c1891b1729376bc7a7b390247ac96492a0058200212914631d52cc0febbc8823c5a8220c9a21e0af51039bb0c7ec01dc88a712944012e34360365cad8be14ebe6e005f53c982fa429f998c59cc6db45d16cdd0c35e399b2544303c2778431b33b3bede19824c87a1d113e13cccd4fd4e9b98fc69de9b2dca2ada903265a704ee1b4211db47d70f355439ffd25948b54b170537e8a3111de05c15dc700582002acae78e0fe171b7d0d1168ce676329146cb1e11586f9dad3d0d649a05e4bf4464035f815892e00582002f7504ecf3a0de04ff6df3ace726e18ec5a6d0c0f148d7d55a973a060f4e745466dfde0631b9b0058200256ca619e7c1eff29c8f1304e1d85e86187eabd3cdd5796d382b8541e0e24bc44012e343300582003b4a454dc3493923482f07822329ed19e8244eff582cc204f8554c3620c3fd04201b203221cc7a6fa87d30414e9f1509d92d849662a96f26734c126aba3e4964ff6877a01410a005820034aa1ce9fdc12d538db10f82332d81a95772e2cfd4ef76aba162630ac3c6d405820ffffffffffffffffffffffffffffffffffffffffffffffffffff5b73a6e5e02700582003d03c7d82aa7241953d458d631c40d5253fe671a336906c75fa69246fdce0e05820ffffffffffffffffffffffffffffffffffffffffffffffffffffac745826451a02191980030d4f1391475dad081bf604cdd83e461e24f26acfe274356bb687b8ebd5c29655037b996d3d81931c7f48b149f26bac11b515b23dc4a97538220c2368d6d8e9eecf03b5c7d42851596d007fadf8b31301f1e099798956505b6d60ad00fa4b14c9b2cc037d6cd29a70918eb5b02b46595ebe64d7ef4614b5cd03dffc4c360876884dcee70219feff036336f442763e83265b7062cc92d87f0c1f87738aa0139099f2cb6b831be6f620039134dafefdeaccf652f477e8bcd0a4f91e2f3156ab9b2fd1a7859c324abe490300582002c20db3f959450f741ba3ef20cfb24d16cae2ac1f1f90f7a0636c1a4debed6d4701fdba51dffa96039dcbe52cc885ce8cdf8eee3fd54bc70d14bd9b88c148251abd5fe8ebf0f09f430392783a56c4de1de6354e84436f796e5f9a037030bf404e9634040af083de3b7303580ccaae9b9c735c666bae0c0d19049bd6e9b8a6cc8fdb39dd26d0ee369712fe00582003a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a80410500582003b25a6a9cf8d8c642adc8dd2ab93a86967be708f854f059cf110638072594705820fffffffffffffffffffffffffffffffffffffffffffffffffffe09347e8dff74005820030a55596ebd9f3fdb116c4264d608b2af816628d6224c3cbb8173c5ab0b318044012e343500582003bfa2c111124164089039b1eb1ff7fe379cd061d62e1ffb566ae8fa8597dd405820ffffffffffffffffffffffffffffffffffffffffffffffffffffc347b7f89fa3021984600304968147ddd214db1f19dc5ecf5e83716c60c0b5d17ecd22fe309d46d574c53703cb6dc1811dd11a8b27a8ae8683265e811019ad02e5af111ae3880d08484a03f000582002fa80cd85a4d732ac3163d3f32e62d965787158987a7101b078a3cfd0b084cb469eda25fff730034e5198a59bd9fc2a23c245f5f6699c8b3a561a36123f75900bc0c531bf30bc3003b477757b73f244c8b0c39ce5bea166eaf2bf148df7140de5f81ea16866e380de039616068f00f1d44ae45967d2cbabf9a9898dc639e3067d65e6a46344906ea050034011971aa62b633a531c695727fa8c692a7187d53ad8af0616bc0afd7d3a418100582003389a9561e9ffcf046a1652cd5c01c174469925e89428300cad875653a7e89046108bfa5e407c00582003d7b5282bd9a3661ae061feed1dbda4e52ab073b1f9285be6e155d9c38d4ec0570100014fadefe2a5eb1bf9e1d3dc4a9fc85daa5c5c660b02184403c1636b3cbf1e37fce574f5bc47c24de7cba2bdb8f1a21a6f2e41aeed3a2c52ef0219ffff03b8925cf8c50fccf360910ae843b0b44cee4ab7eaf6a1a89b8a81a053807b4b5703ce24de7aacb728d438ad90cc00ad8ed8c3c560083cab19c8d2365d770696ebb1031186939e76fa5b43e71347832fc419c30674c2ede04a9d46c7c73e983f243eac031fa29a280f6fa02b6d0bee57c81ef144f9e639c05c2580084f008e95e1a6d240034bf37a98e952c0561385ec982e32bda3a94ca8739b787c5e596de29e3b1a2db403d817b0955fc8ea4cdebece578c49818d1d8e502373177b1f93c6cf363b9696fe031f2bd62f2f00c8cc6becfe71fbf3712411dac644960ea13313d16dbbbf6e64a2033aa07a34f57a7b14a90ee9de1ece5b751bb1f7353cc726f19506cea4ca8f18b2039c986882bd3741668f81a81476d21842cdde382b25f2baca82405cf587d70e30037e96d58e373d56e37003c356f7635ddaaa7d4e56f2f34458ec667f33a2c0099303b2e459c16bfdf16efb4d6e4fc9032fb8128db368f3720efe6da4222b00d54e540335e0d78811fa83b549d510865498cc460eca2a95800c554f2cf71c62a1e5ed2f034335b010ae272034d47e002563c167f3de7470d7dd82a4a7850834c4260c03e4005820037dd6eb4d641ec1d44c19e430bec0dd23e5e58f2be599cc8b316ae74bcf38105820ffffffffffffffffffffffffffffffffffffffffffffffffffff31e7ff7f3d3200582003966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c704123005820037975c301132bbf6e020a379212eba432bf6560fe65766d638c3bcc243c1fb04649b4c4bafc02005820033185fabc38217e4634710db266b207250dfe969df5d2130e46069234a56c004701dfc0dc932cf9021980c802199fff0345c40afa684be0bd8dbad049f90ff7dd4c00fe9beef1ecb2291e179fb9cc13dd03cfcfe4e58998f8133049c961646417df57d839d7356740b0eb1263d0d050a1e103be2400afdc56fd48653c231ab8dc29fefd496a7d1fbdfa21d0f981eef7da3189038ebfa2d7ee6c69991c8d180e34b5f4b1777f92623146d3fbf83591be06d5113d032a5bdf557bf062f6b77732ed65ea93c5b80ace51dfeafafac7f569ca6d397bfe03e9db214ae3933c459daf61c9266736e9965e5711fd034a3266b98d42823571d103389a163a3d9b97d9f705b7e02421fe6feac9e068bc3a303ee7d24a7fe59fdce9005820032211b0ae7ca5920c14f1658fe62ef14d83454c71d6e21daeb2aed9203faf905820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005820032222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f055412a5a8444226757424297a7d16ffa40acb93f94000218210379849ace08bb8fa3b3e7d3832d75bf92b1bd9a1317cb8c9775a56daee8440c4e0058200278e4407bdaf57e0cba74af38c58720c02e249c3cd60c4c099b4221c83c46ac4624e153f5b1fa005820027fe42264939d94d0dc08e0e258744f0bc01416076bb44882467836fa6d01585820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03d3ddca332029b3fb4c9ad693b7a8321d61a68826757c131258a5178b6effc01f03530045d61eeff2f5b2b9ca40a08b25cb0e99240b5dc9d7a30e103cfa2b111d37033ea2b0e9102333c1910a24107f5cd12b9ffca5ec779eb693c05c2ec7957aa40103fa1c8b568be2bca61d6d4d04cc0f27321ad5ac6e092dadfc088c5168234bd16a0387c13dc687f0e79ba561eb553a9ed193dcd1fe627fd0ff2283215bf06dc4c6ee0219ff7f0219ffff05581e03378cb0c4994cc58a9bcd8e4bd8064923067bcd97f52e6e75e3842611c007011917fc033d20d3bee04ebbecf658cbe19b68cd00d29354f27ff62e636d93d3dafbbc145102197f320385558081cce36b998d546fdbf7814c28eee6728338ce923c7dd70381580d371a0370c453f427259f5d2224f63a9bd99b3752d4fe522236a5cad429df827252b88303879537ffb72241e607572a00032583f1dd3109c2a82b0153f00dfbba8f52ec2b0351454853043ef77794075256f9c73cc8adb43d604f532a4a3459c8b0c4f6287d03762a909098b76df8cc0752500301509bcd006e878eeca3fdbc4c6a6f26e16313034b87222ffdac15163f7c052c5ac47861e611443825fed92cb6f1fad6cb84952f038b9f458e66ddc629b539c33971919e1a95c31e70a55a61a4a0c9ffd1276cf8730374564e1926738bd9c29facd3cf32ea04ce8223f9d5f625d51e2037adc5c76b8303825adaa3d4c269a4fd335f19e8a3a930fef93513b8ef7d1d1a2b25c1b26a8c4903a89ec299173e383e89f566928b575ba18663c2775fe0d0ab03827402c1f383ef03f05c4ea9a3c73a2974e34bd6b1320759e7111698934c06b00651f9e37eb3883f03e27b5ed53e2e7ec1ce9047f33f0036021fee09f4ae1d8eee03e2588b270590bb0219ffff03e675888ff08f50c630a5023943646d3d1c161f0d7109ec12dd57c993b6e8445f03f42372b51a032c2464cf901bb52bdc4d71b9fe883f0ec0b869aa9e387b8c74f203495af9ca43728fdeeb2eff392d21bd9949342f5a331b5e6c603824872f9a0a9e0308eebd94d3d3fe8dbab143cecd02912ed4c6949549580c81762c8f60fb267f6a03d7ab61dc9d0ad33f423d773b20f9348c6d6f8de45451bbb86447abc90cf8ab8a038cbde9b440a9f485dcd1f25d3f63c5ed91ebb94db6b40cc4033561c4bc612dc803508333a96ce39918bbd89355bcd90acec561b00f3559e2343ccd955cc3ffa3bb039cf7e5366e72cd68f0ec2ebe6dab2e288408b364100baf3f3ec2001c73a5002b0219ffff03c5c577424167226cecc3845c15130024e42e3179d91cfa5013bf3a823ce971460347b52c07a9a38ecc633089f5b553bb121f1c278f4f3d7d9f64c728e3e3e9bba4038a982769c1ceb325d66069464bd90f3a70952479f2cff01866835653bfdd6b3d03137657762ea6e30b24136283f4def353cf3eab4cde75525f9d306e4d89a7390e0317a1acb1863ffc1e49b8d47eb42a2ea76b85e4ffca5d2754fccf6f6883b00b58034ce1f038ba274923dec642bf79ae6f33006f43ed337e9a5ee3615361673696280316c14754b1baf265246ce8f7cb2aacde9f71835a4bd2d6e3ee0d733b9c7db0bb03567562d4c59ff97a729c572f5bee860b951efee2b67094e32e3103cb8d7b229003ef41a5ace95f2ccef5a89455ac6dda09bd813c90ad6c615b39ed6319941812ad0325e99aa68daa10726cbd6067af2bf7b5e9524c8936e8419b3a6dd59289fbc8cc0308111add4075a207ad530b9d0f0b42a32e81b60a26862be516c32185c9d7ecd6033caa6d9afd7bfc9132d6448eba130bbffee2ee07019a24026fa8f0283c47f86f03762ceb54d4f4c1a4390eb285454cdf26038de58f51f3bb50b4da9b642f55f73c03db6aea606ba942e267bcad4a92273cded9121474e2cee5cc78e8da752e35a11d03dc1ba8221ef3c12fb63e6f4c075f0c2690f7e9d28a5dfc63e898a3708b7e7d550219ffff03ff25ea72f718a31d35abdbf3fe3f529659fab8c20aaec12188bef573d82ef3a0035b93e868b700d752865d11aa1068bb2a1b722979ef928e0da02e239dda4bc8f10379df07187e98f0f2a7fa137b22d993dd89d8d3edeea55a6cad140d088264df810317aa0471662d5096c90610a7665cef4306ef3b2c0a7de29e937f39f8ff9202970344a29ee083cec8776ab8d69b0483e3a8b39b3cc07c6b8f0677b98f62cac6685f03f629bf32e0a88893616d208f901082c7d6a2b72ae6b6a59294c492d187de662103a11d41cbd2a3ba4771b6a4773aaca1807d7c5f73ea71742c0b7c20ce01b5fbab031e67a536d9df078fb58e2b8ebbc65f642127981d9ccae39bc83a224792f9877a03903c196542be38f792ac875f610c0e50d92fc5dd73ea47f0be0e6c2b69b7737f03597140da1f550d077229c1760d617dfc5f577468913bbb78b0494ea906a8085b032429e0a7b8d96d8f50bd1677d987eec0dc1af4b8413612d3f7088ba94eda259603385a0f8c13dc2d429d874649d54eb239f185f4cff7fd2e9d4c964002d3a976770375d411576c429ead9912a647b813ea12a693986768a42187add5f57ae32074de03ed4549a40945e22c09cd6a5c7ba90ac6a15ecb29e65def3df9d6ffb07f73076d0219ffff0219ffff032e5d6c73efd9304aa21fbaa8ccdcede91625225e2f1d3fe88a3125a7a466f61d0388553627e5c52c776ac754df2194e0e93dca71d3db2903362852a17f6688c24c034e03f4ed69293a99a19aa45b7de904a2f12f59000113179669d6964e8157a3fc03922fa6614ec199efa1506d6664a2345438fd1cc54178e0b1d64529f12fae45080390d949d2730dbecacf2154cf727d74a5cbf67777c41466b85dad7a3fd583e9990326865b0dd79045bdf664832b466f3be30eb3385b2ea3502f773a492828a72e5803ed01b055043c85afcb9e4679d5d5f47c57cbf091413a78983994b99d7740564503fd9c93e462d82e10da65dd7f9957bfa9634dd63c3f0580ca3f1506c0308a3e6d0373cfce1c4f399a93e841ac2f6328182d667546b315c28565025cf56fed75f03d038abbde8aaa21d911b71c6be8c16ad39f94e5ef3880f2bf4fc43a1d357d678ed40344d82579e708c39068d4f8e52fca5c261625399335dfa97748c619f4bb45a43103d6093299f13c6d9d00cc08c4ea903739324d895edd8949a15ab0515473b64b3c03fbcd27553e56d94fe42b2a7bb02e035078acf809a3cbd139870d8f09ef00077703063395c3ca00b39a9a7385dcffb498d54431223fb88f1fbf9457a247800851e103f9d7e688d6290063a97025852821d8581d8885ccd94d4e5c873c1dd578a67805037131fc0383ad605143550d78b31217fbd5a78c79013635c6340bba34aeff09c703d84ba39ff6e6d7162539d15742bf366475a7017fab909acb568e2966af6ccd2a03ca9bedba41863ff70540e7a89ed5eebca8cc5d715ef45bb237379032c34c4638038c55d2fbccba96ecdcb8eededd7bd07ac269d29fc31a3060131f7ae084f9f97e0359e941664c53657d8aab3c80eb04c3e7828b453d7978037041ab7a9f63586d2e0396a16f23a797d660d8f3a4c94bddc24cb04d287ddcd4ea32ea73381ed0d8c5fc03f1e04e565562deca18675923b272991da7b6df6aa22b49b44370df052fd7abc6032d250932ff79139a4e11ccec180183c44893e46900b78dba09f2e9db34a2f340036c6f7a506b705841fde64d2953a89cf76947c82f564abcfc10f60ceee8515451034f5d1ed87445a6728ee1d1e10454052324871f7181ff1fb6dedd5f7838565463037ed094472862137d6f93b7c724b590007aac28d8a03dc57f277cc888e5c499d1031207815b8724fbebd84e275db707e9b44e47c267da45d4c8d2eafb54982c99dc035a14c99e6ae97e283ecd212831b5fe466fe92f310acb865da4da894115af0afb03d6d5eeea1e72cc5d81d6045d18e7f2912f5979b8a4724c3e5cb6c1bbea988b0603f33531467798b7788090d8110235625efcf33e45bfbb2783ce64f7412cba750f03ce71c3edbe186620a39b2c9427adcf647dd0c6eefc0ae0f25a1879f86be4de8a03cdd84fd36ff3c3052a6b2de8017dd05a571d815c0c37f2710322da8a0d51adc4035b886c7c31a01af81c3d9acba26396cf496686ab9c0334dc73ead5fc94719b940374e0f7f6115bdf47166cd51f67968e30f045a0d3a85a9cbb9f511f8e05c09536037aacf29e645c0213f44eb1deeba746964c0eba977d3f5888e856fe4cc8264665031076ba0aa98ac11158a2c2baa3277b63c21f99e9d0612abe228376bdf8c2ba2f05581e03c8cfd2a2195f31d0d5c3bdd473d8c3dfb34bc915c24ec04d84e84720600c014651dac207a00005581e030a89984a2e4d476ebff2ac1c2f2df274feb59b54c04285a805b598be500c0347016bcc41e9000005581e03e54a02bce1d9fa1028366a0e299a657ef87db82ba07bfa11d811514ad00c19012b481058bdcf28eec00005581e038426c4518ccd1f062c426b53cfe8fb82259a40f2268d750baea0742e300c0247acdff78c161e9e03de216ab95c2cebd2bd88a4d0a4d83f3bfbfb2da32d19d174469df0ebcad4faec03c3d732adae8d04ffd2faa32b25d577dfa119ef17440318f9933278515ef3761305581e03b953a07f730c3f005e8b9aabd7fdb5556b49eec8b53f79ccbb9c11e3500c1855466a4aaba9014005581e0346e04e6a2b4175284acf03d2a3e7dd302b581ccf48bb504650370b12a0040205581e037e82ce55a952139e79ad57761b3ab5de3d8245ca64336a7a8da980ebf00401031d7b302ffe0b9d2ff0be04ed6fa453ad6bc8ff44c13e855f98a4903b5f590e4902191f4f03b5efcba6536e0581474464b9620f573c4b581897fbc2bc97a89ae8273f4a22a403e37cfa9d1f7b3fab0c6c8b7bc52fc3d1ad330fc14f1357b89f39a95dc1411ec2034f8dd44eed80fef14b7d931b972ad914768f779e7a3747261a2a7b19c27bf27d03fb52e8346e660f781ed894c9b373c35e64553af4bb4544669310c2ecd13ac0f803010ef8ada87041671c047c58ad24eba26701e6db6b10ed3c7e299f97b1304cae037eab63904940c38b258e52e74449ba505cfad0aba7484dd755fce955012e1f47036de6500d66dbb90624b117f2b611f38de322276869166acb2390ae4198d54bc00219ffff037bcc955b2ffa142ae8b172e326f23073a2410d46b86587e73cb628e84d8d3ee103d035a37988ae72947074396c7fafbbd13ac13b648beabca615d35bc8cc3a1f430336dea29fc8eac50c7b4e9a51c7514a459eae82d5a99b156e5a59f2a00a99687e033b8cc47d316bf4a2b9c384858745b64a6a75f6ab7baa949b6cd8d8342dfa13d103cb0f4f4cbd1444ec264d8b1380ec16f997bd5861dc2ea2ff60dc1323110748560368fc753b01435a3a51f24e72db9379db9544af0077df886dbf1138d40b9d39fa03b87a3c261d5f213e5ff05aba3b503ea58c378d7936963f6d0511af86d839207e031c04ae51ea7f600c37d3888edf86378fd7eb9a4cc67897e85e82470d44d3914303dd35813900aed6ac3157ac2eb24d5405b5ab808d3175e527c1902d66b371325203389b582aa85c5c794824060be73f284f51735d468740e066b0b151c1b1cd000a03fb7f3aa6b11fea6ac8add987b5645e65ca8629335eaf598bedd5c24ad319355f0219ffff03c4107551bf1c4524fbed9ce1a945c1a6e7a74ed64b59bfd112d10c6d05c96702037f4f5c7a4c6e9a145dfee645bb3ae1798c52b3ec8142051e1cc1b6e89fb4add903a79900fab5a29745c74610582aa22379a91e0978f9b1fe286fcb7027cac83b0703bb4401992c3fa73ffab291ce57990e5dee011c3fb86ccb2e88a7abf297682a4a03e1ed6c3ac971958b2333e007f6fd083f50741081341c80738f90d628cbf66ad50219ffff03239c82e304613079bf7cf121825e20135110f7d707f8227f31a254cb94b05a3403eec90064d95c1ebc04fe5b66a735cacdb439b76f7eea2f3c688f7600c41d8b5c0219ffff035b6d5c074b857eaf08a0431677575b1e5233d272db815494fd1557d49c7ac46f03bb220cf68a9f842a25d11865e54545de02f8c61e6209d6398c6103ff356408120346c8bf16613bc50ec34bb6f096e317af043ce71838aeecf72e743c84da078977038fe8e160de7fdd70f751db2609943011d1ca5564e8ef8efbc14be5310b3ea2510329364d8ed0e799b029bf3deff096debea3ec406fedf6ba7059ae8f2780a51eee03935e07b6895190691a8ad3b053746bf2aaaf95e484339aac97f1c72b1f6fd6740364be292226434b839cb4bd388bee92b415ff12d336f1556570be56a0d2449d9203be3e695b0b9966c53d8ddb29b6f08402de9be1064d3561d9b2a998abeaf8b3c203d739e429ed1e072f8476b9e585941f18e80d2e816a99c947fccff0604d5d33c103b2b4b00dacb165faa92130ee87402d900f4037e4e6ab020338a83a257c64f84703600ee5db3a0014dfd1d7b122e866ada568cfe8e0b0fc6da767d79debcd7fa52103288252053b654352f856fb30ccd8d8c264de10cd478131dce9b8d13453a88077035a60fcaa0b588c5b7fb2a95c3606f7b814f9399a44bbdc2b41fd87c14633a25803eac01d934a8a35d7e737ee2f7d0078059f373aa877266a6acca2f2064779682d03dc3e8883b94e63b3657f079d97a9ab584a434030e7c6cb23cf4493da7768af5a03da057e3ccee99bd003c135a894e86f30b57fbf0e67aaff788a94e1e10411d953036bc7733b5f92f4d4772d8862412279e91c881e8a4c7b2d4806606b864905488d0317a9a7d0bc390435dac707520f7ca3866fa38a5dd95a230d190626ca9e84e7d205581e03a82e3876df42156252732c2b5cd04db40239feab766aa0c96e2e56b00004080459567e608060405234801561001057600080fd5b50600436106101ae5760003560e01c806370cf754a116100ee578063c45a015511610097578063ddca3f4311610071578063ddca3f4314610800578063f305839914610820578063f30dba9314610828578063f637731d146108aa576101ae565b8063c45a0155146107d1578063d0c93a7c146107d9578063d21220a7146107f8576101ae565b8063883bdbfd116100c8578063883bdbfd14610633578063a34123a71461073c578063a38807f214610776576101ae565b806370cf754a146105c65780638206a4d1146105ce57806385b66729146105f6576101ae565b80633850c7bd1161015b578063490e6cbc11610135578063490e6cbc146104705780634f1eb3d8146104fc578063514ea4bf1461054d5780635339c296146105a6576101ae565b80633850c7bd1461035b5780633c8a7d8d146103b45780634614131914610456576101ae565b80631ad8b03b1161018c5780631ad8b03b146102aa578063252c09d7146102e157806332148f6714610338576101ae565b80630dfe1681146101b3578063128acb08146101d75780631a68650214610286575b600080fd5b6101bb6108d0565b604080516001600160a01b039092168252519081900360200190f35b61026d600480360360a08110156101ed57600080fd5b6001600160a01b0382358116926020810135151592604082013592606083013516919081019060a08101608082013564010000000081111561022e57600080fd5b82018360208201111561024057600080fd5b8035906020019184600183028401116401000000008311171561026257600080fd5b5090925090506108f4565b6040805192835260208301919091528051918290030190f35b61028e6114ad565b604080516001600160801b039092168252519081900360200190f35b6102b26114bc565b60405180836001600160801b03168152602001826001600160801b031681526020019250505060405180910390f35b6102fe600480360360208110156102f757600080fd5b50356114d6565b6040805163ffffffff909516855260069390930b60208501526001600160a01b039091168383015215156060830152519081900360800190f35b6103596004803603602081101561034e57600080fd5b503561ffff1661151c565b005b610363611616565b604080516001600160a01b03909816885260029690960b602088015261ffff9485168787015292841660608701529216608085015260ff90911660a0840152151560c0830152519081900360e00190f35b61026d600480360360a08110156103ca57600080fd5b6001600160a01b03823516916020810135600290810b92604083013590910b916001600160801b036060820135169181019060a08101608082013564010000000081111561041757600080fd5b82018360208201111561042957600080fd5b8035906020019184600183028401116401000000008311171561044b57600080fd5b509092509050611666565b61045e611922565b60408051918252519081900360200190f35b6103596004803603608081101561048657600080fd5b6001600160a01b0382351691602081013591604082013591908101906080810160608201356401000000008111156104bd57600080fd5b8201836020820111156104cf57600080fd5b803590602001918460018302840111640100000000831117156104f157600080fd5b509092509050611928565b6102b2600480360360a081101561051257600080fd5b506001600160a01b03813516906020810135600290810b91604081013590910b906001600160801b0360608201358116916080013516611d83565b61056a6004803603602081101561056357600080fd5b5035611f9d565b604080516001600160801b0396871681526020810195909552848101939093529084166060840152909216608082015290519081900360a00190f35b61045e600480360360208110156105bc57600080fd5b503560010b611fda565b61028e611fec565b610359600480360360408110156105e457600080fd5b5060ff81358116916020013516612010565b6102b26004803603606081101561060c57600080fd5b506001600160a01b03813516906001600160801b036020820135811691604001351661220f565b6106a36004803603602081101561064957600080fd5b81019060208101813564010000000081111561066457600080fd5b82018360208201111561067657600080fd5b8035906020019184602083028401116401000000008311171561069857600080fd5b5090925090506124dc565b604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b838110156106e75781810151838201526020016106cf565b50505050905001838103825284818151815260200191508051906020019060200280838360005b8381101561072657818101518382015260200161070e565b5050505090500194505050505060405180910390f35b61026d6004803603606081101561075257600080fd5b508035600290810b91602081013590910b90604001356001600160801b0316612569565b6107a06004803603604081101561078c57600080fd5b508035600290810b9160200135900b6126e0565b6040805160069490940b84526001600160a01b03909216602084015263ffffffff1682820152519081900360600190f35b6101bb6128d7565b6107e16128fb565b6040805160029290920b8252519081900360200190f35b6101bb61291f565b610808612943565b6040805162ffffff9092168252519081900360200190f35b61045e612967565b6108486004803603602081101561083e57600080fd5b503560020b61296d565b604080516001600160801b039099168952600f9790970b602089015287870195909552606087019390935260069190910b60808601526001600160a01b031660a085015263ffffffff1660c0840152151560e083015251908190036101000190f35b610359600480360360208110156108c057600080fd5b50356001600160a01b03166129db565b7f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4881565b6000806108ff612bf0565b85610936576040805162461bcd60e51b8152602060048201526002602482015261415360f01b604482015290519081900360640190fd5b6040805160e0810182526000546001600160a01b0381168252600160a01b8104600290810b810b900b602083015261ffff600160b81b8204811693830193909352600160c81b810483166060830152600160d81b8104909216608082015260ff600160e81b8304811660a0830152600160f01b909204909116151560c082018190526109ef576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b87610a3a5780600001516001600160a01b0316866001600160a01b0316118015610a35575073fffd8963efd1fc6a506488495d951d5263988d266001600160a01b038716105b610a6c565b80600001516001600160a01b0316866001600160a01b0316108015610a6c57506401000276a36001600160a01b038716115b610aa3576040805162461bcd60e51b815260206004820152600360248201526214d41360ea1b604482015290519081900360640190fd5b6000805460ff60f01b191681556040805160c08101909152808a610ad25760048460a0015160ff16901c610ae5565b60108460a0015160ff1681610ae357fe5b065b60ff1681526004546001600160801b03166020820152604001610b06612c27565b63ffffffff168152602001600060060b815260200160006001600160a01b031681526020016000151581525090506000808913905060006040518060e001604052808b81526020016000815260200185600001516001600160a01b03168152602001856020015160020b81526020018c610b8257600254610b86565b6001545b815260200160006001600160801b0316815260200184602001516001600160801b031681525090505b805115801590610bd55750886001600160a01b031681604001516001600160a01b031614155b15610f9f57610be261560e565b60408201516001600160a01b031681526060820151610c25906006907f000000000000000000000000000000000000000000000000000000000000000a8f612c2b565b15156040830152600290810b810b60208301819052620d89e719910b1215610c5657620d89e7196020820152610c75565b6020810151620d89e860029190910b1315610c7557620d89e860208201525b610c828160200151612d6d565b6001600160a01b031660608201526040820151610d13908d610cbc578b6001600160a01b031683606001516001600160a01b031611610cd6565b8b6001600160a01b031683606001516001600160a01b0316105b610ce4578260600151610ce6565b8b5b60c085015185517f00000000000000000000000000000000000000000000000000000000000001f461309f565b60c085015260a084015260808301526001600160a01b031660408301528215610d7557610d498160c00151826080015101613291565b825103825260a0810151610d6b90610d6090613291565b6020840151906132a7565b6020830152610db0565b610d828160a00151613291565b825101825260c08101516080820151610daa91610d9f9101613291565b6020840151906132c3565b60208301525b835160ff1615610df6576000846000015160ff168260c0015181610dd057fe5b60c0840180519290910491829003905260a0840180519091016001600160801b03169052505b60c08201516001600160801b031615610e3557610e298160c00151600160801b8460c001516001600160801b03166132d9565b60808301805190910190525b80606001516001600160a01b031682604001516001600160a01b03161415610f5e57806040015115610f35578360a00151610ebf57610e9d846040015160008760200151886040015188602001518a606001516008613389909695949392919063ffffffff16565b6001600160a01b03166080860152600690810b900b6060850152600160a08501525b6000610f0b82602001518e610ed657600154610edc565b84608001515b8f610eeb578560800151610eef565b6002545b608089015160608a015160408b0151600595949392919061351c565b90508c15610f17576000035b610f258360c00151826135ef565b6001600160801b031660c0840152505b8b610f44578060200151610f4d565b60018160200151035b600290810b900b6060830152610f99565b80600001516001600160a01b031682604001516001600160a01b031614610f9957610f8c82604001516136a5565b600290810b900b60608301525b50610baf565b836020015160020b816060015160020b1461107a57600080610fed86604001518660400151886020015188602001518a606001518b6080015160086139d1909695949392919063ffffffff16565b604085015160608601516000805461ffff60c81b1916600160c81b61ffff958616021761ffff60b81b1916600160b81b95909416949094029290921762ffffff60a01b1916600160a01b62ffffff60029490940b93909316929092029190911773ffffffffffffffffffffffffffffffffffffffff19166001600160a01b03909116179055506110ac9050565b60408101516000805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b039092169190911790555b8060c001516001600160801b031683602001516001600160801b0316146110f25760c0810151600480546001600160801b0319166001600160801b039092169190911790555b8a1561114257608081015160015560a08101516001600160801b03161561113d5760a0810151600380546001600160801b031981166001600160801b03918216909301169190911790555b611188565b608081015160025560a08101516001600160801b0316156111885760a0810151600380546001600160801b03808216600160801b92839004821690940116029190911790555b8115158b1515146111a157602081015181518b036111ae565b80600001518a0381602001515b90965094508a156112e75760008512156111f0576111f07f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28d87600003613b86565b60006111fa613cd4565b9050336001600160a01b031663fa461e3388888c8c6040518563ffffffff1660e01b815260040180858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b15801561127e57600080fd5b505af1158015611292573d6000803e3d6000fd5b5050505061129e613cd4565b6112a88289613e0d565b11156112e1576040805162461bcd60e51b815260206004820152600360248201526249494160e81b604482015290519081900360640190fd5b50611411565b600086121561131e5761131e7f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb488d88600003613b86565b6000611328613e1d565b9050336001600160a01b031663fa461e3388888c8c6040518563ffffffff1660e01b815260040180858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b1580156113ac57600080fd5b505af11580156113c0573d6000803e3d6000fd5b505050506113cc613e1d565b6113d68288613e0d565b111561140f576040805162461bcd60e51b815260206004820152600360248201526249494160e81b604482015290519081900360640190fd5b505b60408082015160c083015160608085015184518b8152602081018b90526001600160a01b03948516818701526001600160801b039093169183019190915260020b60808201529151908e169133917fc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca679181900360a00190a350506000805460ff60f01b1916600160f01b17905550919890975095505050505050565b6004546001600160801b031681565b6003546001600160801b0380821691600160801b90041682565b60088161ffff81106114e757600080fd5b015463ffffffff81169150640100000000810460060b90600160581b81046001600160a01b031690600160f81b900460ff1684565b600054600160f01b900460ff16611560576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b19169055611575612bf0565b60008054600160d81b900461ffff169061159160088385613eb5565b6000805461ffff808416600160d81b810261ffff60d81b19909316929092179092559192508316146115fe576040805161ffff80851682528316602082015281517fac49e518f90a358f652e4400164f05a5d8f7e35e7747279bc3a93dbf584e125a929181900390910190a15b50506000805460ff60f01b1916600160f01b17905550565b6000546001600160a01b03811690600160a01b810460020b9061ffff600160b81b8204811691600160c81b8104821691600160d81b8204169060ff600160e81b8204811691600160f01b90041687565b600080548190600160f01b900460ff166116ad576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b191690556001600160801b0385166116cd57600080fd5b60008061171b60405180608001604052808c6001600160a01b031681526020018b60020b81526020018a60020b81526020016117118a6001600160801b0316613f58565b600f0b9052613f69565b9250925050819350809250600080600086111561173d5761173a613cd4565b91505b841561174e5761174b613e1d565b90505b336001600160a01b031663d348799787878b8b6040518563ffffffff1660e01b815260040180858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b1580156117d057600080fd5b505af11580156117e4573d6000803e3d6000fd5b50505050600086111561183b576117f9613cd4565b6118038388613e0d565b111561183b576040805162461bcd60e51b815260206004820152600260248201526104d360f41b604482015290519081900360640190fd5b841561188b57611849613e1d565b6118538287613e0d565b111561188b576040805162461bcd60e51b81526020600482015260026024820152614d3160f01b604482015290519081900360640190fd5b8960020b8b60020b8d6001600160a01b03167f7a53080ba414158be7ec69b987b5fb7d07dee101fe85488f0853ae16239d0bde338d8b8b60405180856001600160a01b03168152602001846001600160801b0316815260200183815260200182815260200194505050505060405180910390a450506000805460ff60f01b1916600160f01b17905550919890975095505050505050565b60025481565b600054600160f01b900460ff1661196c576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b19169055611981612bf0565b6004546001600160801b0316806119c3576040805162461bcd60e51b81526020600482015260016024820152601360fa1b604482015290519081900360640190fd5b60006119f8867f00000000000000000000000000000000000000000000000000000000000001f462ffffff16620f42406141a9565b90506000611a2f867f00000000000000000000000000000000000000000000000000000000000001f462ffffff16620f42406141a9565b90506000611a3b613cd4565b90506000611a47613e1d565b90508815611a7a57611a7a7f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb488b8b613b86565b8715611aab57611aab7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28b8a613b86565b336001600160a01b031663e9cbafb085858a8a6040518563ffffffff1660e01b815260040180858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b158015611b2d57600080fd5b505af1158015611b41573d6000803e3d6000fd5b505050506000611b4f613cd4565b90506000611b5b613e1d565b905081611b688588613e0d565b1115611ba0576040805162461bcd60e51b8152602060048201526002602482015261046360f41b604482015290519081900360640190fd5b80611bab8487613e0d565b1115611be3576040805162461bcd60e51b8152602060048201526002602482015261463160f01b604482015290519081900360640190fd5b8382038382038115611c725760008054600160e81b9004600f16908115611c16578160ff168481611c1057fe5b04611c19565b60005b90506001600160801b03811615611c4c57600380546001600160801b038082168401166001600160801b03199091161790555b611c66818503600160801b8d6001600160801b03166132d9565b60018054909101905550505b8015611cfd5760008054600160e81b900460041c600f16908115611ca2578160ff168381611c9c57fe5b04611ca5565b60005b90506001600160801b03811615611cd757600380546001600160801b03600160801b8083048216850182160291161790555b611cf1818403600160801b8d6001600160801b03166132d9565b60028054909101905550505b8d6001600160a01b0316336001600160a01b03167fbdbdb71d7860376ba52b25a5028beea23581364a40522f6bcfb86bb1f2dca6338f8f86866040518085815260200184815260200183815260200182815260200194505050505060405180910390a350506000805460ff60f01b1916600160f01b179055505050505050505050505050565b600080548190600160f01b900460ff16611dca576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b19168155611de460073389896141e3565b60038101549091506001600160801b0390811690861611611e055784611e14565b60038101546001600160801b03165b60038201549093506001600160801b03600160801b909104811690851611611e3c5783611e52565b6003810154600160801b90046001600160801b03165b91506001600160801b03831615611eb7576003810180546001600160801b031981166001600160801b03918216869003821617909155611eb7907f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48908a908616613b86565b6001600160801b03821615611f1d576003810180546001600160801b03600160801b808304821686900382160291811691909117909155611f1d907f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2908a908516613b86565b604080516001600160a01b038a1681526001600160801b0380861660208301528416818301529051600288810b92908a900b9133917f70935338e69775456a85ddef226c395fb668b63fa0115f5f20610b388e6ca9c0919081900360600190a4506000805460ff60f01b1916600160f01b17905590969095509350505050565b60076020526000908152604090208054600182015460028301546003909301546001600160801b0392831693919281811691600160801b90041685565b60066020526000908152604090205481565b7f0000000000000000000000000000000000005e8b2285f864419ac400be90719681565b600054600160f01b900460ff16612054576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b1916905560408051638da5cb5b60e01b815290516001600160a01b037f0000000000000000000000001f98431c8ad98523631ae4a59f267346ea31f9841691638da5cb5b916004808301926020929190829003018186803b1580156120c157600080fd5b505afa1580156120d5573d6000803e3d6000fd5b505050506040513d60208110156120eb57600080fd5b50516001600160a01b0316331461210157600080fd5b60ff82161580612124575060048260ff16101580156121245750600a8260ff1611155b801561214e575060ff8116158061214e575060048160ff161015801561214e5750600a8160ff1611155b61215757600080fd5b60008054610ff0600484901b16840160ff908116600160e81b9081027fffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff841617909355919004167f973d8d92bb299f4af6ce49b52a8adb85ae46b9f214c4c4fc06ac77401237b1336010826040805160ff9390920683168252600f600486901c16602083015286831682820152918516606082015290519081900360800190a150506000805460ff60f01b1916600160f01b17905550565b600080548190600160f01b900460ff16612256576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b1916905560408051638da5cb5b60e01b815290516001600160a01b037f0000000000000000000000001f98431c8ad98523631ae4a59f267346ea31f9841691638da5cb5b916004808301926020929190829003018186803b1580156122c357600080fd5b505afa1580156122d7573d6000803e3d6000fd5b505050506040513d60208110156122ed57600080fd5b50516001600160a01b0316331461230357600080fd5b6003546001600160801b039081169085161161231f578361232c565b6003546001600160801b03165b6003549092506001600160801b03600160801b9091048116908416116123525782612366565b600354600160801b90046001600160801b03165b90506001600160801b038216156123e7576003546001600160801b038381169116141561239557600019909101905b600380546001600160801b031981166001600160801b039182168590038216179091556123e7907f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb489087908516613b86565b6001600160801b0381161561246d576003546001600160801b03828116600160801b90920416141561241857600019015b600380546001600160801b03600160801b80830482168590038216029181169190911790915561246d907f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc29087908416613b86565b604080516001600160801b0380851682528316602082015281516001600160a01b0388169233927f596b573906218d3411850b26a6b437d6c4522fdb43d2d2386263f86d50b8b151929081900390910190a36000805460ff60f01b1916600160f01b1790559094909350915050565b6060806124e7612bf0565b61255e6124f2612c27565b858580806020026020016040519081016040528093929190818152602001838360200280828437600092018290525054600454600896959450600160a01b820460020b935061ffff600160b81b8304811693506001600160801b0390911691600160c81b900416614247565b915091509250929050565b600080548190600160f01b900460ff166125b0576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b1916815560408051608081018252338152600288810b602083015287900b918101919091528190819061260990606081016125fc6001600160801b038a16613f58565b600003600f0b9052613f69565b925092509250816000039450806000039350600085118061262a5750600084115b15612669576003830180546001600160801b038082168089018216600160801b93849004831689019092169092029091176001600160801b0319161790555b604080516001600160801b0388168152602081018790528082018690529051600289810b92908b900b9133917f0c396cd989a39f4459b5fa1aed6a9a8dcdbc45908acfd67e028cd568da98982c919081900360600190a450506000805460ff60f01b1916600160f01b179055509094909350915050565b60008060006126ed612bf0565b6126f785856143a1565b600285810b810b60009081526005602052604080822087840b90930b825281206003830154600681900b9367010000000000000082046001600160a01b0316928492600160d81b810463ffffffff169284929091600160f81b900460ff168061275f57600080fd5b6003820154600681900b985067010000000000000081046001600160a01b03169650600160d81b810463ffffffff169450600160f81b900460ff16806127a457600080fd5b50506040805160e0810182526000546001600160a01b0381168252600160a01b8104600290810b810b810b6020840181905261ffff600160b81b8404811695850195909552600160c81b830485166060850152600160d81b8304909416608084015260ff600160e81b8304811660a0850152600160f01b909204909116151560c08301529093508e810b91900b1215905061284d575093909403965090039350900390506128d0565b8a60020b816020015160020b12156128c1576000612869612c27565b602083015160408401516004546060860151939450600093849361289f936008938893879392916001600160801b031690613389565b9a9003989098039b5050949096039290920396509091030392506128d0915050565b50949093039650039350900390505b9250925092565b7f0000000000000000000000001f98431c8ad98523631ae4a59f267346ea31f98481565b7f000000000000000000000000000000000000000000000000000000000000000a81565b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc281565b7f00000000000000000000000000000000000000000000000000000000000001f481565b60015481565b60056020526000908152604090208054600182015460028301546003909301546001600160801b03831693600160801b909304600f0b9290600681900b9067010000000000000081046001600160a01b031690600160d81b810463ffffffff1690600160f81b900460ff1688565b6000546001600160a01b031615612a1e576040805162461bcd60e51b8152602060048201526002602482015261414960f01b604482015290519081900360640190fd5b6000612a29826136a5565b9050600080612a41612a39612c27565b60089061446a565b6040805160e0810182526001600160a01b038816808252600288810b6020808501829052600085870181905261ffff898116606088018190529089166080880181905260a08801839052600160c0909801979097528154600160f01b73ffffffffffffffffffffffffffffffffffffffff19909116871762ffffff60a01b1916600160a01b62ffffff9787900b9790971696909602959095177fffffffffff00000000ffffffffffffffffffffffffffffffffffffffffffffff16600160c81b9091021761ffff60d81b1916600160d81b909602959095177fff0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1692909217909355835191825281019190915281519395509193507f98636036cb66a9c19a37435efc1e90142190214e8abeb821bdba3f2990dd4c9592918290030190a150505050565b60008082600281900b620d89e71981612b9957fe5b05029050600083600281900b620d89e881612bb057fe5b0502905060008460020b83830360020b81612bc757fe5b0560010190508062ffffff166001600160801b03801681612be457fe5b0493505050505b919050565b306001600160a01b037f00000000000000000000000088e6a0c2ddd26feeb64f039a2c41296fcb3f56401614612c2557600080fd5b565b4290565b60008060008460020b8660020b81612c3f57fe5b05905060008660020b128015612c6657508460020b8660020b81612c5f57fe5b0760020b15155b15612c7057600019015b8315612ce557600080612c82836144b6565b600182810b810b600090815260208d9052604090205460ff83169190911b80016000190190811680151597509294509092509085612cc757888360ff16860302612cda565b88612cd1826144c8565b840360ff168603025b965050505050612d63565b600080612cf4836001016144b6565b91509150600060018260ff166001901b031990506000818b60008660010b60010b8152602001908152602001600020541690508060001415955085612d4657888360ff0360ff16866001010102612d5c565b8883612d5183614568565b0360ff168660010101025b9650505050505b5094509492505050565b60008060008360020b12612d84578260020b612d8c565b8260020b6000035b9050620d89e8811115612dca576040805162461bcd60e51b81526020600482015260016024820152601560fa1b604482015290519081900360640190fd5b600060018216612dde57600160801b612df0565b6ffffcb933bd6fad37aa2d162d1a5940015b70ffffffffffffffffffffffffffffffffff1690506002821615612e24576ffff97272373d413259a46990580e213a0260801c5b6004821615612e43576ffff2e50f5f656932ef12357cf3c7fdcc0260801c5b6008821615612e62576fffe5caca7e10e4e61c3624eaa0941cd00260801c5b6010821615612e81576fffcb9843d60f6159c9db58835c9266440260801c5b6020821615612ea0576fff973b41fa98c081472e6896dfb254c00260801c5b6040821615612ebf576fff2ea16466c96a3843ec78b326b528610260801c5b6080821615612ede576ffe5dee046a99a2a811c461f1969c30530260801c5b610100821615612efe576ffcbe86c7900a88aedcffc83b479aa3a40260801c5b610200821615612f1e576ff987a7253ac413176f2b074cf7815e540260801c5b610400821615612f3e576ff3392b0822b70005940c7a398e4b70f30260801c5b610800821615612f5e576fe7159475a2c29b7443b29c7fa6e889d90260801c5b611000821615612f7e576fd097f3bdfd2022b8845ad8f792aa58250260801c5b612000821615612f9e576fa9f746462d870fdf8a65dc1f90e061e50260801c5b614000821615612fbe576f70d869a156d2a1b890bb3df62baf32f70260801c5b618000821615612fde576f31be135f97d08fd981231505542fcfa60260801c5b62010000821615612fff576f09aa508b5b7a84e1c677de54f3e99bc90260801c5b6202000082161561301f576e5d6af8dedb81196699c329225ee6040260801c5b6204000082161561303e576d2216e584f5fa1ea926041bedfe980260801c5b6208000082161561305b576b048a170391f7dc42444e8fa20260801c5b60008460020b131561307657806000198161307257fe5b0490505b64010000000081061561308a57600161308d565b60005b60ff16602082901c0192505050919050565b60008080806001600160a01b03808916908a1610158187128015906131245760006130d88989620f42400362ffffff16620f42406132d9565b9050826130f1576130ec8c8c8c6001614652565b6130fe565b6130fe8b8d8c60016146cd565b955085811061310f578a965061311e565b61311b8c8b838661478a565b96505b5061316e565b8161313b576131368b8b8b60006146cd565b613148565b6131488a8c8b6000614652565b935083886000031061315c5789955061316e565b61316b8b8a8a600003856147d6565b95505b6001600160a01b038a81169087161482156131d15780801561318d5750815b6131a35761319e878d8c60016146cd565b6131a5565b855b95508080156131b2575081155b6131c8576131c3878d8c6000614652565b6131ca565b845b945061321b565b8080156131db5750815b6131f1576131ec8c888c6001614652565b6131f3565b855b9550808015613200575081155b613216576132118c888c60006146cd565b613218565b845b94505b8115801561322b57508860000385115b15613237578860000394505b81801561325657508a6001600160a01b0316876001600160a01b031614155b15613265578589039350613282565b61327f868962ffffff168a620f42400362ffffff166141a9565b93505b50505095509550955095915050565b6000600160ff1b82106132a357600080fd5b5090565b808203828113156000831215146132bd57600080fd5b92915050565b818101828112156000831215146132bd57600080fd5b600080806000198587098686029250828110908390030390508061330f576000841161330457600080fd5b508290049050613382565b80841161331b57600080fd5b6000848688096000868103871696879004966002600389028118808a02820302808a02820302808a02820302808a02820302808a02820302808a02909103029181900381900460010186841190950394909402919094039290920491909117919091029150505b9392505050565b60008063ffffffff8716613430576000898661ffff1661ffff81106133aa57fe5b60408051608081018252919092015463ffffffff8082168084526401000000008304600690810b810b900b6020850152600160581b83046001600160a01b031694840194909452600160f81b90910460ff16151560608301529092508a161461341c57613419818a8988614822565b90505b806020015181604001519250925050613510565b8688036000806134458c8c858c8c8c8c6148d2565b91509150816000015163ffffffff168363ffffffff161415613477578160200151826040015194509450505050613510565b805163ffffffff8481169116141561349f578060200151816040015194509450505050613510565b8151815160208085015190840151918390039286039163ffffffff80841692908516910360060b816134cd57fe5b05028460200151018263ffffffff168263ffffffff1686604001518660400151036001600160a01b031602816134ff57fe5b048560400151019650965050505050505b97509795505050505050565b600295860b860b60009081526020979097526040909620600181018054909503909455938301805490920390915560038201805463ffffffff600160d81b6001600160a01b036701000000000000008085048216909603169094027fffffffffff0000000000000000000000000000000000000000ffffffffffffff90921691909117600681810b90960390950b66ffffffffffffff1666ffffffffffffff199095169490941782810485169095039093160263ffffffff60d81b1990931692909217905554600160801b9004600f0b90565b60008082600f0b121561365457826001600160801b03168260000384039150816001600160801b03161061364f576040805162461bcd60e51b81526020600482015260026024820152614c5360f01b604482015290519081900360640190fd5b6132bd565b826001600160801b03168284019150816001600160801b031610156132bd576040805162461bcd60e51b81526020600482015260026024820152614c4160f01b604482015290519081900360640190fd5b60006401000276a36001600160a01b038316108015906136e1575073fffd8963efd1fc6a506488495d951d5263988d266001600160a01b038316105b613716576040805162461bcd60e51b81526020600482015260016024820152602960f91b604482015290519081900360640190fd5b77ffffffffffffffffffffffffffffffffffffffff00000000602083901b166001600160801b03811160071b81811c67ffffffffffffffff811160061b90811c63ffffffff811160051b90811c61ffff811160041b90811c60ff8111600390811b91821c600f811160021b90811c918211600190811b92831c979088119617909417909217179091171717608081106137b757607f810383901c91506137c1565b80607f0383901b91505b908002607f81811c60ff83811c9190911c800280831c81831c1c800280841c81841c1c800280851c81851c1c800280861c81861c1c800280871c81871c1c800280881c81881c1c800280891c81891c1c8002808a1c818a1c1c8002808b1c818b1c1c8002808c1c818c1c1c8002808d1c818d1c1c8002808e1c9c81901c9c909c1c80029c8d901c9e9d607f198f0160401b60c09190911c678000000000000000161760c19b909b1c674000000000000000169a909a1760c29990991c672000000000000000169890981760c39790971c671000000000000000169690961760c49590951c670800000000000000169490941760c59390931c670400000000000000169290921760c69190911c670200000000000000161760c79190911c670100000000000000161760c89190911c6680000000000000161760c99190911c6640000000000000161760ca9190911c6620000000000000161760cb9190911c6610000000000000161760cc9190911c6608000000000000161760cd9190911c66040000000000001617693627a301d71055774c8581026f028f6481ab7f045a5af012a19d003aa9198101608090811d906fdb2df09e81959a81455e260799a0632f8301901d600281810b9083900b146139c257886001600160a01b03166139a682612d6d565b6001600160a01b031611156139bb57816139bd565b805b6139c4565b815b9998505050505050505050565b6000806000898961ffff1661ffff81106139e757fe5b60408051608081018252919092015463ffffffff8082168084526401000000008304600690810b810b900b6020850152600160581b83046001600160a01b031694840194909452600160f81b90910460ff161515606083015290925089161415613a575788859250925050613510565b8461ffff168461ffff16118015613a7857506001850361ffff168961ffff16145b15613a8557839150613a89565b8491505b8161ffff168960010161ffff1681613a9d57fe5b069250613aac81898989614822565b8a8461ffff1661ffff8110613abd57fe5b825191018054602084015160408501516060909501511515600160f81b027effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6001600160a01b03909616600160581b027fff0000000000000000000000000000000000000000ffffffffffffffffffffff60069390930b66ffffffffffffff16640100000000026affffffffffffff000000001963ffffffff90971663ffffffff199095169490941795909516929092171692909217929092161790555097509795505050505050565b604080516001600160a01b038481166024830152604480830185905283518084039091018152606490920183526020820180516001600160e01b031663a9059cbb60e01b1781529251825160009485949389169392918291908083835b60208310613c025780518252601f199092019160209182019101613be3565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114613c64576040519150601f19603f3d011682016040523d82523d6000602084013e613c69565b606091505b5091509150818015613c97575080511580613c975750808060200190516020811015613c9457600080fd5b50515b613ccd576040805162461bcd60e51b81526020600482015260026024820152612a2360f11b604482015290519081900360640190fd5b5050505050565b604080513060248083019190915282518083039091018152604490910182526020810180516001600160e01b03166370a0823160e01b17815291518151600093849384936001600160a01b037f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb481693919290918291908083835b60208310613d6d5780518252601f199092019160209182019101613d4e565b6001836020036101000a038019825116818451168082178552505050505050905001915050600060405180830381855afa9150503d8060008114613dcd576040519150601f19603f3d011682016040523d82523d6000602084013e613dd2565b606091505b5091509150818015613de657506020815110155b613def57600080fd5b808060200190516020811015613e0457600080fd5b50519250505090565b808201828110156132bd57600080fd5b604080513060248083019190915282518083039091018152604490910182526020810180516001600160e01b03166370a0823160e01b17815291518151600093849384936001600160a01b037f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc216939192909182919080838360208310613d6d5780518252601f199092019160209182019101613d4e565b6000808361ffff1611613ef3576040805162461bcd60e51b81526020600482015260016024820152604960f81b604482015290519081900360640190fd5b8261ffff168261ffff1611613f09575081613382565b825b8261ffff168161ffff161015613f4f576001858261ffff1661ffff8110613f2e57fe5b01805463ffffffff191663ffffffff92909216919091179055600101613f0b565b50909392505050565b80600f81900b8114612beb57600080fd5b6000806000613f76612bf0565b613f88846020015185604001516143a1565b6040805160e0810182526000546001600160a01b0381168252600160a01b8104600290810b810b900b602080840182905261ffff600160b81b8404811685870152600160c81b84048116606080870191909152600160d81b8504909116608086015260ff600160e81b8504811660a0870152600160f01b909404909316151560c08501528851908901519489015192890151939461402c9491939092909190614acf565b93508460600151600f0b6000146141a157846020015160020b816020015160020b12156140815761407a6140638660200151612d6d565b6140708760400151612d6d565b8760600151614c84565b92506141a1565b846040015160020b816020015160020b12156141775760045460408201516001600160801b03909116906140d3906140b7612c27565b60208501516060860151608087015160089493929187916139d1565b6000805461ffff60c81b1916600160c81b61ffff938416021761ffff60b81b1916600160b81b939092169290920217905581516040870151614123919061411990612d6d565b8860600151614c84565b93506141416141358760200151612d6d565b83516060890151614cc8565b92506141518187606001516135ef565b600480546001600160801b0319166001600160801b0392909216919091179055506141a1565b61419e6141878660200151612d6d565b6141948760400151612d6d565b8760600151614cc8565b91505b509193909250565b60006141b68484846132d9565b9050600082806141c257fe5b84860911156133825760001981106141d957600080fd5b6001019392505050565b6040805160609490941b6bffffffffffffffffffffffff1916602080860191909152600293840b60e890811b60348701529290930b90911b60378401528051808403601a018152603a90930181528251928201929092206000908152929052902090565b60608060008361ffff1611614287576040805162461bcd60e51b81526020600482015260016024820152604960f81b604482015290519081900360640190fd5b865167ffffffffffffffff8111801561429f57600080fd5b506040519080825280602002602001820160405280156142c9578160200160208202803683370190505b509150865167ffffffffffffffff811180156142e457600080fd5b5060405190808252806020026020018201604052801561430e578160200160208202803683370190505b50905060005b87518110156143945761433f8a8a8a848151811061432e57fe5b60200260200101518a8a8a8a613389565b84838151811061434b57fe5b6020026020010184848151811061435e57fe5b60200260200101826001600160a01b03166001600160a01b03168152508260060b60060b81525050508080600101915050614314565b5097509795505050505050565b8060020b8260020b126143e1576040805162461bcd60e51b8152602060048201526003602482015262544c5560e81b604482015290519081900360640190fd5b620d89e719600283900b1215614424576040805162461bcd60e51b8152602060048201526003602482015262544c4d60e81b604482015290519081900360640190fd5b620d89e8600282900b1315614466576040805162461bcd60e51b815260206004820152600360248201526254554d60e81b604482015290519081900360640190fd5b5050565b6040805160808101825263ffffffff9283168082526000602083018190529282019290925260016060909101819052835463ffffffff1916909117909116600160f81b17909155908190565b60020b600881901d9161010090910790565b60008082116144d657600080fd5b600160801b82106144e957608091821c91015b68010000000000000000821061450157604091821c91015b640100000000821061451557602091821c91015b62010000821061452757601091821c91015b610100821061453857600891821c91015b6010821061454857600491821c91015b6004821061455857600291821c91015b60028210612beb57600101919050565b600080821161457657600080fd5b5060ff6001600160801b0382161561459157607f1901614599565b608082901c91505b67ffffffffffffffff8216156145b257603f19016145ba565b604082901c91505b63ffffffff8216156145cf57601f19016145d7565b602082901c91505b61ffff8216156145ea57600f19016145f2565b601082901c91505b60ff821615614604576007190161460c565b600882901c91505b600f82161561461e5760031901614626565b600482901c91505b60038216156146385760011901614640565b600282901c91505b6001821615612beb5760001901919050565b6000836001600160a01b0316856001600160a01b03161115614672579293925b8161469f5761469a836001600160801b03168686036001600160a01b0316600160601b6132d9565b6146c2565b6146c2836001600160801b03168686036001600160a01b0316600160601b6141a9565b90505b949350505050565b6000836001600160a01b0316856001600160a01b031611156146ed579293925b7bffffffffffffffffffffffffffffffff000000000000000000000000606084901b166001600160a01b03868603811690871661472957600080fd5b8361475957866001600160a01b031661474c8383896001600160a01b03166132d9565b8161475357fe5b0461477f565b61477f6147708383896001600160a01b03166141a9565b886001600160a01b0316614cf7565b979650505050505050565b600080856001600160a01b0316116147a157600080fd5b6000846001600160801b0316116147b757600080fd5b816147c95761469a8585856001614d02565b6146c28585856001614de3565b600080856001600160a01b0316116147ed57600080fd5b6000846001600160801b03161161480357600080fd5b816148155761469a8585856000614de3565b6146c28585856000614d02565b61482a61564a565b600085600001518503905060405180608001604052808663ffffffff1681526020018263ffffffff168660020b0288602001510160060b81526020016000856001600160801b03161161487e576001614880565b845b6001600160801b031673ffffffff00000000000000000000000000000000608085901b16816148ab57fe5b048860400151016001600160a01b0316815260200160011515815250915050949350505050565b6148da61564a565b6148e261564a565b888561ffff1661ffff81106148f357fe5b60408051608081018252919092015463ffffffff81168083526401000000008204600690810b810b900b6020840152600160581b82046001600160a01b031693830193909352600160f81b900460ff1615156060820152925061495890899089614ed8565b15614990578663ffffffff16826000015163ffffffff16141561497a57613510565b8161498783898988614822565b91509150613510565b888361ffff168660010161ffff16816149a557fe5b0661ffff1661ffff81106149b557fe5b60408051608081018252929091015463ffffffff811683526401000000008104600690810b810b900b60208401526001600160a01b03600160581b8204169183019190915260ff600160f81b90910416151560608201819052909250614a6c57604080516080810182528a5463ffffffff811682526401000000008104600690810b810b900b6020830152600160581b81046001600160a01b031692820192909252600160f81b90910460ff161515606082015291505b614a7b88836000015189614ed8565b614ab2576040805162461bcd60e51b815260206004820152600360248201526213d31160ea1b604482015290519081900360640190fd5b614abf8989898887614f9b565b9150915097509795505050505050565b6000614ade60078787876141e3565b60015460025491925090600080600f87900b15614c24576000614aff612c27565b6000805460045492935090918291614b499160089186918591600160a01b810460020b9161ffff600160b81b83048116926001600160801b0390921691600160c81b900416613389565b9092509050614b8360058d8b8d8b8b87898b60007f0000000000000000000000000000000000005e8b2285f864419ac400be90719661513b565b9450614bba60058c8b8d8b8b87898b60017f0000000000000000000000000000000000005e8b2285f864419ac400be90719661513b565b93508415614bee57614bee60068d7f000000000000000000000000000000000000000000000000000000000000000a615325565b8315614c2057614c2060068c7f000000000000000000000000000000000000000000000000000000000000000a615325565b5050505b600080614c3660058c8c8b8a8a61538b565b9092509050614c47878a8484615437565b600089600f0b1215614c75578315614c6457614c6460058c6155cc565b8215614c7557614c7560058b6155cc565b50505050505095945050505050565b60008082600f0b12614caa57614ca5614ca085858560016146cd565b613291565b6146c5565b614cbd614ca085858560000360006146cd565b600003949350505050565b60008082600f0b12614ce457614ca5614ca08585856001614652565b614cbd614ca08585856000036000614652565b808204910615150190565b60008115614d755760006001600160a01b03841115614d3857614d3384600160601b876001600160801b03166132d9565b614d50565b6001600160801b038516606085901b81614d4e57fe5b045b9050614d6d614d686001600160a01b03881683613e0d565b6155f8565b9150506146c5565b60006001600160a01b03841115614da357614d9e84600160601b876001600160801b03166141a9565b614dba565b614dba606085901b6001600160801b038716614cf7565b905080866001600160a01b031611614dd157600080fd5b6001600160a01b0386160390506146c5565b600082614df15750836146c5565b7bffffffffffffffffffffffffffffffff000000000000000000000000606085901b168215614e91576001600160a01b03861684810290858281614e3157fe5b041415614e6257818101828110614e6057614e5683896001600160a01b0316836141a9565b93505050506146c5565b505b614e8882614e83878a6001600160a01b03168681614e7c57fe5b0490613e0d565b614cf7565b925050506146c5565b6001600160a01b03861684810290858281614ea857fe5b04148015614eb557508082115b614ebe57600080fd5b808203614e56614d68846001600160a01b038b16846141a9565b60008363ffffffff168363ffffffff1611158015614f0257508363ffffffff168263ffffffff1611155b15614f1e578163ffffffff168363ffffffff1611159050613382565b60008463ffffffff168463ffffffff1611614f46578363ffffffff1664010000000001614f4e565b8363ffffffff165b64ffffffffff16905060008563ffffffff168463ffffffff1611614f7f578363ffffffff1664010000000001614f87565b8363ffffffff165b64ffffffffff169091111595945050505050565b614fa361564a565b614fab61564a565b60008361ffff168560010161ffff1681614fc157fe5b0661ffff169050600060018561ffff16830103905060005b506002818301048961ffff87168281614fee57fe5b0661ffff8110614ffa57fe5b60408051608081018252929091015463ffffffff811683526401000000008104600690810b810b900b60208401526001600160a01b03600160581b8204169183019190915260ff600160f81b9091041615156060820181905290955061506557806001019250614fd9565b898661ffff16826001018161507657fe5b0661ffff811061508257fe5b60408051608081018252929091015463ffffffff811683526401000000008104600690810b810b900b60208401526001600160a01b03600160581b8204169183019190915260ff600160f81b909104161515606082015285519094506000906150ed908b908b614ed8565b905080801561510657506151068a8a8760000151614ed8565b15615111575061512e565b8061512157600182039250615128565b8160010193505b50614fd9565b5050509550959350505050565b60028a810b900b600090815260208c90526040812080546001600160801b031682615166828d6135ef565b9050846001600160801b0316816001600160801b031611156151b4576040805162461bcd60e51b81526020600482015260026024820152614c4f60f01b604482015290519081900360640190fd5b6001600160801b03828116159082161581141594501561528a578c60020b8e60020b1361525a57600183018b9055600283018a90556003830180547fffffffffff0000000000000000000000000000000000000000ffffffffffffff166701000000000000006001600160a01b038c16021766ffffffffffffff191666ffffffffffffff60068b900b161763ffffffff60d81b1916600160d81b63ffffffff8a16021790555b6003830180547effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790555b82546001600160801b0319166001600160801b038216178355856152d35782546152ce906152c990600160801b9004600f90810b810b908f900b6132c3565b613f58565b6152f4565b82546152f4906152c990600160801b9004600f90810b810b908f900b6132a7565b8354600f9190910b6001600160801b03908116600160801b0291161790925550909c9b505050505050505050505050565b8060020b8260020b8161533457fe5b0760020b1561534257600080fd5b60008061535d8360020b8560020b8161535757fe5b056144b6565b600191820b820b60009081526020979097526040909620805460ff9097169190911b90951890945550505050565b600285810b80820b60009081526020899052604080822088850b850b83529082209193849391929184918291908a900b126153d1575050600182015460028301546153e4565b8360010154880391508360020154870390505b6000808b60020b8b60020b121561540657505060018301546002840154615419565b84600101548a0391508460020154890390505b92909803979097039b96909503949094039850939650505050505050565b6040805160a08101825285546001600160801b0390811682526001870154602083015260028701549282019290925260038601548083166060830152600160801b900490911660808201526000600f85900b6154d65781516001600160801b03166154ce576040805162461bcd60e51b815260206004820152600260248201526104e560f41b604482015290519081900360640190fd5b5080516154e5565b81516154e290866135ef565b90505b60006155098360200151860384600001516001600160801b0316600160801b6132d9565b9050600061552f8460400151860385600001516001600160801b0316600160801b6132d9565b905086600f0b6000146155565787546001600160801b0319166001600160801b0384161788555b60018801869055600288018590556001600160801b03821615158061558457506000816001600160801b0316115b156155c2576003880180546001600160801b031981166001600160801b039182168501821617808216600160801b9182900483168501909216021790555b5050505050505050565b600290810b810b6000908152602092909252604082208281556001810183905590810182905560030155565b806001600160a01b0381168114612beb57600080fd5b6040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c081019190915290565b6040805160808101825260008082526020820181905291810182905260608101919091529056fea164736f6c6343000706000a03a9409bae510ed64997f30991bd0b568895c0ed059f94b2a0c7fa157b6c54ba1c03c39458febf9d85729e05bd59cef4117b89d112c94079c576bb38be1061ee6868030db2dc2aa93eaf5b06fe9ca9029de720df7aca85805301f423e6090424177f680355770f6d3f490777fcd32e6b21ecffadf8acf0166533033b1117c026f30ca7f903caf6e82a98aa63ab2f062b02acb78a98a1301bd606cc19a70a5e3c43d1575ff203d00687cb6d0f43cdc3da0681e56f4cdbf11032df61900acb54a154639825bdcc03b4005a49eb0d447acbdc866bfaf6cf7236fc912bf2c00a133fbc6c9ae35f7764033505601faab00774dec9e01e33dc36bac7fb1932bf7163af088c7a343ebb101d03bc1b9dd3fb6a5a5881a43bff81c58599c70e780d75b55a0fae58b303acb3f47803016020132bff32488bf526671fd6be86925759d43706442c66bc51b69257d984039c837eaf4c67ffe3d80665cbbfc1c437a98b075d1ff34286ea647ac85f31f72503db5b0a176f2b806cf76b6d88965c28cb86c9dcf8c4d434f8a950a5b0116528d503a5358c6a5f79ae42b6210e2df4d430c489103760986acbf001a634b742e2af4803459b91438aeeead7737cb7bb4d139a66a2cb6b477f545012bc473242d6e34aa003bdf4876fe625534fc83cc47cb296acb2f657b20bf5708945ec4134b9864db09b0303345d57f975830c79f32e99db1b7bd1b05ae85a80734e488928a34e7a3082c303b511b279e462635396c5c2e08151c7b45118abd475f3616fa92699a68b1e035f03f6c4d262648234ffdcde2eda5c6a1df943089aaa2067b2d7bed9b47bb4f6b864031a9a97eadaa2e96c70a232aae5d6c14c0995a3a61d5584982f9990bc6f3e48a1033c07fcadb9bcfd84091216785d44a9c97328512bf42528561b379b6eb16b037003ec96c60024e2e95d8ff59c87520b35847622386e2fedfabb4191af98a838778f030b8de65ce98c3fbb4197b3b43a682eb13860aadbddf198afe2d4e5dc166163e9032a56f7a4fd1e71931d251128f2865d182cc8320fe3b00ef7aa2d330185e2b15b036ac56f130dd6914546e5e17e93bd5d03b406c303a5bf2ca66eeff2236f86a27a031c919f8c538e95edc972213c19f7256b183d2130c6ff4170ad3719c9d50007c503a809dbcfe4eea2000d66407a28837ca6dafe6549d52ef565ec99df4d707d0956035d907c4a82aec27bc4ea67013bfc763a8e7c47316b631fbd0f35f97d3ea1c0f20399c952c583a8538e9c332273b9390c53148080b5960da078471120784c783831033710c1b16a0099946a555bd128113ccf7e8a9c380905490842ac74efbe95a88903ff6e430266a49811c8ca1cea5911052aad1acc9ec30e658504686a3f0bbd584700581f028f0685396670ff72cccd173c115e4162d76b7899e3c426cf264f8a019e745820fffffffffffffffffffffffffffffffffffffc94584a2fc867910b23ee9a573203738762bf453e3604edd8bb1b92cb7844765757bf7ca6fddd100d15d2ef0ef5890397df86e2db01fce5b66e2a53d59c321a318caae06d6f4a7b8d4f1381cdbdfdd700581f028f709fab72b19b2c4f0d64da3b47381ddcf738d326ba3c9edaceff8cd26e520b8035d4fe9a8bf66b277a2f5218a9032cf900581f02949e8fe01ebc03bbc0eb2f200858aec6ebbce476409d3808c56ce0ee2fe5510288fc475f48be0582741bf76374f302d900581f023f9ee1b9263c14c142f34feadffd223bf4986ea01226ac408206b46a4dea5820fffffffffffffffffffffffffffffffffffff24f3f0d6960f3a14b49651b22ba00581f02d0f8a7374d603ac7b817e98139e55985288f7b18b30591228bc1a36efa6b51d018c6d3eebfbb724eb04e21bfa3f9492703066588d82452e7440b9e52f3df54cc05f9993b70309d4a980fb0061697651a0e03e3db0928f8c9660f594dc5f79e726842b4b9faa9da487e10642006631928089e03c4a1d95d42cea538ed53611045dac42053250cdf290906d1a3b69c9878847ed600581f03badbf71f64fbee1837c8443357f822a308ce7606f1ee812e188c6bb4d3304e8cd8cfe37585bfd2396525aa8b1400581f0313ee94b42df922917157cf6124f5b034f85f74dfed5525a3e1e1d4a6508051a57f50bbcbb640a2abec354b98206b96210219800403dcc8bc835b3f2c14a4df80126bc1d00ce17599a03e3358943c18b4fbdb4e73a40141090219b7df030a3f27749aa61f9e4562c2d66349f2ef48b443654121ca1d95394f9b62a9595103cbd6536e2787001dc3e647b7f604aeb036093e0aabad0eaf0667a68751a6a3130219ffff0219ffff031ef1d0fcad25b625e83b69a94c4f7eb6e5307defc19ee92585940dfe323f0608032fd3bb81f0a798f48c2132f1776434c8a5b034bd119dd4d84d92e07c1705f7a403fd765f02b08a207635ec4de326b415ec9101d572b9a3db6aa5972c4230e7fde403aed8d0bf34a9a9aa76912eca8a0b52f54a817d56dcb81a5f33e8e493a1e0243103b5d534d903858071ca23b2a144b5079d54f9955854d2ad6b4fec6fb5410898160334c5a3022c962d1a9af7a85d25f46fc1ff8b7e1d7c0b07ff30a2a88944b4dc5003a608c12b1054756ed8a6c45dea02df721263f183b7fca7b132468e76c345ec67031e769b33c503eb1469416457e0a38cec4d24de47223f1568cf5dced0e1370b3603af1656f0c8cab195473e3e26d78d4064e38c71bcb4ef91f8c3e0f10b11564bc300581f02a270d34a878c930c200816f97e27739de97d856508c62f1a70c0f0a87c0951688f5974132c86bc481ef98d07a14a5f8900581f0271f66e770122af4ae0917a75a1962bc3f3f49fd956b7a5f53e1863453893510709abc1a6a92c71b4e7efc399776848ab00581f023f38a9332cf604d720097fe85a95bde0c644e5f7fac7df870ff2deab889c4e553aefc416af25c8b4a3ede74b160379e19c3fa9583e916e40800a0ccefef0dc40897e9111288a2b736b680c960c6d0358f170543ffc5ea8f40b4ae819c9efa998c463c930e44008f7d1fe6d0b66d0020352b6d995882095f8cba2eaa59cfb69198787ebb942a8648ac796f6274e1345ac035d8a50a4fe8862abdf75e25419c8eac42d46b9f880ab1a057de5221d24e7c40300581f0207d2aed1c22e4caee8114f1030a54d68b5bb045decda87a967692d2ae8f65820fffffffffffffffffffffffffffffeed5add836963bbba34c25d5a88e6c6041800581f032ae9de3a703a4e67beb7417ca9d3e1c00ea360d1727270ebb63174542ed04e1710a1db042381984d4765a565050361dcfc1abce5d6cf760509ec06dbd3dec7993b11f938b577ee6ff55e37328b4800581f03f1ea8dcaf4e3082fb14f2f614c5cc395fee9ba77ad1cc0ef543d465cfb905820fffffffffffffffffffffffffffffffffffff901829fa4246ccb593074c2d79a00581f03cd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5630581f010002d302d302aa02fcec00000000000045c7df8041e139325635ef4b0bd00219403803436af0c2f3433db1fce59d30c34721fdad29ccc950fc3b07cb47f654a74b4a94038bc7d5a1e3cdf2fd15cd26cbfecf79c5bdbb580787dc682d78417265731d132c0219f2f9037bff3319d045a2e30b4575a21a2ab6b64123e267c0369b2d116a928af97b218503b61360653831f8c03f18d4f3fce23dedc9a4c4e1328ca22608eeff50975b327f03b9a2e05f35836286c97299b21805e09b90b5c55fc17c35aa69bae7bbf08c781f0313d877437922945ea00abf5e98399d9690507215a518a66a72a51e0c53f8b7f2030f5d791c1282ed1b19337bfdfd9d9dc06086e93facac0d7d89b43df0b624cc80036ab69179cd1827f26168ba2637271265c3ef70578a82d3b8e9e3b03a6fa406e903b10db331e6d3a3474b010ff2229e0a33f4da26ca8c5670465b9aba1e334df84903f9e8bb56141f36cedd13216ed148f93a3070d6fdc36b169e4a87f14a889a9e3203cac01ece6cf9059a76c68750eba956b1432a3ff1efde27e553092ec425007ac903834fb3c9ec8beb36383d5ed9439eff1a7e4f2b6ae727e4c949e13fee01cc9ba60385443e6ff0f3f97187e5f9f41f42f634296ccc749e9fcedb6963e30711d7022a0399087c15a9af7b09a484593c689db576eb75396a92c68e12f5c99c0f5622677e0385899877ea0148bffd805af55438ff13a27339596fc7ae69e799c7f00e89fa600341b689d97e85b11a5c91571cbea96d0f2a5ae08e6a76e6453ed861ea25e79b6b03429488e06b2e859a7148f4229497bb0ef22655194231475a3e3a54c30a97a4460219ffff030c8e6528b775dc2a19b692b963553ad2fbe88dbd0612f7c82fb430cd759916050304d1f495a26259a75532f2997574ff6765400c3575b89a0b152fd34fd49d12eb0350cb3eb259f54874839a7313e27cdedaf1ef9e3a948a7946a4c308d44b5ef030038e9cb8eb024670cb50c2aabdfc39b4f8e87934d30d21409c55c648795d8642170302b08c944cc96e6903b06bece83724f8fafb463bad51c2ff049b9150665b625f03fd1608caa50e20e8aa4fb197e0dcf7c4f43e24910b636f9118a45196e2db74bf03edb7010bd41174754c8f3f41bf0750f2319e3377abc0e78c903bb6a3ef18b424030299ee32bb36d4b5a1c7ee2916b397826e22c7ce2f9dd2f4e7f1fac66871f474037c10008aecda11ae2688322bcdf057cb138ab2b0a4751c3ad0e6f7cb230608bf03d4c59cdddefa2e2df38c870a9e1367e75bd8a83dd1b5bb11b8fedb41a082460f03b66c1167576a8fcbae0ee344150e3682e7be051935c72f69f2bbe92e59d3f924036d6da17e2e9e8b392aa41f532404e564f40dac2174af9c675c897c54130db5fd036a89b3cbb4c90bd1b4892e3b1cf858acb68819b5ad6a028043bc07fed8da064d03bc9753120fbcbf2dc5f2be0cca687745071c9011fbfa2301315662af213ba3ce0374e556991e977b6c4c0bf5a66bef23ff40d0d0081b3f67aabf6b86c375cf8fd2030c5164bbd4f63d97461a2a390bf1a81e318777ba5ffe88fe1a460fefe0f6f14003f9bfa1225818d1c7fb0b87f16d13fab3b19da0245cb2e94bd14d55aac0089b8f03d0fd5a7a33637b19b5facd1590e10b8a4eeef8df393e8906d51141064916c2c500581f02de3ea156a256657977afdda3ce260dda02aa100ef127e64e1238171a200e5081dfa7de03a8ae4f86dd851f10943f0600581f0316a046199394ac214b95e44629cee22356a47f8b773f6a63c7e887fba1b057025c7774f466ec000000000000000000040119743984e200581f036f1a00d0564fb1469582048fca055cf89d83902c9086424d852045a1d830520b974a16e3ef32e40abd2c0df622473ed55b02190c00032021bf42ed60817004baf83216fea40eaa22e39d5c38bf67b151cbc6896967e203f4a06995ee11c2e72ff931bc67450f10db12d6b025588cdac11d0e86907537d103e7657221b054e9dc7ce980c4af9c7725156e716ca1ebba66e9f28a3e97857e2a0350aa7f1a9d1810b5c3a5159c75f36eda04557bf76a1cc0faf5507b8fddf46df000581f026d262cd811a30284b81232e9bf90882750e2f7d15621d1b2845454a40bd04e035ff198cdaed211943e6c4ac12703bf60dbd7cbb369583ca3c5bc5021e757d2bbc58302d593a80bfa6030176e53bb00581f028a798104329a85436c589204bdb85c62de3c8f5b26732a1dfd8e35ca70a04e10f49ba6f671a45838625b819c510389323ea2bed538707f96da1ecf4a0213d57ebd2c5064c2e7f4d50e32bed24e8e03b6675df11704690c9c8ae27f3edfa4751ccf200566caad681751f012c3aeafd700581f020bbac14460fc71bfe9fe4011cb6afde50947dd718d58cb5ea07ec442fe634e02b7ae1237079eedb07ce6a541200357f1e4b0235bf39f500c8f7cdc86272949e92cd64a74783d2cbef0376dc4994c0219fdff032cff2600796171f674444e883fdcc2699f8563a7b6e072a842d206e70fe1aa7703dd35804b97781fd3dec9701f7af8e8d8cb12d32b5fdf77d51a1f938c61e4e5d003f3758f35078bc721a3c5152d65823f0d5bc0c66106eab892fef9d41747718b150219ffff039e0ac21f9fa9b207d1126c4dc7d4db2eb0fb5d81145859d2dceca3ca745670220219ffff03bddbdbb6fa4c722e898472d01bcde3c448ee81465baa829a74b819f1a3c124f6036f7823f33dc31a8900cb26efe909b235878a7427941edd85becd101b7aac3c34034c5d8a4d6cef2a707054a4b189c138b6c99ad1ad2899dd08839169b4c14e03ea03a09f2cbb082e98dafbcb095356f3448402439e1be314179e743151fb6611a43603793a4a912393b8c5c1e3ba64115233b700239a6651a9a6eaaffb8abb26ccc6e5037e0a85faabccc107886233f00bcad8a7b99a5d4cf8d75d62ddba8587b1dc597a0350fbd47b370708c3eb0c36662cdc1b00563ec2848dbafb5f60d2dafe393add4303196fc24869b7404a73ef35a5a1e107a8511121cd328787f801dd1d70ee5f78cc00581f026f67741fb5c97fd4e6e38587aed89a4c5f99bee0295dfbcb0e7c15a7126b5820fffffffffffffffffffffffffffffef29b5a3f7d0e6bb297400b624e55488c1303a9f555ef0ffddb36d4a0abc960bc6cafa5cbfb6dcb801ea0d4252ee5cbc0245900581f02038109903912d00002891821f81f0a003742e7d185c60385589efd6527eb51bbd881ba23f711227efd1b4228521fcfce00581f030d8531f14ffa0edd1b45a359742384f2590e0f5b52314fc8a83c6fc5386051315636a20608c5d3ddfe83c952f198010000581f037fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace0520f6f9f815473230c93883203defa73787d8c02190110034a61fd180e182b77c38735a82ef8c961455d08b6ed71f227bab71a02e91fbd2503cd55653f846e8ee798ee5c6e64940560e26bfa1b1d330986f296e14f504594a600581f024e822ad8405b6f4ecf0e8b15d647cb42e09803d2e97a698b7d9870fdc44c52033401fcc8a02ed0b99c7a0ae488497a1d0703a627bdf904d65b61931928965f7c96dfd0eb3d931f5b058806e208b46f67221000581f02810f14e8c3d6e05584679faf9ffd115180b163d5f56815227280ae2a67bf5820ffffffffffffffffffffffffffffffffffffff8b1c82d67baa165f845dd94ffe00581f0296b1232df14614e5c12968ea51a5d83ac39ac0e2000def3a7dfd1b98d383511d2d29a9f6f630d5c4d7617d82a289d7e6034f5034a4be179c1d3f7b9f98500cf9316a0de5f5ec59d19b4c0e9d557765d12f0219fbed031bcdde4711079fabce471db5c2950b9d418e838dc2515cd452795893a9a83df7035e388749675f518e68949f0f6b1c6f0ed8142c088272fd0f0381743e44c2edf8036dc85e7b6a71436eb70613d5df031131ffc301a80b46e18cc542b0b354d860d603f21ba55d0654baa912c07dea53a9dae4d09d3f53c7d25ac35a9f321b2b95f432036d9b4d4802c6262c2e570aa67e22ae432ec6c826a2806ac1277169693f7dc0c40336dcd5f921f2a83e85c7a06691a7a799499c2224228618a5ef3099d639e3c55903eb35ee7c17b1f53bc08d0a1da3f4a767619e25e0069327c61224ac70cdd2e8c3038a63dedf1bbeadd536ad901183f549c175b9101c09deb223ff76b8cc771132cb03a67cce624b7ae642159fde902398c1d4c3a576aa648c73971094716c69dd4be903a7254876156edbef4b4351c8361290ae780dacddc45c7711bb7c89fb1b8b5efe0219ffff03f063aff4ce3958bf5f6b4e02386a22be93021ee5d06c9e26bd426423971523bd03f363d1e223fb42ab1efe5b734435918135c3c0008e00f5361dff6e5dd340b9a003fd2787c70cc36406507f16879a56348854a53790611b297a7ccc8a52cb4d3def03ab32e9b68161e053b0a9aa8c2e44f56926d0eec6c9597ca415a432301e12ce8103b012b8db350b4d23dc2169726427c241d35de675516a277765b02eb65dc130bc03efb316b796cbdcc5e0e602366f128b41b42cf8e8889d36ace2d52fef2620f93f03b7a988e7735361934ea075822e45ea08658405e84d80258a3787617f24cb6aec03512b472b93b296ff2929c5e5f2205936c81755263f1d91a7ae88874af8edd966033b588697493eb4308a70f89e6e95c9a06719271ed3b481e16b9c11c3b966830403673760184fa27fb85240f0affbab7e5620322c9cec408374c6f8eb0479c531fa0386ead46ed6063d126127df53aaf73d9e893db5de64dd35e11b076afb7df8d51f03b214091ca9fc2fa282dd7ed4d2e281340b708235a16d28806c8aa66aa9f295cd0360336e10433369ad6eaae5df02bab1c22e16409f23cba3cc5e6c883f7b01c8e603af145b9e9a63f8a5c6eb69be3897a2e34ec7479186c698f384fe60102a5a9c4803babbf1a8708ab914140052debcc23202b130acd103a5b312d87416f812eb61880219ffff0361a931b7a37936ee3ab97f1092df4ce983d1be53469eb1d8555f7e0a714e29bd03e802e767c9432d810f42871bff6ec8a065b814e1767d089c046fe4b3c132fc8a0341b1c6ff9e3c292ee3e6222e9134afd81e48931e7188105cf89779c9fcc7696f0353cb073b344d7de6c2c16a01b2a9454f7665a93b68b7a89b049a447bb94b94fb035c22b188fd0cbda44eb98b93648d1bee115045fbb7b14a9a97b6fef5c8d651060313a1a0364af6cd7dd8b38a0a9d0b326568b6051da6b52d1f4913f3f1f4f3c87803ef9a336a59066a4711d67d7a7560051b90d6f4461eb60a3083440c54ddb8956103d7d07b8d990691565c98e60dd3a2dd1d34c83f8a923258fbfff0fe3b36c347e9036554ac39eb32410fdbd96235a15484cbf9ab5cfd1fafddfad0df095261f2aed103ab3ad6cd7789795a0ae3235bcea8d84c6becacbca69e2a82c60779ba6bff475603c15bdabd61fbb63a543452d10953e014b46a633e733c11f97005504bbe5a97110338c4b4ef22fe3014b17c0010e5747680a22b069c9998f94fc577ef8271eaf05e00581f02ba1cf2c48b5a9399a62a63b2116a7c133d0438b5df93e94796943ecd1f7d4e05a1b4816f4353e63449a6025bd400581f0247bf785e9d779515b449783a1a33cf271d11ed14ec1b706f4eef86ae23da5206627b038d4541453768ac9af2cd58435cc703ab43c1190b83f416c6861c57a56ae231952dccf71ed2ffbcf2ec7e39024fef2a00581f0243360222720d8eeabef21c0606cf97bd83cf3c088d4a3d204e51fb6008035820fffffffffffffffffffffffffffffffffffff3938f4be20ddf2fcd54181d821e03ba74d5308dff5f5c64b3ec3d11f46892290c42e42faa88cb74d3f72d1c6c433d03fefad8ae05efdf123e7abef1bfd6fda44bc6f290448bee9a665f6ce640918d8800581f03f1262cf1e4601e1bf98804a053ebd8835d7debb65f979f9bcbf3eb7762604e019650f7ac0fab2b04f6ab6d019800581f035117417fe47c3d02a96753d77ad7889bc91398ad48f31c2e9ef1c975c1d05820010000000000000001ec37f6fdbb55510269284dc500112c786d3c936637523f0219880000581f02563383614c87da8c03f1ff71f7c99ba90044075b850c9836200b3f0328fd5820fffffffffffffffffffffffffffffffffffff8c579131a2e5fa790b2d52f59b803af8d275d7a7e2f042a567fa8df1944623b2aaafae0d7fe8a27b6eab0e6a318260375144daa5dd6b59b7809e62e7d94bc3f172523dcd3823ef9c5892456da33ee02021935df03bd920536bea62a2cbbd3606a9667d6d383e20dfef30652f093ce94216d969776033c1746a75b512594ee06420facc1920ab04af68da89861b960ca917e3191f2560392a1b569bbaada052c9ca9b5768ff13bd739f8091e91c7324e889543cae42c6a034a7a9b5a822ef78def5bc5c3469e92301a2e9ef42a89b48661f0cf6f5c5fe922037c286434ac9d7d57ebfe9fb6ca613b9358a811a3ee37f6a46bb56d91232fa7780322ef4e738a1a9aceb513d1bedcdc272d0c7fc2d9fb40dc5f5b09989ebc5119dc038882de9b6f4a91c84f44067f08d90cdecf8200eb49d1ccae38c441fe955490850219ffff034f2b5a61a17d576d5dfe0c2da646a2e2eab6b39dd3c93dbfb5889bb5abd9ed17036b69fd77250901874972c2e61ed5fe6a8e97ed45db12fe2bc02f1525df3d3b6e03b7acbe0a836d3042cabf60ab4d5ab772b00e81df58e535d907cd077ca82aeecd032c41e58b702a6b9702e5876f9c7e1e58691926f2d7861a76f8536b571489a3a303a41d24c23a0a2f6882ef34ed091a04a4448113b5ad0b77d1011b46feb8fde15e03e72b201a505a5d5e3e1e91e6613f509a46ef2e3d52f12886d1f2a5f9ca27445c03f1abcd9fc69d3004bb705231d43ffdd0b29567bcf846c0515aac64af6747475203babd39e2140f4eacfbfeef6f804d475cdb76a1abfeb76c0e231c7c120a24644103235742662168dfe94544a76c7dbbc9fa89c80e602dbdddbdc3d5c5c65e599c25031529daa320cbfabe683a42d45f90616a8c6cbe1cb77c436f0f1662b511540a5503d0eb17fda48489bd22b68deaa159cc070e602abe29fee71b1c78af7f246d314c03804e965352bd418bc709856cc1591d3ca673a0ad45ee91fd682154a52cb33d220356bffa2615752bff5bf3c3375308ee50525e4220cc477e5c0536889ebf3c89b60219ffff03d34e8e3ce4792ce730e5ea446ddd68b7c3da4a669ccb48f4f2c0f8cc15499d0b037fd1f7100c7a13ecc7ae5479425b40d178f3e2ba96991ba49b5b52ddd8df30a40394e5d71141713bdeebe3bb4465a4976cefd7ff3af1061a2f53bfa67ddd0d5f47039b8b4481f83006607eb987af29e90753240aab38eeb708ed38cca6522fa3ddb0034fff37801bd1ffb8292bb216aeedaf8c4a8fe2e913670eabfece7f6d5e0ab9cc03ab380126b6c4d2aed83093cc4c291b01e1e48c4293a7fe2416a1344a3ff6e30200581f02ef777138e4cba411dc9a767ac7a3203ddd92591952e807b18685d870205f4e02d9433ef737541ab79e5f09b7e80353652b0797cedd0dcc286078eeaa19d6447bfac2782e13ac614e1c578ea499a100581f02b0b15ad32f4a3347914b2c46395feb3521463d0beddd02b85aa680ae331f4e12892552db462a920e445d1dc14200581f02d5c9f25a67df122fb5586efdc297d289427d89ff95b50a4c8a9eb37ccbdc510336bafc106b09e08f8da10d9bc686ef4d0386ed514a9930a23b0a420c99b5bd8f8cb8d50ec1165caf11eac3f57a22e57ceb00581f0262c29930c0510253905f46e9c81e80f03e10438f5af5055c401ec034da214ddbd10e6168f1b6a7fd06347f150373a4367d619814b17e0bf359b43e8dd84d2340e0036e999988828b3f332b399b03db125e419c22f4a92f5eb12c08b376ce9c1eb5025d72ecd996ac2bb15fa94f1800581f02cbfe3fb6d0a18c485d6970cd873d21dd443ff3fd069a95a75bc4a5910d434e029cd2a67410084125629de490c500581f02ff334b747aaf899e44d0c71f6bbb85e4fc46c637662e1e1034a00d93bf855820fffffffffffffffffffffffffffffc428fb20bd91d7a0534a159c31eaca7342500581f036c5e2313860013aa316ef141a1d2cad49ca101920cd8c14019f4c0340f205820fffffffffffffffffffffffffffffffffffffc828dab6d53225f2b7615e1d4f000581f036ea11320a3dc190c8b2deba80987707c0bf2e141ebf5528c6be2ff3a7e905820010000000000000001ec3943b29363d2e50baddce200112eb38626776638116700581f03544f515c5b75294603bc55cbe838349aff0b71fcba6350c77372c8f76470570385e6d07bcaa50000000000000000000000000018ae6302195010030a771f999059b03f62dccbe48e146f40d829355f0fad65afdd83a1bfabdf12510219f5fa0323418dc7fe1d61a02500f275034bda9c1d335ff352279b36f2a331fc147b33f40306e269dc49cfb476748636461693cfa00595f68063a156f4d01be09b4336e1db035b90798dde1ba9b27a5960832a75acfd6f22ba4b0349e930cc9572112b1f7c8b0337fc15ad2a9a75a54632b561bf3a8ec2530532930b46cb7b50154c0183164adc03fb6e231b16c7d3676d5c1937b2b7296194784711a6e43deaa87b54e13a4ac28a030b6194b36cbcb072bbc494cfe5b3b13fdc95a415d6dd2558341802c70869bf6303277521c48520205eab2f78e6a5062e682dfc45ca2f6ed805ca7f8077593b321403fdb5241c2df70824f252fc5985f3f134ed46e4b9bc4a350863428dee99a3999003b213fd19e871e82201ab6fcdc6ab269d168ea0a9276f7726563095787b7b8d6103d6528d094a639af9f6d1238c43c1be250c0cfbf2525d42193a4634020c7f6fab03edc4aa4fa43c1f2d5cc30a00694c52770ddaf5a157c0f7744628a0da5a1b910003e9641262ec2df4c7f411214b8ccd79cb61889dd6df133395859e8fa17c9a2d7a033b97b334b1fe00f37446650ad70f792643a24472e559e385e663a8063958731903a5f6a77270b71c00f62be49b3e4549ff59459302a24632ce66b5db5ea9c0fa0e0219ffff030799b44fb99efb9f4a8e750497262f6b78ae46385607e3886c046a2bffdea62403a3a79bd47f849815e0d1dc39a4139e9659331405d7029288bbb45449429ddbf40361662c50a90f6878bfe85ad62f1fe480026d7a8868e0028adb539f6170912e4703a78d4aaeed34e9c2d2a2f0cdaf29d0722deefccc834c38a2448b573fcd1fe5b80378b5a699ee6b52d115b79f0a0fecf912aba09795095e57d92be804820eeb225003c40a66aada0600991db53827e14be1d86aa979159ea39eb0cff9586cb4f2a3ea0393cd7ae831ac7fd1b2f24aa7391ae85d2feb6854e7bd82aa1df31228f7e31f600317bad6feadcdf436edc80fa3cc2a011bb4cd402b22855c248b547a7380e7189400581f021cbd3f4ff6b2e1ffdc9724674a9dbd7424f3307c61703c94c0f4aa8277f94e0595e10268dc42533fdfb1e31a3500581f02304180e4199135efbe7733fed16c32a46c6fc22b64bd08e6915d78f7997d4e0177bd23edfc737f455439d9adc00342a7d642d810faf29a87b8571f29e5fbe02afd2eaaa977267ed90d6b57947e4c03e94470e6364aa93956f48eaf977686657b0d3c541367c1fb8c4ddc3fcbfbc1bb00581f03cfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b0489339152819261ec400581f03e2898c2da6141ef1728d6a6d3c435e4437a98398c1be7a27025ec8c0ce004d0ef3077d434c0b6377b8c4cf480219148000581f023ae9a21ea4d07d15120a8a6f81b993753a000cef837befbb7c68a148311b5820fffffffffffffffffffffffffffff7ec2a7e9cc6fe18fa4f653882291d4108f50396576604990d6331227986c73ee2b9e2897cc478fffc06a6ff26357cf9c84bdb03de7d0f3505fd7f78967b4cdab85ff93e50cc03020d16de4b463cb361a31f826b00581f023c0c993e7c49514d47779dd63c54669ea03f430c64f9b05e50d712bab3c45820ffffffffffffffffffffffffffffffffffffffa06f4a6417f9d31db0bb8f022f00581f02b043595506c12c0e78b27989e21617b71e63b7e3a55d2f5e472c10973ba05820fffffffffffffffffffffffffffffe24bbc04bfe0c60f8493c54a5dc197e17e50399bf7948bcbfada507c1baf5ec90a15c2d2d4dd58465b2d6f0c32b87e616a905035fbad3d94611fb9aa471cc1231276dc2ecbb572be679fee8c1066e91e8a84d6300581f0234b85b242cefc5dac60a1dd953b1881f44db9e4e8ab3150f0799b32b44b25101013be52bbd8522223bd05058d2c72e0900581f020774babe33ae54966706c76740c86f170435a4f1ec938b624dacd13d408a519649e7be9f42bb8878c04107149b0bfdfa0219f7ed03548eda36e44db26b1e3238b12daeb8a4f0600d4232d304ec38e96ab4874267fd03a513dee84e9fb22c126b74255a2ed981c5cb8b884bef707c0ae05e85c1c21ae503982fe9565483650de8f25e27a38f3e38ff99e4aee463925fde9fe5b94471fc2f039ddd2851a2aea90207ce339258021d954a6be615b388404a2e9f65deff7b7d6403f68d303b2fce068fad597904008c9fc70f0b3da9cfabffe0514355bd5e39c12d0368c44e2ea0a92c46c55708ce3d7c5a1b8670063fe5ad9936814ad9146e619504031e9d4b2f9a139c988905a579e6b35f95b24adf367091895739bc7e13acfab0cb031585757b8290d68b4a3e4b96926c207bc68b6c9dd35e9509fa8c286827b66cf60341d86af9cb9e55d85aeda4708ea5c72c7f1fcf70ccda606d88dfcfdd8a1ea9b8034fc89f555826f3f706cfc024d1068a7192f270be3b78518f79d17aa9061c816c03afa118e9175f2ea6c0c364a6f3a3104fce6ea304215af310d60a9b42aa25ff3f03a0c5ebf6c1d2aae7be3bb1f6bcb008c6d7f64592a2c6f3c64c42f53aed3484b30219ffff03f016351b6ad2e63e8aa51d6b52cfb7ffe023946b6990246905756c0e8dfe695603fb0b06c89cdd39b9c293e4f3eebdfa1a676e7059c303394f22779e9a16c81c7303fff60c7dbd4a77cc8b102a998e1190e6070c04bf03555e9ac61047addfb912a203354435d5eef754960fc4d357ebfcede84bb5cda68214ff7505d2506a06dedc560302b4c77ec92ee579dbfd86019bd9144d4f10110a21b3710ba827f90fea3105d703e80bf5d52315608b107d0123c44c005df917ed88c90950c44dd5fab505395fe703b809f65a75f4950b06c787b0eeae070674a397e052fd462e74dc8a5e8c42c51503f1c38a32b3cece8af2f9cb1cf01727950ad3e911a6c8acf1b854d5e62918c07c03a32de4f2ed74ab765ccc1780c36bc3ece58b8bbdc905ac3a3373e075e117c21603607abcf39cb8cd947ec2fb48f97253ec7ecaeee3f1c8f7a5a45e675ddad38ae803d6ec675bf99209a102ca5522e16d237ad2bf52e050a9093cb8af37cea8c2e6c003d7ae3dcb194974fdab42cd858821a34bd9eec472c5f1872629fd7d26819004e303da670fbe7d1dab388abeefd34e032151b475af94605f76bdc8df338cd678a81300581f0207a4ec6c5c28665131640ffa84926e0ace27570afab248f8c15cbf229e2d5820fffffffffffffffffffffffffffffe1e75d0947fb4ae306f5147ea67b38d8ced0394ee3e003a9c41ce77dedae2bd6fc0c2083858ffa708e35254452ff5f28d6e48036b7b63f9817f31dbee9a852759a41fdc69a9cfa779f98b192e29f869910d8e97038bfa43912476b04701a4e6603d5dd3be092f21ef342b2183eec4c957ad3ee9c100581f0214a316424a65ee1cc2cac6333a589c5b71cf1291b2c85025eb39a2c97b435820ffffffffffffffffffffffffffffffd54c4f7c8432c054b6516447d540bdfbc900581f02e4f5b8634c00c4b684f425f427f4e3105a7a435b85d690dd96c0715f6bb54d67534969d3bfb3150614cf9cde00581f0283ca3d3ef68e03615301483f3a5e619a56c87af63e1023d6216767bc27115820fffffffffffffffffffffffffffffffffffff359389c60a0d9b8dea3c0283d1600581f0258dfd7ff564a85b62fa11d867992f07ecdc8dccabed021e37861f06a43515820fffffffffffffffffffffffffffffdcd1958864d507ba10aaef5d30cdb0ce413031183a65dbc16b4382ab825641a88b929b180c6edc48ef44a55c012df51456a4700581f023be2e1951dbadd3468fb5d5c8407f62ab18103c8e7ec6a116524bd3f9a6c5820fffffffffffffffffffffffffffffce5aa83186accce08eb5985d3ae04b419ee03aaff3b175d1f92adc3ccb5287e4e32b458247c6e27829140fecc2aca04bc17f3032a56fd8149de2f94fdcfc5b65030def24546087711b0a6ebf4766956a94ef81c0320ccdf63b359569ebcfd0a59782c1881c69ecd306b23dc5668d65ebbb393f8c403edac7c8bd2777987e9be58b52bdce1551549aa51fe7f250f42f1a172f9899fa000581f0337fbe85ffccd90a955153fa2054d1f2d72999fc3a961f8ca21187f4fb9905204a8adf2ef2b2c164194aed35d087542fcf900581f034507d586979bec0bfe77864feb7a81fe1b84f1a7a44c9f02d95f6c28bf105820fffffffffffffffffffffffffffffdf042caf79f0ac44ac5ee44be8aafa0629100581e02748c526dc96145113197d472bfddfc33edc8a4959e57156030b2d1a3095820beffeebdbf659f7fefffdf6fa7feffadfaffe7af7fffccd7e9ffffdf7fafffbf00581e02cb6b5095bfed1797135c69be933c8c53931683aadb1ba383cf68ab5daf5820ffffffffffffffffffffffffffffffdfcca261af7b4896be032dfcfaf36c232002191010021906010219fffd03f407bb6136d2f62e49990649a8471f608a5f817fa31fcd504d328289dd803a8e03df32b99224cc97b3fb2aea035e4d7724691dabdff95f29a291dcad836701a98203e412f925034482068b876168680802aecd703cc71c84e3bdce7c86c2840b1718038cdea0e04338c6941957967f90d8afb63d1828b7e60b494312812c09cc097551030b7ff3754ce97c18083c6f8d260715c9ed98f8713e485211be5de882e286db750219ffff036622b32c1c7bfb9e348f41df6fd7047e6bb1feebbd129a01a643c7d0c0389d7e0219ffff035b5b02f9d161fe3ee95333c7f1861820efd97ad1fa8f9368850246ac666dbe8003d3521564268f7b75f68c8dc7cb151ec165836fba4350ec72556142550f388a97030036defdac4d5c34380c10461a7810a139ac78ad1891e6e7bf79085a06604e690339c7b76f6973a9fcabbe74d51ad4daa7183ca137c51f6fb31b2cf8a52ff7093c00581f02793934b943f755112caa96686980b1b7337280b94ea845017e7f87c22df651035a7fda787c083959f00d8d7e409ebdca03d8d15ae27ae5b0d60d6f6fe881be28b64add0c95f2212438618bd5af9397dee500581f0269434dbf0ea894eb6acec7c1074c347fe29250ec4c79a70c425721c43b594e25556937a72285bdddac770b9b0100581f02c8f6eb03b481be333f71428d9cf4004530417ce14258b4c6e8dbe640d8bf4e0d9f7a5752c42facfed022170a8c00581f02e6e7d4503cbffa8f3d16d0a3eb7caecdd9df9b4b4fe5c5dda6d031db25a6515e88a1aec6bfe66d2a5dfdacb7f243736303805f160280ddf3c14e59550b03ce33ceed0e8b9e74df82df12839a2d9614373900581f0274db522868fe88399bc0ed269c760feb5b366b4ac78327d04a1f9d73debe4e2aaa7c407ec6105b8a0a67cbee230324581c9e98379ff626b4af4d704566686b53892d56f723bae06dbce11b9dd23500581f03d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf604e8e9b1766756a8b5a48ba91cf5e7d00581f03764f3cfcfb28d82879fc2defdf54ba5690a340a31265f2f71981be4128f051255f75f62e721bd80569b9fe11c347a3f50219040400581f025139c16086ef4d952c094dd5dd000a98da680e0ed19bf0ede11d7b9aa49b5204d235bff6faa9a0d8ac3250876ad6c770420219dabb031dcbae02c6dfba872583b86368c87fa128b4109e2a197d889d6a30359c3ef783033878b08b3c77a06be5e6d90b938c4a07e3c2ec60f17c4434756cbb6edebaf3de03b0214625fb38f75eaba1d738ec33b81072da34d89cb5f2078543189c95074cff0375c2e3b31ba99c9fb932fbfcbc4a03a375f8ad61ec67e7fd56d49493691f6a1f03ab4c49cf53ce9005707730d712a145ac1a15d4646266ad7138dfb81833956b0003f12d7916b256738a6d160bc371cafbb38f18f28f036b2280e1007ca20f890148035f4755e2408b9fc216358271820c3cb43744c03243aa34b4151f229a6bd633c1034bf30e96bf389eac5b8f5ff136ec5d63fb8ea00e63b272de492b90e5803a328803ce19b478bfa4330d964d45476675269a70ea43cf800055801c445a6192602be6030f78d6d32a4c5b43b165ea8f5ae5830a9a892d129f8dca861f4a85f670fde61c035c6a51741eb0eeddf765af8c454469500c641727b91ce5672c5e0d4ace4265b30357d486179db04db6c087951bb86591b5186243146db894c63cad891cdd10263103d84f76124bde619e8bd743263620204f21470bb6001ba8e4d4e4f2f821b67ad2036e4e381a6e73204a879b9182779c180389e0e40ca3e8d0f7d94b746990b37266034b3d40183aafd90d8bfb8bc09799de7751434407ea04b5ed56ea7baf1f97fba50219ffff03cd2d68e1e192fd651e02f4abd2829c73cfb56989aca5d2109c553d6363aaddbe0378b3c88c367c4233347b0003397a00cbb8718d3a3bb39def7e270aff422efefe0382709d6782d4ef58145dc819ef652a7619ba64f411f9a49eec724a70e35cfe5c03842d54f693b8db764364e9f82c23f45e63a167b595695177aa3ffe08d08f36d7033d4086743081ffe13040f9f9c7db1ed9988ac538eacf37ada140088fb1f997bf036499fa197c1cbb6237531b358961c6151da46b4979c6030f69474a392f3a1e350309b6b14e4923364af8a3fbe2a1d4e9bdbd878c8c0165f7dbd899a80f04248df30347f7edd3b2fec7bdea7c9f572a6e42227837deb0efa7977dda7654f285d894d7039c322c25678921b00ca301c2018e000d2d2e0ae5e4e9d999e7866c0ae26881e9034f934c45b030d717419cb22dbe67ef34a0e2f6eee5cff64dd0845434fae1d7930381bdaf7599575f5915af94c48735411f3420ed964d769e6775affc79e87d5eed030044db15fbce1522c2f4b352461af28a026ecfc7b66c903844ef45cb45813b370368ae27a731d32e240ca272bebabee45e87e33bdf2106a5c6208cce8453a39a5003e519440a1829520acaef3fbfb22c2b2d207c403484d33671a4fe660f48df461c03cff891a8ea3c5bb3421ca14767af282294b4c228779b98bd987fddb2ece7b53403c88b5acc568ffe7d69497f5472afb14e95e847dfe8192aded618daa3c7cb896b0379168edfacbfb1924967b5bb7e306f113f539999eee8ce7af7f5eb59a0363db30392cae48520e6e6fa121c250b499605cf105c06538e275864dffad417690a438000581f0214dadb570a06706cc5010f8ac94e7038cd9b30706cdcabe6208f3b395c6a5820fffffffffffffffffffffffffffffffffffffa1ebeea7f998b0536c053389fa400581f03697f8b1b12e4ca9f5b7298ba1ba7141e1c9bc369eebaaffece9e21fd3c205201726554ad73e901814ae21c01f89aff720d00581f037f5a3d3d997b75eac0379536f1dcc1fffead67291b8d1453cf4447431d60520f4ccae30241069d02d884a9a85fb79bbde602190a00033518bedc6752407385b842305a3a90171584b762b7445d12883036a35ca1586a00581f02d5d1a3791354a10e552903043182f1c2aee4990924314f82f9e29f5260d35820fffffffffffffffffffffffffffffd3615f9271b42da9ba2f067b801826b772303ba21a0935fe09dde5196814362189923103d79620a472563e99eed84b4e1c86d00581f02c6c8c829d617accfb2f90c577868f888cc22bb24c73f5694f64399ea620d51898b3b1550b725d86d326b665cd7f063560387bf7e89e6222b309a84d2287c8da959144c233b8785a5e9623b2c4fa553cef400581f02d1b181357214db009b6653e915340148aec6c22b29bc267feb3dc93e50084e01e478f9b489ba2d76552a89bdcc00581f02899a325a081876398e448fc2e66f6921979b47bf2764becd447a3a6ea6754e01f068f0e874da0dca8c34745dff00581f0226710f052e8fce6ef67dc970a1d192aeccc36780956ba2fbe2b96073bb4a5820ffffffffffffffffffffffffffffffffffffffa7b7fbc3d880e9243ee46056e200581f027edd22f8053c5c4afdbacafb159fdaa7e0a9bf5557809d147aaf66949ae15197e98108a7da7dbf8d9c381b1544697c0f00581f028f9140c1b174b70cdb64920f4a54b1fb1001d6333ef05e11b3b71ef8be975177408f683f7f0b5b64e9a2d04dc85713dc0219ffb80219ffff0345ab3faaad38d306750825b5e805172baf459e014a4fe994bcb1aa1f6c99bfaf0328e74b2cb9dbe673a321ef13f29b12f926748b5e4f4c758057f03891b6efde6d03df7693d023e91975fc426f6abe9b27fcaeba063b717fe8371f5003932d5927dc0311bfdd78e1f560c56b0d8e934723f49b850798ce35c679842beac79755eddfdb0338fb0047df31fb05b2392b28cbaa62f3527f5e1b4e7d71065db7d9a3ceb7870903568accc8bf1e792955e97acf721b488cec3f8972721e1b08281a39ee55dfb56b03da879bc234efb8a2d2961e4f2bd4e7bbcc0a21dcae53c35f68010776ea5bc5c00335e60b28208395f45b33541b9159b8748d307b73803b284fc446829fac17082603ab8ada123035407c04d110fe99ea4343d067ed50ad7ff2e42b24571f6c964e5303670d49780e64f569e8caacf9b97cbd6cf45c56f68f93150d3c548e24c5b67e330219ffff030076db5bb84287a030521c9cb155070ef968c63f1a3009b5d7a4b88ba8346bac032badb1e26ca12b416e29c53d311d2aa5b65ba3f26c71aba6d9be430f1bd7ddd0037e81688edfd5949734a228e86f138b676cd748f019d107a08bb5af600b8dee2503cfd1a539be7216a1a71542467b95fc7a778513b2931d280e745e179e15ebf88303c6871c757f5478306c34effc8351dda2bc93f29cda5bedac4161efb26926cf0a0321366c2a24cbeb90f7039629749b21100e107de01714d95c74ab271a780be7fd0351de48abc2bd81e5dec45089430855a3cb2092fd1797e53519c655c5a40c170b03bbfda65193e744e411d2b9dc7574340c551eb0db5d7e5d6d436f177f18a8bcbf032a78197e76090df39924d55d0db2a747babcf2b6d989feb8dfb5b0a7801d3c6203ac8e1a7499292e45e5c13b062e552b2470766bce5b0dd986aed4f89af22998a003224ace33c0c31a8b9b1f1b9f78bd9ef1ddc50653619204e08f1471a8e676067703623a0e3b4f6e0ea70edce595bd4813c4c800ecfe9333704a27e700345156fd67035a0c11e2f3e5a2716749d92c13626e99f2ad7c5278c98ed4bcea6f02107139cb032090335f7c2b3fa666740c200ef6dd7c274853674062e819ab2862f712b1b63e00581f034d75319c3142c743a371d62c035089d18a717d2a91d0f0f7a58ee1306ae058200166293e970000000000000001ec1876588714887120dbf84200110257f29edb00581f036c8ffba777432a6aa34b94b2cb4a55bacfd8a15149ef3cfdef22ffa3cf005820fffffffffffffffffffffffffffffffffffff99b221da204ccaa829889bcfa7900581f03a68eb6cd8a5f2da94248c19c00debb68ace29f3b59b67b3e2eef8cf686c05820fffffffffffffffffffffffffffffffffffff87aa8b77a990d269887a71e151b0219102103267348baa530bc6bc8d02fe6b109dd01f7d3a26e1905d63a0546acdb5bf48c2b036807f0f629317bf7e506b3ea0a2d209eda206667bdc965d87064e376275bad4a0308d4de54ec1e4306a7e96cb8ae9511f51906bacef6181a4a4ef38a5718d755340312e8d8b1913d3123fd979e248447f2001d375d45c51acd5ebe59bf37803ef6ce0386a0ad911b1226bd97e5029408161d884f9ee9624ee628575746a3823e1b32bb00581f025330bc944dec7f2d1e9dddc7b5bcd05a1ae664a5c600c1c5c5928e81b9844e0dac1632e9f680f869c39a00708600581f026470392e4a1ca535314f3b20da7d7c2272baab98098daefa7cbc704cae6851010000000000000000000000000000000003a0b04bb4759d50a72438a22761453925e333421be4e178ba3dfac66cfbde223700581f02ca7c58b81b20d4c7f46cb9d81e6a02abe34c351bab2beabcbfe1680d55425820fffffffffffffffffffffffffffffff9838efd23fafd8e27be5ed0f7d70840fd00581f02510616274517d748501d8da95e114c05e79278a162f66da660af8c0cb9cc4e0189b267f1c2937ab6d788f298bb0219ff62039ac5a5bd910fa7b92f63e2c463c4cedd7f1b9acc8fb3fc8966337fe918234d3803bb42fc228886f9f96d7462292e5894f60ffcfcbaa191bd768cd5c4b7dd97f6b603225546ca1cbdb6494a84b03eff6f9c487366dfcdd43344587ccd2f0db23c052d03daba5e85c10eeda0936fa9b03c7187a99d403b8b21dfa02c04e8b225fdf134270219ffff0303fa9bfd6108a3bf9e00414f8454b8b68221fc0a257ab7c3860838ab00c3c954033b58a986e8019d1c41620eab7af609350ba4c9e6b563f1bb320c6252cf7258e103006e584da2248aaae606270024fc7134eaf59d5c99f62d4d1513658b5a6c4d3f032b4c61670f51a7bae8ca99c74729dbd2130e6251cd689bad152679bf4167fa3d03422ec39d1082a760f6253bd0333f4397085cf2005bf901c258ab58e48fd94ad203f9f9bfa06a1b6fbfe0b7b6eb26dfc4595a87f43d5b0fca5b7442a6d60453b7c8031ab48ccb520018517b9996f0d861cc266cda66fe2d58bbaac4c48708d685e1bd03ca9223f1e0934602de1d3b37bcbdc9dc75064fc1c92d42d4969ce25a59ee811703b1a7662391b87d9f32a893d7ca10b051ead8fcbdf86ee23010252e6482a236a90325da3b809ec95688084f6761e8871c10ad104545ea2be8cc873f0139405afc60036753e5d793bcd4fb3ef4710f6ee05e1b31b629a59a2e23fd7cc8cf9944c6fa5703bf07a20c910225784ad0f78ff79b8c17bf6602e4b38ec3b5ae940627df8f2d420219ffff03d3414f8aaa4b9c3d86852475d480d0c9c099706cbaf29e3d7c959e4a60dc0c3103504f5df128857c099124f8eadfc732af3c35a6d2738e72a0b380fc77626a83d10374adfcf7025fa87617675543d0fea5364ab79f3d9563fcfe320d66dfd2e45e790219ffff05581e033e8ac06c4bfdb2157482e7a0a264c20bfd39bd733cb3bf258faf72c4b0070119567e05581e035cc3326b15d4214027feba1d57c66e93819dfb9230fb9164794fa0d730040703b118731ad716c6e8af4d155f5c808fb6dc606d84914e7538713b1fc0f91df89805581e03f8426f382a41d09c9d0763379a4a28c2fdbbf448ec3b2323e29e14c5400404033d060472145459f111fdcd47ad128a07a8a4be7d018e0c07f2ef0e1a4c2e89620365f2b060d60d8738e0ee34086ad609cdcb0b23e62c3a0c3d79a9b1ea4b1f4403031f667e6a796099f0d6530a3cdc55500b1f711c4910315aadd09fe9bf32d3d69e05581e031b791e75585ef506d27454c49c394e3f3315e93e149a124a53714d486007011bffffffffffffffff05581e039313397e5cec9598415ccd0f40eab2419de3d1f535ddb7b6610157d0c00c034701cd36bf697c0005581e033ab6fcfe51a9a320ba7905f5943c4c6588b5357d4b7f421ae4cd1e04d00c014501e7561a3802199e0f03964704a77d5cb79c17e67dddabeb1f5650211c6a52002d3248abe0b73c5d89940306f1c94536a974578ea76a27be59a23096e8214ea6448414ac2ece42ea55b12d03a00f6c683040fb9265e447aeb527f9895e8c19a90c2bb7adc5f9b79eb6ef49b10331b400b291891b46aac642b8eb6e19eb945dbd1bb97972c4b14154f78eb67a410365f20207c74a0bce0639d2e7fd13d7927bbb363498bba3cfcda603328627d8f703f23468e936cd596fe67c77ed959e5ce51d4accd939701c0f11cf72e9d49f80830319b0b13ce1942e30f8dd93611b7ad8747815834a092f794bc195514435c9c28103ec205affeef476cffb983a9f99f000fdb042ea6912abffdfb87c4ccc46a95ec103d34a847509e2932e76d90760e9dfc07defe5a093c42c584d322ff311742a41d1031e0dd35265090eadbe34edf5b581c88327cbeb22970f7fb505e9f6fff7a3173a030aa4908aaf795d8a4e75d59dd391657625191cafd87347f2e8addcb53c070f5503121e0517b36d4de39c2b540147815c86fd8cb506ca545e0fa5d3c638ac2bcc2203bb5899c0b5af9775cc10ab2ccf4183bfebbe120d85e20563eb4cb1b2b1c5649c0219ffff037d747532b09dbc1d26a112e3f99a986e71af8e0f4f06f451e4301f0e596aad1703b2179bac83c6f4558c5be896f9d46c002ff8bf7fc009cd690bf7c7ee2aca680a0332273f787883130a022c726a15ba779c2bf47923705fe0b91e1fe5985ce8a6fd039bc6bdd022a2378235f2f96c0c0e80fca056ae5b94a26ede18e6e076bf1720a003744ae8dfeb2c1cccd638b330cf3703513f92cb30ccdc6ff59d3633e48af6715e03524c1c63bcd263a6c8c4bf9ed55974d539749241203e3e58face7cfa16fa82c003e9eee9366290cc4bc8c10c63ef5050e775867103dc1a9c4596e77bff5819b0250219ffff0370fc4eef9937c57ca7adbccf8e75fc181111ceddf2a57b44d9f6b6205d30ff0f03ce0be5123257f25a6c1c71fa4df6a9afcaf9bfbcdd8e47f86ba091fded474696032d8b691c289d8b6cace2ed3aa1d845c5dea587425d1565536613ab1cf2d5cd86034aff47c494c87fc7f45915a29dcdd05fe4a3dee2cc2258b602f478b34cba0b4903f656b53314ab90d53449d29bb1c5230e71611e321882f52826216fd746b8af5503f2614619186390a175e432805686245dd53094972a50299371204a859868a85103f97eb79b750113976f136e87d5294c7bf584678378ac5c596c4a0d79d90e605903dfbfc80491434888169793da5ba612f3ad8b8f4c9ef935f4a3bb36f5c3aca7fc032e7fba818766e6d7f783407ba91a8ca33e2bbc0be23d9b7572b3505199f785c003de46b3c49eaa8f5eb6fae0de18c2dc26a0761b670a09a93786243c239a8582f20219ffff0330f942ef493c6a65d920955a7bd4bbcbe1f5d195c6cc8f7972868150182adf90034754ff019e25a4625fe4b087a2dfccb92dcf96efafe75b9ba30a692fe46abcc00305c983605f0eed0bcb49940363535320a298d99182ee900444fd5f97779bc30703335c6eb59060aa6ac3b669181d3d567c249d1670f0e0d88b92dd6a68f81d1e3d03dd6a7b012af3bf0130abce97eac7862674ebbbba1106ff84b548044689f5ba79032540d804232339db0176313ea5b5c412336213a40be8e19e26720e592ef6492a039b0e5e69662c65e3693881d0fab830ba6317b0e395d0b55674b37e4c58e4abd50356adfeb34885c8d67b99f7d9ca4ca223ca621e56503425c331c9903a476dcdb30308f7d5052ea42f71e30a9672de03bacaedc1f0f8930df659697d69ce47e969f4039924c2b085927ec92e9aa5238509286849e9a66cd90da36bb06ebcd7ad582acd0395352566e236e397685d7b2b553a7c7e5a0c8f45825dd3e99b41413d8eec4ff003fde8f43351294d02c0e8b9c8dd7d315d8a3c7e18e0a1bc162cc6620c5c5f9f860219ffff03d991d8bab348bfe7925d61110205d09e87b30a5d03fc499f028700a64299f81e038c8f93da9b31905c31af72d4d7bb5cb4602d443f9c1e285edfe2b991e0e2af9003f1371ba1d32627545a39fbfe360f0829057bb4b8d104a60a1ec16cfed241710903eff84e36ccf161eaa28b42f91d3fa143307e0ff63f38d0797f937cce161e52b8032ea3a3ef41c6ec40463d2fdd5d54ecfbae27145af49ddecd0f2d82a712c25bc4035271da1df0eaac0e8ac43597d04ffa5be2967d8608f47584a39a20b28b4832dd03b42a1ceacd93248a4e10b141d4787f72a7cff7b5af2649cb432a2afa64fae63b03f91ba8c51d3a5336619d830c93ec78ac209296fc47f10feb8fbb39f6569cb069037728211d9815a452010bc4f7c1fa83d662a0878f0e9fcb1826fdf58805bc0e1603d1c99660c3ded148e04a729df5c45f5bcf25c31eb12282191bcb28b9361b527303a4fd89517328b99f94878bb6a10365c44f515d6208e6f69d91adf833fdb7162103af7e8d9e9a7b41b3fddfb11b67252efe784571bf13fa808c60a0fbd41a887fb80381c0436490f0c39aa850b757e8486b6cb34b991361213ec3f5472a452699ca810219ffff0387813c8ee8947d587eff8fd565f0f0b07d9697294ddcf8389e097ae88985cba1030897e8048e2e806a5e16e05af46bf686a099d0897492b58d8817d25c156d0d2103a8e036fc00d7fd84eb6f16f85a80c49aa6cf256d48afc5648771c4a42269042503068c7dbce1ca5b0fd40d458cf282a35b97e54f2698f7bafbe1daa66e243a8f6f035e2419beacdfff670daa5b9d1c0cc2d1723f9db8a2da15e86218725d9be5abd503408fdb85f41e0102bf605b55416aa39b436bd759a2e50494824fb387eb6e9eaf03b5a2fab75e6db4928a53aec2e7598b6e690b65c07ac77ce0bee5b94e9d33430503b25ed85c9f559970138745c558462ac1e7fb0292ecc81fc0cc7e361f517f2d4403097dfa7b202e8df00f12040b698d796465d90ec4ca5f921e9f14f2740f458b2a036500bc59ae3697259e4a74bf4b35e6f42dc82b7b831bd8d23c678808ecbbab0e03a6fe11315d7832429db054d1ef6753a196a13e3117e524650439510271d01ca4037a21c7b99c57eadd297a8812633e8346142732e739072413efe9f98e29cc2cb203515380238e4e81f070f91af301188abcb2f64d6009da3f13f5a44a4c1d369bd60312f044ca414c4ca664a880a35f2a99269a711745dd7014d83c7c1a429508a5830330d11ed7bfc7b4fbff84be97b91c4fa0971652a00b263d7b0e88b2157a4295e2035f41f795151c9491c36594a4cbdb211e6e472acb2e04575af5f175498b505eca036f69b17ef35251c81c13443c70ebc8c360a90638efabd4e26bbb7bb060ff59ed03b4a39a5418b80c771b7422bbefc82af000dc4049ae9af22253eef8e646471b55035a197ee85cb248b0e9b4a52f081914b952614230aabd59af987992d23ab9b2bc03a4d3218c87c4d77d240906f5e4787f36edb05c879f58b314870c2bf0a2925d0c03e0a5f9c1eba89ab7ed33228bb12b378fbb75d09aab8275dbd6b6edaa2b7149e303f3ce2fdcd9f13fa517de9dd704a66a02ded5f91f155ea099c40821f4abbf547603de016f018239ad60487734f97d13d217fd19f01da1a7d840bdcff7e7b4015c9d0338b87f77965bd82aa9cd2b28e000c5fb1979cefbc2e413c06c3473e12081e56f0365d5135d92bff813c2e85bea832701c859f711557a021548a4197ad8cff1398f033e9bd6b855223fd97a0ed9cec03ce278179847c91ea1bbadc2cf199fd209b63203b9163562c386c38ee8d7a4745b4060afaea6c994eb84d2ced063a3c16006c4e703ac021760761e272d799ef48f16438c86586f4231799ede28d145db34a84969aa0366b29765d4793c92d469b520ae3809acef4f14b76eec1d9cdf2140a42dab428b05581e03b1ca458d01246eea6fbe769c335cb7ab3f938c5a8726fc54777b261330040205581e033d616945a7d42cfd2e5310f685d39212557c18ee821c96579e5020555004188d05581e0352507f09d93289f2000854ca21ec6d108c413ec69d69e55501c7272540040105581d0265a919c155a4eee6caa20efae6287f002fb003186271ffb1ad95f7c60c02470b7c2e85dcd80005581d02ca4549cdbff3ad31d60800a5e862e8ef6deb21f722a281579428568f0c1a000e5ad7485d776e5046bc42cc05581d0252594122e76166b01cc01f13d41fb1c926568c2f2a14b8ef66fdc0de0c0c47026c8613727150021903800352fc2d0cdc5601d1f944529012aa66566856c5b49b1600c7b40dc84ebe04a38d03b94106c7f2b6483fd61c1a29dcf684bb89827596178cbe8bacf65fa13b7e0214021987980380d001ddcee9ea101119e75fa2e405e3c2692ae311028d66bd3f36f87e638cdf037091a21f9119a0e30de0f79ec10a7ddb9ee682de20b1d04eb704d9a3753c8a2203303f30a69832e044400f1e6aa606035d8d7854c10c6f54af14a98457c0e9550d0326874a857979efae008ac4b127574d26dff1ee8389b59349a3dded1837ce9fe103028a841ca5c176a591a1da152eb113146c302f376a67ed4cd46d23ce74407f5f039228bb9a0e66b2462d2f3df607e607ea6310ad4765c1a8ac4631af32391efed103e24f04b273a50516a875591af81a04efab31bf0bdca2d78ecac0651d1f72af4f0219ffff03502dd208560b06be266608bf7855d9f9b1c5962784a7bb37cf5c40b664222ccd03e4b0a420f2f745c2e87d85ac5e86183b8b736a1046a2ef855e935ca2c85a9c1e034fd035d401c8b2714b394786d9c759450384ff6390b1f9b5d344b6b9742b57d7039671b64ebcbd2d5a600ea2ac533eba08daf4b23a9fd4f111bead6c1d142d263703467da5b1b780bedbf41be4fc64f791cdd250cd2e362ff40b812571c379e5b9fe0348557eac8b036c75060266624bd2950728afe56fb12ac0a05fe94fd7a18ebf5f037addf07169f18ff9a3c64973dff35c61fd334dc2e275b02cd09a39f19a2512b803a526d4fb28d0561f6fc8452ed73b1ff29ffa06de7640940a4f5d54948aa3a156038270eb544111e20f7a5ad6050491c629a8c4ad29a1c455c3af6ef8bad054bfc103b63726813365321dbef638b577751ef3476ba9a96a63badba738f111ebf92a2f0219ffff038d4cfaeab5e0b0c4df79902ce6ebad71043933ba01d30803c778334da648855f039726d9ccbf738f95297e2e063ba8c9b219a663ee44b2fd31ac7c7e9af0ff12620219ffff0375701d8ac53c5803fe17160dba7e830087c0f4c43761ebc8daa4e6420b791d8703d8b258ba1e790b8933e1c13e6b05bc2a1beb2c6f7703f185d80f306d2138fd31038a684741e7ffdb317505ea6fa3c4d86f1bb7ca79e8a9640c4b0655acf3c37651038f624d55d66c4692d2e1db274f8e3dde972642e981915fed8e004945d27fa97103e4252717da8cd1d7f1f76680c32d386c95a01ae89d98b21690810d9d1317b3a603e7484171673c9c8b138cd5010b7ea816c515a32100ffb964bd612eeb8fc78df6035f456c3f3e1803093d4f7b57407175519983fd8ef35f6c949e4ea72f6783b80903b438910ef295b27a6ff70bee3a919c2535398315f89ba5c682f065ada39a8b720359c19a2574f3e87948c3eb2627631faa218c8f6eb65fb53ce315a3ffc950ceda03b3ff8f94013115e56b652edd2ae8b52a43622f00c12a0382d82259d3305b4260037a7cf8add3755895a3bb9339ebca6e06a757f5352a10fcc24448f348e80d584b0343daf5610cd7c7fe98e53ec3b4ecb18db95a775f079b06d80c2a59bacabeee4103a078fd3149c07f3e9276bc9fbc8283e9b1e4f899e2fd8c82bf8d23832caf59630219ffff0303a154a3cbda26d32691451246c64a985c1623ed7dcd97c464b9fc4a8495928f03a762200ad413f40b91b721b0769a5f9caf5c4c8f5f6f4d7aa9c2252690d9ca0603fdce3a21c7a979ab7fa3cf1504acb93e35eda98148b5d1e9bfd59b6b894235ee03d0ba94131672ea440341f0e1fc9e5c86e61aeffcde6d9141c91d5af63ffd6391039c4798b654dfe34b249df6f84de2f6eb37b99735cc8e09715349955ef1f8beee033da27527ac1da7b7f224fb441c21c8f5329b59518c98d5c48d772d91dbcc274803e40608176c4de05c1931342801d08880a425c79e33f018850283ef9fd24aa30a03116df2722d5e6ae67d605d84ce92be61205895774be112974ec1dbb2a2bad0a60378190f02a15d45a1ea2fde8e32aa472a8d371aa6b9e1d62c7f49799f4c5493bf03b408c382f0a69e57d127cd388a86ff39640f304f1bce85532fefa723b3995b600341698b9ac9f018f28ca4aa18b567db1923b65e98590f33ff8de3a4344993944403bb1f0150034d7e5c03ff7b50a58964fffd267c2dfe2a70498c9a90745b67357703947025c5500298bb50ab92b164497d243e699144cb4830777434ded310075ce103d596ce52f5ca6970799a9346d9e27bc55feb1f855891aa64b2ee8a78fa5ba0ae037c3e02fced610fc56e13792f84dd47d592bab0449fc3b87c0915f9ec1a1aed9603db1c42ffc773faac1df01ee9e4f28fe1d55282a60c56c7cf05e6eda1f38e64f1035e3bd65780b3c4de8e8a00654de4d4efd772841fa307e6853aeb327fa50750d1030d14dfbf139f14e6628b88bc4b91547cf33ad69c0e493e1afb3aa9247000b7af03e1cb01d2a1df9a6671717aa4f64fb5b7404d64b2eba49664cdcedda908e551f203f2b605970b7b7c3132dd85dc1ef7031d4af99a726150ad45297a5f906f797cfb03ae34215ccaf1c4fa833e03ce2533d1953b64b3a899a3bd52b3f0a7446114ba54038160bbe2b4f69ba2dbc672804ac49360d12511da483bc24d4d9afe7ca5f3552303c91745db06df2eb45e6b137569a4c2b3bcd42b6cf7f66363e98bdb56fe78ac5503bdc9182f5dbb432f8bd38067c95fe2c515f0b6a4a8994edba9096742a20c833c0374cfb47ea88f440c6aaf47d387525d66e3350b132c5b2cb86950c8901fdfb51a0374f5b69166031c7ec4dee73a7506cdb68a114e9306fe328acf1d5407e1a8691603a9900442a14cd891c0b1b4f83d350f4d7bc7b884073e8fb84cc6f73f702d962e030588693480317cfaf4a83dd6e60bf34e1bbe639d15ebeb29be7b9522c5b58da50339a9ac31a9273270abc60490672f0e74d2fe41e63452ed961a355de65c5b9ad603c6cb74633e15b3ebbc963d6bb80001a122351fe36157920136165e7c5562b497036fab87237644cc5d83557514dc95ce40c0be11325e7ed6645f5261f9f40ea67103173a7a1c82fe9b77ba2552f139f3d482cee46e728b9630ef21c242ff53e62e3f0370fc53c90d73202e0ba112493353c52dabf2ea3199af726e9efc42aafd0a1bf603f491cbf32f1972e68d6c47a883522652e0976afebc8b5666cf1fad0ace7dc7a003c20c231ae1c923a1df3087cee404a3f9b42d2c8b7566fa1433885acb5b7488bc05581e0381a4858f151d00bbc606373b3424a475c15bb9a1e2902beb4711e3224004030315912207172c3d593d9c96475a5ec48d19886d2a6c4da8f218b127a4e2d8c7d703fcd0b189aa44b77cc8188cbd309a5cea6125a76b904c3df3853f569d047a198005581e033d2f1bee6a7938a70784d8ead1845ac0430e120fb63fedeed117aa3b00040203bf930c8925e943c5b56035749edf07c99ece773d35852f2e8f0f8492639a107301410f05581e03ebf22c083347a38a76e1e05cf674abc591e5e43c3bedd10af07455f6e0040203aefdf0b549a4dfddb17ab79884222d73a82f71697b1a96128c0256e16d4ec9840605581e031c29047e358bfcd52d4a1ede9d01a1f7ed8abb0fec87eec095fcb6b5c007011bffffffffffffffff03f6e1273e7a2757aae6bc42f33113455d52736a1faeffc865244f06deed9872400605581e03b726d05c2b751555642e2f8c62c1a3fc6d375de5b07a29db3b03f1c23007011bffffffffffffffff05581e03e5df3e50faee4046322232b02122e22521b1b6149b34db4d18dc3101e0040105581e03a94716c9815f4408b46a306d9516288813e70b9cd2ab2d55059897ad900c014701811ac25e725605581d02ff792d852853f0fdc653735c0aea8249019940b289f2524e65dd09e9040105581d029e8d0fcfb587e7ab8a22802818d35e529fbb57e9c98d1da6bf8cea4a0c1647845699f539b358021918000219e4fb03c66de87f6cf3a0de896f63a2bc4fb25f10aa9e0555ad72c5955747c434a68d6d03bb0fd651a65251cc18e519d401a042a1732c6c1a8cbde0a2c23b11b44f78ae41036a128332b862596ae031ff5ff28770b17736161f185bd9417c82d89aa45bd1220399916d95e25185f3461511157f33e45c9e1d10a68119f38b23769b9d5537c9f40372f418c638ffbce9de1f30a457c3d6df888bc5505e6ea0945ee3d72b69b704c2035aa925871a4f4136b3085ef98eba1075de08711ad17a2d3182c0c52e7709b8c603079faf8eebffbdb1e4d8410280fd3d2ab424584a12d340b6f99453b6c02d3a5b0393c1068dd79084b9aecf2709765c4926b99005b768e884185aadf0fc5186b13b03eb6586ebdb4e0553896479204d40756b7d5fd6eb1cb39b7964173e07fae46b9803a1ec8cede4c61f785e8aa21210c001b35fdcfc795f611a58179a1bfe31020e6603d1a3e9bfb2e6bc0ba2f45ca9cb62c0c2271df5d977a4d457eca640fb6c9f15e5037c02d2aa1572aace996ba19036fd7ddb51ac0a2e6cb8943c4df15100f31c0f390219ffff0219ffff03493fdd4a53a841a01710830d6f9ed64f372a96c0d5d5bd574767d5185ca15aec03a04fce8e30dda49245d2ab1eedffbb2f55095c14d4975466245e8e97d0157e0a0354b303864ed7e440c6b4c063d373187b91da1426aa2d52829db970b9a84f09960350636fa0084db8422ce2953d09091aa2a747aa3bf993ccb8d8f46f3e4f057d3d03f2ed07e68bf6bd0cbede25363ce899198b2b42a782e2a1a8b07ae2c1358d412d033a0e13452f3b56a997bcb577f3c597c9e479eeb2d9a05b4eff2a50bbde63a032034d043b2cb3c0f323c285271bb9eb63f439f0b630a010aac02d211fc7262906b70219ffff038d0d35bdeb51f2f680e72dee34d0e89d664a51c1d7151faaac1e71bb36e3f8bb0379c8123b1436af2b2f4525b836e4d646d04de62072b6ed3b20d3a847be8be3a603b2c7b931aa198e7a534ee7e24dcf58e6ee3a78c318d1787c6ba693ac6ea6e54703c768ccd1e6c1cea7aea0e0851b9a3333850ec3b8e28739db271434fbf7e0d12603eed1591fd2abaf02301c8451c31cb8f637138a5af5b4e9f008847a6434fa096b03918d0f9d8225728769a64c885061309dc64c3eb0ef04efdc1baa0100e6ca204d03e5aec56b298aa966765f70ddb78e974c7748a079e0bcf72f7d0ceaad7208e6030331d7e47e7293c29df099b899e8d150e761a5c40d6c3411609bdd7d41db2510d903e21b80af3ac50069a3f7999319ee5d139a795d3d71f8d4e1d1546aabb224bbd90362dad3cb8779c256316a30ddca2f7bed6f17394028b70478b075c9ad9f23527b0219ffff034bbae16ae94cb05f40654c6617a3c5166664ae4ec4fe5d3f9459f87c272d124603b5f8b5f5c046f0604cc09d8ceb6bb425ce5c40bc4ac6f1e5773b67005be5e9d703da2c8b20a62e6da94eac9cd62da39eb5ef892caa27a232790ace8cea2cb2feea031841639ebdce762f2066f96df2b44cfe3e1d676f686dc57c4b8a5d136ba2490503977da587ea197771f781ad3c507b6c34861da46263fa5ec44bb284d49f1c7b9103f193d2c792be598fc97fa44932f3f6f1fda8367e6406e56dd9dbcdb598e8f9fd0333411e37e28250346435c09772319053c282bb61c0813d4225e891fab2262f2603ae88a44105ca9db64c543ac12eac9de13f9940ecc2f4a9159853f695a804650203794fa4a08875afe4c9f54b42cddebf0de4fb879db40a51600404612016c6378f03a8c5c80852d972427404b6e3abb4f5f4034622b398ededba58050f2c28015d17038096c11fe0f5dc1767a4a7cba6863694735543a19d77983512cdee683459aff4030cbe73a9b88aef88b4b4aa86ae0d23583cb628a8a458e74b3eaa52e1336ef85703b0048ffe9cae416a417df5cec87b60f8ad5cac3e0143af20933cc73d1f6195dc03c09514af7455fbda47998004446ea68dc86321b83325a09cbaf07761742f1e5d03e8e7e357b4a2883094bd8e93ec5cb1c208ee36f132a2da2d65d6ccce2db359d8032f5ca4b731e7b08798690b48f42dd30ed4e3761d06324ed9188f9df538f67be7031f8841b8d425431aeb9ae6105ac446c2d5685c5ff69965a440c7c31526bf4b61032cc5309d6cfd154bc813b3956e43a1430bd2970bd684fa8792a30b4c3c3e50c3039aa17156388fb72604faead016cce3303d12c018e821003552adf65424e4a37003ef54810f866fe386a77cd06d6d2af08167c0814545c140d23f1bdbcb2afda7c003b38f0d17ffe24bb7e20b45546ff97be4ab86529e61cee1c2ca0e4d3e7f6ab32e03b0fa2008a12683375f3df1512ae71e947784012f4ef61c182ff4af3d4073ffa605581e037b7477e418ac5a14cf7e77dc59297e2bbeb8116bbc91babfa2212f2d40040103a7e48f0d73eaf7c47528d6ab59ffae7b4aec13c5eb888b7359f014521767c84803f4894f5d93a9fad81ddc3c915bba04e8b24b153184b4a38622d4e353288b537905581e03068fa055a4a7f1eb5000acb98e70096615babccb7b6dd93ba5780f8de007011bffffffffffffffff05581e03463e9b6649e5a09ad0a1dd0c2c26b00c455807a27e0fd38ce5bdc7cf00040b05581e030269386a471b57d4ee9e7d93e34959d7d67a2ed4e4a8facd79970507900403031b460c826a854d61dca82f718e088b8b4c4082ffeb93752d7691bc62c51dc0280605581d02ce38186ac605c94664fdc47f237dcc0f428fa17fda7fd25fda213fb307011bffffffffffffffff04591a2460806040526004361061011e575f3560e01c8063751039fc1161009d578063a9059cbb11610062578063a9059cbb1461033b578063bf474bed1461035a578063c9567bf91461036f578063d34628cc14610383578063dd62ed3e146103a2575f80fd5b8063751039fc146102a95780637d1db4a5146102bd5780638da5cb5b146102d25780638f9a55c0146102f857806395d89b411461030d575f80fd5b8063313ce567116100e3578063313ce567146101ee57806331c2d847146102095780633bbac5791461022a57806370a0823114610261578063715018a614610295575f80fd5b806306fdde0314610129578063095ea7b3146101695780630faee56f1461019857806318160ddd146101bb57806323b872dd146101cf575f80fd5b3661012557005b5f80fd5b348015610134575f80fd5b5060408051808201909152600681526548656c656e6160d01b60208201525b6040516101609190611533565b60405180910390f35b348015610174575f80fd5b506101886101833660046115a5565b6103e6565b6040519015158152602001610160565b3480156101a3575f80fd5b506101ad60125481565b604051908152602001610160565b3480156101c6575f80fd5b506101ad6103fc565b3480156101da575f80fd5b506101886101e93660046115cf565b61041c565b3480156101f9575f80fd5b5060405160098152602001610160565b348015610214575f80fd5b50610228610223366004611621565b610483565b005b348015610235575f80fd5b506101886102443660046116e1565b6001600160a01b03165f9081526004602052604090205460ff1690565b34801561026c575f80fd5b506101ad61027b3660046116e1565b6001600160a01b03165f9081526001602052604090205490565b3480156102a0575f80fd5b5061022861051d565b3480156102b4575f80fd5b5061022861058e565b3480156102c8575f80fd5b506101ad600f5481565b3480156102dd575f80fd5b505f546040516001600160a01b039091168152602001610160565b348015610303575f80fd5b506101ad60105481565b348015610318575f80fd5b5060408051808201909152600681526548454c454e4160d01b6020820152610153565b348015610346575f80fd5b506101886103553660046115a5565b61063f565b348015610365575f80fd5b506101ad60115481565b34801561037a575f80fd5b5061022861064b565b34801561038e575f80fd5b5061022861039d366004611621565b6109f8565b3480156103ad575f80fd5b506101ad6103bc3660046116fc565b6001600160a01b039182165f90815260026020908152604080832093909416825291909152205490565b5f6103f2338484610a86565b5060015b92915050565b5f6104096009600a611827565b610417906305f5e100611835565b905090565b5f610428848484610ba9565b6104798433610474856040518060600160405280602881526020016119c7602891396001600160a01b038a165f90815260026020908152604080832033845290915290205491906111af565b610a86565b5060019392505050565b5f546001600160a01b031633146104b55760405162461bcd60e51b81526004016104ac9061184c565b60405180910390fd5b5f5b8151811015610519575f60045f8484815181106104d6576104d6611881565b6020908102919091018101516001600160a01b031682528101919091526040015f20805460ff19169115159190911790558061051181611895565b9150506104b7565b5050565b5f546001600160a01b031633146105465760405162461bcd60e51b81526004016104ac9061184c565b5f80546040516001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a35f80546001600160a01b0319169055565b5f546001600160a01b031633146105b75760405162461bcd60e51b81526004016104ac9061184c565b6105c36009600a611827565b6105d1906305f5e100611835565b600f556105e06009600a611827565b6105ee906305f5e100611835565b6010557f947f344d56e1e8c70dc492fb94c4ddddd490c016aab685f5e7e47b2e85cb44cf61061e6009600a611827565b61062c906305f5e100611835565b60405190815260200160405180910390a1565b5f6103f2338484610ba9565b5f546001600160a01b031633146106745760405162461bcd60e51b81526004016104ac9061184c565b601454600160a01b900460ff16156106ce5760405162461bcd60e51b815260206004820152601760248201527f74726164696e6720697320616c7265616479206f70656e00000000000000000060448201526064016104ac565b601380546001600160a01b031916737a250d5630b4cf539739df2c5dacb4c659f2488d9081179091556107179030906107096009600a611827565b610474906305f5e100611835565b60135f9054906101000a90046001600160a01b03166001600160a01b031663c45a01556040518163ffffffff1660e01b8152600401602060405180830381865afa158015610767573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061078b91906118ad565b6001600160a01b031663c9c653963060135f9054906101000a90046001600160a01b03166001600160a01b031663ad5c46486040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107ea573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061080e91906118ad565b6040516001600160e01b031960e085901b1681526001600160a01b039283166004820152911660248201526044016020604051808303815f875af1158015610858573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061087c91906118ad565b601480546001600160a01b039283166001600160a01b03199091161790556013541663f305d71947306108c3816001600160a01b03165f9081526001602052604090205490565b5f806108d65f546001600160a01b031690565b60405160e088901b6001600160e01b03191681526001600160a01b03958616600482015260248101949094526044840192909252606483015290911660848201524260a482015260c40160606040518083038185885af115801561093c573d5f803e3d5ffd5b50505050506040513d601f19601f8201168201806040525081019061096191906118c8565b505060145460135460405163095ea7b360e01b81526001600160a01b0391821660048201525f1960248201529116915063095ea7b3906044016020604051808303815f875af11580156109b6573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109da91906118f3565b506014805462ff00ff60a01b19166201000160a01b17905543600655565b5f546001600160a01b03163314610a215760405162461bcd60e51b81526004016104ac9061184c565b5f5b815181101561051957600160045f848481518110610a4357610a43611881565b6020908102919091018101516001600160a01b031682528101919091526040015f20805460ff191691151591909117905580610a7e81611895565b915050610a23565b6001600160a01b038316610ae85760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b60648201526084016104ac565b6001600160a01b038216610b495760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b60648201526084016104ac565b6001600160a01b038381165f8181526002602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b6001600160a01b038316610c0d5760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b60648201526084016104ac565b6001600160a01b038216610c6f5760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b60648201526084016104ac565b5f8111610cd05760405162461bcd60e51b815260206004820152602960248201527f5472616e7366657220616d6f756e74206d7573742062652067726561746572206044820152687468616e207a65726f60b81b60648201526084016104ac565b5f80546001600160a01b03858116911614801590610cfb57505f546001600160a01b03848116911614155b15611072576001600160a01b0384165f9081526004602052604090205460ff16158015610d4057506001600160a01b0383165f9081526004602052604090205460ff16155b610d48575f80fd5b610d746064610d6e600b54600e5411610d6357600754610d67565b6009545b85906111e7565b9061126c565b6014549091506001600160a01b038581169116148015610da257506013546001600160a01b03848116911614155b8015610dc657506001600160a01b0383165f9081526003602052604090205460ff16155b15610ecd57600f54821115610e1d5760405162461bcd60e51b815260206004820152601960248201527f4578636565647320746865205f6d61785478416d6f756e742e0000000000000060448201526064016104ac565b60105482610e3f856001600160a01b03165f9081526001602052604090205490565b610e499190611912565b1115610e975760405162461bcd60e51b815260206004820152601a60248201527f4578636565647320746865206d617857616c6c657453697a652e00000000000060448201526064016104ac565b436006546003610ea79190611912565b1115610eb857823b15610eb8575f80fd5b600e8054905f610ec783611895565b91905055505b6014546001600160a01b03848116911614801590610f0357506001600160a01b0383165f9081526003602052604090205460ff16155b15610f825760105482610f2a856001600160a01b03165f9081526001602052604090205490565b610f349190611912565b1115610f825760405162461bcd60e51b815260206004820152601a60248201527f4578636565647320746865206d617857616c6c657453697a652e00000000000060448201526064016104ac565b6014546001600160a01b038481169116148015610fa857506001600160a01b0384163014155b15610fd557610fd26064610d6e600c54600e5411610fc857600854610d67565b600a5485906111e7565b90505b305f90815260016020526040902054601454600160a81b900460ff1615801561100b57506014546001600160a01b038581169116145b80156110205750601454600160b01b900460ff165b801561102d575060115481115b801561103c5750600d54600e54115b156110705761105e61105984611054846012546112ad565b6112ad565b6112c1565b47801561106e5761106e47611431565b505b505b80156110ea57305f908152600160205260409020546110919082611468565b305f81815260016020526040908190209290925590516001600160a01b038616907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906110e19085815260200190565b60405180910390a35b6001600160a01b0384165f9081526001602052604090205461110c90836114c6565b6001600160a01b0385165f9081526001602052604090205561114f61113183836114c6565b6001600160a01b0385165f9081526001602052604090205490611468565b6001600160a01b038085165f8181526001602052604090209290925585167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef61119885856114c6565b60405190815260200160405180910390a350505050565b5f81848411156111d25760405162461bcd60e51b81526004016104ac9190611533565b505f6111de8486611925565b95945050505050565b5f825f036111f657505f6103f6565b5f6112018385611835565b90508261120e8583611938565b146112655760405162461bcd60e51b815260206004820152602160248201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6044820152607760f81b60648201526084016104ac565b9392505050565b5f61126583836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f000000000000815250611507565b5f8183116112bb5782611265565b50919050565b6014805460ff60a81b1916600160a81b1790556040805160028082526060820183525f9260208301908036833701905050905030815f8151811061130757611307611881565b6001600160a01b03928316602091820292909201810191909152601354604080516315ab88c960e31b81529051919093169263ad5c46489260048083019391928290030181865afa15801561135e573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061138291906118ad565b8160018151811061139557611395611881565b6001600160a01b0392831660209182029290920101526013546113bb9130911684610a86565b60135460405163791ac94760e01b81526001600160a01b039091169063791ac947906113f39085905f90869030904290600401611957565b5f604051808303815f87803b15801561140a575f80fd5b505af115801561141c573d5f803e3d5ffd5b50506014805460ff60a81b1916905550505050565b6005546040516001600160a01b039091169082156108fc029083905f818181858888f19350505050158015610519573d5f803e3d5ffd5b5f806114748385611912565b9050838110156112655760405162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f77000000000060448201526064016104ac565b5f61126583836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f7700008152506111af565b5f81836115275760405162461bcd60e51b81526004016104ac9190611533565b505f6111de8486611938565b5f6020808352835180828501525f5b8181101561155e57858101830151858201604001528201611542565b505f604082860101526040601f19601f8301168501019250505092915050565b6001600160a01b0381168114611592575f80fd5b50565b80356115a08161157e565b919050565b5f80604083850312156115b6575f80fd5b82356115c18161157e565b946020939093013593505050565b5f805f606084860312156115e1575f80fd5b83356115ec8161157e565b925060208401356115fc8161157e565b929592945050506040919091013590565b634e487b7160e01b5f52604160045260245ffd5b5f6020808385031215611632575f80fd5b823567ffffffffffffffff80821115611649575f80fd5b818501915085601f83011261165c575f80fd5b81358181111561166e5761166e61160d565b8060051b604051601f19603f830116810181811085821117156116935761169361160d565b6040529182528482019250838101850191888311156116b0575f80fd5b938501935b828510156116d5576116c685611595565b845293850193928501926116b5565b98975050505050505050565b5f602082840312156116f1575f80fd5b81356112658161157e565b5f806040838503121561170d575f80fd5b82356117188161157e565b915060208301356117288161157e565b809150509250929050565b634e487b7160e01b5f52601160045260245ffd5b600181815b8085111561178157815f190482111561176757611767611733565b8085161561177457918102915b93841c939080029061174c565b509250929050565b5f82611797575060016103f6565b816117a357505f6103f6565b81600181146117b957600281146117c3576117df565b60019150506103f6565b60ff8411156117d4576117d4611733565b50506001821b6103f6565b5060208310610133831016604e8410600b8410161715611802575081810a6103f6565b61180c8383611747565b805f190482111561181f5761181f611733565b029392505050565b5f61126560ff841683611789565b80820281158282048414176103f6576103f6611733565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b634e487b7160e01b5f52603260045260245ffd5b5f600182016118a6576118a6611733565b5060010190565b5f602082840312156118bd575f80fd5b81516112658161157e565b5f805f606084860312156118da575f80fd5b8351925060208401519150604084015190509250925092565b5f60208284031215611903575f80fd5b81518015158114611265575f80fd5b808201808211156103f6576103f6611733565b818103818111156103f6576103f6611733565b5f8261195257634e487b7160e01b5f52601260045260245ffd5b500490565b5f60a082018783526020878185015260a0604085015281875180845260c08601915082890193505f5b818110156119a55784516001600160a01b031683529383019391830191600101611980565b50506001600160a01b0396909616606085015250505060800152939250505056fe45524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e6365a2646970667358221220cd1a2118a4607b0523b2990d6e1df8402151cb294405e0fe19cfb7a9df76bc4c64736f6c63430008140033005820028dcaf8ddc7d8d7c8ec1cff0af39bb6085e0d747114b3e53e8eab85c25fc9b04101005820026b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db05445a70532e427ace9f67dfed997ea7e42020af43e0058200228844264611e87bf6f3a55f5811d7a50ac0200f039fcd09d24ffacda02530947071afd498d0000005820026c5842df362c305f1a74e75bbde32f564084f950af916ff4e2571ab57b38ea48012a6d8e112200000219204c00582103b6847dc741a1b0cd08d278845f9d819d87b734759afb55fe2de5cb82a9ae672047071afd498d00000058210390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56305445a70532e427ace9f67dfed997ea7e42020af43e00582002ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c6847038d7ea4c68000005820025f43060733ea08919ee884c1b0f088e2c90b6a12a123ac7c3f989bb70a235b47071afd498d00000219800200582103df627f2e691bfba981d13cb52cc40c4aa63b23def8c6704de4da43573d28a12047071afd498d000000582002e38a03e7cea2f9e901c34b8b960f33d7bf92c0a40000743ee84738a3c9515047071afd498d00000058200205629435bf2f6c33497c91b581845a7026307c25f9f42ca9fa4dbcf990ee5147071afd498d000000582002de8ffda797e3de9c05e8fc57b3bf0ec28a930d40b0d285d93c06501cf6a090547a250d5630b4cf539739df2c5dacb4c659f2488d02184500582103d1108e10bcb7c27dddfc02ed9d693a074039d026cf4ea4240b40f7d581ac802047071afd498d000000582103797ca6e0d1beaf5dcf85823eede7cb3baeccce81be37693a7da8f107e5f3e9c047071afd498d000000582003b4a454dc3493923482f07822329ed19e8244eff582cc204f8554c3620c3fd0410800582003a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec3444047038d7ea4c680000219018001410b00582002eb37b7e5acbfbcf37bb7477ce1a97e325fed8cbbc157cc8e6ae66937d71e9747071afd498d000000582002c5eb6aff7937d3b55bf0593144d4cc1bd0cd1929d8c24db628c5f95248dbdc4101005820026d7b5282bd9a3661ae061feed1dbda4e52ab073b1f9285be6e155d9c38d4ec57010001d95fc8448cea732810bfa312a3ebaaafa2c6a3cb00582002b5281a71e22392d2aa0bd95be8716e0424f3ccc46fc4db2e15c35d50feaa8647071afd498d00000219c0c0005821037b6990105719101dabeb77144f2a3385c8033acd3af97e9423a695e81ad1eb50411400582103652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f044012e3b670219bb5f05581d02cb4bd96e136ca13b090740d3f00be10f1969f73d302dcac11e7cce450701191a2405581d02b82e746dab702ac7428790fc53bc0262a96826827b3fcddc35c82706040102188a03cfa20bebfd29f1d73e7d6cad1625a81bacf15e427a9561f985fa08ba1bc67f4503562d59a51820d47f520c975e0b2bcffac644a509749a3161f481f57b6e826d210605581e03c2bc73103e30a60b92a2f75102c74e298599640d59e80c6fe56abfd72007011bffffffffffffffff03af35f51f228c597a8268940546953e12e15894466096b84eb339d97fbb38497805581e03bb993e3bc70436604564b6c61f558fc1de7a78559b778e04cda7cf01400c0247047fa8a932fd5e05581e0369318c4aad4e25e697671a74011c5f52a88bb77a09d16b7750f46ca89008479e58b728612c000219fd0d036ee9c60abae91129cb30bb0295f5831c66eec16438f18fd976330cadd6e02b78037ca5ed52c8162fb565e72c9ef1355cb5cc7dda8a6a5c1e48449c8e1a9c640d2b03ef17d7bd346608acd5e4eaf97ee3130758f66fe23d222a8fa1fe837e4002c4fd0315ae3cb0aada2558f82aae70c8d86a2eb04079fcdf1bdc5831313859bf2d0d2803085fb4fa10bcd6a9da0e89e758b31df4bdc3de33962bbe4c73d02905a5e8c322039555281d2b809790bc436b44b98d13f6f22a037dd2a54405cdbb8dd0f861003503c883a3bfe444a7701c40737aecb49dfe5b94d66552f1dc82d58e197a67cd580503438c8dc56d7b0c6c15869637a7c8271c346b8a6c6ebfab5becb82e18f65b79c103739ad4b716a9612c1c30ae5b86c7cd20d6dae8bf473f0cedc3509d035274a3fc03db0c664c2cecb55cbdb936ec9b3549350d4d2ae7d13784a760deb4eed95fa7b203e6dc4a9754c5653682edf43b3e4a1d0df8588dc0243bedd7a974158450799de703d200c5142ac6cdcd0d17ffb299aff76f0c30a81db62fbe41ffbe5a11167431e7031cd5a8ebc4e7b1a14c892d2eb886821b9068a455f4c3cbb7e576ffc3c66ea5b90219ffff036f13b1331ebd56022d8c17e8efb30fce481d6eeba82c539f18b7ba0335ea253b03970a55a7f9dd9d3975d598c06427f6911247c23a4a61dc23ca735bb5f934bd7303929ec690ca00ec49576a6eacf776b9605f65f3dcfcfe9c13677d4d8aeb9d0d1403f8f661e40e170f1ff30e07c35d58ceb1e89486cd2152b9f4c777118e087a4a6f0219ffff036e223791a03afff7a27a8ca4e194a841d845fbaf90cc3376b110fc54f7c0434203d8354ef84fc404002bfe0c5666603b034cc1eacb667f395cc98b7f0d8f66583803dbce0d35a3e0f46271a57b5142df96008e13c67dcf3ca912761ed6cb59a0cc3103ef5b8b2b992c41a3b7fd9e60e4d71f0e441cda1411b382cf0f0a15fd6957e5c703cc968c167c6bccc444881e21b9d814f16cc4ee72883083153ef701dc474c62860392df3d69da7a1aa6085081c977abe28fb83a7cf92d01d63949529cc7ca022bd5038aef9af98cc989c304e1da0954a1a5c0cb4e9ae8c6c00c7f6cb199aedad1ee220219ffff0344981ec9072d8fbaf350f6b182bd797e3a7f02caeee3261318f113a0a310577e03e2ef479408e0c57d15ea7f36bc80aa0fa6c12f5fdb7ceb108cb1aa7545e8851d038c1428fa26399ce8e02e7fd2f2f5758072228ed44b500ff1e4c61bc83c9ed6ee03c8092b2371b0c0c1ebbd04515611b4deb3e56f4965867982433d9aac5d7c2dc703ade981cbb648abbfcdefd827e3b6226f95f4e99f141a85757d16afd6df89b97d03d9683d27b02aa039871e5121f0ebf4544fe0cccdfb79279dd5e98046b674f0ad0320051ea881bc8cada86b8a81ffe289cc931cf98de735d1c40d1d80041d89df46039873eef0b601b9910709ced9b29a387ce74714bbfd009658d07888df4a952cea038ba3c131f779d436e6d7c22ce751110e0f9960cbccbec9d8bae1bfd74f2b3d920336da30787cca2e0b35bf668c67c6edea11121247d372776589525b1236140d5c0395744c74c312e27eafd2e1e46e9790fa1eb293267630b04f8fda1a7ef2a840cf034db51eeb5b18b379e60160010fe2c3c7bd14954dcbcda10ee9d417f65d4a665f038a39a886fffeac2f55ca5657cb8d0785d94bbfc44e6ef3d788a37fc8d51e871703e7136f8beed024146551c699746551980d3c9ebeee95c28d0c3bc479ba6c8b870309e468098ede3c3a4b38e67effdf24acf665c8f215648a2a15acd048b691dc52038d6d3f5728053b789c9263fb2c4f53244bc6981314fef24d1ba4387544bc8df0038dfa433881271d1f16194f29b4848d8455d3bb04832eefeac90d24a40b651fbc038317f338c421a3ff798cee3340331c0e29acbb4fd199daff3678dfb916a60da303a2d833f5221678f65bb919bab928e1ac2a21fb16c768c802385743c1f20f33bd03882b4ff677a16011e26972d50b9aad31dcdd6d90ce5ad017be729be29ac66bd603f3cd42c03862ca5c89e09108921410c77399a67e00a0c87ae3873a4db3d0712203e73b2ce57b9957bc498d47a6ccfa795d9cce036014c96c82550ad4d4b99d8a350349f87b79173f2152feb529877b8ebf378bb494867b7b5fd46e21c38c04b56b39032c53e4633f67dd8f1cd3b97857e2fa1888a08113bd4ab0ddd9f3f0667b75f31d03ebfe5a0154be1c4918681bb0b84185583e2381f1c034dbfeed740abe5a18a73e03de89818740c35966944b33a3f7b7776730866295e9ab6c54dbc38adac9e66efa0354fdd1dcf7c140089c99e10173a00bde1620974e76caa85b89c61386854d2e1e03a4fe5ae7bb248ad34f6d6c3cc546f3945bcabb9ea6832e5b2c126fb2948d37b0038436e7414d7bd51873cf6d66cd7100548bc241391940c4596cb8fd8318c9eb43033ef1123618d733594d9bde1b472f2695835b273426ef0e82d2901eced8db740203c6d4fb8c8da2f5bd13806af815634247d82f88e6f346c80f4bc6aa9c4ea3cc6103d27584f834991d48dd9d39eaf146fd21299f67af62a7f0f8c56f316b58c812f503c22ffb33a73ace056ec28dccf8f19920c8075a47151913e13bb76de334454350035bef61b2be3a91a5d66d92b259cb46bbe3a8a5866efac22f59f73f70174791b503c892127d3f6cceb7c41af2f17aa67450342f091879adb9acf3a7b5a8c1035ddd03556d3ec12e5343a2ef72db2e7ef381104279b2476ce4ae4a56b660b50347595203835ea527241d3ff19f15c1d172c75245fc94e1e2680fe5d2551b6225417ecdbb03fffa2540a1573b228956fa86248745aed31ae6e2c983714f32fd9dd7fc58c36a05581e0329328c87be9836183768908f30171daf16714c1f39a360b617bd23df400c0a4629546746ae0805581e03934cc196d443b1d2c5588f9fe55fa70f673923ac0eef128d9203835d500c0147c7a8357a0fa80005581e038bc60869ca403f3d41ddf8575cfa5be7785783dfb7dccc395b1ad999b00c01470d35cd2acec40005581e03d4abd7a5e810b20841e6aaf7fdba78949602e371cc5ff9aac8f74876200c054702c0246afc0c000458613373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff01550003937866b2e3eeae09cac3da53ab3a3bb6633acc55519aa5b62419c8832d8a0d5903e23744b1cd69412517ebb842ddba816b5ce57ac732a6fd9363683b726e9283a803552df02361ba73b9049088baf173f8a0374b49f5cc58d2bc702a2267c168fe900326943072e1d48562b1c43df99e402973cb58b21353ab9e36143cdd2e464c5a5d031d02ba81346dc86f3f0d9d2bbab2f2404d3200a7a5e6114d98717b944905405d033795bd5ac86c4e17541443163cf72b9d8c5c777b13b289d7e47799300567321e03ed2c9ef2b442b2e5d9aca2ff7982d84cff5e0b6fd6245ee60e7402071147810d034441d7d432b21d0be73ecd8f4c570391057bc2c8d9a3b1f7b2175c2afcae0ab90058200322525773051ef8058029d051a1f4c5d9eeaf40559917fa7a2a89b947411fc0446637ffd303f2b517c7bd2cb565778ec56e4b1bfd06c830eae417e5a559fd449e14e0306efd03fc38564d10da0417ae8e4fef08e4caca4aefc0ab3425fe72082dc30a2025db41038aacbc16b5484f3b1b382ca8c2bae263d31a97d90c53f8698a1a342704fac644005820036b0ff83c28e07db9b87f650bc5521a4928b98f2734605e31b316abbb2723504466372f6b036feeee16e8cd34c4a4e2ad2701022cc4e7b30c157fc8bfed017c26a64df68a1d03bde8679935a676b06f226dd38fee8fdfef7280023b4d573066a721dbc52c5ba5036b90ab86f9d4169f06e40a786cc722c207b8b1a3292b4e2a28fdfc34bfc0e189034410d32bf3aacddf86198a4c818896fb858a78657254ca8d9d773a76229b6a7600581f02532599d1c11e7369ee73db3173b6120a2b3af0c17f34dffef22f3442f09958205ea4091731346538d72cf90a95e0f2dfff48df3e799350a5c640bea8156abab7031b26617514c3d8caf5e8b48c5fe7eb56c37f34e892fe142ca39b492942cdc43b00581f038a122f53eca4d8d94ccd6add8a4fa3332488b96554e8b3d828e0d67bcad044663691d300581f03a82608c1838115f9edfb7aec3769d6fdc9ee0b8442ac2c321a90b1e1c8d05820949babe22c164f1faa1e7ca69cececfe3edad8dd793e5e0c9fabd5a5a0fea1eb0219600000581f020114cbe4c25878e8766c3e411142f2c25bfacd3d1c3938aa3a1517ce6b1758203a61ab97118928d845ac6ba99cc8927a98a9b09dd3dc16d4a3e5dda3a5b37bd002190a42035e05c858c3dc6126f674ed9ec1b6cb5f0fa49bc0f763946b138324afc8c2c8df0219ffff034ee76324509814a1d2d107abd70d1c0a7987db3b741b6dc568765ff3fa0a293403216f39b2bd0fc78a5be014d7c10d5ca17f4cb5040dbfb4bdcb2ba9acffd643ef0372e11e94396a1dc3d3b2bcd2ba5f04298277121840d9e9e5d53490ea3e25a08c03cb5b8ece8d47cca9db5e57a518b00a40f16f6b742dc6bb573eea02ee581f6c5a0376ca3ac70bb67e269832cf892d8769b5aec01647f69017b7b1fbc10fca001c9703025f4268fc74d169599f9e3fe45bce9fbec72e3fcce0d22f32e9761ddfbb86c903d81fad9827042c42f019adaf547024d3726ecc023555e7021dccf0e09c8d9a320386fd7345c676a3e7d0442713d1b2738062024bb4fc7f5260931a24c85ef33d6603110cf5cf3249b42e01a92e778bd9ad1d9c60c8c16677b968b4c62bdf681841ab0316780baa4e9ab6967c4931c43adaf52e1c5563b6c984bb50d3a597564cb2fc7d03ed679cdc905534d412aa05f773251e36e2a29848ee7a75eefe35b0a1b03543600340a80efa0498d313affc426ebe62fa68d353e2988764a32ae4aba6bfbf51d1fe03a5d11f8f61e288dad50737eef189aa0328a9ae2ed3681871836ef1a71f44a5d003a9161f54db36875d6c9f2a88b6f5b1b3cbf6117b29567d463d4fb2a52672117b036a1e9b7769dbacf559c0aac32b5030844cb77a33884311fe95b989395ce0bb350219ffff03dc397481f7b66fac54c693a71f57973d20800cbcc0dd39db91b6d6beebae586a0324552a31a3381865f03eea83fd92267405d9d4c6d8b833155a043236b2c10062034d401f8a8ac8cc7ea008c08991465754708f32b95e8e59e6c6b4339050e3f82703325c5e51e339e8e028bd0ca5f34c25a956e2087f86e2770282bf5633177bb78703266f9c1feb2381842a4e72917acb3adb0dbd4cafe8bd18420884b210934497560383f99cfa9161e369b5de5ad0b00ad9c84552e70bc5f10d868adcadfb5b58bcef035e5e1acb44162ae62b2726d4af15ceae5d916684876b6f05f15dbabe01f87c010324d81147467399c9db37d7a8764392b4368dc4d56d6e5dcac55b251b5a518b63037e8b40363f29defdb9d67efe0a1b33000b87aedf2d865dac2152b2e1a2a8c9cc03f0abb689ba349b34ba85c03da0d043e061fd08063dfe2c2cb7e49510800d8d0903a5df213e8071389427a506fd6e87bf303c6feab688524f6d03252b12ae1ab18103910711caa538ce4699447351bafaa45e25fff0299cfe7f560933ca6020aba35c0305070c61118e6c4ac4c1173490637e33d3bd52f58bbd0bf9203ce7bd03bee23e03c2d8dfc11d4cbc0af6ec50492f7ddcbc0747d5ddedce7409f09b529f003dec6c0301db8c4038417fe2676a7739c631c5f67ea20e33d48f6d4157f1f7a03622967700582003fbc1d063fd648afaa24087e3cbfd5c59ac8a9dc70349754e4e6104f1e78790581fe3f8ff0bd64314c7a13216f4dbea169e005d4a8929c6eb34eefd4f45fcc1ab038b0934374b0e4452204ae2e1f1707734c90e843b6f098a98034915681a30d53f0342034c7626f1585ff5207e7709e064bde06b1f4b63b67adc807646a1ddfc97d200581f02c6be597f7e493807a863ec7ae62d42d79551505c85a7d890c53c7039fd2c58209287be006d02da10be48ce53e3aef024a8014adc23d0b777f3953b51edd361ce03590a944822ea07f84d87fcf520a3fe871cf78dbab5256472c20c22cc73d510e800581f022ded293bfb22084a304fd076f6803ef9bec175bce1e43a9e603c9d272d0f58200fc5440d10f9feacba2a5a8104b45b28ff781b2892f3598c8337e61fd34625b40219080903939c6f686c811be1a5dce8e63ae68592a92e2b5ad91bd5f37fb56a952eb6d6ff031c7da40902002743940fdd2202322926ed0242aa753eba13d45012edc83a57250308d99b804daa5d2bbb1cb143d8d72eab73ae10422b318afd20105bf597a99a4e03eb606f7258ab6e4ffec273c4858c50c400c712a6275817b6f638d24e8015b7b30332b12f8cee009d5f50c0bb3959d5fffee16908c39ce32c5df6e2fe33cebcc97d0355c38bf29d70bbb4b167df6e6f097b7f68ec3ebf506f24bb03d9abbcbc27c737035621e9dd3e55543a1e36d0aace467d69dd864b7ce001e27329a731f86d6445e303e31368b9d85122a6cd61ef5d373488fe32c5395d581b43f08b86f00cc313ff7b0371014a6f5449f82411cd5fb121e70243d6305e286a87e8dd3af89e8a5d3e449c0305b124f6154376ab74d4115d2622436979f339dc400a1e4479b02bd7afbf002f036c2b9277de3384b5a77d165ea4e4f3699f237972db7c912ea8770e055d3f7a2f03c0259f1742d625f2a933bd94c7fa55906204083edae8deff3f2f80ec8e948f890219ffff0342c4ad5304fba46ac0184eb99276d01c5b4e5b05f432a049d2011e966e47ea4f0357a686dd6dd84660ffb48b2b1ec89603ddfc1c4fa0f292a44d1c78cfe3ddfb5b03d1f5df086ab5e3d367e2763df2e4bd2abfd1b0238427945f1a1def291d7c902a03c1b6e4d64117a14c462fbbebad18a455baf68215840cbfc39ab586e30cc4b33403a3f59cab71dae19e15cf0497a5ff305844c63e2b6a8dd783fa4458fe13b738df03f80fe21d7fb08d63ade3011710bf71d060cd91f7e98a40516ff88ec433c8c9120305716a1b0e21eb3859e566c614e76f52c84d291b7caad9e251e8e627859d30d8033e3e367e20785bf1086e03fde650ea930b501fe925cd9a34aa4fdcde63eca23e03aed190326c5d834cd3c5e05a7dd07455dbf89405df83f880426c20f3498eae5c03dc177b41f7a2323b34b4be78006508b3294c57da53531af974d9ac73f47ac9e303cf7e41ab704706cef980ee74b37b7ceb77272d51f9af23510dc0c4f263dc7ec30219ffff0219ffff05581e03a92c6bc4c13a5ec45527f0c18ea8932588728769ec7aecfe6d9f32e4200701186103efc4a95eba2b0e8ca1e7050522d539fcaee456a9baaed13b74846ce6e9f5284f05581e0387d430445038d44dfe5f67a6a29f0e61137f74004f19eb39df5f6507100c134622a41c687260035a6395100cee14b8619641123268781a56f8d9fad4ec4d364c15d99ab81fd8650219d595035d3b51473708235f94b7786ec3d9e1141cffde44d1b3c2f8f50735bc89406bdc0219ffff030cebfba2861bd2195e500fde0315af37f8f19a646878cc545a54b8f105f57aca033cb2c49436d7563a7ede67c0fba8e668dd17402082e216765a5457d2b8d7e12103e45da9fe37e969afed21a06e65aab4b8219e0be940cf6c2d96a3312dde77c4ba035f774524ca9f406192b4860444877e4d5f30a9c74bd06649e043a5c2dae88d8003d60fa00a626bce0862148fce6082480f621fdf13ceefaa278e5233b2972f532b03aa882e77de9c5268423e90105935f45eb651f54826727dcd0f60ed0c10f2be7c037d3a4a4affdefed487969723b53a5ff8092517074155829ec03241729f8707fa039b519826b67d1b6667ddd827e85822817f1993869dc5dec0ea1501f750af7e8e03cf09e161567ed1b6e29369d3cef8499f2d95e76f2624bd19ba1ee8af8ceb0818035b00880acacec5456581db61930702e5a416c16dab3d50468682fab6386b0f130219ffff03442811299717fb1da8525d9983c69db00e5a3c03d5652bd184e685ccb7f32f08033a90df83b4a40124fb24efcfa5fa860a84907c5cdf046109196ac6f7be49c0d7039f70bcaf5f415ebd778713b48e628e87a886f43d045c15ab9571aab101fda5830307ef8875886e41a3368d284a461ef82abad1515f9971c7fe974112160fa2e51603181e879df1290efca0721758aad4c72e49df9288eda58ef9daf22351f6bba84503ab69d28d1523d9836042326370ace5f56c98c030cc7ca5beaa0914ecc8d104ba037af13abe00b7ccfd8eca09cea3b096331e4f9d5e33ec3749b78a29f47a5069ca0377cb1b430cc0deb5ada6abed6c8d385e3fca8be5d3e067d72cb4800ae861befd03e70accd5c589e2695ae8554f22f19077a1e4313f19a6e78983a53b91e5bfa3320219ffff03fcaaa0cccabac4e22069a25dd0a91988b5e23e0b603978cf3bba6a72e9753463038024169a38953377af68b03764d0ffa2a9173f78a514c8d74e8b310a369428410219ffff031805e202857d7a867806b155d24ff3da4b351e73fb0d193c61a5e0eb6c3a6f7703f58832d8ca62a1bb0f7f04c3b74c89dcf4b26f7d2cb0bb25ff68534084bed3e8030046751cfc01bc978c36b688ca2f567c463d622ff42051ef1e211c637612ad8c0390e8657735e4cc3a3f4d22b9c4091fd642dfcd7ce26855bea027c18d5cfa72cb039483bd6c4f03e1ed8f5a49da52216551d0292a29c7af06969d247f2c2dc164ab032ebb6547eb5f487752b34948331dcc91de40ce71c8038ac259fe1d47f583d646039ae024332a4c29464c22bd924be9710e527bf665d8035fb5a930f74f1c91c28d03dd8ba265265bce74de1dc0b678a081a17aa0343fd24ca286507b2f49bc326d070219ffff032059218ffbd59bbd2c643d574b9b0160e76f8af398d553317bbd4c9383c7bc3d031a8549b15a8de9fc463234e894707f434bf3191e67e3e22c6a2fb4fe224e72b203e8ec7d2dca594a84df8979579d5841d99ea780d8792a52335b7c0dbfe1ed014203edea73fa5b74b0cb1514e70ec8a0a7e23840b1d107a8f2242ac79d7c1abacc0003bba764670960a04fb0e7d967d19ccaf44b2fe6f869df6d3c23a11d8971af95bd03edfb28b5d8a788e9cb02d90166d242961a5c25f65e4a573c60cba62f5b96cc2f0395c1cc870f7e555b5d28091b5eca92c61c7cc043340e1b7fd8348bcbca68cb2d030f8fdb7be4d63644cfb4dbcaf49e0b8151d67aa6de4a949da0deb861f014e9dd031eea401960d763474c5684a1571a045de24f89062688169a36f0e520a41745f1035fdecbffd336b25ac511c3928dea987f25315c04a697713495f81fd68fdbb4cf03222a7b39ecad82f5bcf6dd66b3509995361e3ab5bd494c50ed887482ae57b50203a683634f5cd4d5d0eb611d3bd2799c7c7c35a1384bb353b16b9ad5a869f622c403ceb7fdc45ac190cd17cdc3b3a59a3759f9eb5b35b7c4187431ba10dc8c3106c803e19ce551cd9e9e29cdd143e283e4519154b6a8573c38ac8feb16d9e6fe2ccba503733a2fa98119c3b6129e6ef4dd7f457ad37b5fc0573f5a7642dbac6f09e493b503094cd6cc3e2d641a6dd5d30ad0c90d3195c8de75c6a0abc4cdf046f4f0f85c7c03ffab77a99f43866c0a11b8d44abbbcc353154a868598aff5035a3abbb98f030e038a08fc9ec5ab5b20ece1f4a11dc0ed19d097cd220ab39d43e96abcbb8d86c04a03faa85b7af9788ac79906cfb2d04774dcfc9173c55cf311711061c17e42a0e34e035fea21fe93b872b1cfc986cc87fa88e3d5d314f1d22cf968e0f23da5604c4dd503822ff9f069841a5daabe830b8dad40e42c8d8cc8a3485ef673cf863d680d8e7b03f5ac526ada6cae4e2fceacf9ad9c48393e9c39b93254cf7d9b03b89eb6bf59fd03a135a6e349b8547bb3af29c8e8384f3740f8c21adf44ffa6db6afd893c1cffce0374f3ca42e3efa67b0509dfadbb5c6d7821f2e51888a70ac8361e309121b3224d03dcb23dae44c20e643fcd2fb504eaa85d722a4c82508eb10e0a1491c0a71cadd90333e548a9ebd2365a4c95d586c406c1fab91f8bc7360795bd2f162ef995bdbd2903734d40c0cc24c5834bff68e0c54b0e9e0425646e3008194c307dd0a0b894736d03b84e7b77031dfa81f5c2b98c991805575ef8dfcb303b2472240b1e16d1e7a6e20301aca7142cc0eca5777f121f503560692edf28a5b08c856b37600f003422ceaf05581e0313009004d334f90bc1c18eb48eae5750f357946ed62c6e7d3fd92d1da0040303e2c69d13bf1f89fd6cab48bd92c5c38cad9be2dada05d58a51c44e1169b2c2f403b207911b8627fb12682c2f664bb3cba8df89444932fe19ceeb5a0104aab29bd003562d59a51820d47f520c975e0b2bcffac644a509749a3161f481f57b6e826d210605581e0350c0cb385de15c36099ebe2dd6f6578233b0bc2f507450884255a0f31007011bffffffffffffffff04592c1d608060405234801561001057600080fd5b50600436106101b95760003560e01c80636a627842116100f9578063ba9a7a5611610097578063d21220a711610071578063d21220a7146105da578063d505accf146105e2578063dd62ed3e14610640578063fff6cae91461067b576101b9565b8063ba9a7a5614610597578063bc25cf771461059f578063c45a0155146105d2576101b9565b80637ecebe00116100d35780637ecebe00146104d757806389afcb441461050a57806395d89b4114610556578063a9059cbb1461055e576101b9565b80636a6278421461046957806370a082311461049c5780637464fc3d146104cf576101b9565b806323b872dd116101665780633644e515116101405780633644e51514610416578063485cc9551461041e5780635909c0d5146104595780635a3d549314610461576101b9565b806323b872dd146103ad57806330adf81f146103f0578063313ce567146103f8576101b9565b8063095ea7b311610197578063095ea7b3146103155780630dfe16811461036257806318160ddd14610393576101b9565b8063022c0d9f146101be57806306fdde03146102595780630902f1ac146102d6575b600080fd5b610257600480360360808110156101d457600080fd5b81359160208101359173ffffffffffffffffffffffffffffffffffffffff604083013516919081019060808101606082013564010000000081111561021857600080fd5b82018360208201111561022a57600080fd5b8035906020019184600183028401116401000000008311171561024c57600080fd5b509092509050610683565b005b610261610d57565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561029b578181015183820152602001610283565b50505050905090810190601f1680156102c85780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6102de610d90565b604080516dffffffffffffffffffffffffffff948516815292909316602083015263ffffffff168183015290519081900360600190f35b61034e6004803603604081101561032b57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610de5565b604080519115158252519081900360200190f35b61036a610dfc565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b61039b610e18565b60408051918252519081900360200190f35b61034e600480360360608110156103c357600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060400135610e1e565b61039b610efd565b610400610f21565b6040805160ff9092168252519081900360200190f35b61039b610f26565b6102576004803603604081101561043457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020013516610f2c565b61039b611005565b61039b61100b565b61039b6004803603602081101561047f57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16611011565b61039b600480360360208110156104b257600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113cb565b61039b6113dd565b61039b600480360360208110156104ed57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113e3565b61053d6004803603602081101561052057600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113f5565b6040805192835260208301919091528051918290030190f35b610261611892565b61034e6004803603604081101561057457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81351690602001356118cb565b61039b6118d8565b610257600480360360208110156105b557600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166118de565b61036a611ad4565b61036a611af0565b610257600480360360e08110156105f857600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060408101359060608101359060ff6080820135169060a08101359060c00135611b0c565b61039b6004803603604081101561065657600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020013516611dd8565b610257611df5565b600c546001146106f457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c55841515806107075750600084115b61075c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180612b2f6025913960400191505060405180910390fd5b600080610767610d90565b5091509150816dffffffffffffffffffffffffffff168710801561079a5750806dffffffffffffffffffffffffffff1686105b6107ef576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526021815260200180612b786021913960400191505060405180910390fd5b600654600754600091829173ffffffffffffffffffffffffffffffffffffffff91821691908116908916821480159061085457508073ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff1614155b6108bf57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f556e697377617056323a20494e56414c49445f544f0000000000000000000000604482015290519081900360640190fd5b8a156108d0576108d0828a8d611fdb565b89156108e1576108e1818a8c611fdb565b86156109c3578873ffffffffffffffffffffffffffffffffffffffff166310d1e85c338d8d8c8c6040518663ffffffff1660e01b8152600401808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b1580156109aa57600080fd5b505af11580156109be573d6000803e3d6000fd5b505050505b604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff8416916370a08231916024808301926020929190829003018186803b158015610a2f57600080fd5b505afa158015610a43573d6000803e3d6000fd5b505050506040513d6020811015610a5957600080fd5b5051604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905191955073ffffffffffffffffffffffffffffffffffffffff8316916370a0823191602480820192602092909190829003018186803b158015610acb57600080fd5b505afa158015610adf573d6000803e3d6000fd5b505050506040513d6020811015610af557600080fd5b5051925060009150506dffffffffffffffffffffffffffff85168a90038311610b1f576000610b35565b89856dffffffffffffffffffffffffffff160383035b9050600089856dffffffffffffffffffffffffffff16038311610b59576000610b6f565b89856dffffffffffffffffffffffffffff160383035b90506000821180610b805750600081115b610bd5576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526024815260200180612b546024913960400191505060405180910390fd5b6000610c09610beb84600363ffffffff6121e816565b610bfd876103e863ffffffff6121e816565b9063ffffffff61226e16565b90506000610c21610beb84600363ffffffff6121e816565b9050610c59620f4240610c4d6dffffffffffffffffffffffffffff8b8116908b1663ffffffff6121e816565b9063ffffffff6121e816565b610c69838363ffffffff6121e816565b1015610cd657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f556e697377617056323a204b0000000000000000000000000000000000000000604482015290519081900360640190fd5b5050610ce4848488886122e0565b60408051838152602081018390528082018d9052606081018c9052905173ffffffffffffffffffffffffffffffffffffffff8b169133917fd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d8229181900360800190a350506001600c55505050505050505050565b6040518060400160405280600a81526020017f556e69737761702056320000000000000000000000000000000000000000000081525081565b6008546dffffffffffffffffffffffffffff808216926e0100000000000000000000000000008304909116917c0100000000000000000000000000000000000000000000000000000000900463ffffffff1690565b6000610df233848461259c565b5060015b92915050565b60065473ffffffffffffffffffffffffffffffffffffffff1681565b60005481565b73ffffffffffffffffffffffffffffffffffffffff831660009081526002602090815260408083203384529091528120547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14610ee85773ffffffffffffffffffffffffffffffffffffffff84166000908152600260209081526040808320338452909152902054610eb6908363ffffffff61226e16565b73ffffffffffffffffffffffffffffffffffffffff851660009081526002602090815260408083203384529091529020555b610ef384848461260b565b5060019392505050565b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b601281565b60035481565b60055473ffffffffffffffffffffffffffffffffffffffff163314610fb257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f556e697377617056323a20464f5242494444454e000000000000000000000000604482015290519081900360640190fd5b6006805473ffffffffffffffffffffffffffffffffffffffff9384167fffffffffffffffffffffffff00000000000000000000000000000000000000009182161790915560078054929093169116179055565b60095481565b600a5481565b6000600c5460011461108457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c81905580611094610d90565b50600654604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905193955091935060009273ffffffffffffffffffffffffffffffffffffffff909116916370a08231916024808301926020929190829003018186803b15801561110e57600080fd5b505afa158015611122573d6000803e3d6000fd5b505050506040513d602081101561113857600080fd5b5051600754604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905192935060009273ffffffffffffffffffffffffffffffffffffffff909216916370a0823191602480820192602092909190829003018186803b1580156111b157600080fd5b505afa1580156111c5573d6000803e3d6000fd5b505050506040513d60208110156111db57600080fd5b505190506000611201836dffffffffffffffffffffffffffff871663ffffffff61226e16565b90506000611225836dffffffffffffffffffffffffffff871663ffffffff61226e16565b9050600061123387876126ec565b600054909150806112705761125c6103e8610bfd611257878763ffffffff6121e816565b612878565b985061126b60006103e86128ca565b6112cd565b6112ca6dffffffffffffffffffffffffffff8916611294868463ffffffff6121e816565b8161129b57fe5b046dffffffffffffffffffffffffffff89166112bd868563ffffffff6121e816565b816112c457fe5b0461297a565b98505b60008911611326576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180612bc16028913960400191505060405180910390fd5b6113308a8a6128ca565b61133c86868a8a6122e0565b811561137e5760085461137a906dffffffffffffffffffffffffffff808216916e01000000000000000000000000000090041663ffffffff6121e816565b600b555b6040805185815260208101859052815133927f4c209b5fc8ad50758f13e2e1088ba56a560dff690a1c6fef26394f4c03821c4f928290030190a250506001600c5550949695505050505050565b60016020526000908152604090205481565b600b5481565b60046020526000908152604090205481565b600080600c5460011461146957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c81905580611479610d90565b50600654600754604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905194965092945073ffffffffffffffffffffffffffffffffffffffff9182169391169160009184916370a08231916024808301926020929190829003018186803b1580156114fb57600080fd5b505afa15801561150f573d6000803e3d6000fd5b505050506040513d602081101561152557600080fd5b5051604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905191925060009173ffffffffffffffffffffffffffffffffffffffff8516916370a08231916024808301926020929190829003018186803b15801561159957600080fd5b505afa1580156115ad573d6000803e3d6000fd5b505050506040513d60208110156115c357600080fd5b5051306000908152600160205260408120549192506115e288886126ec565b600054909150806115f9848763ffffffff6121e816565b8161160057fe5b049a5080611614848663ffffffff6121e816565b8161161b57fe5b04995060008b11801561162e575060008a115b611683576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180612b996028913960400191505060405180910390fd5b61168d3084612992565b611698878d8d611fdb565b6116a3868d8c611fdb565b604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff8916916370a08231916024808301926020929190829003018186803b15801561170f57600080fd5b505afa158015611723573d6000803e3d6000fd5b505050506040513d602081101561173957600080fd5b5051604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905191965073ffffffffffffffffffffffffffffffffffffffff8816916370a0823191602480820192602092909190829003018186803b1580156117ab57600080fd5b505afa1580156117bf573d6000803e3d6000fd5b505050506040513d60208110156117d557600080fd5b505193506117e585858b8b6122e0565b811561182757600854611823906dffffffffffffffffffffffffffff808216916e01000000000000000000000000000090041663ffffffff6121e816565b600b555b604080518c8152602081018c9052815173ffffffffffffffffffffffffffffffffffffffff8f169233927fdccd412f0b1252819cb1fd330b93224ca42612892bb3f4f789976e6d81936496929081900390910190a35050505050505050506001600c81905550915091565b6040518060400160405280600681526020017f554e492d5632000000000000000000000000000000000000000000000000000081525081565b6000610df233848461260b565b6103e881565b600c5460011461194f57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c55600654600754600854604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff9485169490931692611a2b9285928792611a26926dffffffffffffffffffffffffffff169185916370a0823191602480820192602092909190829003018186803b1580156119ee57600080fd5b505afa158015611a02573d6000803e3d6000fd5b505050506040513d6020811015611a1857600080fd5b50519063ffffffff61226e16565b611fdb565b600854604080517f70a082310000000000000000000000000000000000000000000000000000000081523060048201529051611aca9284928792611a26926e01000000000000000000000000000090046dffffffffffffffffffffffffffff169173ffffffffffffffffffffffffffffffffffffffff8616916370a0823191602480820192602092909190829003018186803b1580156119ee57600080fd5b50506001600c5550565b60055473ffffffffffffffffffffffffffffffffffffffff1681565b60075473ffffffffffffffffffffffffffffffffffffffff1681565b42841015611b7b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f556e697377617056323a20455850495245440000000000000000000000000000604482015290519081900360640190fd5b60035473ffffffffffffffffffffffffffffffffffffffff80891660008181526004602090815260408083208054600180820190925582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98186015280840196909652958d166060860152608085018c905260a085019590955260c08085018b90528151808603909101815260e0850182528051908301207f19010000000000000000000000000000000000000000000000000000000000006101008601526101028501969096526101228085019690965280518085039096018652610142840180825286519683019690962095839052610162840180825286905260ff89166101828501526101a284018890526101c28401879052519193926101e2808201937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081019281900390910190855afa158015611cdc573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff811615801590611d5757508873ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b611dc257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f556e697377617056323a20494e56414c49445f5349474e415455524500000000604482015290519081900360640190fd5b611dcd89898961259c565b505050505050505050565b600260209081526000928352604080842090915290825290205481565b600c54600114611e6657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c55600654604080517f70a082310000000000000000000000000000000000000000000000000000000081523060048201529051611fd49273ffffffffffffffffffffffffffffffffffffffff16916370a08231916024808301926020929190829003018186803b158015611edd57600080fd5b505afa158015611ef1573d6000803e3d6000fd5b505050506040513d6020811015611f0757600080fd5b5051600754604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff909216916370a0823191602480820192602092909190829003018186803b158015611f7a57600080fd5b505afa158015611f8e573d6000803e3d6000fd5b505050506040513d6020811015611fa457600080fd5b50516008546dffffffffffffffffffffffffffff808216916e0100000000000000000000000000009004166122e0565b6001600c55565b604080518082018252601981527f7472616e7366657228616464726573732c75696e743235362900000000000000602091820152815173ffffffffffffffffffffffffffffffffffffffff85811660248301526044808301869052845180840390910181526064909201845291810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb000000000000000000000000000000000000000000000000000000001781529251815160009460609489169392918291908083835b602083106120e157805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016120a4565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114612143576040519150601f19603f3d011682016040523d82523d6000602084013e612148565b606091505b5091509150818015612176575080511580612176575080806020019051602081101561217357600080fd5b50515b6121e157604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f556e697377617056323a205452414e534645525f4641494c4544000000000000604482015290519081900360640190fd5b5050505050565b60008115806122035750508082028282828161220057fe5b04145b610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f64732d6d6174682d6d756c2d6f766572666c6f77000000000000000000000000604482015290519081900360640190fd5b80820382811115610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f64732d6d6174682d7375622d756e646572666c6f770000000000000000000000604482015290519081900360640190fd5b6dffffffffffffffffffffffffffff841180159061230c57506dffffffffffffffffffffffffffff8311155b61237757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f556e697377617056323a204f564552464c4f5700000000000000000000000000604482015290519081900360640190fd5b60085463ffffffff428116917c0100000000000000000000000000000000000000000000000000000000900481168203908116158015906123c757506dffffffffffffffffffffffffffff841615155b80156123e257506dffffffffffffffffffffffffffff831615155b15612492578063ffffffff16612425856123fb86612a57565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff169063ffffffff612a7b16565b600980547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff929092169290920201905563ffffffff8116612465846123fb87612a57565b600a80547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff92909216929092020190555b600880547fffffffffffffffffffffffffffffffffffff0000000000000000000000000000166dffffffffffffffffffffffffffff888116919091177fffffffff0000000000000000000000000000ffffffffffffffffffffffffffff166e0100000000000000000000000000008883168102919091177bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167c010000000000000000000000000000000000000000000000000000000063ffffffff871602179283905560408051848416815291909304909116602082015281517f1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1929181900390910190a1505050505050565b73ffffffffffffffffffffffffffffffffffffffff808416600081815260026020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b73ffffffffffffffffffffffffffffffffffffffff8316600090815260016020526040902054612641908263ffffffff61226e16565b73ffffffffffffffffffffffffffffffffffffffff8085166000908152600160205260408082209390935590841681522054612683908263ffffffff612abc16565b73ffffffffffffffffffffffffffffffffffffffff80841660008181526001602090815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600080600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663017e7e586040518163ffffffff1660e01b815260040160206040518083038186803b15801561275757600080fd5b505afa15801561276b573d6000803e3d6000fd5b505050506040513d602081101561278157600080fd5b5051600b5473ffffffffffffffffffffffffffffffffffffffff821615801594509192509061286457801561285f5760006127d86112576dffffffffffffffffffffffffffff88811690881663ffffffff6121e816565b905060006127e583612878565b90508082111561285c576000612813612804848463ffffffff61226e16565b6000549063ffffffff6121e816565b905060006128388361282c86600563ffffffff6121e816565b9063ffffffff612abc16565b9050600081838161284557fe5b04905080156128585761285887826128ca565b5050505b50505b612870565b8015612870576000600b555b505092915050565b600060038211156128bb575080600160028204015b818110156128b5578091506002818285816128a457fe5b0401816128ad57fe5b04905061288d565b506128c5565b81156128c5575060015b919050565b6000546128dd908263ffffffff612abc16565b600090815573ffffffffffffffffffffffffffffffffffffffff8316815260016020526040902054612915908263ffffffff612abc16565b73ffffffffffffffffffffffffffffffffffffffff831660008181526001602090815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b6000818310612989578161298b565b825b9392505050565b73ffffffffffffffffffffffffffffffffffffffff82166000908152600160205260409020546129c8908263ffffffff61226e16565b73ffffffffffffffffffffffffffffffffffffffff831660009081526001602052604081209190915554612a02908263ffffffff61226e16565b600090815560408051838152905173ffffffffffffffffffffffffffffffffffffffff8516917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef919081900360200190a35050565b6dffffffffffffffffffffffffffff166e0100000000000000000000000000000290565b60006dffffffffffffffffffffffffffff82167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff841681612ab457fe5b049392505050565b80820182811015610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f64732d6d6174682d6164642d6f766572666c6f77000000000000000000000000604482015290519081900360640190fdfe556e697377617056323a20494e53554646494349454e545f4f55545055545f414d4f554e54556e697377617056323a20494e53554646494349454e545f494e5055545f414d4f554e54556e697377617056323a20494e53554646494349454e545f4c4951554944495459556e697377617056323a20494e53554646494349454e545f4c49515549444954595f4255524e4544556e697377617056323a20494e53554646494349454e545f4c49515549444954595f4d494e544544a265627a7a723158207dca18479e58487606bf70c79e44d8dee62353c9ee6d01f9a9d70885b8765f2264736f6c63430005100032032e2bc0c0ff22609eac8f10e1c8736f3e780dcb85055451e7ac674e2667ce4b570058210390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5630480463777a4d8c892d00582103a74ac629773b39717320a4a7c0089e4b0667da26a07887255087b04485870cc05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0058210366cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c688054c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2005821032575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b05820b90c1a14c088c1d56a6b8fd976e6ebf88b023497d5454cd783fa3cb22ebe2d7100582103f6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c70410100582002f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee35820663811bb00000000000010879dcb019bd68e000000000000012a6d8e1122000000582002a2f775275d4ead74b6836a12a0af610b14ed125e826aef45fdd1edce554ffd480463777a4d8c85450058200252222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f543339474491b13ac638f163d634fbbc722cd279160218580219b50505581d02aefb66e9c244bedfa953b8857e1f3a45010a7631ba683bf8c76f33910701192c1d05581d0291f3087fcb4d685046c028fa385eb98adc69b05fe9f7f5e990046f5d040102190880036349f17e9fcb4ca4b31440d7858e575d9f0d6f4029f73060f120236998ebcdc305581e03676fbd2738568fd9e0c66efefbcb7703b2595566a1b6484011fb69a7100847028ed6103d000003ff20557d6be1c48bc106100d951206a4a961c50b1af816568c92944301459ab8036f1babd1aad365df43a426574d9f1bf6433ed3d5841aa3da7d1b7adc99d2e1fb05581e03f503cb9c8781baf2682ced17ee8d6a3b410312a358d58675ad94a0640007011bffffffffffffffff05581e03bee7418d20ef338b3ff21ffa994121d3bdbdea8c687bb3c542e04d4a700847d4464ef554600005581e03c2b3991f35ae9c607c17e57f9db582c79334f33a929ea345fff7e215200c01471a3e3b72f7502d032b513e44f3337828c5d75b8bdd51bafa3b1eb9b47c194d6bc35ed4fb7b2449420219dfcc0331705cd11b32b3bfb5d70a3e504c8e319cd4f96d67676a68b91fd30919a94a9703629f6d7531204106e6edbe9e67ffab3ec8bc34394ca4dfe7c58331e7d1746e8703be49e45138114c6cf393eb1723e2dfc97444bb81b4006301467634cafafb3f30033a4c1de5fe0db02813547929437b8c5887db566e6d51b0c12f9239843d66c9170219ffff0346453db57f270d1869a278dade60b187f322a49943aae3372bb113f3849d476f03cb1e6cc8b6ad6f28db9f5034eb3015dba9afa2192455fe9de6f7a2cb7fc8d82f03ea11dc7eba948738186f53c51eacb3f65dd5e0257df100592feb430deaeceaed039786472a5b2385243c3400d426e7b197c32b599813bfd0fd2eab844fdceda3fd037e2d26860c1a0308b2dc4c49218decc0ed9439387fbf8d61251e4af81de3526d038b393e0aef6d453c3d12a9a62a06db9e9e08d1213ac87a995876db420a5e403a03933ab0d170efb7138e112118d824a3c703af7e98d3f216aca3a929501e97ebbd03698d4e605ca1fa06f9b80f8bdcbe9979e1c7535ce6ecc434fc7411af4a2ef3530219ffff0331328ad597681762e5dda9ff4d95579f469750cd0e93cdc74843c352a8131a4f0383b86ee3d24977d22f3e8d012500ae18b9611d89272bb96ac16af2ced52585a6036ec3189a3597126249c999e0db19c66479dd6451fbd46d54a9059c563487b19b039a722529f68aaec786073ee41cfcfbdaa0deaffaecfb72d8fc018b477c4e958f039b758f1eb9f1931eb22550b73ee674970d08fe7c76d8386cc81c2efed76f92fe033da7b9e572ed69cd245b8cb6f959ce6b6a83986b7d155b904cb00c3c079a3b8403bb0752e3e62172c2de1accddaafb54f8c037f25d307f70b26b014ecf1174c86a03b68fdc79d20fe9e37db7d154d86213765c74c664b92c76678f01b30e496f6d850313376f50413b542806ce74e560667332b8442bd152a6706875100de8b6cfd2e1036d68e4a372ca16a93ea84f36e244a93d3e010bbe83c6e86bdf8082104f7045aa031e0d2ae1f5a9eaf5126fe9973bd426b783eaf746ae9e86ac6251437bec3eb19d03469b7bcdbfa0250673e3a6727a4e9b029aeef9a9f1f565f6557773e73d336ad003eb0bb279af016b8729620835624a1c4667d06cd2a65adaa74409896b76f4db8003f471d9b3fa2b0e94cc6f123b8758ad5dcfef98de9448cc4760670725f5ced70b03f36aa718d6715f22cc414ad81e5603a68f059da2cb137e98b733d33aba0f735b0219ffff037a215217da5c353fdf83cff67421f514e0c6a06859a63f6e9a016133e7768baf03c3fd54bf8e15c40621fce0177167e6fad85b6aa84042ad6bb9003b95db897e4703a8bd733be28a2284f56687d5e9df16b8192ae019ee02ba878204022b0510f8da036ec570fe3b9e8d08dc5c76ec68f701fd01ec58e9263d7b236e619be74f96f4d603e89ccc561fe2b73a3779df36fded2411d4499cdaca3d9c88f87c815374a0575403e1f7f61734f65ed17d0a3ccd2c82c02b2941649e532baa7d6afd5f9eefe20b44033f5353ce899131d33056ea9841924ec913641a5571c0b68eae67f5dbe3cf473e0219ffff03ae3729bde1c448cd7665515f5b58e43ee482b1e1f9773471d458b2925658eddd03aa3d9e84099495fcaac4bca1b3f0986556f97b0684777615df8df2080bd768890372678cad52484f10978d735f381915ac93f3d93c4d3eabe599def609ebcdc19b033ea6185656815ade5a793a0529fcf2dd87aaf05c328f1dc3777f968832ee593c0388b87518448aaf50db386611e44da23c7be5614460b563862e5778da35bf0aff03eb17d7a0c27b6cbcc486b2a9e924afd0449e91871342c5f1fc996e197d3f9e83039cc4ce48ea8137f2be42dded37e0648a1b7c0809f3c97ffdaf6acef974a02b9703683278c6c78a1c7f5bc9c71d1c927816c874f40660ac88f031760fa68e76056603a982b0b5eefd99e38f046321c7d07d635b37556b29a64dd1bae9077fe45fcb20039669cd279adece0ab34f7a63d2281b9e3cbcc395854f753a832ed8e3665100f50336af8d2a62efee81bae60be46828c5f924a853819c3566e6ef05aca39cfb412903d3b6d35624f74dad9482943b16e978f85c1740c64f08b5e7d80bf4d35dbf54cb035146979b790985f73d6aab896489cf0984f7d55753c356a469c532eaf43473580381724697ed2f47d4ed166a3c4b53d5902b7faa46e51a9e202f60bd02e8475795031acee7df0b775954ce7a6cc74781a2c285f295d442deb6371519cca8e7ed65210347a3027b7da73d1241f89d724be5acf5c6c21c24fd33e132eb8acd526dabf04203e896389c449479b8a548befab0c7fbfa47f829e6ded2fbb39f4133ec8b036bee03f5944cf3c1ecdd90abe2805bd4b99058d300603394d5179ee90fd71b4ffe392c03c0649dce44d2236b86042b0f6e2a6099a24f72ccc578e0f4196994fec2977a5d037b557e67a2a59cb8be4f7b9209546dfebec8beff549ac4d1cf5e25934c4e85b103a07467479d8d844764178e00694f1f69265474541f5cd151f3e2d5675787006f03c27d32054e84322bdfb70a37930ee3de85043aebb34cdfde62cdc5c22ed7859103287e2780039aa361044170ed14051f0baf77dc238619da2da89edea68bac033c03b332227b17565726f04dda161bbef5cf2543ef2cd0552fff11611d4a56f110ba0352d83eae12221d3199730d03f01431ae8f311f05d5dfb750aec3690230cf7939031ef7bafc2428bed1014196a346035b87a5f292040f02842dd99f12ee1138421f03c8bdf22c8233cd5b2297befe578ed4795fca563a75487a3996c8bad4fc5bcd1f03b6b6f23f7300a838709cbd132ccb0cfaf80197e9d2034f4a591246c7b6f8499d0303b65196a8cf63075b5c7759679c4a0e6adcc16a841bc7900b0eee4782e5d41f03c8d5e30378a1ccf2976579229d51d39ccb05ce8285933478475108b66e2193e005581e03520f1c1144b24c2ce87e7459e0bc768a2b327757bdee9be235cdfda630040105581e03adc92a3d57103e87d72eae67f3ea13a730225d0e1e8e0af52b59c2b6800c034655e9e86224d805581e0320dc1086e8876e02ff4f7cbb69e79bb49001d246469a012822f6b05d700401034875555289f79c2c2f7431de427e59d5d70fd6f1d70b7aa84e71ebb4cb3d247305581e0314055b1c9397f758dc84a39c23a8056b3bc19add63abac4f3ddca21570040105581e037e2a455dbb9216f5da64ca9338618bd59782bba0426a3a3ef68c0cff70040105581e031aed6efe4c866f79ffb7b91ed02c6af216d04ef1757d9d6d53658273f00c054729084c5e764134038a70a36b03df65c40adce9a36ad23eb646131d89372524efd60698ec88c1a3fc05581d0287d6e269b41b8c7406da2c48373f4c3dba68e9d6d0acfd71821198f4040204595ba8608060405234801561001057600080fd5b506004361061036d5760003560e01c80638456cb59116101d3578063b7b7289911610104578063e3ee160e116100a2578063ef55bec61161007c578063ef55bec614611122578063f2fde38b1461118e578063f9f92be4146111c1578063fe575a87146111f45761036d565b8063e3ee160e14611075578063e5a6b10f146110e1578063e94a0102146110e95761036d565b8063d505accf116100de578063d505accf14610f64578063d608ea6414610fc2578063d916948714611032578063dd62ed3e1461103a5761036d565b8063b7b7289914610db0578063bd10243014610e78578063cf09299514610e805761036d565b8063a0cc6a6811610171578063aa20e1e41161014b578063aa20e1e414610cd4578063aa271e1a14610d07578063ad38bf2214610d3a578063b2118a8d14610d6d5761036d565b8063a0cc6a6814610c5a578063a457c2d714610c62578063a9059cbb14610c9b5761036d565b80638da5cb5b116101ad5780638da5cb5b14610b6a57806395d89b4114610b725780639fd0506d14610b7a5780639fd5a6cf14610b825761036d565b80638456cb5914610a4b57806388b7ab6314610a535780638a6db9c314610b375761036d565b806338a63183116102ad57806354fd4d501161024b5780635c975abb116102255780635c975abb146109d557806370a08231146109dd5780637ecebe0014610a105780637f2eecc314610a435761036d565b806354fd4d501461094c578063554bab3c146109545780635a049a70146109875761036d565b806340c10f191161028757806340c10f19146107fb57806342966c6814610834578063430239b4146108515780634e44d956146109135761036d565b806338a63183146107b257806339509351146107ba5780633f4ba83a146107f35761036d565b80632fc81e091161031a578063313ce567116102f4578063313ce5671461056f5780633357162b1461058d57806335d99f35146107795780633644e515146107aa5761036d565b80632fc81e09146105015780633092afd51461053457806330adf81f146105675761036d565b80631a8952661161034b5780631a8952661461045657806323b872dd1461048b5780632ab60045146104ce5761036d565b806306fdde0314610372578063095ea7b3146103ef57806318160ddd1461043c575b600080fd5b61037a611227565b6040805160208082528351818301528351919283929083019185019080838360005b838110156103b457818101518382015260200161039c565b50505050905090810190601f1680156103e15780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6104286004803603604081101561040557600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81351690602001356112d3565b604080519115158252519081900360200190f35b610444611374565b60408051918252519081900360200190f35b6104896004803603602081101561046c57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff1661137a565b005b610428600480360360608110156104a157600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060400135611437565b610489600480360360208110156104e457600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166116f2565b6104896004803603602081101561051757600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16611853565b6104286004803603602081101561054a57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166118bb565b6104446119b4565b6105776119d8565b6040805160ff9092168252519081900360200190f35b61048960048036036101008110156105a457600080fd5b8101906020810181356401000000008111156105bf57600080fd5b8201836020820111156105d157600080fd5b803590602001918460018302840111640100000000831117156105f357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929594936020810193503591505064010000000081111561064657600080fd5b82018360208201111561065857600080fd5b8035906020019184600183028401116401000000008311171561067a57600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092959493602081019350359150506401000000008111156106cd57600080fd5b8201836020820111156106df57600080fd5b8035906020019184600183028401116401000000008311171561070157600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050813560ff16925050602081013573ffffffffffffffffffffffffffffffffffffffff908116916040810135821691606082013581169160800135166119e1565b610781611d23565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b610444611d3f565b610781611d4e565b610428600480360360408110156107d057600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135611d6a565b610489611e02565b6104286004803603604081101561081157600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135611ec5565b6104896004803603602081101561084a57600080fd5b5035612296565b6104896004803603604081101561086757600080fd5b81019060208101813564010000000081111561088257600080fd5b82018360208201111561089457600080fd5b803590602001918460208302840111640100000000831117156108b657600080fd5b9193909290916020810190356401000000008111156108d457600080fd5b8201836020820111156108e657600080fd5b8035906020019184600183028401116401000000008311171561090857600080fd5b509092509050612538565b6104286004803603604081101561092957600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81351690602001356126ef565b61037a612882565b6104896004803603602081101561096a57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166128b9565b610489600480360360a081101561099d57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060208101359060ff6040820135169060608101359060800135612a20565b610428612abe565b610444600480360360208110156109f357600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16612adf565b61044460048036036020811015610a2657600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16612af0565b610444612b18565b610489612b3c565b610489600480360360e0811015610a6957600080fd5b73ffffffffffffffffffffffffffffffffffffffff823581169260208101359091169160408201359160608101359160808201359160a08101359181019060e0810160c0820135640100000000811115610ac257600080fd5b820183602082011115610ad457600080fd5b80359060200191846001830284011164010000000083111715610af657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550612c16945050505050565b61044460048036036020811015610b4d57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16612d7a565b610781612da2565b61037a612dbe565b610781612e37565b610489600480360360a0811015610b9857600080fd5b73ffffffffffffffffffffffffffffffffffffffff823581169260208101359091169160408201359160608101359181019060a081016080820135640100000000811115610be557600080fd5b820183602082011115610bf757600080fd5b80359060200191846001830284011164010000000083111715610c1957600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550612e53945050505050565b610444612eea565b61042860048036036040811015610c7857600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135612f0e565b61042860048036036040811015610cb157600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135612fa6565b61048960048036036020811015610cea57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16613109565b61042860048036036020811015610d1d57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16613270565b61048960048036036020811015610d5057600080fd5b503573ffffffffffffffffffffffffffffffffffffffff1661329b565b61048960048036036060811015610d8357600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060400135613402565b61048960048036036060811015610dc657600080fd5b73ffffffffffffffffffffffffffffffffffffffff82351691602081013591810190606081016040820135640100000000811115610e0357600080fd5b820183602082011115610e1557600080fd5b80359060200191846001830284011164010000000083111715610e3757600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550613498945050505050565b61078161352d565b610489600480360360e0811015610e9657600080fd5b73ffffffffffffffffffffffffffffffffffffffff823581169260208101359091169160408201359160608101359160808201359160a08101359181019060e0810160c0820135640100000000811115610eef57600080fd5b820183602082011115610f0157600080fd5b80359060200191846001830284011164010000000083111715610f2357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550613549945050505050565b610489600480360360e0811015610f7a57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060408101359060608101359060ff6080820135169060a08101359060c001356136a2565b61048960048036036020811015610fd857600080fd5b810190602081018135640100000000811115610ff357600080fd5b82018360208201111561100557600080fd5b8035906020019184600183028401116401000000008311171561102757600080fd5b509092509050613744565b61044461382d565b6104446004803603604081101561105057600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020013516613851565b610489600480360361012081101561108c57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060408101359060608101359060808101359060a08101359060ff60c0820135169060e0810135906101000135613889565b61037a6139f1565b610428600480360360408110156110ff57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135613a6a565b610489600480360361012081101561113957600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060408101359060608101359060808101359060a08101359060ff60c0820135169060e0810135906101000135613aa2565b610489600480360360208110156111a457600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16613bfd565b610489600480360360208110156111d757600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16613d50565b6104286004803603602081101561120a57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16613e0d565b6004805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f810184900484028201840190925281815292918301828280156112cb5780601f106112a0576101008083540402835291602001916112cb565b820191906000526020600020905b8154815290600101906020018083116112ae57829003601f168201915b505050505081565b60015460009074010000000000000000000000000000000000000000900460ff161561136057604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b61136b338484613e18565b50600192915050565b600b5490565b60025473ffffffffffffffffffffffffffffffffffffffff1633146113ea576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c815260200180615824602c913960400191505060405180910390fd5b6113f381613f5f565b60405173ffffffffffffffffffffffffffffffffffffffff8216907f117e3210bb9aa7d9baff172026820255c6f6c30ba8999d1c2fd88e2848137c4e90600090a250565b60015460009074010000000000000000000000000000000000000000900460ff16156114c457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b336114ce81613f6a565b15611524576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b8461152e81613f6a565b15611584576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b8461158e81613f6a565b156115e4576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff87166000908152600a6020908152604080832033845290915290205485111561166d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260288152602001806159146028913960400191505060405180910390fd5b611678878787613f98565b73ffffffffffffffffffffffffffffffffffffffff87166000908152600a602090815260408083203384529091529020546116b39086614163565b73ffffffffffffffffffffffffffffffffffffffff88166000908152600a60209081526040808320338452909152902055600193505050509392505050565b60005473ffffffffffffffffffffffffffffffffffffffff16331461177857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff81166117e4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a81526020018061575d602a913960400191505060405180910390fd5b600e80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517fe475e580d85111348e40d8ca33cfdd74c30fe1655c2d8537a13abc10065ffa5a90600090a250565b60125460ff1660011461186557600080fd5b6000611870306141da565b9050801561188357611883308383613f98565b61188c30614224565b5050601280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166002179055565b60085460009073ffffffffffffffffffffffffffffffffffffffff16331461192e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806157fb6029913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff82166000818152600c6020908152604080832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055600d909152808220829055517fe94479a9f7e1952cc78f2d6baab678adc1b772d936c6583def489e524cb666929190a2506001919050565b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b60065460ff1681565b60085474010000000000000000000000000000000000000000900460ff1615611a55576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a81526020018061598f602a913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8416611ac1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602f8152602001806158c1602f913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8316611b2d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806157346029913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8216611b99576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602e81526020018061593c602e913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8116611c05576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180615a7c6028913960400191505060405180910390fd5b8751611c189060049060208b01906154cd565b508651611c2c9060059060208a01906154cd565b508551611c409060079060208901906154cd565b50600680547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660ff8716179055600880547fffffffffffffffffffffffff000000000000000000000000000000000000000090811673ffffffffffffffffffffffffffffffffffffffff8781169190911790925560018054821686841617905560028054909116918416919091179055611cda8161422f565b5050600880547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1674010000000000000000000000000000000000000000179055505050505050565b60085473ffffffffffffffffffffffffffffffffffffffff1681565b6000611d49614276565b905090565b600e5473ffffffffffffffffffffffffffffffffffffffff1690565b60015460009074010000000000000000000000000000000000000000900460ff1615611df757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b61136b33848461436b565b60015473ffffffffffffffffffffffffffffffffffffffff163314611e72576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526022815260200180615a306022913960400191505060405180910390fd5b600180547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1690556040517f7805862f689e2f13df9f062ff482ad3ad112aca9e0847911ed832e158c525b3390600090a1565b60015460009074010000000000000000000000000000000000000000900460ff1615611f5257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b336000908152600c602052604090205460ff16611fba576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806158a06021913960400191505060405180910390fd5b33611fc481613f6a565b1561201a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b8361202481613f6a565b1561207a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff85166120e6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260238152602001806156c96023913960400191505060405180910390fd5b6000841161213f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806157ac6029913960400191505060405180910390fd5b336000908152600d6020526040902054808511156121a8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602e815260200180615a02602e913960400191505060405180910390fd5b600b546121b590866143b5565b600b556121d4866121cf876121c9836141da565b906143b5565b614430565b6121de8186614163565b336000818152600d6020908152604091829020939093558051888152905173ffffffffffffffffffffffffffffffffffffffff8a16937fab8530f87dc9b59234c4623bf917212bb2536d647574c8e7e5da92c2ede0c9f8928290030190a360408051868152905173ffffffffffffffffffffffffffffffffffffffff8816916000917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9181900360200190a350600195945050505050565b60015474010000000000000000000000000000000000000000900460ff161561232057604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b336000908152600c602052604090205460ff16612388576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806158a06021913960400191505060405180910390fd5b3361239281613f6a565b156123e8576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b60006123f3336141da565b90506000831161244e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806156a06029913960400191505060405180910390fd5b828110156124a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602681526020018061587a6026913960400191505060405180910390fd5b600b546124b49084614163565b600b556124c5336121cf8386614163565b60408051848152905133917fcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca5919081900360200190a260408051848152905160009133917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9181900360200190a3505050565b60125460ff1660021461254a57600080fd5b6125566005838361554b565b5060005b83811015612698576003600086868481811061257257fe5b6020908102929092013573ffffffffffffffffffffffffffffffffffffffff168352508101919091526040016000205460ff166125fa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603d8152602001806155ed603d913960400191505060405180910390fd5b61262b85858381811061260957fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff16614224565b6003600086868481811061263b57fe5b6020908102929092013573ffffffffffffffffffffffffffffffffffffffff1683525081019190915260400160002080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016905560010161255a565b506126a230614224565b505030600090815260036020819052604090912080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff009081169091556012805490911690911790555050565b60015460009074010000000000000000000000000000000000000000900460ff161561277c57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b60085473ffffffffffffffffffffffffffffffffffffffff1633146127ec576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806157fb6029913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff83166000818152600c6020908152604080832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055600d825291829020859055815185815291517f46980fca912ef9bcdbd36877427b6b90e860769f604e89c0e67720cece530d209281900390910190a250600192915050565b60408051808201909152600181527f3200000000000000000000000000000000000000000000000000000000000000602082015290565b60005473ffffffffffffffffffffffffffffffffffffffff16331461293f57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff81166129ab576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602881526020018061564d6028913960400191505060405180910390fd5b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691909117918290556040519116907fb80482a293ca2e013eda8683c9bd7fc8347cfdaeea5ede58cba46df502c2a60490600090a250565b60015474010000000000000000000000000000000000000000900460ff1615612aaa57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b612ab78585858585614531565b5050505050565b60015474010000000000000000000000000000000000000000900460ff1681565b6000612aea826141da565b92915050565b73ffffffffffffffffffffffffffffffffffffffff1660009081526011602052604090205490565b7fd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de881565b60015473ffffffffffffffffffffffffffffffffffffffff163314612bac576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526022815260200180615a306022913960400191505060405180910390fd5b600180547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff16740100000000000000000000000000000000000000001790556040517f6985a02210a168e66602d3235cb6db0e70f92b3ba4d376a33c0f3d9434bff62590600090a1565b60015474010000000000000000000000000000000000000000900460ff1615612ca057604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b86612caa81613f6a565b15612d00576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b86612d0a81613f6a565b15612d60576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b612d6f89898989898989614571565b505050505050505050565b73ffffffffffffffffffffffffffffffffffffffff166000908152600d602052604090205490565b60005473ffffffffffffffffffffffffffffffffffffffff1690565b6005805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f810184900484028201840190925281815292918301828280156112cb5780601f106112a0576101008083540402835291602001916112cb565b60015473ffffffffffffffffffffffffffffffffffffffff1681565b60015474010000000000000000000000000000000000000000900460ff1615612edd57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b612ab78585858585614692565b7f7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a226781565b60015460009074010000000000000000000000000000000000000000900460ff1615612f9b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b61136b338484614956565b60015460009074010000000000000000000000000000000000000000900460ff161561303357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b3361303d81613f6a565b15613093576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b8361309d81613f6a565b156130f3576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b6130fe338686613f98565b506001949350505050565b60005473ffffffffffffffffffffffffffffffffffffffff16331461318f57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff81166131fb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602f8152602001806158c1602f913960400191505060405180910390fd5b600880547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691909117918290556040519116907fdb66dfa9c6b8f5226fe9aac7e51897ae8ee94ac31dc70bb6c9900b2574b707e690600090a250565b73ffffffffffffffffffffffffffffffffffffffff166000908152600c602052604090205460ff1690565b60005473ffffffffffffffffffffffffffffffffffffffff16331461332157604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff811661338d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526032815260200180615ad26032913960400191505060405180910390fd5b600280547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691909117918290556040519116907fc67398012c111ce95ecb7429b933096c977380ee6c421175a71a4a4c6c88c06e90600090a250565b600e5473ffffffffffffffffffffffffffffffffffffffff163314613472576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260248152602001806158f06024913960400191505060405180910390fd5b61349373ffffffffffffffffffffffffffffffffffffffff841683836149b2565b505050565b60015474010000000000000000000000000000000000000000900460ff161561352257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b613493838383614a3f565b60025473ffffffffffffffffffffffffffffffffffffffff1681565b60015474010000000000000000000000000000000000000000900460ff16156135d357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b866135dd81613f6a565b15613633576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b8661363d81613f6a565b15613693576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b612d6f89898989898989614b49565b60015474010000000000000000000000000000000000000000900460ff161561372c57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b61373b87878787878787614be7565b50505050505050565b60085474010000000000000000000000000000000000000000900460ff168015613771575060125460ff16155b61377a57600080fd5b6137866004838361554b565b506137fb82828080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505060408051808201909152600181527f320000000000000000000000000000000000000000000000000000000000000060208201529150614c299050565b600f555050601280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055565b7f158b0a9edf7a828aad02f63cd515c68ef2f50ba807396f6d12842833a159742981565b73ffffffffffffffffffffffffffffffffffffffff9182166000908152600a6020908152604080832093909416825291909152205490565b60015474010000000000000000000000000000000000000000900460ff161561391357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b8861391d81613f6a565b15613973576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b8861397d81613f6a565b156139d3576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b6139e48b8b8b8b8b8b8b8b8b614c3f565b5050505050505050505050565b6007805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f810184900484028201840190925281815292918301828280156112cb5780601f106112a0576101008083540402835291602001916112cb565b73ffffffffffffffffffffffffffffffffffffffff919091166000908152601060209081526040808320938352929052205460ff1690565b60015474010000000000000000000000000000000000000000900460ff1615613b2c57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b88613b3681613f6a565b15613b8c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b88613b9681613f6a565b15613bec576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b6139e48b8b8b8b8b8b8b8b8b614c83565b60005473ffffffffffffffffffffffffffffffffffffffff163314613c8357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8116613cef576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806156ec6026913960400191505060405180910390fd5b6000546040805173ffffffffffffffffffffffffffffffffffffffff9283168152918316602083015280517f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09281900390910190a1613d4d8161422f565b50565b60025473ffffffffffffffffffffffffffffffffffffffff163314613dc0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c815260200180615824602c913960400191505060405180910390fd5b613dc981614224565b60405173ffffffffffffffffffffffffffffffffffffffff8216907fffa4e6181777692565cf28528fc88fd1516ea86b56da075235fa575af6a4b85590600090a250565b6000612aea82613f6a565b73ffffffffffffffffffffffffffffffffffffffff8316613e84576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260248152602001806159de6024913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8216613ef0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260228152602001806157126022913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8084166000818152600a6020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b613d4d816000614cc7565b73ffffffffffffffffffffffffffffffffffffffff1660009081526009602052604090205460ff1c60011490565b73ffffffffffffffffffffffffffffffffffffffff8316614004576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806159b96025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8216614070576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602381526020018061562a6023913960400191505060405180910390fd5b614079836141da565b8111156140d1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806157d56026913960400191505060405180910390fd5b6140e8836121cf836140e2876141da565b90614163565b6140f9826121cf836121c9866141da565b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a3505050565b6000828211156141d457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604482015290519081900360640190fd5b50900390565b73ffffffffffffffffffffffffffffffffffffffff166000908152600960205260409020547f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1690565b613d4d816001614cc7565b600080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b6004805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f8101849004840282018401909252818152600093611d4993919290918301828280156143235780601f106142f857610100808354040283529160200191614323565b820191906000526020600020905b81548152906001019060200180831161430657829003601f168201915b50505050506040518060400160405280600181526020017f3200000000000000000000000000000000000000000000000000000000000000815250614366614d50565b614d54565b73ffffffffffffffffffffffffffffffffffffffff8084166000908152600a602090815260408083209386168352929052205461349390849084906143b090856143b5565b613e18565b60008282018381101561442957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111156144a9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a815260200180615850602a913960400191505060405180910390fd5b6144b282613f6a565b15614508576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806157876025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff909116600090815260096020526040902055565b612ab78585848487604051602001808481526020018381526020018260ff1660f81b81526001019350505050604051602081830303815290604052614a3f565b73ffffffffffffffffffffffffffffffffffffffff861633146145df576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602581526020018061596a6025913960400191505060405180910390fd5b6145eb87838686614dc8565b604080517fd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de860208083019190915273ffffffffffffffffffffffffffffffffffffffff808b1683850152891660608301526080820188905260a0820187905260c0820186905260e080830186905283518084039091018152610100909201909252805191012061467d90889083614e88565b6146878783615006565b61373b878787613f98565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8214806146c05750428210155b61472b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f46696174546f6b656e56323a207065726d697420697320657870697265640000604482015290519081900360640190fd5b60006147d3614738614276565b73ffffffffffffffffffffffffffffffffffffffff80891660008181526011602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98184015280840194909452938b166060840152608083018a905260a083019390935260c08083018990528151808403909101815260e09092019052805191012061508b565b905073800c32eaa2a6c93cf4cb51794450ed77fbfbb172636ccea6528783856040518463ffffffff1660e01b8152600401808473ffffffffffffffffffffffffffffffffffffffff16815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b83811015614860578181015183820152602001614848565b50505050905090810190601f16801561488d5780820380516001836020036101000a031916815260200191505b5094505050505060206040518083038186803b1580156148ac57600080fd5b505af41580156148c0573d6000803e3d6000fd5b505050506040513d60208110156148d657600080fd5b505161494357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f454950323631323a20696e76616c6964207369676e6174757265000000000000604482015290519081900360640190fd5b61494e868686613e18565b505050505050565b61349383836143b084604051806060016040528060258152602001615b4e6025913973ffffffffffffffffffffffffffffffffffffffff808a166000908152600a60209081526040808320938c168352929052205491906150c5565b6040805173ffffffffffffffffffffffffffffffffffffffff8416602482015260448082018490528251808303909101815260649091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb00000000000000000000000000000000000000000000000000000000179052613493908490615176565b614a49838361524e565b614ac3837f158b0a9edf7a828aad02f63cd515c68ef2f50ba807396f6d12842833a159742960001b8585604051602001808481526020018373ffffffffffffffffffffffffffffffffffffffff16815260200182815260200193505050506040516020818303038152906040528051906020012083614e88565b73ffffffffffffffffffffffffffffffffffffffff8316600081815260106020908152604080832086845290915280822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055518492917f1cdd46ff242716cdaa72d159d339a485b3438398348d68f09d7c8c0a59353d8191a3505050565b614b5587838686614dc8565b604080517f7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a226760208083019190915273ffffffffffffffffffffffffffffffffffffffff808b1683850152891660608301526080820188905260a0820187905260c0820186905260e080830186905283518084039091018152610100909201909252805191012061467d90889083614e88565b61373b87878787868689604051602001808481526020018381526020018260ff1660f81b81526001019350505050604051602081830303815290604052614692565b600046614c37848483614d54565b949350505050565b612d6f89898989898988888b604051602001808481526020018381526020018260ff1660f81b81526001019350505050604051602081830303815290604052614b49565b612d6f89898989898988888b604051602001808481526020018381526020018260ff1660f81b81526001019350505050604051602081830303815290604052614571565b80614cda57614cd5826141da565b614d23565b73ffffffffffffffffffffffffffffffffffffffff82166000908152600960205260409020547f8000000000000000000000000000000000000000000000000000000000000000175b73ffffffffffffffffffffffffffffffffffffffff90921660009081526009602052604090209190915550565b4690565b8251602093840120825192840192909220604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8187015280820194909452606084019190915260808301919091523060a0808401919091528151808403909101815260c09092019052805191012090565b814211614e20576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b815260200180615675602b913960400191505060405180910390fd5b804210614e78576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b296025913960400191505060405180910390fd5b614e82848461524e565b50505050565b73800c32eaa2a6c93cf4cb51794450ed77fbfbb172636ccea65284614eb4614eae614276565b8661508b565b846040518463ffffffff1660e01b8152600401808473ffffffffffffffffffffffffffffffffffffffff16815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b83811015614f23578181015183820152602001614f0b565b50505050905090810190601f168015614f505780820380516001836020036101000a031916815260200191505b5094505050505060206040518083038186803b158015614f6f57600080fd5b505af4158015614f83573d6000803e3d6000fd5b505050506040513d6020811015614f9957600080fd5b505161349357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f46696174546f6b656e56323a20696e76616c6964207369676e61747572650000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8216600081815260106020908152604080832085845290915280822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055518392917f98de503528ee59b575ef0c0a2576a82497bfc029a5685b209e9ec333479b10a591a35050565b6040517f19010000000000000000000000000000000000000000000000000000000000008152600281019290925260228201526042902090565b6000818484111561516e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561513357818101518382015260200161511b565b50505050905090810190601f1680156151605780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b60606151d8826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff166152dc9092919063ffffffff16565b805190915015613493578080602001905160208110156151f757600080fd5b5051613493576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a815260200180615a52602a913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8216600090815260106020908152604080832084845290915290205460ff16156152d8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602e815260200180615aa4602e913960400191505060405180910390fd5b5050565b6060614c378484600085856152f085615447565b61535b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015290519081900360640190fd5b600060608673ffffffffffffffffffffffffffffffffffffffff1685876040518082805190602001908083835b602083106153c557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101615388565b6001836020036101000a03801982511681845116808217855250505050505090500191505060006040518083038185875af1925050503d8060008114615427576040519150601f19603f3d011682016040523d82523d6000602084013e61542c565b606091505b509150915061543c82828661544d565b979650505050505050565b3b151590565b6060831561545c575081614429565b82511561546c5782518084602001fd5b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181815284516024840152845185939192839260440191908501908083836000831561513357818101518382015260200161511b565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061550e57805160ff191683800117855561553b565b8280016001018555821561553b579182015b8281111561553b578251825591602001919060010190615520565b506155479291506155d7565b5090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106155aa578280017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082351617855561553b565b8280016001018555821561553b579182015b8281111561553b5782358255916020019190600101906155bc565b5b8082111561554757600081556001016155d856fe46696174546f6b656e56325f323a20426c61636b6c697374696e672070726576696f75736c7920756e626c61636b6c6973746564206163636f756e742145524332303a207472616e7366657220746f20746865207a65726f20616464726573735061757361626c653a206e65772070617573657220697320746865207a65726f206164647265737346696174546f6b656e56323a20617574686f72697a6174696f6e206973206e6f74207965742076616c696446696174546f6b656e3a206275726e20616d6f756e74206e6f742067726561746572207468616e203046696174546f6b656e3a206d696e7420746f20746865207a65726f20616464726573734f776e61626c653a206e6577206f776e657220697320746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737346696174546f6b656e3a206e65772070617573657220697320746865207a65726f2061646472657373526573637561626c653a206e6577207265736375657220697320746865207a65726f206164647265737346696174546f6b656e56325f323a204163636f756e7420697320626c61636b6c697374656446696174546f6b656e3a206d696e7420616d6f756e74206e6f742067726561746572207468616e203045524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636546696174546f6b656e3a2063616c6c6572206973206e6f7420746865206d61737465724d696e746572426c61636b6c69737461626c653a2063616c6c6572206973206e6f742074686520626c61636b6c697374657246696174546f6b656e56325f323a2042616c616e636520657863656564732028325e323535202d20312946696174546f6b656e3a206275726e20616d6f756e7420657863656564732062616c616e636546696174546f6b656e3a2063616c6c6572206973206e6f742061206d696e74657246696174546f6b656e3a206e6577206d61737465724d696e74657220697320746865207a65726f2061646472657373526573637561626c653a2063616c6c6572206973206e6f7420746865207265736375657245524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636546696174546f6b656e3a206e657720626c61636b6c697374657220697320746865207a65726f206164647265737346696174546f6b656e56323a2063616c6c6572206d7573742062652074686520706179656546696174546f6b656e3a20636f6e747261637420697320616c726561647920696e697469616c697a656445524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737346696174546f6b656e3a206d696e7420616d6f756e742065786365656473206d696e746572416c6c6f77616e63655061757361626c653a2063616c6c6572206973206e6f7420746865207061757365725361666545524332303a204552433230206f7065726174696f6e20646964206e6f74207375636365656446696174546f6b656e3a206e6577206f776e657220697320746865207a65726f206164647265737346696174546f6b656e56323a20617574686f72697a6174696f6e2069732075736564206f722063616e63656c6564426c61636b6c69737461626c653a206e657720626c61636b6c697374657220697320746865207a65726f2061646472657373426c61636b6c69737461626c653a206163636f756e7420697320626c61636b6c697374656446696174546f6b656e56323a20617574686f72697a6174696f6e206973206578706972656445524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa264697066735822122005677c3919f4b149e065a5983baa9e2fb099cab5463ccd06429f70b32d8d9bdf64736f6c634300060c00330058210390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5630410103bdc011f506efa58f1b2550ca80958d2a7c39f979f40d537e110b221c62364ebd005821031347d38f9027b008ab764477dff5db62e1ce3cab675df1efadd25bb228ba1ee05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00582103216fe201c80e31523065747b4c3a11b5ce64700caa277669094d49e7d42e4c105820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00582103d1108e10bcb7c27dddfc02ed9d693a074039d026cf4ea4240b40f7d581ac80205820ff1bba8c5962591285628e226cfe21a129312cfd293b407070da1d82f098460100582103517498924e66ba831af2769eea8312cb5261242822e63a2d7cc49a5c707be180500785ee10d5da46d900f436a00000000003b30d5e39bb2a266518592f8bf752ce6d25fcfd24908b3c862f5f487ddf91096100582103e453bb62162b907fe0133f23a577de7bec75205eca5e557cfe3ef2dcc72205905820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005821035ca2f3d6ffed7c02f4752b25e41af1736966153795558b64be92a8283b5b1500430f42400058210317fb9989707d00d29edf83f0ce78ffe6efc3e7e2f0a33e6a7370cf9390ae63c058208000000000000000000000000000000000000000000000000000000000000000036082dea626a28fd5714b7132ebeda7497f1e481e325c85a848f757266e4fd23c0219fd7405581d020ab04938f26cadaf9df6fe1389ad0a110acc2ffd76feaa1dc72a138e0701195ba802182205581e035c0f632b2b143a27400257b92b6008ecc69a7ff00d34104f16bff667200c0147038e3c48e101c2021976da0375422af2844a3f87b243c9644f9f0ace175126b6492d47fd381fa2a357c1cb80038ac19fcc050a9e37b43a0f86466b5d2c5f9fe6e03a54a31532e042a2af4d43fa039b617dfcdc2dc5aea5cf6c052a8ae308801747f0bb7e141baeef835768b7806903eddebb3d9c98cca8c716e36fcb1ca053ddd936471a00f8cbbdfd18edab81e54403fd6aeaae8798f0258a6ccee26583471aacf6b73c9ebc543b7a58e8ebb3e31e9e0301169c038c640981817684b51c3e5cfc459871984377458a9bb7da2ba71e0f4d03378358a91de0e8a9248740f7b8fd7151b8fb651d32714e9e651d71f969f9de4e0393fba4b24677444331d035dca0bb5a72cd32ca6763e82c6ab222e08c1c6f6b3f0378dcd74e07bca5759db73a24e62d4099a3600f084d0458830897538df421b76803996080aefabffcc80a0feba456709477d477bad68d9d4179d600201790235f570319a9ba3f2108048e07191495f1499e56c1a33a96d3f7e135d3410d10cb6e527d0376afc3bf5b27d160ed41d005ba5d67039dd2f6f8f874efb2ce15b84a89675a6603f984c38c2d5a6efa92af5a93f30139b77e3d9c5b0c6ada51b61c69068461cc250219ffff03af6682305ae7b03099c2e54de946ea6b56dd4ed1dada269eba0a47f38b120f320388c8cdc2a2b3326610660efe808a73c1c21992c3d17d85cb94df4df17f35b88903c5ad6f8fb9bedbd6231dd2a9dd8c86c51334fd0116e62006edef50927010ff7503430b0392e91a099347a3199c0c571fd7c80482f7c8e5c2fe49605d3114ededd50380e40eb2eb5e139a0e8ede6832ff0e970845863c9998be7c74bb1d0127e1d94503d4f110db03b2d5f9414b8e8d371a50bac4209a1e0e50865c34707868819b6cf603ea5582cac2968a17342736076a86fe46936420f91c3a61d1afb94fde99e60cd303aec7eed066d191a30043c923ef3a2be192fefcd6d87cfed635c15f5d2d9f276b03f0f23bd5a2e7bf67f7ccda1d98611afc610954eca5d71ef9bc83b1c3c62f343a039fa1c7a07e22cae10147e96e609a945ed6c979f236ffbd190fba130d76f7017303f386a057c0d2f1e078796fb7b44f7504b34adb1e05e8df9a1e595af6621e9bd60219ffff0359508ed025d5670d667c5d4c3d261b629944968c5fff83afee0f637a85d9f04a0375e25eef1c36b06fec7425b95d7dce58181d36a182a560b23f3ac5f2854282130387c050177c7e945a8391a0db9d8b3be6556ba18bef2b258cef6289abb6e3f0540344568888dead34c24a9355c7e8a7bac606ae69b9995d3feadccc83772d1b3c2a03d7c69884c08b9a4c24e359d236ed8a10572d9d1da9091525c7c712a1264aa19d03a1b9ce321035888eb5b1692e748edf6c92dcca701d34598653009943cdabdb3d03d7e4b01565df73aa64da6950dc48f70a102f003deb8feeec644276784fb95b550219ffff03d8ecf4a13835840414860013d56cf1990685c7809c6b934c6e18c78bebea6aa80332bd5487c0b187e28e5402454cb28ac15ebe5f17f292ed11cd3906bab9ff1d6603180550b5c639c09e562e53aeffb402379c16d957743e76d75b59d5938e7a85dd030ee473ad0ad8c3ca34e81f9cf6d174ca0350791acd10efdb61eb8fc14fada7e103bedb52411df67da524a539222e9e2e19e2147bbf0e2fd27bccdac57e8b0f006b03f210feba3d08531a5979973c54165af4ce4c706544daedfc8a55674af3a795d103f0f82fd96b26fc01967f78a7c4cb3427cec2da5ce2c7587f2b3f3df9f9b7e3310219ffff03bd6c5438f4cb63aee0960d50203cbb14e9080767d320058e75d827f2f32480120313d29f4c8e3c7fad64e5865dbc5ca27c234da7c1f68a651df44da2f4948da4e503b074eaa37cdc5af1e5cae7d3c8990f8cba89d45540fcb51fe802c44ba9bf0d940219ffff0352364ad1b316271c2f8b7b05604ce2662bddafe9b5bd7861674349148ebb4a81036bb759bd5dd6f1455ece7a3eaea4a225ebc721f3a4278f5aaa6b241a022041ce03f09d3fe65db5559fe43b961150d7767b00b24f06c654ae541795401b83353ae6030cd43626894c44c1d18c1edeb6896a76cc11a6cac8b892e08db0c76a9f6e1c6503a57e385726bae4d4591ce8d4954aa59d137b07310f60ca749a354cefed2a777b03bd9793bb140af19d25c449a87e56f340f2c7ea439a5a2e85c06f564575f9358e03e719dfdb336a2f2a4eddb2e1d0147fbb096055d8268a416d5c53d4d18ad17c2803e814fcb5f465db443532245ef1c3e26ff54878b8bc0c9342ddc7cb14a221e0a803b589fe3f0efa2175970556cb83f11198fbf26a4d6b3460b32ba0c1b29c8ef20503603d2666dfea444b822e10cca08af350e14f76cd27409b875d9d4e546ec3bfe5038932270520b0a21b5666a579dab3063540060a5d6b0189e295436ef86a3d0ac10326cf37c124f939ef5c7d3349904be0225c6ba054c3108285f6b88608807d3d1903224a867aaabea5993913aa07656725502c819ee0123d38009e7aebc36eaf1a8f03c699c6e495b9ebdaafbf526074b63632d5d9a709a2b8f3a22f4c4468874c001b03bbbd99cf9792ae6ae78eeb4270b30219875ae8d120d497eaf0c4c5f72f658df9039735f29c896d8fd2e5873c5c7a143f5cde13e8cbcd37c472aaef87859d369a0303f36f84e203beaffda0838e787138f9a9ed38ec08fd0f92bbed39a5f649017eec03f7e86666a7cbb9e9478f5d4f20a95d9e093202a2878a6626a27e8930ede112b5037fb3aa31ee298275284260565a045bc33b3e1c0eb3b284ad3022223c0bfe65cb037ee788b804b612219758892881df2103dfb1701825933ece0eb865a355c7400503978f59b94b41585bf997ff7bf53602355f327c758001a66c40da79213114e7c103418a57029b7336ab35dcde64aaf6c05b5688763c4d996af8e19b12ca532da253037952d4b82231bb973797740c10d1da8adaa3591416498df2cf483895190767b70369c40c88a986d2ad437475c38832a39a1d35c152ce661e7d3c2b7739e41ad65d03ad5472fccb9fe8a4e6f6dbde546251fc0a28ec170939f338d90ff7ccd3f8a23903b762ef1046b76c49a11dee46475c0d8309e09bf58d5831db149aafe187b00c5b03d1dc93fa8e17727b09e442be0093007840f9838681dc03dfe23455799cbe29550354f67d476b8244833ad6095849f58377eb3d3feb994cb99e1f110b9312e5a68403a578ddda08950c2deaaccd631b13fee192edacc442a855354102a174f13bb9150367e87802d1d939c4d58d69b9ab35794ab3338f8f8303a5e38d98a904e29fa1ab0327b93e3bec8a3b8794bc9d0c08ba63d671fc99f272d4e28699ca6a6c363b5693034951106ae2c24eb77c97bd2d07ec7b129b3a242bb99744df7437e22f1e94e28e032e4286627a6424c4c7e6d6cfd3e0716f73c0c23317d41ac61ce9add54a92465f03ac0e42480713a4294bb759de853fd2ce9ae7f73a268b6c2f7319e0647367bf5603d3e935f774dc01717b0587ca55a3be582c9d5886f5b7a05c7c2756d9e12f7d40038467c84b267451852fc9c08c93e23bbedb3f06367501191d455e54ee4948856e03428ae31eae9618588c602d7dbd4dc45bbd0f09bd582106c6298761666e14e7b4036b65ba160e6025cd21626b158f610373fbbeb1e5e6efaafc5e14fe487495df3903319b863edbbf5ce2f7fb7e84a43a4a6ebad10737fc8d8df61779fcb16ad68fc603897e27f0b8a59b641ae12e0d9ab54e2aff3b997c73c4e68a0e690ac46d10d97903b6b57e6dc03e26a18d637f9ac7c8d1e9ef09f707f2528398e6a666306bd50e4603664b5351af410fba4b9207727c45c38a950adfc0791550cbed9a9a8370d9f308035323fcb3d61183749bb11b848abb44db7e89a0d90f9f7461f8e3ce6d372059a2038045a8809e3c1f7bc3431084c44db03257f71ac796532fcd2bc6f997128b70bf031de2d69f69e8c8f179f426682bb46db3cafc2dabc9297a93ef921f5a21e67e9a0315a9251e4d534129b846355bf23fe7a2590cf4e87d2d961b5d3544a87d97fab003ee20ea803e208eb1785247b9ae64261d55d8fd5c63fc9ace45094d78eaf15cf103dd7054ec140679765d6482262f0d55e3efc4fdc4efd857b62537590c5c6cb22c0385df93e938c128515fe41e4fed157930124adb1a703282459871c72853e784d3039eb6c9001b4cca9709fa924b2939010df907f2c0838bea811f0e7d5526c8c9fc030a4fff2cae7d62385c3c841da8a176d61cb1ba035ecd31d2742e73aa3d3744aa0339af3aee43c7a8692c78c5a29690e90581fb4f4263a1b2877516306196a4a2cb03f8c290a3d8233cd8cfc9f641c538e808a3356e49ff45d122486ed708a77c64f503096525cfb301c70c6842a25028a5474ec59658795ab52415171a9873cbd1477703de5eb12d31c632181f0fd669c2f39323f78b8ee7147da74d975f505e811d3b9f0340a67714071de96b6f686568f1528fd2515e085228c15c2bdb97253f3707358e03dfca6059b898db973fd8cc0ec55d587f5cd26cdcc54c76e394ed190722d0a9e9031dffb577ddd3f75d8b951b93f0b006f8f9c9406282d915dbce809616f674e97f03c701f9a90e796a542dde21183f757e2bdcbce5f4b9c70ede5f110e6e89e9e7460458ab608060405273ffffffffffffffffffffffffffffffffffffffff600054167fa619486e0000000000000000000000000000000000000000000000000000000060003514156050578060005260206000f35b3660008037600080366000845af43d6000803e60008114156070573d6000fd5b3d6000f3fea2646970667358221220d1429297349653a4918076d650332de1a1068c5f3e07c5c82360c277770b955264736f6c63430007060033031a02a0d9e5db3663cc6c814456e1d6cfead7da963f9ae6178901c22cf7f72479005820029bfa28377b0cbf3f30c94c44d53591ce0f993529b974ca13869af2e152de5554c66c8aa97065f327fe94b6c845acd7e5aa6f6117005820020decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56354d9db270c1b5e3bd161e8c8503c55ceabee70955200582002367a1e93015b6cf6c6d03b95206549c303b115c28581050a0a76640a7faa295487283071aed93b5ffe43fc97c8d387a57cf57a7b02190a1000582103b19ca0c95929a2a946820150c3c2aa0ee9fbecab56f94ccd76692eb3161f8b90410103c6b0036da1814edb8668263506f5f2ca2d7af469aae95ceece4f0c87719d64b100582103fef4bf8f63cf9dd467136c679c02b5c17fcf6322d9562512bf5eb952cf7cc53054a3014707943bbcccc2c36aa8e5e5cb13db463f25038a9c8f495de1c0f701f7379648498eceea2cd5ff0f40b07b3cc88351a2e4d51b00582103721fbc08ebc780272f50db65b723bc840f50ef981305a91aa8affda1e04fe6104101005821036552f9043f7dbde1a3842eccf846aa02c647c3d99f9371aae3c93798f73f1ab054f48f2b2d2a534e402487b3ee7c18c33aec0fe5e4005821039a728692d694b98d801b72ac20272f7125aa98bed3754df76a17b01e5dcc92304101033507edcb9f82807ce6edbda335c3f99b008f087adbba61c670e3a8dbcd1413e803c11545bb8c1038ce6a5af743d780e7f125983e4d2371a7a6605b566793652192005821034809fc24e46d8b3ff23914aa5485e86bca85b07a862c6c61e2b739a9b0c4712041010219bfcd05581e03a7c44e6e6f10dfb3fd54c75fc36f8e7944076f9e43a475efdaa0c8d4000f01492863503fabb4faa03c18ab03af05f0b75e5fc52f54c5dafa6904b9e6c8a658cc7f71e5e66e8321c5d56ba6d803b41b3a47456e6a4475858dcd426c8b16241ae0b927e11e373b04b4e26241f77405581e033e7cc04dded55ed71e97a7d6d0a4baa236f187a72d2fbe7a31bfb09290040205581e032d0f73c5bcc5473bc059937195e69ecbda5067b8717e64978e7ce10ed00c0d463b5d6a61e1c805581e03cf36789ff8d54aa3b7f9ac9a45c6e59558c9f250bd96699edb42cbd0e00c034740f192ba252e000336e9689ab9beb84ff0db471dbf1e295f7d52d3f18d7bf0716707a06a6f8c443b01410805581e03d6acdd4641b5e1ee166cef3b43f06bfdeb0ffbea03f84b87636a6f5bd00c0146574fbde6000003eb1d68002e96989f80bdad20de8a948a869a1468f3e501ed999eacc3f7cecc610388b62f9296ff82271178174d78caae5ca16d6fd85f68fe7979719740c4cb440805581e039d43066fb8d2ba31305681825250e148b5f4785c309c8d1120d5c151e00c0245f6ccec680003e91d804116acafdaeb23fd10a3a4a65817e857d8ec6a423ddff0af6eb05d3f220141040304d9b8953c7907632ac2c29265d3d79c864578f40a153aa73cebc4456e94a7fb0219b6ff03fd1c7757e911ae4c83adb077dfe60fd07134812fc4367fa0628b94607d5572fe03ef36f517a07bcea02cddce661761a82b40b00b338e567d498914d8ccefea8f1c0219ffff03f82714de77045284ed82da2bada0e10561e931e27a048d8cdd64dc1df880f92803c2e4fae8ff40375054993a076088b2e9341f13a8cc98bd9fe207ea695d28d1b90219ffff03389396210692053efd9fcb65227652af87df90bd54a0803f636ce9af8c3402970333f487d2788628ea68791b29052968e68e189ba03e0b4e9b7c147d8f534fa6400301236bd58f2ccc3ee37e4a9f12a875c9ecb5d65f9904970244f2c435a1d1ed58030a46a46fc79d53cfb896d617739840f5467ebca1f5bec5db09ebefb7d5d5916103bf12abd89e00c1c83b527d24a763621f92cbe51e585bab19bd2b9845ab3a266503129169cac2a2fa5a0611da44bb1e193def1f71ebb9da9c31a8f11d7c88ab70530219ffff03ab7247f0f5f55fb3988b0b3902fffe993fc70e4bef5cc32d2bf552de9175c3fa03d553f1143818714e91f2c08e7bd928ee980b9934db42264f8f13c0889d4e58ed03d58757d4a979bbce0b7c8191d31f09af20d0706bbe014ec61ea61a61e1a24d66030cba6a5c550773435f67cb2f30164ce96371fd643ec1a3cb32d71a56d698b69f0219ffff036d6f7d35e5702cb31a57f7438f302b696763333edf8687b9f8cb576de6b50a4203c23910c7daf887f202b36d06782533cc216f0c772e4ceef0ff1de7b0cc513ce603afde653ec955b650c5fe30aef08d51f61de99ad968aee94c48f2a54a53a232240330740bdd4af7f6171c2529bf02047d45e154e2ad5483b3a2a34d1fcc404b70010374660bd74573eec75b5001615d460ebc40e83420e1206f6f022d323493fff69503b973fc0102b1ffa1e5d066d6a1361dddebcdebffa0de32cdd3526a724b722fbc03291d3f0a869d63fcf173b46f45760efbbefa76a80694b979c2bd9d0f8ce728fb03184d10bb3f1957446e61d32fe0c3f79c4bf6c7c64692e81fc0b5d07a05c68db1038927d8649a41eec8455c94ed8300762ff9c4b92bebcc8d5e79aebba084deee9f037f7619d4c3f34d6dc8171fa574e85e058c3b8bbf2895efd97b58f27586b8a99d034ede30a6e02b494c2b3184cc5da663c5c38c1875ee25a99f49daef30ba63ab3c0374c6180634ac0cea7765d1bc613d7a12b8dad5d3b289e8b7d3191079c3f7a192037f918e61096db1c50df1779268cec06d761904f1e7c877fe9a7aa588b61eaf48039c8b0bc2dab6ab3a45479847eb9f46210fe850d8f6e7699036d76f25b4f5551203f4edea215e7baf2c4b90cc8e4bb920e15cb87ea9db03c297e9510fc5c7e1527503ef740e71dfb185ee90da49f3db3387d3ce84114e39c381d915e02498124dd90803f336ec88b29b64bc66200b86dd3a25a87f660ea846f19040963c85aca5271ee403a9e6a0ace9e3173c42857d13ea5a27522d4aff5cf994470560a8bdce97dd92af0314f12d354ad21627d6d7259a4b522025f24c66131b8cca9cff0e64c338a0a87503055fbe66b56e775ecb4ace42d665da13a2a13fee3933e1b68916c4907be4253a03f50f99f2e8268d0d7e9b2599e48c28271a60db0358df7c0a0e452acee8a8d663036c2584a9790f5e314a95ad055c7b9b5b5db52e13be75fbb7ba1acf7b78861ef9037bae4e065fc732aa1a2c2ba04128999b627c01c26ee72c557e0004678ee4c4c6030741835d3eae443c2e227b89a05aa6735b807ea279d0d21ddcea33dd08558caa038fbfac409146c2a0846f5223362232a7f16d55c00464c29b1eceda7f05f313b003629146347868aac64580e2dc448d613e1a8f4f49bc6e512c2574c6a62b3d738003941121fceacd92d983e19a181011a7e82a81ab963819c07f13006520b920fbde0347e388e376c185802674234ac0e44cee6f21a277b9d4234d9a5f92210944f26803866a6fa658aee93b63294366b22f8e52c3df6ad005e3559947da06769a7292780304ed8bc22bf4b6ce55298c16a6d0ca122b4aaee857d81498d2b0a9af8c84f72503571188b3bc8995eca1f7b473358d0bf7b527fdbb07ccaa45ffd8ca77b6c0037e0333056bc198788e42ed1ac03cf3bc44ece69004f1017a9ff27ea9a54b7dab421203d3578a5be899aa434bca75e068d02b179c0648b5adf72733f6fcaba9f7442afb038490c71a584e93fd0fd5097edeb47f4305512c30860d6406afebfbb029178f4d038fb11e57c16b243aba4707f77aef3b4b0eab088012878aa0392670212dce6e1605581e038053ff4e96caa01c2152e6e627b230012baa5a386b231f7d44afa609c00418bb03562d59a51820d47f520c975e0b2bcffac644a509749a3161f481f57b6e826d210605581e038697117fd81c06b06b788ae2a0f80977f36f9340e97ce65e362344f30007011bffffffffffffffff05581e0345f688654aa37a3a79501210323d7f64301f3195d8e888a6e8c55048200c044680aa3629f4f805581d0324e09cfa3143cc228d32c5727d891a57009cb512dd1ce09638de6e4004030459031d60806040526001600160a01b03337f000000000000000000000000e8ac39f0a15dc7eb9cf32c7639e633f24f9ec9ab82160361006a575f356001600160e01b03191663278f794360e11b14610060576040516334ad5dbb60e21b8152600490fd5b610068610106565b005b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc54165f808092368280378136915af43d82803e156100a7573d90f35b3d90fd5b634e487b7160e01b5f52604160045260245ffd5b6040519190601f01601f1916820167ffffffffffffffff8111838210176100e557604052565b6100ab565b67ffffffffffffffff81116100e557601f01601f191660200190565b3660041161018f57604036600319011261018f576004356001600160a01b0381169081900361018f576024359067ffffffffffffffff821161018f573660238301121561018f5781600401359061016461015f836100ea565b6100bf565b91808352366024828601011161018f576020815f92602461018d97018387013784010152610193565b565b5f80fd5b90813b1561022a577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0384169081179091557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b5f80a280511561020f5761020c9161024b565b50565b50503461021857565b60405163b398979f60e01b8152600490fd5b604051634c9c8ce360e01b81526001600160a01b0383166004820152602490fd5b5f8061027d93602081519101845af43d15610280573d9161026e61015f846100ea565b9283523d5f602085013e610284565b90565b6060915b906102ab575080511561029957805190602001fd5b604051630a12f52160e11b8152600490fd5b815115806102de575b6102bc575090565b604051639996b31560e01b81526001600160a01b039091166004820152602490fd5b50803b156102b456fea264697066735822122030cc9d72e095e794d8286b088776326d90cf6b4d62a84a41c9928eb1eb96b3d064736f6c63430008150033036bf9111a4e63036ba43b1ad8b3a21ea3a1bf1583e3d327b04a5fa000cdf1cd8b00582103b6847dc741a1b0cd08d278845f9d819d87b734759afb55fe2de5cb82a9ae6720582096e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f0058210390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5630545c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f005821031ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c680541f98431c8ad98523631ae4a59f267346ea31f98403ea63581ce01a703eb6ece6ed458d96aa452acf103a0200c73dfd99b33a2d69a603f7d48beefb88db52e1984205b63aeaa6bfb90eeee62ec4971fd93c72d7538823035f386ed974528f8a87cfb29c07e21ef4118cd779b15b7b44a5f99dbd3484dba0005821035b20eef8615de99c108b05f0dbda081c91897128caa336d75dffb97c4132b4d05446affe1b4f3fc41581fd20fbaf055daeab80a8b503d5f2071d9b48cbe281d6cedf2cbed725df2818b6c6fe0755b39f69b97f07ad3e0058210366cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c688042271000582003e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6054c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200582003d2d76d1f4b7be834882e410b3e3a8afaf69f83600ae24db354391d2378d2e041010209039b8aa1895cf233ea696284244e5cc7b2d367995715eb2953c9fc35b16432614202190802032b862613ed3bfbe3f9cec1078efd720626732f4189967861f59a9ebdf0d549ac0327cfb13450cbe366c6f97e00de1d7befd7887919cf37871946a2a889d2ebc1400058210328a5566b8a884201ab44e2d991177ce8b88325e02e52cbc3da6e67b3ecf29c60410f005821033f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee305820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0219fdff05581d0365e1068e21405c053274a59672d54b44fa24c79ea53efea0eab136a0070219031d0219200801410a05581e030ea64d72d2dd2d80d58f0e44353cc7296175b643867dedf40d2477be600c182d47478144d653500003e09b1c7ba83761b81aca61084cb1fcba49f5226c000307819efb42abcad1b1c40377c2216d17f562429d033b705155e92fcef8f524b026ad1a42ca5771e8d82b4c03e3d9fa01a5830db36bc5ad827315ea63d76349f0953d2bb2d5570a5b15c00ff903647d5d7eb5af6fbf9ed95320cadf658cec3819ffa536adc7b6387fdf1b72123d0373c5dec0c39df458200472668fadaba3ba732e214eb245cb137876ede47c718605581e0352ba6cb1ba80cd19836ab5fb85366505097beff6427103919802d8ad9007011bffffffffffffffff021967b7036f6f9b23e43e770a16783e16695fa63f9e580d5acde23d3ff4eaf7bb450dcced039bfd18a448a023cff2ffd497548898cefa58a8015c140d52a700e05d9ae3d4c10219ffff0336cf901dda928ebd2e88cc3c743ec9df0eb64b672b836674614bd41be6fa20b303f08168fad6b0b396cea26f4892aad7c5de6df60fecdf61014e8bc76f2f53748103b134808a4a11546f8bb6167bf2ce7270593a55329da355d2205a6d641a6894ee030b0b1db88e87f55903f19e1120ecbf7257f6fb03525b0445f3e81f0953066231030cffdcf0a99a360af2dc18397167ad4c251422da70cc7143beab3d72bcd85b6d03ff18afed4c580a3d24c285db4d8bc1a6ffd99fd265aa72dcba937ab67678285903b359987ad29384443fff76147295e15aab95364155a5b043df2eb23710e78c6d037c4a0e45ef3c635904ad0ba464c6e19ebd428923d9c21c15a41ce81c4b9dafcf03f72a3508db1eab1758a7b804f59e70d93069077158f9600ec21d130375618eda0219ffff03d32c706bab37cc22aef0134add434ec5d1195059b4c408e8bbf22f9f522897a1034d81cb1939b7a12b32a986fb5ed9668a057e929fcf2af32938f5ecd7560cae95035455530a9f5a0024a3f940ae45967482ec5315c99739c71291076ce6a366afdc030d7cd74af3702cc9f41e5f50f4093ccaab2beb69bbf6d70cd1f4cccaa71ba06503251407306071300dd8d78c67cf47b7c4969ae8dee7641a4a909b3f01b00e679203520cc597c7caa9272e0b46a1ed218c5c7094c4be0989fed44c0422b235d5324a038c706cb54cb361a2687e37d8c75e0c418bc22e5e39529a729049cb73ae99a12703f002f181b5556b581aab3a4e448b61b9b5c86aa5b699e36cdba7ebbd9735285a03548a3e68d18d64b8fd4e7ff9a81f4de383619fe636a7963e8004cc7872d5f1ac032ad9a8bccb245f6397e0d65a91e4304a80d57a1bfa1483512c1776e95405399e0332874933c6c6d7ea3d0705bc1ecfc886c284213012699d246526d36fd4f237c5036e3535701d7be395f62105f72ca9981dde21d62967d96b790a68730a84a209f9030376ab238766f1115a5b33874254afbee61ad2e0ca9ffce4d19118d60f8c029b0219ffff037c92233674d33dbca0273e095151636bc14f583cd900880517ad78b29dc158d2034238998146ad8b2c18f0a34c21fc91700469ca0a1cf47cc5284780afbd81bb6003669173f6174a96cd4016b93ecc779bcb69f5365e02debc4ea6daba9bca41da99037d105dbbc01dcdb7af970d387e6c5af8c02a9f8ef8e71b7c2c5b27ce0fb8a3f80219ffff0219ffff03b186caa2538d41b8dca12f97ac3b784895c65de9016e1a4ae4b1cfba5ac0a567033e983d5ee1b269b0ea817d21438f6f82822965b4a37b6ace33953d598830c35703a073460b076b486cf2cb53a7ef1dd47cd72153d52c9d40a9a8658d862fe1014003effddc50a5d0777c8a6e155c4af0641a015dbdd491ed6562d9ef1036ddb51ea003b240b6c8ab4b1cea0c7605ee236b0cbbad021b35f09c7cf3b61c386c728767e803dcf556de2d232d37e3ecc5ee35fc69457d823d25e7f50f72f45dd8f4fb5c0a930356dc69a7f77e2d419c82be3a228d954b8ab0a0ad5ddf3e9dd8b06ab785b5730903ee2024175eac266f791ddb179f2a6a13357a55cecc6b05c100112e33bba8118a03c8ec90acf29a630ccfea290468f284b5d84206a25792171002b15791b4732f7d03b7bf8198a8e73b5476accf662f19506480460a5e604d22863b2bac078708dcc40383654c14b2171246dba1676b1a25926951f64ad1e44cde2b77e776aaa9973ef203c5aebde8074ee94ff8f74d27833159509fa32a8ec0fc210ec3f4391cc7effe9c0314bebe9a1613fdd47f63874da68db4a1f39fc492f73b46278d1c2587f9e5841b03245b052e9ac5b39c5389070e8aace1000cd54e5f498ce1d905db6200c15248f603091af36fad56d652564fbaf5120ae0c2afbd8cf57cb03aa3aed7639745b87e7a034fb27a6582e90c45d0aeab04c5964b60aad642b50f94ac46d91b4e377b5a511e03fba684274bb34cf7c48d32fac8d39ab665a905ee7557d5f68dfca87cfda2dc7703adec40fcc7ab3cc7f0712662f9d233eb42de95af9937dd95ba082d92ebc02690035b64abbe65c04421537d46cd8511cacbc886849b03353b85b3a36b66161a1b8803817eb9d7e8a2d204a274b71182b3146da62e5b1284b199fbe825132ff275e6b103f23e911b3ef6465114298f92ae392ccb4a46f1a7efa2248fa6a86ffb49a0d6550324402e73ffceff9c2c93a52debddd26f1838ffbf4c78ef9f2e62d421ad2bd696032ec506a6e63b9a33ccb8fbdc4f2676198cba04eff249b1c3fc0cd061d01508ff03224b96a96a29c5fb6e5f5a927654495b3017bfb4ab2af004f226de944e05c0d5036ea96670aa7bc642c8b8a41c1601380830309d367f25506df309fadb315f33ab03c6516b2ea9e2b29080570772881c27bbe8a5ad980edea45c143ce94b04760fb1031a3d2bcaa4aae5d656d02224b3b3672534f52d52bcf534d6b8b47e1d31a094b603b56492a8ca2b998c6f2f4f7b1b901f578703d2d54846f4aca309148e57cec73303501b157b052dc0f013a8159310fd0827eb38cc988d90a44ca60d5e27d07438ca0318f809e3a2f48e14c068f3dbff656c3cf118dbdf810ab2eb41edd1bb23872869034328c0137f07e61954349dfdfbe1494e4670e783b257e04e9d37b474c7dfb54f03ff17b8dc15f2c325cd10a460d557d67eacf058ac2f0612085f0866ce39b4b65903243a9f01304270c43dc78314c426eeb71c6ccb0b3ed7c1a55b65cca43e7f8be703e3bb184faa99eca75aa5f8d2e9551d330dfd5d000ed4ac4a3bc1caec8155e20f03b5a8e99289b03256d5a0997f34af8e83d58ecd6a0b49131165c27404c9aa2ea4033e980d9719d068f9e564061def92bf7f0c1431d4254bbbf4134d4b56e021b6f903fadfcbf91a29bebd608931329c96b5811c1343583d8780cb5dc71dca219e689303c195880ebbcba856c416dc363c0bc61c632185933fe60317daf93f8b83e1a2c203578d7e5061b0306312621fe8134e68f5a3d0b59b430ec61739b13830b302c8b703788dbd2c23272499bb1ee5d6d1eb8244881495819586f601ce216de575b43c1603080ec8d006505e440305393abb865ef357cb46eb07a2e49d12a0d4811ff4e8f103eb761f9e7a92608f196a5a769049c7672d0ebd858054ac860d075f22c6e9fb7e03aeb90d8339ba90a180fa43481f2f6bd4c34a3e96b63d11167a687a959837054803cafb14f596be3a83a9ceadef4abc65a5e189ca93c8717a88ff5054391e4e9425037b1f1dc37610377e65ef3b6d9a83b95915458aa8d9d77d968f839d20dd9ce3a2030dca87ff2b319300aa6ba1f70644d41ccb4f56bc1e4e04bf4c6ac6e91d1bef4d03ff225e94f3d003221ae3f25d9e068bb5d233b486ab2007928badd9545fad814303f578ba6ef4f25243b80c1c8cb146563fc96f1ee19de194b8e143969e9fee136c035d79f70921fc86c1b68c54953219565a84d7c4943e71b9f20a193eaf8c1e9ac703f142f0b4965485de3d6941254f6cf57f8f30327ee97e0d22e800a6cdc5f2a33603f698858960c714b534baa70ee7d932aee37457e3d9491f2028b7ab76ef322b4d037dc581e8ffa0a7bdae13ef673135d95ac3f5acc1d3c74e20ceae28d2b1df11ac03a8bc69c936b28a6c39af23f5fcc4a3844384153605b544573093a1f65b5bfa16031aed823221ca6e0df03846414604a669841a222d8e4f931a80e94bbd23c1ca56036635dcd3e6195e9cb56b54aac693b577c7a61efae8a78e083ab4ab09dc0dc0f7039c69cbf3404d2600ba5dd29baab894cbf14eaf90604ecb0bc386234c050659e30322e543fbc5747a2fa6dcc745e6491bef82ee61621dd4f0f0ba122611bd2fc15105581e03548b4e00738642ef76c64430a458d2935ed62e3bb3bb3f3a2470bd72d00c19162c480b1d3e8cbfcc170005581e0357c90cdec79e0dd51ce7237f11dd64489032e3714ea2ed321ce6d4f1c004060303e7cbf53173e4de491288d5c010b4057c59c17d261133f477e22024eec8b14a05581e034c4f5ebd61961ca5f1b4da64d7a11efef7be683e47db94d78dc6ec1d300c0d47bdc4517c499cf403efdb6b712223be44d1f5ef236e1bbcfbb0ae48b941a84685a2073e2fe150b69705581e030e50e356b1f2d5cc60d54f1aacbff790a893b046c3889f2be31a47c060084812a1d8db3911700005581e03627fcc272f5aef2b30392948ad61991871f9e8171e558b7c1134f2b5f00c18184711b353c10a378f05581e03a214e046930d119c8772e4e619de95a950854c7a64fdb08832c351b7500c014807595f8f907a9a600353f3aed87582fca2e8818e5b578a8330637961f4f9dc132ff55fa7a932ba187503460c59fb861f1fa0852ff099b65087576b513d3220558b1d3968636ab27a4ec70219f9f003a82c025aee2624c99460cafe3db7cd7d733a9a51c7d5e3dc3cb233189045a6ac03cbc2bcf31ece53dcd0d9079f7497e9c5eed93282b21160c11fbd687e7f6d437003d18dc0dd814609fd8a314b13e38b14912c55c74cd2e21defa42dace63317eb320219ffff030beaadce9630d5cdeeafb6467ae330e69f0eec5af85cd6c6445fd6c4d435a03e035365b4103b78d8b6420a49b9a0bb0f088f44bcc56dee3f1b320c33b059dc60a4031e2ab9773e06429aa19fa29a1d15b0f85d516d7e8c11b92f98a2c2c3d7222fb70219ffff037145fe75d6a3310cc100e016f5120da353dcfb8888fb5f7017ee196c07f0aa2c0219ffff0302b30b0139b6c26982c18d904687faec24ea0a16c103aedf9a4edad8c965a47803f344d687f34cdea3e0d18782034c8b65c8d9935b256c9704f4aecb3b11827cd2030eaeb8004668e873b93fa3a3ea16a7f64a90db03a7747a2f79e1afd06da7103603fc696cdbf4e499529b60beca74089b7aa1b5161a492a5d883336f97bd0c7b655034de29fd0e14f94db934f8c135d93931b115930530b9da1a4e0ad727be26747200397142835a94b1a991a6b2c550a194cef962527547611cc219dbcbfd7612d76de0219ffff03c50bcb203a41a16c08d3dfd531ae786367868245515553b70f07b94c0e70939603761de03f8b32d4c32186628e8f84d0568af458b452a404d68805e5416eab9010036e48c086cdd9fc24241a2b6e65502c724efac0ec270df7687476fba2c40310e603108985700001cc576ae21d2c6e43e8b59303b5c27997237ef8d83124ce25ea2203676d3ccfbf3ee1f28df40319bc66f4c0752160ede57bdbedefd79adb9275082f0219ffff03f68d5ed6fde67f4406b5b5e54f2cc64e39aeb698ccd7136be39a414ae04103da03253402151c9ca9ff9b6dee891d1e3b0b4d9d14f87473fc880488a0e39e5a55af03ab8850bbae5a0ad03e5c2e0221cf6a540ef64c437feac87c7b43b499bc8adce50341546ad3caab670ae9cae83b9c0893505e82290a5952013b00c47976880acb2603315c99c9894209f3d38ef67f74455b28de0dd1fca7192f9674d9a19fe5fd672303cfb5741e4916b3fe8c8bc7bb1297d2bed485e256197046f36abe5709467950a003b278a0772315357617c6f3eff4a014a1ccda4640f1e0e97ea336ab839b322fb0036d79d19cebfb940c28efde48476de4c452e5e85c2f7f8fdf077c951eb58f4a75038fe324fb3989ba342baccca9258a42038f4b0e757a35b4e9f1c617c4a38651c103d8fb315375a0e5264dfa174f224633ab0ddf1e7f992810f74a32362dcf42ae46039f10542a3cd7915fdfba116433ca3e6e9b1c092e5757bd47ecb44ee487479dfb03e1b70b6d470577a702c3abab952e4d95767583420861efc021cfdbc9cf5d33cc0357194ba875a179406ad5aebb002f81ef8156f695d42172139a808752e03a8e2b03317268799d5bebc6f3ad1a6f54a1534b630856badb078d83290c81a0c6785fb90317820f7ac4e833d407641b67a34816d8248d835cb958b36a2efd37814d09ad0703cc66e3514513fbe365cef60b181488afcd17a713cfadf6f0f728958a648ca458030730feee2006bdfa00b5eaec816058bbc190d5be6c42be714ef74811eb36af320388d010cc2e166bc14c4f0d63654563a017c3ead7d90a0845a948801eaa37def5034d1df4a20a1e941f617d4de0388dc109d0f68458d1801c28646a5a6f85fabd230302ad9013f35d44b208f68c01019d09c04b1421b92cb513d72bb5006926318b5c03b2ad7bf1b9375446bf38edb86b56094f1728ed9154d6439c520527ab83570e980392125ae83569741f781df3fe83e372bc0e6b5c41c62b6f96442661ccbda347be034cd7c3739595cf95e3a4663092229f2ce621c880b5eb7f191c0231b1594a9fec037143eb6b32224df212b81bc6c70983c9c5fe7d9761c05693d45ddb5f164f6b9203974d54d474149197203c18b67c7b7f5f5e13fff780756b053b9afd30dbb3c40603a15cd0fcc6b16f9d20593477d2ee0fb809263904bcb584769dbf4590570cb909033cfcbd4b051641e63b8908e591657d8ba43677bdc56c36a2acf55cc6f0c073dd03b54f1b4131fc4f441424069d2e0cca3321755b5ad1faea1eec21f277541f3ab2036f4f7c303bcc466031d7a429a8ed54254650f89f8363aba24938c4b7c39c8d1803aef155417bd9fa006038ed7121422d0e2ae29462c7ab24bc74899e56128459ab03e7d7cfea7bcaad854ece8546542759d52fdd6defdd1febbbd61d7c641ce1af9d03dee5e17a9d94b4adec65d4c445f8603acfbd99ad4aa9ae8eddfc86144abe979203cbe5ba54bcb58d836fd7891b4f31032a3c84ecdd3aa3b5a77e838177c525e2a205581e03bd37fd152ca264eb62ca42c9762fe745c21220c74b28a045255cf6d5b00c014709faa779c79000045955b760806040526004361061018f5760003560e01c80638803dbee116100d6578063c45a01551161007f578063e8e3370011610059578063e8e3370014610c71578063f305d71914610cfe578063fb3bdb4114610d51576101d5565b8063c45a015514610b25578063d06ca61f14610b3a578063ded9382a14610bf1576101d5565b8063af2979eb116100b0578063af2979eb146109c8578063b6f9de9514610a28578063baa2abde14610abb576101d5565b80638803dbee146108af578063ad5c464814610954578063ad615dec14610992576101d5565b80634a25d94a11610138578063791ac94711610112578063791ac947146107415780637ff36ab5146107e657806385f8c25914610879576101d5565b80634a25d94a146105775780635b0d59841461061c5780635c11d7951461069c576101d5565b80631f00ca74116101695780631f00ca74146103905780632195995c1461044757806338ed1739146104d2576101d5565b806302751cec146101da578063054d50d41461025357806318cbafe51461029b576101d5565b366101d5573373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc216146101d357fe5b005b600080fd5b3480156101e657600080fd5b5061023a600480360360c08110156101fd57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020810135916040820135916060810135916080820135169060a00135610de4565b6040805192835260208301919091528051918290030190f35b34801561025f57600080fd5b506102896004803603606081101561027657600080fd5b5080359060208101359060400135610f37565b60408051918252519081900360200190f35b3480156102a757600080fd5b50610340600480360360a08110156102be57600080fd5b8135916020810135918101906060810160408201356401000000008111156102e557600080fd5b8201836020820111156102f757600080fd5b8035906020019184602083028401116401000000008311171561031957600080fd5b919350915073ffffffffffffffffffffffffffffffffffffffff8135169060200135610f4c565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561037c578181015183820152602001610364565b505050509050019250505060405180910390f35b34801561039c57600080fd5b50610340600480360360408110156103b357600080fd5b813591908101906040810160208201356401000000008111156103d557600080fd5b8201836020820111156103e757600080fd5b8035906020019184602083028401116401000000008311171561040957600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550611364945050505050565b34801561045357600080fd5b5061023a600480360361016081101561046b57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135811691602081013582169160408201359160608101359160808201359160a08101359091169060c08101359060e081013515159060ff610100820135169061012081013590610140013561139a565b3480156104de57600080fd5b50610340600480360360a08110156104f557600080fd5b81359160208101359181019060608101604082013564010000000081111561051c57600080fd5b82018360208201111561052e57600080fd5b8035906020019184602083028401116401000000008311171561055057600080fd5b919350915073ffffffffffffffffffffffffffffffffffffffff81351690602001356114d8565b34801561058357600080fd5b50610340600480360360a081101561059a57600080fd5b8135916020810135918101906060810160408201356401000000008111156105c157600080fd5b8201836020820111156105d357600080fd5b803590602001918460208302840111640100000000831117156105f557600080fd5b919350915073ffffffffffffffffffffffffffffffffffffffff8135169060200135611669565b34801561062857600080fd5b50610289600480360361014081101561064057600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020810135916040820135916060810135916080820135169060a08101359060c081013515159060ff60e082013516906101008101359061012001356118ac565b3480156106a857600080fd5b506101d3600480360360a08110156106bf57600080fd5b8135916020810135918101906060810160408201356401000000008111156106e657600080fd5b8201836020820111156106f857600080fd5b8035906020019184602083028401116401000000008311171561071a57600080fd5b919350915073ffffffffffffffffffffffffffffffffffffffff81351690602001356119fe565b34801561074d57600080fd5b506101d3600480360360a081101561076457600080fd5b81359160208101359181019060608101604082013564010000000081111561078b57600080fd5b82018360208201111561079d57600080fd5b803590602001918460208302840111640100000000831117156107bf57600080fd5b919350915073ffffffffffffffffffffffffffffffffffffffff8135169060200135611d97565b610340600480360360808110156107fc57600080fd5b8135919081019060408101602082013564010000000081111561081e57600080fd5b82018360208201111561083057600080fd5b8035906020019184602083028401116401000000008311171561085257600080fd5b919350915073ffffffffffffffffffffffffffffffffffffffff8135169060200135612105565b34801561088557600080fd5b506102896004803603606081101561089c57600080fd5b5080359060208101359060400135612525565b3480156108bb57600080fd5b50610340600480360360a08110156108d257600080fd5b8135916020810135918101906060810160408201356401000000008111156108f957600080fd5b82018360208201111561090b57600080fd5b8035906020019184602083028401116401000000008311171561092d57600080fd5b919350915073ffffffffffffffffffffffffffffffffffffffff8135169060200135612532565b34801561096057600080fd5b50610969612671565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b34801561099e57600080fd5b50610289600480360360608110156109b557600080fd5b5080359060208101359060400135612695565b3480156109d457600080fd5b50610289600480360360c08110156109eb57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020810135916040820135916060810135916080820135169060a001356126a2565b6101d360048036036080811015610a3e57600080fd5b81359190810190604081016020820135640100000000811115610a6057600080fd5b820183602082011115610a7257600080fd5b80359060200191846020830284011164010000000083111715610a9457600080fd5b919350915073ffffffffffffffffffffffffffffffffffffffff8135169060200135612882565b348015610ac757600080fd5b5061023a600480360360e0811015610ade57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135811691602081013582169160408201359160608101359160808201359160a08101359091169060c00135612d65565b348015610b3157600080fd5b5061096961306f565b348015610b4657600080fd5b5061034060048036036040811015610b5d57600080fd5b81359190810190604081016020820135640100000000811115610b7f57600080fd5b820183602082011115610b9157600080fd5b80359060200191846020830284011164010000000083111715610bb357600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550613093945050505050565b348015610bfd57600080fd5b5061023a6004803603610140811015610c1557600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020810135916040820135916060810135916080820135169060a08101359060c081013515159060ff60e082013516906101008101359061012001356130c0565b348015610c7d57600080fd5b50610ce06004803603610100811015610c9557600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135811691602081013582169160408201359160608101359160808201359160a08101359160c0820135169060e00135613218565b60408051938452602084019290925282820152519081900360600190f35b610ce0600480360360c0811015610d1457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020810135916040820135916060810135916080820135169060a001356133a7565b61034060048036036080811015610d6757600080fd5b81359190810190604081016020820135640100000000811115610d8957600080fd5b820183602082011115610d9b57600080fd5b80359060200191846020830284011164010000000083111715610dbd57600080fd5b919350915073ffffffffffffffffffffffffffffffffffffffff81351690602001356136d3565b6000808242811015610e5757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b610e86897f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28a8a8a308a612d65565b9093509150610e96898685613b22565b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff16632e1a7d4d836040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b158015610f0957600080fd5b505af1158015610f1d573d6000803e3d6000fd5b50505050610f2b8583613cff565b50965096945050505050565b6000610f44848484613e3c565b949350505050565b60608142811015610fbe57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc21686867fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff810181811061102357fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146110c257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f556e69737761705632526f757465723a20494e56414c49445f50415448000000604482015290519081900360640190fd5b6111207f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f89888880806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250613f6092505050565b9150868260018451038151811061113357fe5b60200260200101511015611192576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b815260200180615508602b913960400191505060405180910390fd5b611257868660008181106111a257fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff163361123d7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8a8a60008181106111f157fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff168b8b600181811061121b57fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff166140c6565b8560008151811061124a57fe5b60200260200101516141b1565b61129682878780806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250309250614381915050565b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff16632e1a7d4d836001855103815181106112e257fe5b60200260200101516040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b15801561132057600080fd5b505af1158015611334573d6000803e3d6000fd5b50505050611359848360018551038151811061134c57fe5b6020026020010151613cff565b509695505050505050565b60606113917f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8484614608565b90505b92915050565b60008060006113ca7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8f8f6140c6565b90506000876113d9578c6113fb565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b604080517fd505accf00000000000000000000000000000000000000000000000000000000815233600482015230602482015260448101839052606481018c905260ff8a16608482015260a4810189905260c48101889052905191925073ffffffffffffffffffffffffffffffffffffffff84169163d505accf9160e48082019260009290919082900301818387803b15801561149757600080fd5b505af11580156114ab573d6000803e3d6000fd5b505050506114be8f8f8f8f8f8f8f612d65565b809450819550505050509b509b9950505050505050505050565b6060814281101561154a57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b6115a87f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f89888880806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250613f6092505050565b915086826001845103815181106115bb57fe5b6020026020010151101561161a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b815260200180615508602b913960400191505060405180910390fd5b61162a868660008181106111a257fe5b61135982878780806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250899250614381915050565b606081428110156116db57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc21686867fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff810181811061174057fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146117df57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f556e69737761705632526f757465723a20494e56414c49445f50415448000000604482015290519081900360640190fd5b61183d7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8988888080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525061460892505050565b9150868260008151811061184d57fe5b60200260200101511115611192576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260278152602001806154986027913960400191505060405180910390fd5b6000806118fa7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8d7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26140c6565b9050600086611909578b61192b565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b604080517fd505accf00000000000000000000000000000000000000000000000000000000815233600482015230602482015260448101839052606481018b905260ff8916608482015260a4810188905260c48101879052905191925073ffffffffffffffffffffffffffffffffffffffff84169163d505accf9160e48082019260009290919082900301818387803b1580156119c757600080fd5b505af11580156119db573d6000803e3d6000fd5b505050506119ed8d8d8d8d8d8d6126a2565b9d9c50505050505050505050505050565b8042811015611a6e57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b611afd85856000818110611a7e57fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1633611af77f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f89896000818110611acd57fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff168a8a600181811061121b57fe5b8a6141b1565b600085857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101818110611b2d57fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166370a08231856040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b158015611bc657600080fd5b505afa158015611bda573d6000803e3d6000fd5b505050506040513d6020811015611bf057600080fd5b50516040805160208881028281018201909352888252929350611c32929091899189918291850190849080828437600092019190915250889250614796915050565b86611d368288887fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101818110611c6557fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166370a08231886040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b158015611cfe57600080fd5b505afa158015611d12573d6000803e3d6000fd5b505050506040513d6020811015611d2857600080fd5b50519063ffffffff614b2916565b1015611d8d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b815260200180615508602b913960400191505060405180910390fd5b5050505050505050565b8042811015611e0757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc21685857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101818110611e6c57fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614611f0b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f556e69737761705632526f757465723a20494e56414c49445f50415448000000604482015290519081900360640190fd5b611f1b85856000818110611a7e57fe5b611f59858580806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250309250614796915050565b604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905160009173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc216916370a0823191602480820192602092909190829003018186803b158015611fe957600080fd5b505afa158015611ffd573d6000803e3d6000fd5b505050506040513d602081101561201357600080fd5b5051905086811015612070576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b815260200180615508602b913960400191505060405180910390fd5b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff16632e1a7d4d826040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b1580156120e357600080fd5b505af11580156120f7573d6000803e3d6000fd5b50505050611d8d8482613cff565b6060814281101561217757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff16868660008181106121bb57fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461225a57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f556e69737761705632526f757465723a20494e56414c49445f50415448000000604482015290519081900360640190fd5b6122b87f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f34888880806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250613f6092505050565b915086826001845103815181106122cb57fe5b6020026020010151101561232a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b815260200180615508602b913960400191505060405180910390fd5b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1663d0e30db08360008151811061237357fe5b60200260200101516040518263ffffffff1660e01b81526004016000604051808303818588803b1580156123a657600080fd5b505af11580156123ba573d6000803e3d6000fd5b50505050507f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1663a9059cbb61242c7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f89896000818110611acd57fe5b8460008151811061243957fe5b60200260200101516040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b1580156124aa57600080fd5b505af11580156124be573d6000803e3d6000fd5b505050506040513d60208110156124d457600080fd5b50516124dc57fe5b61251b82878780806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250899250614381915050565b5095945050505050565b6000610f44848484614b9b565b606081428110156125a457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b6126027f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8988888080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525061460892505050565b9150868260008151811061261257fe5b6020026020010151111561161a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260278152602001806154986027913960400191505060405180910390fd5b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc281565b6000610f44848484614cbf565b6000814281101561271457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b612743887f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28989893089612d65565b604080517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015290519194506127ed92508a91879173ffffffffffffffffffffffffffffffffffffffff8416916370a0823191602480820192602092909190829003018186803b1580156127bc57600080fd5b505afa1580156127d0573d6000803e3d6000fd5b505050506040513d60208110156127e657600080fd5b5051613b22565b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff16632e1a7d4d836040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b15801561286057600080fd5b505af1158015612874573d6000803e3d6000fd5b505050506113598483613cff565b80428110156128f257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff168585600081811061293657fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146129d557604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f556e69737761705632526f757465723a20494e56414c49445f50415448000000604482015290519081900360640190fd5b60003490507f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b158015612a4257600080fd5b505af1158015612a56573d6000803e3d6000fd5b50505050507f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1663a9059cbb612ac87f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f89896000818110611acd57fe5b836040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b158015612b3257600080fd5b505af1158015612b46573d6000803e3d6000fd5b505050506040513d6020811015612b5c57600080fd5b5051612b6457fe5b600086867fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101818110612b9457fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166370a08231866040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b158015612c2d57600080fd5b505afa158015612c41573d6000803e3d6000fd5b505050506040513d6020811015612c5757600080fd5b50516040805160208981028281018201909352898252929350612c999290918a918a918291850190849080828437600092019190915250899250614796915050565b87611d368289897fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101818110612ccc57fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166370a08231896040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b158015611cfe57600080fd5b6000808242811015612dd857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b6000612e057f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8c8c6140c6565b604080517f23b872dd00000000000000000000000000000000000000000000000000000000815233600482015273ffffffffffffffffffffffffffffffffffffffff831660248201819052604482018d9052915192935090916323b872dd916064808201926020929091908290030181600087803b158015612e8657600080fd5b505af1158015612e9a573d6000803e3d6000fd5b505050506040513d6020811015612eb057600080fd5b5050604080517f89afcb4400000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff888116600483015282516000938493928616926389afcb44926024808301939282900301818787803b158015612f2357600080fd5b505af1158015612f37573d6000803e3d6000fd5b505050506040513d6040811015612f4d57600080fd5b50805160209091015190925090506000612f678e8e614d9f565b5090508073ffffffffffffffffffffffffffffffffffffffff168e73ffffffffffffffffffffffffffffffffffffffff1614612fa4578183612fa7565b82825b90975095508a871015613005576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806154bf6026913960400191505060405180910390fd5b8986101561305e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806154256026913960400191505060405180910390fd5b505050505097509795505050505050565b7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f81565b60606113917f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8484613f60565b60008060006131107f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8e7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26140c6565b905060008761311f578c613141565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b604080517fd505accf00000000000000000000000000000000000000000000000000000000815233600482015230602482015260448101839052606481018c905260ff8a16608482015260a4810189905260c48101889052905191925073ffffffffffffffffffffffffffffffffffffffff84169163d505accf9160e48082019260009290919082900301818387803b1580156131dd57600080fd5b505af11580156131f1573d6000803e3d6000fd5b505050506132038e8e8e8e8e8e610de4565b909f909e509c50505050505050505050505050565b6000806000834281101561328d57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b61329b8c8c8c8c8c8c614ef2565b909450925060006132cd7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8e8e6140c6565b90506132db8d3383886141b1565b6132e78c3383876141b1565b8073ffffffffffffffffffffffffffffffffffffffff16636a627842886040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050602060405180830381600087803b15801561336657600080fd5b505af115801561337a573d6000803e3d6000fd5b505050506040513d602081101561339057600080fd5b5051949d939c50939a509198505050505050505050565b6000806000834281101561341c57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b61344a8a7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28b348c8c614ef2565b9094509250600061349c7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8c7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26140c6565b90506134aa8b3383886141b1565b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1663d0e30db0856040518263ffffffff1660e01b81526004016000604051808303818588803b15801561351257600080fd5b505af1158015613526573d6000803e3d6000fd5b50505050507f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1663a9059cbb82866040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b1580156135d257600080fd5b505af11580156135e6573d6000803e3d6000fd5b505050506040513d60208110156135fc57600080fd5b505161360457fe5b8073ffffffffffffffffffffffffffffffffffffffff16636a627842886040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050602060405180830381600087803b15801561368357600080fd5b505af1158015613697573d6000803e3d6000fd5b505050506040513d60208110156136ad57600080fd5b50519250348410156136c5576136c533853403613cff565b505096509650969350505050565b6060814281101561374557604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff168686600081811061378957fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461382857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f556e69737761705632526f757465723a20494e56414c49445f50415448000000604482015290519081900360640190fd5b6138867f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8888888080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525061460892505050565b9150348260008151811061389657fe5b602002602001015111156138f5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260278152602001806154986027913960400191505060405180910390fd5b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1663d0e30db08360008151811061393e57fe5b60200260200101516040518263ffffffff1660e01b81526004016000604051808303818588803b15801561397157600080fd5b505af1158015613985573d6000803e3d6000fd5b50505050507f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1663a9059cbb6139f77f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f89896000818110611acd57fe5b84600081518110613a0457fe5b60200260200101516040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b158015613a7557600080fd5b505af1158015613a89573d6000803e3d6000fd5b505050506040513d6020811015613a9f57600080fd5b5051613aa757fe5b613ae682878780806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250899250614381915050565b81600081518110613af357fe5b602002602001015134111561251b5761251b3383600081518110613b1357fe5b60200260200101513403613cff565b6040805173ffffffffffffffffffffffffffffffffffffffff8481166024830152604480830185905283518084039091018152606490920183526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb00000000000000000000000000000000000000000000000000000000178152925182516000946060949389169392918291908083835b60208310613bf857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101613bbb565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114613c5a576040519150601f19603f3d011682016040523d82523d6000602084013e613c5f565b606091505b5091509150818015613c8d575080511580613c8d5750808060200190516020811015613c8a57600080fd5b50515b613cf857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5472616e7366657248656c7065723a205452414e534645525f4641494c454400604482015290519081900360640190fd5b5050505050565b6040805160008082526020820190925273ffffffffffffffffffffffffffffffffffffffff84169083906040518082805190602001908083835b60208310613d7657805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101613d39565b6001836020036101000a03801982511681845116808217855250505050505090500191505060006040518083038185875af1925050503d8060008114613dd8576040519150601f19603f3d011682016040523d82523d6000602084013e613ddd565b606091505b5050905080613e37576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260238152602001806154e56023913960400191505060405180910390fd5b505050565b6000808411613e96576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b815260200180615557602b913960400191505060405180910390fd5b600083118015613ea65750600082115b613efb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602881526020018061544b6028913960400191505060405180910390fd5b6000613f0f856103e563ffffffff6151f316565b90506000613f23828563ffffffff6151f316565b90506000613f4983613f3d886103e863ffffffff6151f316565b9063ffffffff61527916565b9050808281613f5457fe5b04979650505050505050565b6060600282511015613fd357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f556e697377617056324c6962726172793a20494e56414c49445f504154480000604482015290519081900360640190fd5b815167ffffffffffffffff81118015613feb57600080fd5b50604051908082528060200260200182016040528015614015578160200160208202803683370190505b509050828160008151811061402657fe5b60200260200101818152505060005b60018351038110156140be576000806140788786858151811061405457fe5b602002602001015187866001018151811061406b57fe5b60200260200101516152eb565b9150915061409a84848151811061408b57fe5b60200260200101518383613e3c565b8484600101815181106140a957fe5b60209081029190910101525050600101614035565b509392505050565b60008060006140d58585614d9f565b604080517fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606094851b811660208084019190915293851b81166034830152825160288184030181526048830184528051908501207fff0000000000000000000000000000000000000000000000000000000000000060688401529a90941b9093166069840152607d8301989098527f96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f609d808401919091528851808403909101815260bd909201909752805196019590952095945050505050565b6040805173ffffffffffffffffffffffffffffffffffffffff85811660248301528481166044830152606480830185905283518084039091018152608490920183526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f23b872dd0000000000000000000000000000000000000000000000000000000017815292518251600094606094938a169392918291908083835b6020831061428f57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101614252565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d80600081146142f1576040519150601f19603f3d011682016040523d82523d6000602084013e6142f6565b606091505b5091509150818015614324575080511580614324575080806020019051602081101561432157600080fd5b50515b614379576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260248152602001806155336024913960400191505060405180910390fd5b505050505050565b60005b60018351038110156146025760008084838151811061439f57fe5b60200260200101518584600101815181106143b657fe5b60200260200101519150915060006143ce8383614d9f565b50905060008785600101815181106143e257fe5b602002602001015190506000808373ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff161461442a5782600061442e565b6000835b91509150600060028a510388106144455788614486565b6144867f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f878c8b6002018151811061447957fe5b60200260200101516140c6565b90506144b37f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f88886140c6565b73ffffffffffffffffffffffffffffffffffffffff1663022c0d9f84848460006040519080825280601f01601f1916602001820160405280156144fd576020820181803683370190505b506040518563ffffffff1660e01b8152600401808581526020018481526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200180602001828103825283818151815260200191508051906020019080838360005b83811015614588578181015183820152602001614570565b50505050905090810190601f1680156145b55780820380516001836020036101000a031916815260200191505b5095505050505050600060405180830381600087803b1580156145d757600080fd5b505af11580156145eb573d6000803e3d6000fd5b505060019099019850614384975050505050505050565b50505050565b606060028251101561467b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f556e697377617056324c6962726172793a20494e56414c49445f504154480000604482015290519081900360640190fd5b815167ffffffffffffffff8111801561469357600080fd5b506040519080825280602002602001820160405280156146bd578160200160208202803683370190505b50905082816001835103815181106146d157fe5b602090810291909101015281517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff015b80156140be576000806147318786600186038151811061471d57fe5b602002602001015187868151811061406b57fe5b9150915061475384848151811061474457fe5b60200260200101518383614b9b565b84600185038151811061476257fe5b602090810291909101015250507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01614701565b60005b6001835103811015613e37576000808483815181106147b457fe5b60200260200101518584600101815181106147cb57fe5b60200260200101519150915060006147e38383614d9f565b50905060006148137f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f85856140c6565b90506000806000808473ffffffffffffffffffffffffffffffffffffffff16630902f1ac6040518163ffffffff1660e01b815260040160606040518083038186803b15801561486157600080fd5b505afa158015614875573d6000803e3d6000fd5b505050506040513d606081101561488b57600080fd5b5080516020909101516dffffffffffffffffffffffffffff918216935016905060008073ffffffffffffffffffffffffffffffffffffffff8a8116908916146148d55782846148d8565b83835b9150915061495d828b73ffffffffffffffffffffffffffffffffffffffff166370a082318a6040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b158015611cfe57600080fd5b955061496a868383613e3c565b9450505050506000808573ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff16146149ae578260006149b2565b6000835b91509150600060028c51038a106149c9578a6149fd565b6149fd7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f898e8d6002018151811061447957fe5b60408051600080825260208201928390527f022c0d9f000000000000000000000000000000000000000000000000000000008352602482018781526044830187905273ffffffffffffffffffffffffffffffffffffffff8086166064850152608060848501908152845160a48601819052969750908c169563022c0d9f958a958a958a9591949193919260c486019290918190849084905b83811015614aad578181015183820152602001614a95565b50505050905090810190601f168015614ada5780820380516001836020036101000a031916815260200191505b5095505050505050600060405180830381600087803b158015614afc57600080fd5b505af1158015614b10573d6000803e3d6000fd5b50506001909b019a506147999950505050505050505050565b8082038281111561139457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f64732d6d6174682d7375622d756e646572666c6f770000000000000000000000604482015290519081900360640190fd5b6000808411614bf5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c8152602001806153d4602c913960400191505060405180910390fd5b600083118015614c055750600082115b614c5a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602881526020018061544b6028913960400191505060405180910390fd5b6000614c7e6103e8614c72868863ffffffff6151f316565b9063ffffffff6151f316565b90506000614c986103e5614c72868963ffffffff614b2916565b9050614cb56001828481614ca857fe5b049063ffffffff61527916565b9695505050505050565b6000808411614d19576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806154736025913960400191505060405180910390fd5b600083118015614d295750600082115b614d7e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602881526020018061544b6028913960400191505060405180910390fd5b82614d8f858463ffffffff6151f316565b81614d9657fe5b04949350505050565b6000808273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161415614e27576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806154006025913960400191505060405180910390fd5b8273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1610614e61578284614e64565b83835b909250905073ffffffffffffffffffffffffffffffffffffffff8216614eeb57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f556e697377617056324c6962726172793a205a45524f5f414444524553530000604482015290519081900360640190fd5b9250929050565b604080517fe6a4390500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff888116600483015287811660248301529151600092839283927f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f9092169163e6a4390591604480820192602092909190829003018186803b158015614f9257600080fd5b505afa158015614fa6573d6000803e3d6000fd5b505050506040513d6020811015614fbc57600080fd5b505173ffffffffffffffffffffffffffffffffffffffff1614156150a257604080517fc9c6539600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8a81166004830152898116602483015291517f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f9092169163c9c65396916044808201926020929091908290030181600087803b15801561507557600080fd5b505af1158015615089573d6000803e3d6000fd5b505050506040513d602081101561509f57600080fd5b50505b6000806150d07f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8b8b6152eb565b915091508160001480156150e2575080155b156150f2578793508692506151e6565b60006150ff898484614cbf565b905087811161516c5785811015615161576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806154256026913960400191505060405180910390fd5b8894509250826151e4565b6000615179898486614cbf565b90508981111561518557fe5b878110156151de576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806154bf6026913960400191505060405180910390fd5b94508793505b505b5050965096945050505050565b600081158061520e5750508082028282828161520b57fe5b04145b61139457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f64732d6d6174682d6d756c2d6f766572666c6f77000000000000000000000000604482015290519081900360640190fd5b8082018281101561139457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f64732d6d6174682d6164642d6f766572666c6f77000000000000000000000000604482015290519081900360640190fd5b60008060006152fa8585614d9f565b50905060008061530b8888886140c6565b73ffffffffffffffffffffffffffffffffffffffff16630902f1ac6040518163ffffffff1660e01b815260040160606040518083038186803b15801561535057600080fd5b505afa158015615364573d6000803e3d6000fd5b505050506040513d606081101561537a57600080fd5b5080516020909101516dffffffffffffffffffffffffffff918216935016905073ffffffffffffffffffffffffffffffffffffffff878116908416146153c15780826153c4565b81815b9099909850965050505050505056fe556e697377617056324c6962726172793a20494e53554646494349454e545f4f55545055545f414d4f554e54556e697377617056324c6962726172793a204944454e544943414c5f414444524553534553556e69737761705632526f757465723a20494e53554646494349454e545f425f414d4f554e54556e697377617056324c6962726172793a20494e53554646494349454e545f4c4951554944495459556e697377617056324c6962726172793a20494e53554646494349454e545f414d4f554e54556e69737761705632526f757465723a204558434553534956455f494e5055545f414d4f554e54556e69737761705632526f757465723a20494e53554646494349454e545f415f414d4f554e545472616e7366657248656c7065723a204554485f5452414e534645525f4641494c4544556e69737761705632526f757465723a20494e53554646494349454e545f4f55545055545f414d4f554e545472616e7366657248656c7065723a205452414e534645525f46524f4d5f4641494c4544556e697377617056324c6962726172793a20494e53554646494349454e545f494e5055545f414d4f554e54a26469706673582212206dd6e03c4b2c0a8e55214926227ae9e2d6f9fec2ce74a6446d615afa355c84f364736f6c634300060600330605581d02a7e8b8234a7992da2173c0d9eb404853fe85a65a0881149522e12b2a0f014758d15e176280001955b705581d024ace4a9cf9849185c4b4cd9d2d43e5360f36d915f743ac51fcc9c7f80c02450135f1b4000219042005581e03dc8fc5260164a847b23d38a881b05ec9e601d397d1188021168d7c0da00c024701923a550aa0c803fe15c6a27562a27ae904cb1b828dd828705d1507183fdaa98bcdd0bb1025ee5305581e03f23857a9fa3d0da9ae7310c160afcf49158a232640406bae60c29650500c01472296500eeb160005581e037f46e0bb2789267d0a30b8d43a6d3e2a2e289f0fcb20ea1bc8ee7216a00c024744d7c56d940be603da1a91bd88ab16e57730db064e35880953edf7a3e2f40ec22e887166b95f4b18032be8a5093959bbacdaa2d02b061939a3a07681b5a5351eef8de8b286f8df80ba0399cc8746fb4acef4aa25ad53d2d12e8af29520040e86729cf0fe867839a233a005581e031d2088553eaa5823aa330be942a76137ea4d7416fda40c5c72917239f00c0147028114c7f49fc0034277b3bbc296a60f66b308aceaf2f9ad4e766e1984e39f2bb1256df0c51448a70219cbde03821c17b99606f4ef0404c1e66f9b5847282857f6d43c1d1e2282e3e20358c1a203c4c9fce98e5aa03a1c2976bea62991500383da391e6b93cd7ee617b79075830103454a7715e43a8f5665f3175574c0f374cd68bdd08d8d39beda757efe065a1d010309e22729a72f3d87a3b4e2cbb35b5508a5b4967f51619447d82fcd412c0c60810365c9ef940b1bf8951ae99a658391bc322d955090fc733e730f446262316cd707032dc7ad83f81334ea5c5841d73bfd9a6a887e1d78e06e3905881fa83d258880c403bd0a711bff7996b670c2882e6431d8feede57819c03c580eaa092d1a8f53519403513d856a2f501db5917ad941a82b2f1cca5130e73032c160f94b21d3018abfb20219ffff03ac86231aaaa6826ce152d88375c5f38b82dd0e76a5564441cd6e64288e669d3f03c04a48f47576ad78e3231a2f36d5eda038467ee085887710796779878707d8860219ffff03c3228b8b519627dbd53fe57eb03c86f32d4d32132441c285d89a469f6e98969a03c637d8e3c7e4fdb1139d38880919ec63eee53dc169b0653d5618c6f10763300603c6f89a52369c69f9bc8492439a33234d8e9ecfabaf11dbe3d7876a929ec36c37030ff43ce6633838d3969f23abafbd709cf76075b4750bb94580edf5a0c1b48d4c03bd3dfaea945940faa25ecd693a00b0054131e61c8c072b4b89d5bc0f2a2a0e5603172432d96087eae407001fe525820f99fe41ece1edad5f0fa3cba23417c690950305ca546ef52bca7e571ebca61b3f8db71bc97d3fee9cb79efa729ca89a2de4a703c70aacc6e77a75e52d1e8174f822c17d81513b92696184494b26d4103a30f86503a643a5935a0834c754539b1acdfebcff308652d88c1e9cccd169917b3942b8f5037a1a1a35115e38ca23b634b63152607fdb28a56a8926f0598e3f9dcf5f99165c03a70951a56435c3e8f0bb049a3672a8b2e4dbc0840d84d8ea713cebccb82a363e0219ffff03ed8be2c4096e580a056e6330110e872763a3cfe26881493162860768a33c0e88037328b7c8743732c0eac311177bd28da77294ef3971a2122660bb904b030ddffe032552c7a0dc460a9b828bdfda485a0178f1a4c69577af77289e8fb436dfdf432c035391aee21ffc3ec7a0d439d03b75883bf826bdab6d234abe33064f868521a87c0328d325d76bb005dbfd5e53fb9654f65ad41a1963a0c5a5555a63b750f78e7bb40346889b6d6058db34e6ec212e7cf7498d357646101fe15701bfe5a7fc9f73e02f030bb04c4408821a55f3d79cbe22dfebec2478ef58bd1173604245e324eb2d86df0307f8769ef2df566a4570eb7316af39b248e7485614e5f82ddc8efb91b6d69d6c033ff286d99ca1d1a1f7c2159645d37f27801abc91a6b7fb06594e718992fbfc7003c8b6ba8237dc86c85e2b08d3e0ed9cdd8f8b063d136acb768bd3c1ba6afa859c0219ffff032a0fe47e3339d3eec328f9b8795cbd3c5935c1fcb5cc4c984371ad9af32f4b4303cfb01d920e6782fa1c58ca90a576e87018bce6b89ec1e62fd703692a7f660a78032f77fabb828201453e93b311254e5e08ba2b0a54b4cd6007d8a533fc2ee829720369ce2364235ab7f2141197dd70c31ba9f6dfe1c1f25df244ef475a60ee5e63fd03e628c6722bf5db5edec7776acabd962d6e1cb3d9d7b087dc0f3911a57a4c8da203d5c238ca585964af5a1eeddd92eda4e0e0b4acccaff38232f6babf99ddccde71037291e2aafc399e03815c5c38d9ec5bcc86cdb752637990ccd9c8174ae77498a4031dd8636b55ac8a984c43dbf96637dd122dc44fffc40de239b65bdec927aa3a250344d99deb2681bfc61b00e2a78f717b74c99593614bd1b1a28830479e8933047e03763fb13fd8862a3e1d962cd3d4761c84cdc05c0c811b50a4dec9415420eceafe03cb9ed56a60711bca9b428b466aaab6d5f91f77da615c2cfd571693223670831703f606025ef127336a77e7330c2f344d5c0227e8b2325192423a0ff9e283130e870387a5d18dbfa90051e60d86e934df53943f531e1aa139130d83d6ca094d83c4af03a85cf85433f3b13a17487c4b5979427223543764769835aed1ff096fef477b8503ab80ad10f93cb63a964e8c0e63b336c9174f124035e8b40d01fc465165456023034055d8bec32e330335dfaa6db4b09b5952e350ef512242ba4da987b1d43945b403c357e903314ffe92300957f05be5413168fcb52441ddc0842c48695c6b72b1ef0395459aaf6080b3f8ee9e971df38e176297279b9cbb006b01351910c395de0f2a0362b23bfc7f42168ac33c83afd45b1743f1ef1742e329872b3a0b5a7186da2698036d75bd0f47f761d7f60baa3ccb48a6d3641d0fd34a5b854978783887fa5cc1c2035e8eed0985ed0b2a6a215e8ba4c006afc18e17b1a68ab9b9464320df77ee9b82037d4ce2e0d91b6025cec6bb65065b9a2bdcd8be7d79d771e2327f2283d400d2cc038b4206e28bdaae19f596bd3e8d9cf465d2ac0e7ffb8eb0555a600b1aed3c807103c4acf523da26a9eeb53f093426b97aeae7a442b023508d3e941f3a0d3770fd49033e1a3a6888bba34fd74c4f96e7d784e08a3927c99274767c8b45383243d181e7035a92bb6dcee1c9c9bd2ee3548044251374832475ed9d9e701e7c36cc21f00e4803a9c2664e6e250599e437fea69cc9569dc34e789f5f1b83f8addc5a8580575d4e03dacacfd3dece2d8f09187158ed3ab28e4521918316962201543b53909d25d89f03c21469cd6bbb6d51d3b7ed8ff4a9050c7df6f43be97c18c19ae59b39c75a6852037d337e717ecd4ce5f45c4ea8994e476ce38cf937a4f2924dc40cfa8fe5870fe905581e038e5a09e900731c937b9f9cf98dd46edd55614471018486770032d540d00c0447275e593373250003f8559abbbe0eba3bfeca91b590098c70a24bf7c768b130fde193fb5bcc98b88205581e035a76fba5f3ab8fe576d44c03683d3a2189211bafe0e5d16cb0630b5e00084780b6527a8c355805581e035653eb197c3fd399f1a5cd68d1de6f19edca5b60433ac2b56c7722f0700c054730ef6fc5c179d805581e0309a9efb64b9fc582cb3aa2de516620aca954f1cf2b57fa3c62a951f0e00c014750434de6c4000003ccb10237fbb545478774d6e858eb7a1327708de6c205c7195d3a5dacd596120903647d5d7eb5af6fbf9ed95320cadf658cec3819ffa536adc7b6387fdf1b72123d0309649ef5d0e27de54607a47f0520208f93df8363061237d10e681ecf3ddd9efe05581e03469667fcae85f0588263efb961720305bd89608714ea1d1d740c8f27e007011bffffffffffffffff05581d02d5cd67117eb060eb8283e6f3aaf1a31a9234b6478f940d7ca4cbc360040105581d0299652947601cf1972a660924be5f208f708f0e0e2bc9ac6a34afbe120c014702ce80355f63e80459088a60806040526004361061006d576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633659cfe6146100775780634f1ef286146100ba5780635c60da1b146101085780638f2839701461015f578063f851a440146101a2575b6100756101f9565b005b34801561008357600080fd5b506100b8600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610213565b005b610106600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001908201803590602001919091929391929390505050610268565b005b34801561011457600080fd5b5061011d610308565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561016b57600080fd5b506101a0600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610360565b005b3480156101ae57600080fd5b506101b761051e565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b610201610576565b61021161020c610651565b610682565b565b61021b6106a8565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561025c57610257816106d9565b610265565b6102646101f9565b5b50565b6102706106a8565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102fa576102ac836106d9565b3073ffffffffffffffffffffffffffffffffffffffff163483836040518083838082843782019150509250505060006040518083038185875af19250505015156102f557600080fd5b610303565b6103026101f9565b5b505050565b60006103126106a8565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156103545761034d610651565b905061035d565b61035c6101f9565b5b90565b6103686106a8565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561051257600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614151515610466576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260368152602001807f43616e6e6f74206368616e6765207468652061646d696e206f6620612070726f81526020017f787920746f20746865207a65726f20616464726573730000000000000000000081525060400191505060405180910390fd5b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f61048f6106a8565b82604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a161050d81610748565b61051b565b61051a6101f9565b5b50565b60006105286106a8565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561056a576105636106a8565b9050610573565b6105726101f9565b5b90565b61057e6106a8565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151515610647576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260328152602001807f43616e6e6f742063616c6c2066616c6c6261636b2066756e6374696f6e20667281526020017f6f6d207468652070726f78792061646d696e000000000000000000000000000081525060400191505060405180910390fd5b61064f610777565b565b6000807f7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c36001029050805491505090565b3660008037600080366000845af43d6000803e80600081146106a3573d6000f35b3d6000fd5b6000807f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b6001029050805491505090565b6106e281610779565b7fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b81604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a150565b60007f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b60010290508181555050565b565b60006107848261084b565b151561081e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603b8152602001807f43616e6e6f742073657420612070726f787920696d706c656d656e746174696f81526020017f6e20746f2061206e6f6e2d636f6e74726163742061646472657373000000000081525060400191505060405180910390fd5b7f7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c360010290508181555050565b600080823b9050600081119150509190505600a165627a7a72305820a4a547cfc7202c5acaaae74d428e988bc62ad5024eb0165532d3a8f91db4ed240029032d1394a5a9d2e8c5c8588d8d55fe94829ab07e41ae5b788ac1ad3fb89e25c5b20361de97f7cf7a84eb3f54545791e3563cd289889db9dc76c0d20a52a92b0e2681031f9750ff62ba05c63a63b38fdf3a653a33740eb9e4107d3f087336be33a8264303f4ecef637b0d537e42418fcfe713245d8888c97671e5e9bb62be5b03d7eb042f036c3e86f9176f176b2003402059adb141fd079cc84e28e7168429d72672f8ac870349b348d7327d208634e63b3991e1dbca2220c269469fceeadd143352346fa71c039562514beb09cdb6535c5afcbe206feee98477324f24ba7729ef3928ebf7683203d8acf0d25c0bb6205c64c828c9c8f32a18a57971b5ab6c9cbc12026b8d4411d00301fdcee0b48eb5454f00f9d8f333f7780bf0a6665aa1e79a716bb3e3125e702703f3f6b20be0b51977798f87671efdf26a9d2c5a2bf266fd7d03750e79006692830366c1aec8af137b356a350572d02e36fc0454f3e6464c0a33dc72e01fc2047c6d03c9c5da42d072b3e68c8c5f8f31b21122e99961ee4098fba75d26a320f6c1f0a0034bfe2f8aeddea7910851594b242ef3472e0a8e09219ebfbfd2ed91d71328e0930335a733225fb422ee1157d0da9d6a0060c58d8a4b8d3244eea5d80be5eed089c903bc2643a050ff27fe7f8c50357254442a1d4c5d96dc82d626fef4e66ca1a50f5c03843c1062c2071e81e9dbb732917464e9aafcd6923fb7e2a2605eb7bf1615a345034a10f0d4db5c8b82dd25febf0290e8ebf1ce9441b9b6b25c7df5f3d830e46ba703d91b06a7a5f21cf2cc5b1783c7650e0b2d52a4cdd2917666bb2f589be79ff4e9030d7a89c9c8173a8f28da43ec8b0e92637ca5ed47f9255f45723533f111b1e54303ed9dfd8de6f453358de865d52885eee7868889ee299e26fb9c4a98e7cb52809303b19275be6462fdafcd464a07d4bdc4be4e969d29ef71e8d3eb1c678a7e8c36a8033f0c80aba7059a3d27aa9a1a1351950f0e5141d06030ebcfe52ec410ba295186030eefadb417a643072bd3b66f8745ee6c979229b8ceb8ad7b238bee14ef788bcc0380863a35c1837440975f1fd5554d7d0101ca893f600c0540a572547746f02c1a03ac198e4d0a3e41d7f2d2794c4c7e211e7f90ed100a668aa4bbdcb9ff5c5ca9e10377f374dffe53e26ff75445321643f9f0338f34bf35b67ccd278e1f95b212883d0346cfad0189823dade2dccc8966e987b1fd0f4397b0fd63ff02db29127a519ae90326efb0975f574bd9ae5227a5bb4de5821b0b0b52420cf2199a4aa73ff7ed3076035550ad58bef41e4e9711366bdd072ade22c34761f780fab6078ab9a700781aab0377a7ff0a597eee267c9dc15cd8cdc6a3ae588458f19514697dacd0212bfd54e403e2d583b748cc7eab122b6b089828ce086f9a8206244a67d6b8e4fa113c01b19d03299753fdd35358c6d2f319b1208f0a81b564a8c2679cf2b0b369afe3faf997c1035bbcfc4fba0f9324577b55dbdcc402bd4e45def541b4f489144ba8632b41dda50323a694ed2cf402dc9175a62cac8598dacbaf75dd275a65a7acd230e52837584703c0170493779ec092c574e976da762b94d26b76837df96ff2b72e9c120ab7bec103beeda31608a04610ce04832a7259fc1304c4683b820cd441b8ebfdc4f4081555038cae0d86fcfe3184d9f868080553d0b53e398426acc98bb8dc2908cfac3c4dec03f47b5da58a03fcbe648e2982848791323d744768ee73d7da4c57b5ea188d8c7e03841e883246ab4980e65341503ecc125a7446b1f690003578fe339fc7174757d100581e02a818348987efc21198638a43af425294e0cebbabe1f3c7676a812e34a654807a96288a1a408dbc13de2b1d087d10356395d200581e02abbad38fa3fc14dba397b83ac7e8a7bf4220ddd174389a427fd84435415820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffc46535ff00581e0248b7e2ccdd56cee52699e9315c16e7f3f0dc33119ababa60cdfb8409284417d7840000581e02ec2e256dd7a246351e4b2aeeb0835725f8a68497b181b9542f1d7eaaad5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffff6377579e0219246003474a7793521241abe86002c251264040d1829c2dbbf0f3d3fabeebe519d0c310039c9aaa33d7a10b78c48ea7a91247af19de6b08be5a050478e67c6ed4b06ad52103398def5974c743e0f2687072842f3f0b88654a88fee9d446b459031efe6d9a95035cec8bc04e48d6815da71d69d016d9903d542ee96975155d8cc68c9eb1999ffc03b365e1710127f7adf4b5462712752a6bdb245c5c5c85e81134d95a91f26ca32d036965770c9ae2ba0f13e32cbceb3189c23ba602d6de52a16c8aa3094e8a900ad40306f94e88e5ffba7f14eb583eba0f4175fb26297cd9dbbb1f81b93025e5bce3470219ffff036615f2381952420ea069acb391891d322869efeb4345eb313e95ba3305462a2803c7904080e21f20c4e1811d1ac2633b93ddabadcc51bffbe9bd51a3eab9631f71033db665b6343c3a8119e60fbad4919e07517acd01692550e0ca4408add2e5f3b2032a449f44af25a854288ead943241aa04bfd541a84ef7489676264d5b56ad50e5038b0a79d4639c8b1b50473f8dc93a494f8162c6dd00b462e9b571feec8bdbc5bb0392217fb0c69100419f0a9396956835213549477fc4aa25c0c29636f44478373103716d726da1ff96196f3c5c77741227aacab17c425fd716d6e0842095511a8b4d0219ffff0219ffff0321e292f19666315f76eea003fd8f287c3017d8c57331ff058e1a986c92f2e674030ea925bb77556b665219380d1434e54d5edd73a61aab22a0295732686be83a7e032c17c00e8aa40e9be722a2c6f3ed7e553a409a65d64a7ea69c40351c449414e803fb9b087c1f986faa4a50ef12023ec1ef4cfd29b15fda5a9c8f144137c93fd2f80373afa4c3f1380708d1ff4b4c3d608fd51cfd374902a6ae62cdc324289a96763503acefd0ae1135f4b745757daf247e92663c60a98d240afc08fa387d1916ef5ace031eaf3f24064b3287f0782c7bacdf8887910576ca3e7bc61e0a032b4ba67b3eb803ac8bd69adcc2962b15d58ac1bc89de9da0a2d2027eb0426402f33b7869ed8e55033f5f1eb8f9885e38c2e1570f52c88a40069b6981249a0b91b9c94b66e7766648038022d7ae7170a4e7eea710b0f693003a7c75ececb0027bbb199db90fd2dccf36035d71eaf0318abccefaccc22cd8b96c64503ee1d2d1040993a566b98179148efd03843adfe5989196369d00878f39372282d75d6a778da5fbc2d8458aafa80af22403b8d08742cfa782f2700d970f5ab44c0e5955a368590f6b59d1cf3cbc2beb02ec03172704fd69329c260e4fb98f619acc217be06aace3b5b29b3e89192c2c10a64e03024aa9032772e17014747ad80b20b79466650e50fab474782c3bfeb7c888c9ca0316769a3d167d5822293a3a445cab476e34b272c0f64ccc4796cacc0b7d5d67150390b046d21a57d466265bb0999ece404581dbf350584a283cb26c1d950bb8afc603054a956fbd472a005936a8bd30d61f00b2d2427c60164a276d55c55b75d89723037a7c37c334e95ec6ccc08d99a0d1b16e8db6772becbf3097e2b0566c4459c77e03be84ca7cca885365b4bc0497ce9c02826c74a1d1a72d66a365afbf09e46aad1903dfa48864cb490c59067e6fef08f715bfb4b19b594ed881d098456609324528e90339fe04deceec75d31177cc0fb155cb2f24bcbf9fc3369f2606fb0a488e95dceb00581e024c7e0ef0e5392395ce7452d4e5eb01900919f7ea753570abc24763692d4649ad9291a8f803ce57e5edd483c9ec130333555cd60c9d3082ac31dc303830ada6b26902dc100b037abfed7573ad322f5d2171c142351733c704e3e57d3d2dd63cf21a8be9416a2300581e02260608623466482f88cd4120ffe69d5e8ae6875361c2028d7c5b6967b95820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd2393f00581e020d5ba211db4292cd0fafab9264dbdbd95376e3f2f2edb44a004868a096438b92780219d088032735124218a3ae77f5ecdd298cf5b489ed351035e3bfd973e7c5dea78e372c11036f9c564c5aabf460e5d73c3d745d266a8cc2a7174704fbc56c8d720107ac93e1036808eb62795c554eefd360a80b4b6453dd32fdbc7b396651e69b650c3106a57e0356b1507f4f284e0a38b2236ed687625684110b7ad2c11bb354da6c1e7d778f1b03e757864198898ba272c20cf57e4a4cbcc895e385ee7bb2ba021f0546abf131ff03f43459b2af725d03a741fc1a8284a74a2d40aecd366ef8215525b61651b0a84103495344efdc25cf9d851791218273f116fc0ab61656de7a5244797aa9ccf1f0e2033ca334630a87bba2b41bb9eb9c4f4d2a28566c82ce3c18d1e7b3bf9adf9c22600399362d71dbbed84b8201a649df869ff9846deb7e07f0550d90f40f64c1d636ca032b4b20cc2e462ab9e0d3bafb633f6818e7ba308bacf39772b80767c13b3561330306453a692740961918f0b57fbf6c780981b2867d8d15327a1a97fcfb8be49a37033c3da4ade83fde310d3646f4a8e53dc27423aa7b1d622597956531e092ec7ad20346dfce934080fde0c280820d3c993669c7205c34c115e35b55d21739ba97f88703b95b767f43fa266bb212c3d5410ea4eadabc27f92b18c4ef12c014f3320182b403495e7339e637de0761bbd1da063acfd7c6c50171686f7e47cbde2f2f4c28da830219ffff0329ac7a52e7d31e5aa14019271d40b16fbbc490a3a1f23a29a3c1e8249b55ed6303f768ca52fd5d698e633ef27d44ce6353b8393448d3eb715a4dcf34c2ca92b1a7036a76f9a322a9bdadd57da28e30aca7c49a34587f534889117757f25af6c6dd550385ab65b9e71fa3af5017c930921b74f90adefad7cafd0f7ed77584f696512fa00360277d7bc27d88ab2ad22dbef2894a5a19c0262bff12e2612bba356367321d6503ba8d57cbcc09c866f99e9b99ccd33060dbe8d6bd2d63649b9e11d837ccc5e974032a527598e16584640a8c3c5c4c400f506351aa74fa805a19c228f66238dd39db0343ba844175610b34d409f498376c5f13218cbb5a16a56490420b396cf37c3b0603485850ef9b6f15d866ed058ffd86d9b96c60721509f321cd59944130e2010a4503b655335aeea4e25c8401f4fbc28bffec389a3539ba75ca6eb343933eba9fbcb40219ffff036c5bab4428a7d8caa80d37780deccad43786eec73607d0b875f7e3e1e65959660219ffff03333f95f716d138a883f291352e329fc90df6eb67aa1dd01105bdf9afdca04bcf0341faa5518580bb597aff3d222d039d107bb79f89f22a5f5ba5d499e5002c187f030c5b71fc636b3826521c291f0a7c23d92d8fb9d2376c7a8143f95cb0318f9af20219ffff033245bb0e0055b8372342eddd5c068cc7e24d5e54f60a5ccceb261b35fa608e240335d5d07bff8ecfbed0155e5f43d8c8a341027563bbca7cc5ff58237a8ed3dc6b03d63751f68e29c6eb2168f25f2c4ff1999e0b5870e900f52339829f382ab5814a037a618e0078da5d35c3e1934400a518c8c3368e93be6879216f9ea5831b78c3050314890765438ce0f0c4aa705016320f9d502cf2b452097943d7816aa362d5c9f103d11aa432bf2f33575f82003bae79668b33bf4a9d7d3c553cec0e2ba6eff60d1703a9cf4fd46e3955fa96a567eb5ac95c57a7c585d0af842f6f903a0e12bd0237d4032bad1e3c05b2e943776a7bf5ba7f60b05b15c0696d9be04b84107696c5850cf503e84842709ee6ee1a676fbe5688671555121bb9091b5e01bab4524dc1b8a0db3d0334f8b83f73ab2a9255e951ce2f920041d6b968a0a96430fa317d79959257ac6c0326775cc2575a5d1068ebba6ec134300ddc2084b57cc7f22fb9152b51798fbb0b03c033239784910d7819b87b6b709d145217fe95d721e84f733579b22ba98ab8aa037fbfd12a44d70b0807b6625d72302463507fedd4e56454ec601116500032bd3c03a49290a991d235d373bb73441955d9dadbcce70ef112cf5298e371052620261803b74c2604c04d0028714f476e23fefc1a4e64bc3d6ef7aed6a91135352390885603c76e894328a1884bf9ce797398df8eca8e8c7420d1aa54451746c565e9fb06b003ca40cd7717c851aa3030aaaf6f95bc054de0e26ec90ca781ad6e8edf9c229f0b03d429d2e27808358b0581a5f9a4bec929abfa13a99edd1b6f75f1dff61597cd8c032865b9acb09ddf703dbbda4e3882a820d734ac3dc2bb5c097cfae977749601480324a738cc928b6fcd56460962f8419d15a437029eaf76fad44804ec47f9dd90b20356a93a5f503bd0f071ed0cd5f307cebe218ccc7bfe36c2cb9935b81c45c7a0f00352b690b249f500f92565ddf5a8b870e268a8107090556ff85e1e2004c135797c039987be903ad6d1be40502c359af41e7676dc4789086c6daa8b9e51af2bb3622303d6e98ac179a85020217b607755763f181ebb81679f6507c6a025a12d46766bd6033f3243fea4e2b32a2bbf54d01ed18c983af831b08906517fdfafec6614d7e0b80395cf9dd0a7df374e9ef9591135d41141bdd9de3fba21429892243a38551a37df03a39d477380e09d6ca96ae3547d9f7496991724188e49ee039607092a3335569403244a3d6668b821faa32c0aec83000a7fc9a0e2445f1a462f8b63c4ec15dd7474036aafd97ed0a6d78795f6050cc46b76c873cdbed44d10eb384c51c7640f64f44c0352a1515b9e0b461fa369b3ae2f003b69b8fb8cab87b5ee17de2d4bca12d0fb6d00581e023e9edfe0972ff2c6d0da382f0db3d5eb6a897c60a4c173ad1a225180705820ffffffffffffffffffffffffffffffffffffffffffffffffffffffff09b681b800581e02466c0dcab700c8ffdf1818c4db7f67fb16b68580b759516554cac7ab125443506849d7c04f9138d1a2050bbf3a0c054402dd00581e025d12c224c54a6f8f2aa61e0649e0be7d377ecf7aa8797bf2ed54bc1c055820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e02280b6ef72746bf93b1bec6ef41cf206dad2afc0fcbbdc7a63fe97c3aa85820fffffffffffffffffffffffffffffffffffffffffffffffffffffffff02c4e9402191046030230da90dbb296f36c6bca75969c68c0a93378d67cd3fc8d6ecbdb338f7fe3c403fb4abb28ba7238a79d8770c633ded45e33eb81d69c773ff8fa1404d0be7e55ce0391de9aa784be32dbeba3511a825e749d7f8fed9d842f12a301961e9e5afcef6c0365f9ad6d524d3b6cc405b6ba3b7fe6afffabd89baeac0d1c284a70b06ea8e2a80316ce5044ea13e749b4d8bbed1d085ecb28605b8975cff9f1e91bdf882ad75b8b03412aa50fd6e7cb1644342d982bc884f5194f25c2176d565d573ea2af9b7d88c003ec68034ed055db71e87bf811afc327561d4caf47b07ad704dde8acdcc110b9a601410f03f2529f7dd28c690e44ba05686250da407c66e1296fb04eab25780b73d5e0a80b03ca688d8f1b676d94bbf63f856ed8ea8b9254f51fcdd30594114d1d620192fa1203615d0dcf330f01699694aecdbac9952bdd5612f1d632485274f6aba9d3a1549003347018c85929ff716efc3b8aa184782b1b4c96617fa3fb04079e7474d27b0ca80305e1e5ef1edbaa438dff47ebe6ba1a77990bc9f4c5b334aeb59c12b23823942e0349a182891afcb4f22794605e7d18b0ec6732338a90d6de511c679e72cf149c270219ffff0382373f312f60583b2113fabb83fcbd1175d24564e11e8d6eaff59f883cbc8ef80376f13320caff2d2c3c011c3d8331bb8072e348abe0dd9fad416028c5a4a5337403a963ae3e2b5a469e82519ec5713379a26182193c0e511a1a962e7e912a806a9403588dc86207c03b956ca9f0fe1104e455bc4ccfcc6ced73233383bc3c0a646eaa03d631ae2d40bf1f4acda614cce5d51041cbe08e7824089aeeff20c66ef084f9cb03f333fd1d286b483bf3a2c0c5412645ff364b1600aa68b4f1ecbbe001407e768b03707bb0469811911b14c4e5a0e77f0b10cf65e63421493ea20db9106809b684ce0304168ec8b8c3b3524cb9394f3f3bb965d1a455098c4272401816026bec0d26cc0302f94055ffd733e2be3110515a661b0074dd5bec576ca1f730b0d7b22a14a17a03584e636ffc2eb4d37fbbb1027140e5e9b98531976427674d216ddf5225ead69803049e6efa3afd402dd8ca621653751414c15f8ed8805595197a009744d84e742f0219ffff03781991cea7c297bf1b91d82e22bb177291114785e59696b59732094553c8ec2c0219ffff035d286aa0b1a975830bbd8e2cfa19bbbae44b9a26f081b41ae4270f54ac2223bd03c1f1429240bab02949a5cec02c14ee7385ee4f935782b7c6cbca311821b859a0038a9843b70ce61dc095e8f96e4c618887c97a36183a7ffc761f1a11487b8eb394038b26dbe304d2a06d205790a7b87539b3bbb45f1dc1d737d31406635c1583481403e841f0e699a67b7bdbd57997cad375eea76ea16be40b08c257317ee04cf0236b0306b8cbb4bf0924961b6ac52638d0fcf44b122bc85d253ac633fd2b50f182d5190378167faef717bcf56da03540e11a71e6010798d2172d2516daa3d39e85834c17032ce5405078c8c54644ede972f114a91c09de8e70d7640815f79415ba830ccff003805167ef59891627d38f4a8f631a78d5f8f8dd372bb21a7653875ce9d53327f803ecb58f9e2766b773677e56234e7706570a85cde591f00d422e45cb93eb0ff158035957cbdb0937cff04db7c5e64fc94c5e8a39b08e92a99c8496a92e4ce58d3f9c030e13383ac19b6bb5e519fdd173fb82b799cb2619059073f98258c0d0b266046f03c50c3e24ca8c234cdfa8992e2771c53906543a910205fb4e886057c7c112bb3103d04511fbbf6e043134f01e5d8864e67f20fec3444c573b1552f10031c7ea6928030c50bc25a5b62ebc2a68a3c815e40c390b81de3c6e492c90b5ed2421e5df2c5203d9acd5a7c1f87250f1efc824f5a368c456571b3b7165c4d891af321c4c56c9eb0342b294cfd2a053ee9a54960aff00887b5a331c576bedfe079cbb748f4f7ebda003c93e31896e85c62273988d2cb56bfa81b0ea0337ea0607c2135f194c311b8a2e03cfd3d432e8a991857d1eb9a9fcacb1da80cdbe57037dc3b474a92b1179bab7d703ba3c8f71f6291b4946d0f3efd4c331b0552a9ea6f424b7a18330ec3a791e19e703e939a2318a2eb569883a64b273ca5d9e5d85ba03b6563105d0255fc3e9970cb003966525cd0b791c19e5976a3648e3e1c58c0a71d81d4fd31ca69f33320abef4dc037fab5911ff909e0c0d37ffa2684f6e561c18314198fc23d9ee9b57dd17a9393d03625f6b7951ac82d4bfd1c1090b848331fd3c5a474ee4f70fa78871aeb89161d000581e02c144e173cf8082c512806ccd405676c7cdad094f72e9da9ab7325f3f5f45e04cee622b00581e02a35dc01ebf694423b5d6836373c2b27aef22f1ad357b7d9c17fb8685d95820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd2393f00581e02ba4f4607a8b6f7353472b270fdfb08ffe4239edf77539dc83410e0d15b5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffebc94ca22400581e024e0ef26e8e902e27244fc3ca30ccb7212c7a0509089755772470973fd25820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffc474783f0219f000032dd8b08ce72982dd6394eb699107b5202d21874367a7cfd0514e70480ae164f70352531a3ef545c00dad792a65083077f6745f083d08f7b2e02b961b59ba89bf07032baa760c817b2323b937daea1373ed494db370bed9e3a18a6349424afdb734b1032ece76be24f866a40fcd04c8bcd25f735e9d1651ca31da2f145f2c4f0e4e281303668a1935fe5251046cded5dc0da0a32d5c8a9de0e28608ef83ea5c6c505ac5e60219ffff0304867d762a10e906c583f589fbb8873c8904c3f024db1ccf0281068179182ff203fc85669dbfdf8235b010cebff93cdc5caa01785cb49f3be34b6ffd4bc7e8986003eb92a6575fc2ef9532e40423911bb44968c116dbd5e5ad723cfac09fc7235cda03e8bccdadb18f1e07ad7f186a382bf8a5fbc9130249f70a3df1077b0d7023d686038cc0ea94022a69d56b17da369f10881cf13c493e4b047ba2440062fe881c94bf03edde4fb02785af3df1ba049536ccf7254d1a6373ca3162e3e9fa4398d77b5e5e036e2cddcb6846cd08c220ed095820b25978dbb67c70ce297fb1cb44839b30254e03553994a6d3c403994e864e0d444ed578bd2fa7893b9cd16a72aea9f73522d80103c97b4dc84f4ff9301997440e76825b36b198b994f92eddcaa6fa289954922c2b03f7cf5b9c8a691caa2f6ff14d8d2dd92f3aa8a8aea63c96523dbbfdb555cd7b320219ffff03bd3363fc8a5ca5c763b14220aa6fcfb848a8626f735d227be51f9e8f888491da03ddc58e2fa73dedf498a8a3559efc57027327282de068e7cb3f67561a317028c403fc87b4f06643dbe5407ed51da6a15b454409b4d8732ceb91e5f18efc9c175207039d8d1023c7fde532147a361a1978bcd803ae73dd67bddb3c45ec538f1d6c38ba03819d3db1be69857f5dcffae79e3da636729cb257c56f32ae62e4fdc96d9843d4037e4f59da007e23b751caabc463f05d2241a2c33a74b30f9b3bba334c741fe0f803301b429f36c9b66a04b81ad5ba83990a452fc2911568204c94b700f2b19ee87303bc81528ccc6693585e05d5e5436e6cc8c560ae1ff249479e267bc2a82b5368a30219ffff03afb23d059183314694973496c77bc1d2b250b03da8eeb49ae60f7be65a261f7603a7bd3ba278d59fb3f5801852ffad4fa6beff960f3c9f7951e2979009cec12a8c0374d303faa00a645b7844925338b61452142de531e6a3fa182d9f5b1abb49522b0219ffff034dd8089448b4d40b270442e3de4c51a2ed08bfa743e4dca537554f099a798c9b03a5b34b1323a206258c00952b1d05051af0daf577755851bf7a5c85a5c61da4f1038bf623a60beb336b68724da260a4a348f82cbf3c1b87d5270b5a03ad7c1fcd3c032d075419e2b3c2a8a0708140c5191c3eabdfb41dc66c5dab8f34e3595b9c2d93030f8a52d1b7ac88303e6a1b77839c4d6c5f0d843449434d375dcdefd11993b31c0369adfa105355de14e690b9f68e93257a54824fb958e8d22655f351e3b3d7ca8303764af3a837761717f7f8738fadd216c858287cf0bed05e49b7124de43137ad7b03abfc45d335acdbd07f69c645f54888265dc9c6bcb417c6b1c4c1b32feb450015031f430921cd4f12c732de2b881ff6095f7409ae24e27d56ead5c96c25e44d0bf6036be0262efe13df242959a9064e2df39c9533fd61163eb833f2b5db176eb990030300caad84e5ef6204d244ab83a616b9cec12d0eb591ac799d70482f7aadcd4c90035a87d7d9f81a6b16524a52c16a274384e353543626128187555aafcf55520e920375280e5baef939fcc2af6c0f20f5b6d406a1db938d35cbfdf062b8762a771a0a037a36ba81ed3bed7bfce1c8de2c047fa18b33fe63e9c3da9e77dc9aaabee93e7e039e0a4b980355871183e3d0f1bbc177bf2e23e5dcc5576d51f448f65af119783203eb1111d5e9d537a9279d4898cb69997c81b1fcd3c99941a5f706c790aeb5ed7003de3cd56c62d7a440bd982b253d51fee955ab48fab80797b63e3b6f1f09245efe03a4d3a08db70b789cf1ad66bcbcf7c31b9b64ae831ee027368fd0b15c2b1e99e30315565d1cb6eadc7fb2d7fb52ef087b952358a5aea0d78592f7d6bc40a6b9be8e037c389f3c68c99c0019d0a07e9500440abca50d4090af1140542ede054d91b346033fcdf0173b07815d3996855255cf1795006a5ffadd92c90a85e15c1143c7479e03a5dec9d1e78358ae12d1dccd20c9859240faee9b14f7152a31cf41bac08659a3039e8a0d5413057623af98700691b2adf90135b6a0128fe51c3ad3c8fda4e837b303928f3f22bf8b27acd4a3ab7e864df02d9f37f976049597ff313f7702f2e2e5bb0386a0f49556cbe546c42ae3caf4733227173388127a3d5cb5111d24dfd4be090d00581e02812c5a10a56d26db280b70c0afd5e480044d135397aa6d5210b29ee61f4c204fce5e30444bad38fa535b00581e0256314af12aa1a62859483b57f6a460b4a8147494e90b2b46baf775255a43016bc600581e0228215899711e292069b2a3126138bf052b23c82af7b3f0b31bc7631a855820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e0211a283bf1f3c72fddbb6685655680bfb71e21e3be1bf7b410292969e25422710038e64c4df426e92d53cae8145a5be45c1e3f0b26f9484b7aa0fcea250548f8c8d00581e02527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6544914f61d25e5c567143774b76edbf4d5109a856600581e026c5232a972abbeaca7064450c7b385ab511a2f17f27461fc4afa9cd4935820ffffffffffffffffffffffffffffffffffffffffffffffffffffffed6614a38f02196d9000581f0324b32423619184f0f0fbcc857a2cb681e8ffeca0dbf31c292434286f03805820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffd96ef04803ce3484fc11cfe0a1f3a62751064a49dd0f0a1e1ac0500c909936347cfb47425d03c1ebfa1ad79b92a162cd8eae219c53928db1cc371606dd4887bca2f57ceb135c03c6c1605c818a87d02ae7ebbde8ee415dd55ceb2e8b76ebe2b1a0481a4ec829c80336dd821befbd5b0b7d1a18b83c78999e69c0056608c961ec8e3e3c24d497e09e03928233f6111f64eaaab353745d782f3432a218946621071ed252a53c1252c38b0339f1a65e322ad4f6fdaa9e9ae26c741f055b99b5eb05366462f8ad58eec97489030cdaedf1f014a2e1d547c4aacbfbf248a0412d1e293eaeedab411101212dd74e031ce258e1a3d5c8e3451fab1a73f0e78d9baa004811c75111d05d4c0b37545121030b76f7b2ef016b75916f53e192ffc30891bc8b373e024dfd46baef467553bd4c03a67457f425c4fc360a80333f5c2cdbc2054885a53b3fc6115058c3351836094203b1b18769f7d535cbb8cc26edb157136a148ee6c34d72b0b8bf73ea20b51c3bcf0399241872c4f405b54ffa8c0f0a62eae76ba5057d6f0c819a5d8ffdfe277207db0219ffff03b9720665f0c55636c322687f50451186d87c3ad4149403a6f3dbdd37294ce61e0219ffff030359814d94fe3dd3685711353711b79a5ed6a13ab2ee46f3e2663592e1d45fae03edd95b09fec4941545b0869e21b52e098786042f849d2b6bb046cf65e43f24f103ba6c32665f18bab02021b03b1b269fc6b04ddb253c491a7c75784fe5e445b635037f91ee5c485707361686e0af1254c487c3db44d86becce40e1a2295504c1e9f50300284a7b8fafaa99eac25f43619267a40847434cc2e866e4935bb9279f2c423c032b39db872d99a84289f831b247696af6d9ecdfb757e66643438bc06eb0b58cc90323cf93efd575f487b97593cfde043b3950700ad5884a0250d13da0edf9fd534c03f5fedea2dfd01b2fbbe7f25b194fee02cb65b9aed5ec6c5567e58e6fbd31e2b60322116a4f7084f169161e8ec3a5d2de796335e576afd5b9416910a89a67102b4003d1a95dbcfe573904397b5f69488b1bc589839d319c5a6c55efdd3551126a050803c0fa5ade3c76c54271f167e5f441b485f1eb75940a2e52f648c2cb96f5052d9903db9ef4e1326d4246c472ccbd47515d13a169d957a9c068fec21f4b55e212009303e1970882eeceeaf8e2b8541bcb174205ed78c7b11041b9bbafb4ffa9ba899875032fb8dc063f1b265d76868071db31ab763ea566da8bfdd2b1a7fd4ae052ea7758037c40701fa0a68f3d926c3dcafdab297088fecea37100d78467c831170e7040900219ffff03c4fe2a4d5c993e4c9dadd857e8661db60872b725dd80141becc0f521ae33eab5039a1005af66d8db3e16f2a30040aa6d784e1f1499ad6874c2284810536cf9905903d378ebb7d7ffbfd47a58868608dfe594949c3a155ef04a580263a948ce930b0403a1466dc0cd09bcff7afd87c8455df683bbb69d08ac5bdb43105ffa5f5089502e033754236ab4f269e1eec5004d5158b66eb8d8bc74e28509c17f0dad9d514de8a0035578ead177a4ac0b28d35bf5e81502e14ee60056d73af310d0b9acab5a97c18b03d4bf054b11abee6a131ef65a071e65ded19f0c662438fbf9c1aaef570b843d5b036feb70206553ecd8ddfdb2f927bc3657c1d6750d6142699ee8b88c699c6af4bf03c78502d7e06a37895aaaa104b6c407fb36df95f7b3e598ab61cd5f90482a8aed03d6c71e8b8dc6f8e768002b960987ad7f7465ae6b39bc54c89a95a6ef52279f6f033d1cc383b7f838294ed79ac2cdc8248cf6979c09331f5c953e7a5497e95166b5034df4ec14864c789279a98c3a23e3ef021d4800314f69ec1289079080906bbee503c33f76b3e8a263c3d8f02f83cc3a5c122ccb6f58bba0f8d5719d6ea79077cdb303790eea44945c0a0e06df36fc3bf4fa5d576f76d7e7c1e07f5b3d1e9793a165820219ffff036f283fd3772e345ea810fd7588034c657843cbf2493a4882705bdd2159d5855d03691ff46ab9910e40fc5c4a9015ca3e6fb37eface4bdde33dde68ef75981a900c03b200da8b87f48d6ac016041e2298adaeaa9bf7ab0d0499188f481d60a524e98f0338de70e837a485f014e665a1d90950c82e46b25993cd9bcd1f8a32ffedabf29d0219ffff05581d0292cd7f3f78137497df02f6ccb9badda93d9782e0f230c807ba728be0070119088a02190a0205581e030864713ac2db5e9db0684a29a85b09f8e2455d445c5cf00aa58fcacae0040105581e0382cbe9e9e102d9cff9caf9f978abd0677de42e743578f991d9bdaeb2900c0b4705e5e4dc9af40002195ee703da7d51e611525072c152ef23a7e893fdd90b7ebe1046737cc06a98990dc7ae4403e6b005220f8de7edfd7f06e487749e81895b4eb9f0c56a1da05f1b50aab8e45603616cad53e3488e7876a76561c3accbdb1463c59490ee7d969eea01a64032d0dc034073a2da2953d52313e83f8d7a05a6d27ee5607a4fdc044b6ac35631bef4cb2c0304fc7644d8abe69d5c754e754dc4bbea20bab618d50b57e640a83684673b19790362a752d529651a3b04552c14d59ad4c95736479f82eb39e4ecde3e115d45f77403af9fbecd149a21467bffd1f8ead409d8ccead8dc6d55bf82af70813e74dfc22a03a259f47d33fd03c890d78274e276ea027e6e3051becde575d0d8f6c9e49e80930301899b4da112719a28ffc94405856d1b0caff59c59ebc63d51d096067b1e3a380378a6818c13eb82b0af880762ca82ec9d6e2f7ca13f6d269acd6e565cb49356940219ffff034a74e5a806b7451653b0befa3239b8b886efc5be2587972e99b18d6d249d6272038f133f5399443fb4f07ac8de92bbb37eb18385ad1a05bde32614025ea2cc66fd037a7c89b08d595c99f6077d62659a379f7e23c00066db48981c2ba6ba2363f89703aab0f11075bb9f5b7efc5d64264d22cb076235437efe4a72fa7d53b2dd806edd034bdb44579ce20a1d8529a5a4036a1bc2603c1df75c0eac1df592987a8838242a030c2ddc578d8251ede31ceb3b62657e60f89f62037618ee52ea707209830341a903317050d1976373b607272b71719ba8ac011157da354335f724fc84df6c0ec7c203c4df3c3c06eef83a067db420408477ab74852387bbddffeee67b9057cd7a97df032512aa10789e9ecac403054b1bfbe828e21192de35ca6b2ad86fd55e94bd7f5e0396c597a2a7fcaa88c011339d3950d4de0df1b0d6dafa8b708fa0b3c51d27241d0219ffff03880ba104d2ecb59ce7dfe6a1a379170dc289132b322beb4af1d2b09cba637fef03086a82299f43a1c4032a761c3a50e76d87da1de97b4584013f8bf265640d55dc03b04851d759bd5c80782bd19c8c1977d35f2664b5d7e5a3d44606fa5e65b41b0803f3054aa76f4916f43faca0ffbe08ba17e78c42fc3c22b1711d8d02397f5c165003bc6f5ad2fa95477cbdef51a2ad783820a7565ca99131b8159afff6ae1ca2e5cc038977f27c22bd0d4a018959e14a893efd25ec65f32f1244a4f7716198b4c99cfc036f7a13aed16e8435156ee3528dfff6b80fd05ccc369330da6ceec8619311d5a70219ffff03f90dd51f44d709141150a2da1e49951c34b3e442c256842da5f9b39563ce84c50359fdbeada5f0d028a51a3b9f51b86c49eb43cfb4f55006f8972936e9abaa119103a51bb05ff1ec353ac097d28242a104337d78273caf03398400ee4cd94c58b863031ecba1c5906136d682ffd2bad19a8c20dcb960108df4670781f8d877e61d1f5c032bd7118e2af4fcf7262b4910f9eee20698dffef1e0fc421853c014c325c789e703f37ee78e0e2ca80646734a98257d948fdfd02d63699a4b95f986ecbf42ff57a103894032d0927fa34528691d4102f6be31ab85e171f8b05b53acee703e5372a7c303c3cbde7dbf4a562b01bb3aef58c87b739295355f203e26518f6a7383b77397fd0358a108675607f3205f9f676eae1ab68fc18e77752ea8b67cf9d4d727354391e603fcf1f399d06cf3d2e409e3fc936321f81e416f6f5da2bc7f3ecd6504c1fd66bb0219ffff0368786cd632fec2d35eeb27cca819e38b22d33ea0a9032692a237365dd31dd07503ae40391f2b1a8f4d07178aad646eb6ed1957b099fc598bf967d59f6d064a244e03f8c0fbcd8d3335886a610b40e010cadabc880448a710790a5a70a5750e5c5b41032cf17c74d32a041d58277c8ac5f25d7da3da9ab1bef73a4f7c903ee0a27629350219ffff0378086402f8d6b44c3f5a3b4056d7ba5680c41628e195bc6313acf3d331b9cc6303833c4e7d76058db1ea66c4a5ef6fad4ebf56f93ed74f88d8a800bc00be3ca04d030c9eaae5bd57d2cd14952786a16962b0e45fa0014c11af532c5a121677db628f03f5df73036a717655223313324bad57920a41fb72b2756408e8d4f2c37b649c620313119e73936f8ac3e8fb8b43766a615b9fc7de768d4aaccfa7eb1ed8fbc28f48034c4118458d2ffc44dd371b439321e49c58172cd579841260029e3a33c0a976f903fad5446a54651706d8d1f3d3ce63c52c68fab513e27b61f6f10fc5e8cd5f21e1037a16c64c4e5375897cbf68a79080950f008cd364985e001320bce7b237f5143e034290b94773898fecf8e221c7e3f1a9b7a9c4574acf0430721cc9b5b610e6a7c4030dc98d971398b98ab75fd66a07a8bb607865e06176b1770a0c9637d5b242410a03adb37c6f26403a5053e42b83a8a3b5c8c5c4928b924a5f5f92acc5e6366810c003f48a64110664ff6c292d4565c7f7b66e2167da63ba87b733706d6c0a4df94803035a5a99b0b757bbe5f08cd2c88e02e0887efd67c697f5ab0877549ff7a4ad713503b4d842012ba753f21a32e0dd426af2de97d8237340d672da8ce536a2333ab10303977048204d5146d171cce54f27349cc7dccf4cad69df2d37c591aac2620b15d5034a79886110e15f9aaa2e5889cdb0ed9231663bb72b6695e47968de85b8586593035fc5f29ea3c6d3a7d3765c3a9c3ef3123cf71c0c79e5aafb8bebe6f4c35a0e9103e5aacae1241a19bc118334a7bce9debe74ebcd530e7a4a45554e2b616e7e7ba603fd6b793d4e92cce4e20951b67e706c35c5a3688514e9fb253a731b8576e4254703abd3c3ef2ba060ddc5a47ed1a8fe44633ecfc9e3c4af8b192e26e8682757a9a4030881234bca7ec4e477c0af78683ea6dbb9c65f0d0ff365a005ff9b95b8b9dbe603cf03476199c6aaf69e5f292a749934a89ddc2477658b45cdb55ce5cdc7de10b30385876833fcdde8bfba4c1b5874bf6d9e693fe5eb5a90c5c10142f53721f5f2fc032006ef00535a98e2bcdd761798f0158c6755768c13a41af53e85457d6f8d6d3103755a6070f4a7ac8b8331dee23d75f9da0346606e691436f43be1e2ff0349a0d1036e79df47a630d6a9c878168bca35a50b7bc5064ee27825ba75da630f87eb4163039891eed8e83b6981f157786535e74aca42a89fba169091ce7f72337ff91b176b03aa7c261b60bf7181d497153d2b1cc6cc48043cc5c857ffc449185ed01368481d03d972c583efd5e10666c4f4e31c7e09324884f27815a5c050cfb015c49237701703a31320db79c937bf962bfee62611c2379f29eccb203af22dc6715229c0976e4c030d420eb06000526c12093ace41cb334d7f04939eb63008b5bafad8cd73088b2903117e1d2713a901c9a9d5f1500f034a8167f6259f07577b41e94f86bada0d4ad003644adce2bd67502e088d365e5482a7f01d896321ad2794765871c0c9e19ff52803d2d958748ba7c1dfeb6a22e1e2aea8d9646611e6c3cb938ad9c1ecc5b192cdca037af3d5ccd4ce3958e45f51645277c25b74520406b3cd78e76a82d8ade1363d98035b65aebd14f20f7a7b5b5828b6b32711f0afb503ae76e6b3711170c574cbdd910396d21ebef9038831943b5eb66428189c452a3012c18e07f587e58cc911aa8d26038686730ea5552a75130d2fe491ce834085ff3279a72fce63e812a91caae77d5e03e9277c8ac913f917a8fde9660dd57f792ab0fd112d01b46453ccc2df88e764ae03c3c0fcbe978a40fd7d8fe5236abf13ddad4950e1bad5608079cd866abae018b8031d942c5f793bd295e61894015b65c2a0b772da5420e868784603db7d45fc38ab0365ca75331b2170c759b99680f25803d5fabba9cc4c897a1d09058f841a58083803e3981f17dbc1a00258432c0e34232f267d05e271590ee7a9783a51870f9873c8033a7c4e1b76aa60dc9d631272c2ce6a72e82914e0cd8ffc759889d5f24cdedeeb03ca06c2b4c97d9941e56c3c752abe4c2b0b2cd162e22a5d25f61774dc453deedf05581e03da7d3b581a6515e83a053ac10dee614e37c74397b0001f77359de74100040305581e03e08b15e58c41c3c9db35bc5268e123e5e61f633bd60f8dd66ce320d4d0040105581e03961ea71a2092ede541a5313d58a72b669c614b45d7c9e73ab7f40b85d0040105581e036be6c90bff6b129f08f2d3732f40a37dbc9a95e1c60843ed138e4969d00c1836470135ec955aed3303235db60b9fecfc721d53cb6624da22433e765569a8312e86a6f0b47faf4a2a2305581d024eee85039fc9d8260336899b904d35911f52859f57c1e664281186f20c0147a7a5422d062ab804590c346060604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100b9578063095ea7b31461014757806318160ddd146101a157806323b872dd146101ca5780632e1a7d4d14610243578063313ce5671461026657806370a082311461029557806395d89b41146102e2578063a9059cbb14610370578063d0e30db0146103ca578063dd62ed3e146103d4575b6100b7610440565b005b34156100c457600080fd5b6100cc6104dd565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561010c5780820151818401526020810190506100f1565b50505050905090810190601f1680156101395780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561015257600080fd5b610187600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061057b565b604051808215151515815260200191505060405180910390f35b34156101ac57600080fd5b6101b461066d565b6040518082815260200191505060405180910390f35b34156101d557600080fd5b610229600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061068c565b604051808215151515815260200191505060405180910390f35b341561024e57600080fd5b61026460048080359060200190919050506109d9565b005b341561027157600080fd5b610279610b05565b604051808260ff1660ff16815260200191505060405180910390f35b34156102a057600080fd5b6102cc600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610b18565b6040518082815260200191505060405180910390f35b34156102ed57600080fd5b6102f5610b30565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561033557808201518184015260208101905061031a565b50505050905090810190601f1680156103625780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561037b57600080fd5b6103b0600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610bce565b604051808215151515815260200191505060405180910390f35b6103d2610440565b005b34156103df57600080fd5b61042a600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610be3565b6040518082815260200191505060405180910390f35b34600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055503373ffffffffffffffffffffffffffffffffffffffff167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c346040518082815260200191505060405180910390a2565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156105735780601f1061054857610100808354040283529160200191610573565b820191906000526020600020905b81548152906001019060200180831161055657829003601f168201915b505050505081565b600081600460003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b60003073ffffffffffffffffffffffffffffffffffffffff1631905090565b600081600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101515156106dc57600080fd5b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16141580156107b457507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205414155b156108cf5781600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561084457600080fd5b81600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055505b81600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190509392505050565b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151515610a2757600080fd5b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f193505050501515610ab457600080fd5b3373ffffffffffffffffffffffffffffffffffffffff167f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65826040518082815260200191505060405180910390a250565b600260009054906101000a900460ff1681565b60036020528060005260406000206000915090505481565b60018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610bc65780601f10610b9b57610100808354040283529160200191610bc6565b820191906000526020600020905b815481529060010190602001808311610ba957829003601f168201915b505050505081565b6000610bdb33848461068c565b905092915050565b60046020528160005260406000206020528060005260406000206000915091505054815600a165627a7a72305820deb4c2ccab3c2fdca32ab3f46728389c2fe2c165d5fafa07661e4e004f6c344a002903258c851feb945236d9aa49190ce6a1f954a22ce7fb40e9853c155966b29bc40f03680ba046c0992e4f9f3f73f390ee95b35db0a589eb8ebc8925e450376be8361103bd26ecd72a5379df4385174dda3343359ccceaaefa7e53d6367a04d404306b7b03f65d4bee287a821d23dbe32853da771b450507ce026da5cca1d6cb04258de31603804e1841e3cf2f01ec5a65fb346fb17a9b8087b450657d72146166966028457903db2fe285085d8d887defd67a82c95a7b04e16732675ee52c793c5ddea46534cf0357a2a70a8d7681d7a1b0fbc49e2878f01379de812f8770d62db9c210f139d786033684de958ac1c8c5a8cf71a3356d73e490025908e3488b63ae4498c0aa03f4fb03d77826179b282fccd0ef638e4fdeaf4bf367250f06aa9eba068428df00a4d6f00362470b311e783d332595cd3c4bbe4dc825db0529361159707927acfe5671adc4035829d57b692c44350649ece5e2520ac5ea184b0814ceffab6e4a42dcde102778031164c93dac507600f62223b0d125d0c8e7789bd616058a3fe44dbf04445a5a44035e6c6a3de260d3b3abaf7ab1b8092cd0749b16026ef31be5a0098f356b05e5ff034e4597fb87f732899878120cab91c46037e71e437f574e53918a06d36fda5ced032272e0d26c583d18a7a08d001b99443bf6ec77b3ab5b2378d41c387b63b3d47d0312590fa557d104f681e79733fa2e25f62e37f5a5d0f9fe842a43ffafdf7344fc03b671747f51b1de1600c15dddd85ff23f061c7f9d904e289988b8163541f2116903c93a9c3b189f78e05a52f5ec546c4aae2a47a642c3803197fa1ff7bbf4563a2c03ab86a9a0c49994c5a2cda65842ed81d283c585939edcf27124f23d987169c33803348f9f773e6075dcf53a623b8fd2a0c8b3b66194a8c6e179e7647ecf5af1a6bc03d58f17f0f2c9fed1484f087c800f05a86b8a43ddee0e2e3178a71d397908244203a0c748fefc2168df5b99fadafff88b9e3ff5e460f84058ebe2a8414e65faf91503ab26e18fab352e2b3120cb64a59eecd238e188488136e83c9321c0665866312d03b3fbbcf89da76c5abe29a8fb2920625a6f0135126108058f92383cd46c08193503edb0ef2e69d07120ab83fa70ff2a41edb2b5935e73ad6a78f6eb2fd31e4fef8703036ac77c468e0109e271b9d6c4aa3384e87001503d275f91ee2c078888bdb62b03c5133909a2092ee8f69fb9994c2b3ceec2f366c4dae5e7a067faa49ca9c8ce4603d63f33303029b9ac31d64d217f0b35c9b5bbcc2c1e713de21a3427e98843572803de43649d4dc30ed43b7318315c5f9c07fa295a4d7daca314d42b877c63cf049403dcf9d9ca099c7a2bc926504b977ad5139ad7ddaea3443e4726268c62606fcfb2033b6bcb14ecdbf83c18434ecd297315aa56f2794d03145b7e4d60ff10912e92f003715a79612fcb1f5409855d6760ca3ea2df22c6b99aa5d2a1b25eaaac158286c103b079bd51c810a078b3f356610ebb247a705f2fac062d76328b823f041d30829903c54a43dd80209311302a06fe9c2f10b8db85c5a4c36f676754d6deb425eae36900581f03de4edf38227e7aa6cfcc2d47a4ad10bf7837ec493a892b7ce8ed707d6e505820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0319431d102f4f154c0d6c26669c52157b9ecd33a103b1498bb1e4ac6db5245bb203d69f17f18f715d8b51b112e9b3cd82858e083c21828b7c6e460800a5e33aad09037da5b924acfa93133a58fc24fd7c404a0a3fb1f3fbd5ebd3afe43931660ae0df03a981b4be5e82eef2f5d3a540926d88c64fca7cb8462f7620ed089a18d4fa00860365f23c275d19d10cbb5ba7a8266a27aad06cf79bcc0f8bec863dcdb450483c4100581f036f7427aada1d17cc75f85a8c8d3f1e8c7afc2699d98cc32c03d14eb1b5c05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03226d148e6cf40fbb0e0a1f4201388589c81bd6789b7a95550d13954b1585c0b903335c69df45796d3fd14657a2fcf87e7d0605b44fa473fd07daed648a93ec29b203861553b8a3d2696cd6dec12c9d26805af7cfe33675721bcc9bab52c44a59767b03108244dd50955d0d42ffc3d4241ff2cd5506efc59378e7182a3379199081cdcd0377f1cc94390d788f778e9abc25d122f06f86a5bde750d394eb95158c27a8c88f0305f8f75e7f80cf17073894162f08e1afc79d65b452c47796d85efbac61c13db300581e025ba1f0881c3765cdab4b84d7d20b340828dac5bfeef3f4cba15ef7bbe05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e0301e36f58d79e973c58d6333e4c15a9d54357b52a2c560ab74013b297505820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e032d2f7317c77f9337fb9faadfbaf38b2220fb967651187c715b5c8c47e05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e036cef84d732bef714ea0adb4b602e9dea2eba7850b0554a7a7907bbb50048928b0b4491eb4ddd0219800900581e02a95e615330244b354748bcfcbfad8633457f536ecffec7decbdae8d63f4672c73a2bb00000581e0249c669b03a6e82f72916bf5cdedd57d7c7cfa7601eef8a24631c2ef44b48014401eab384000000581e023e9214fb7fdb497b62fe7c176af51c511e58331badaaa87df64518e2f85820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e0292a97fcd4de9c2ea1f6ad5e5766d330a31c6df01698dd8e3ef20e7fe255820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e02c12c4a7e136bde0738cd13453ec43e2a7c7477c7d619a9f22899fa3d655820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0219c0cb00581f03f218ba386554a53d8f5f98dee787c5e6678bb150027a61cf595e2fdb92505820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03c617af8fb9b62bdb1eff1f090b2246af4c25583e53ae23f26e09e3ad0ee0c6b80219ffff03e2c4c0830a1d9273158498a8fe9a401f77170e3a86eb95c70960f25b1ac57b4e030a500779d4c16d0214671dfe9115cc25279220cddcfca9d7c819fdf4971aaad70219ffff03ea15e62c0e436d052e35a511359053b4d5916192589f2c926a64792888b5e295030aef44e42d967dd913c5b99c4a93912345a945854630bb365457c2e4cf39b39c03ffbee45d95933d183569ed58de393e56969716dd94568bca6e736e87f07f822c035bb0fa263e336ffb84a4fea8117d69a0410d867eb8cb06d8104df0978bf5985b03dc31d8137d9abdbc5040409c3012efa98e90513fc126e7f3e53188e7d2cecac7036296d62cb8143b66a1d1a95e840668e43cdb6c15a72274f6a6f01cfc1fb20bf703816882fd0933ecb6a1974ddf92d634aa910d653560228401fdefc1030388c97a034984d10fee85e494bc53a17b032ee6a8f8d41c9a5a8dad27873c42511d05bf420219ffff0348934b15b99eaf13c5250f752336d6fb8993345f6d44eb7a86e352bcbb201d470219ffff0389c04783905adc49e9114b07e4ed979d47e827648bdacd6d7d19f31059585745031854825d08854929afbd789ac57974d351b19c09c4cae516b44e341df350f48903d1655595038d84f7288f07827e20e2d6fa33e8bd55c27508460ef32f306dae8303361ac274a8b9609257b578e4dc3fa163fd7ffe033cc156d07e3a90838697db1703120ce29f2b558198729eae82fef06629ff5466cc586d63e150949044acdeb197037817932b0f9622c3b9b61b1243d9d72eb5c2e2bbc69c958547b3f77488b0f76003a2fe44394ccac2d4d526d4d555b2e196205f2a191725d10b720335ad503abdf9033fb4a62f3f591e80e595b3a373802badc09417194bcfecc83cf82f9543180d2b03cac3ac2ad715f2b4303a4b0f491b5770f0345ae67d7517619bc2f0f97bdad07d0345ac6c46d08b5348cd1bdd2f291ca2aa7eb913a03c428447c03c520c95310aef030358db319d6e8c2cf850ec2195886e678bcbfcb596f4f992e28173ffc3c8a4f0037d4735cd7262b21f800ed5516be6a25e6f50bd17ec848a2d8d45b6574e8e463f03395d59c6d7233acba9810cebf9887f0daf006301ff079337d849252c45f6dbc00320988122a400f61477df1d24abc11447f6481febf1065d00785281b84c20775b030ef45c09c32347fdd59abe6097cc1ee939322af9f6cbdecc36c63698fd82dc9303800c28e21f2053e0f61b4c9a2968474c3023f64dd8ac008cb9e4e241ad50684b0385987ab8251440898c6b6c9c3691950ca3bc0244fe6f75f3405f5f8a408f6a8f0376965ac98184ae0735f799df4d920e0372165f9d3c98f0ab585977d653c5ac95034a1cebcc94862674c15eba3a7f0a8fc555449b4775a6eead99c417e6fa66cc1103b5f326758f1f17ce11e285a5b203fdfe1f6ffca2ddcfac755acf020bf292b745030d59487a27391a56617c4a7da48822bd16b57f5f882c393ef9790114d1c58f1203023dcd9ee49c1563a0257425e72e4008c7d9c3c522c577ef6754fed5a258323e03aabff8afae7bbb8518ad90fa27d09949e4c7fecc3270fa4c747054300c5e316803fa42aad7b15d6d11eadbc7eb15dbd4d2fb35b658e531f026ae02fd54314e36f603e3c9a332b20c96a2ca2a7547b8ceda085e2f64cd7a9d41813b32f45da5de4d3203138ed42f68459d89e7566321cbc5c933f1daf22a0a230f4c1536a1267fb178c3038d30a4a7362161311aa15ed932cb85478373becbc3f273336f5fbcc9a5610b1903bdfe26133d0bebbea5b741e93ce6495d085b1c70ca6fa5290fe8549ad77244c80381565e82d24d473615b8dd19e84b484775cb628ac39f5a9389d40bd2945ae54e035686b9c790f94f0299f5947476ce5e86b6ddaa63f91441be306872b37ad13cc3034cce4282c5bb922ee7cd826a002e40f59760364837a4c2927b185594e45cc1ac0332d361fc51da352f0ef8ad8752c565fade5a5a8784614c6e6ab6a4fbc131c5a50302325ed5207c523e28ee0bb61e3571b33052830a016ddfabcd8f3f8854d3ca9103d63a2dfdd7d6843bc07f8ff3583cd193991099bd078f33f68a07bf77025d5f2903ea4fb828cc7df5b2859ad7615e64a2b51933c4ffe611f2eb2a991abc9a4b930c03f603344e828ff1989b017051377b024fca8e468083c9a84b970e3546f417581c03912f3af8cd84d62076743b189c7deb0a13bd3c8e8d24be86b09e6968afadf49e03d8f84b83332e48ad175452982b6fd450565a6a629347e3cf4fc4cfa93d597ef803647965599a11864f282cf146aa646672944e93698bd380ba0fb3dbe18bcff8d000581e02aa751d2942ae858b817ccfeb59566840eb4726669f491ae7d8ebadaffe46221b262dd80000581e028f332911fdacbda46d6832b1707d125514b343f6e798896024087b0c205820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e02ad4c21a767eda5b20a0c8b959a7bd51c58cab64d3ca11acbf1dd0624ef5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e02cc94798e7bdf2790cec79e819e0090fafe127cffe4f52e3f76111f1299486ce4eb2514608b4f00581e02e7f17351be8102794fe3ea23a1829528715469b3859634dc1ca4b6743d5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff021995420368c120bc43b6a65acc4214be91e6ad698846b7b5eeafac03956ae02a73699cf503fcfe0990b0a11905d05faaf869af920a86f0ebd125cf40753be168b12905472b03cd18a46f9506f76f8bd4d0e5b8d9883df5586ada1bac1349d33aaddd7c5c846203d11f94e1bc5f8dac25f5c881d1c9b21e60fb761d21228af01b8f453ae6520b100363728cc1ab9920c1227be2f898909e990e7658a468bbd30486c44fc9408cf1cc0376362b4562b4f7fa290a7681f52d472fb4dc6cf760aac5ade7c94692a39a4f8a03bae31e91f417c97fe6fbc6da47743fb9614c062de98deaea1cb81dbae15894b503013c5078b47fb0de8cae649af9d298c02e9dc25c0390231e16cbf81a9c33f1ee03fea100c6fb2ef8691d84415e9afa81004e2f02265696924a2b5bb050444e78190219ffff034e45f0069f1f5e3d7589775cff216fda1f366ce644a3741af1ca967e02ddfb260219ffff0368bd7498f3093d2fb1486ff138506389cd9d3c393d336e369d77bce0e4d9125f0319d188dac77d882fe3700f098c56e60abc002588561941597bd485fb76dfa26e0377b7d516f8ca8887f3f26114f830602a44a122bbe78d2ba47a44a2a09397632903a534a81794408b65fd2cf25e2f7cfe19bbf15f5f17e7046a530502889cadef780219ffff037925663c1a344cc59f4fb8ff151f41a47977deb1332f4533ff9c2efd8e9d31cb036fff43401f307d9d9deccb98013002ad6d56261693a1114583250c7780fa443003d6dd01dae403071419cd4f7d72a3b0e00236456667ef4908b61b2098163d70b403a03f65ebf1bd035789686aaa2ece3a0c33f086eb1f6d3dac9ce1f12acccbc6bc03bd7d5ac863333741062cdd8da18706b8b1e3163b34e709e3c0225a5be58c8a15039c749db34a32b4650418bf9437da11d952187b3a0a3390ad74705baf71f92ae003261f6fe9f2e1f727f8f135bdd220ae6cbaaf3ece2ce15e62b5aa0cae5b828f390389fbcd19ea1754dcc42ed573dfcc2067d90d4e54aebaed3881595715aa47371703c99f3bea8df0d196bf1e6440c77c93dbb0252fa9c4979b186e52b1033aa35b80031ab3889ddb8708578838ce297c17b99b137a9bd515fda90c596cf1f323b26d4003022523ce81c20714ce1187f0d5c18beb8511d27908c71a03770444f6f559ef490325350931e7b60926b17ed605c80592f22bc73f8d88f43f19d4408b7b6f1372780219ffff03cc6275d5a1cf24976ef51f64a9daa386f2a5dd4e19e66604c2360ed0ca885a5503733b14bd6ee1e9fc788c87d764165db027574fc104b9d5372926ac9942d7ce36030b4325b60b37d5a1b1363c8c53ef5f453df82ff19cb5d8a6772212731c15461a03dd30c1cb2b8f681c60ad21cdc1edcf21b6f194bfc9728a065674194516af41630367a34bd572e09e68c20b518d4071dd7e191a798e3c81931e34fb68914656950603b37cd690b925a31e3eb14c6d252a78a19b9bd7bab3e6779cb6967f8262c5536c03d3ffa2e001edf1ca4b232e8896b3cd8140c80ddd99ecabd74fc7c4babd696b5f037f2ed8f79fde90813314f9a05b986d4f39f9ffd34fb6dac2465c9be57eb1cf2603948791b65deb8242a36ba346eeb9acde968f4e43f2e07c14e5494e1b8b9b3619032a60935cfba406bab17a67a103cf458700f83819ab4da64267f0fd5a0ae1b6f9031f6181976a1cf0c100f6c3b648eae53380605d3037189ee9cce454e9527d38ff03eafdc2bd120397d5b280eae2241ea9527f11ffc835dec9d0fd019e0b483268f803888682cb1d206ae4e12aa0e3c1079a0a061e14f998625f97131a019a8c7bf83603f016f91d202f89e2e7b19eb33762cc54dc521a89953931d45e15fc3668beb1af03d35d8327746a6066ec17b28cc71b212a699292ecb4deeb878d4a43a2333072f403829db624900659d648f96073eca5238d4dbcbdb08a3772d0c33c19a72d50e35303bbd42f79969cc66fbb1e980acb4721eeca4bd56a226f972fd3ac0e341ac8e7b8037e4c1e1ba2dc4b60cfea527642132c0543a7e3a70070d42138ffda15994bf070037cb332298c3d2cd403a65abe9a608ba9cb827046ae9b301ff428e360ac3538b603043355d4eb15da2f523f1229d6aa239e80b2931108404007149e9583df097972038939b82a7a6535d6c3d02b359bf59a8613d935020f4845133279cbbe84ac7f6003891dc5ce015979762cfa844c7803cf05ad253635c5db49c69b8b81d4af312f76035a1a3e40b5ffc335c914ddfb05a05b0ea7efbddb7f2122ef07d5bc70b444e0f0034df06ca2011ea5159c5ccf6acba5abf438713702cfd22b8cfb66faf89f550a1303c60f2c3acf9769d1ada019c32f79c5ed807d3b4bc43ccf9f19699341ed1aa4d303e93262f8ad5035eeef9ab1f4205ee5459ca8c6f03132f5fd46c1c8fbee5e821f03b04193f82ed3e46d5ba6d719b9e4cd25332b65ab0609c16d47a655614e5300de034f2f0fb8745c183bb61ee1b6da1b261eee6943e4c1f768ded2a6002eb6b52ee803c311543859b884bd36f93f699a9f00cc845cb241b5b6320faffaca645695d61103b5b8ab144dccbaadcf81df22c4367878970ebc1f47f0bd46484f07066aabb10300581f030b152b91eef7c7f6f99d5a1a7856c862e088efd599fed03e711ebbf8ea80475ea8138e89a6200343d9aea046f63d09cf757ce7f76e4bfe96ecdef50c64eaf989889f66f5dcf6d9030665227d354e4b58aa78b203133a2303bfa06e40b991609f6d36173e1b1b155a038d98dea62e6f9d696a67d282780c11b81880946fefaebca861ce02529d1cbc2b034e8ed3acbbbe78a3b8e27a46587e9628224e3f6582ac8648aa9f014d39231ef603d0d6177aa8698ad24b1913616dc18c13f67120eca250cff6510815a8340c4f2803daff8476daf67f10cfd41e0b4d4db51dbfde14c065f277722f530378c2f324b900581d022a1e34d72db498e4638a83f823581777f4fd4eeb81b3bb9517d3d0f4481a0ce48769dbdff200581d02a39e02329de2638d5a2c976adbfcbfb31fd3b1ceb368f2aad217b3f35820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0219048001410100581e024456a117a782861622dc04f36a4f6b00440d2a1674cade0d00f67182f05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e02059de768331bd21006eb35d7aa8ce3afb2ac9351638629013b590279b941010219808103e1eff0e20b45cc8fb2e31282bb5bc1a54c3f36b31ffb76862eacda1e85454869034362073ab3b2724c5ff643f5455a0fbec1d0241a229c0a3bb71e12a793cd075b0219ffff03dfa3e440ed356a72c2cea68f7eaacc52ad84ce2c495abeef90dfc44280ebf401031d403e9ebba858efce254d2d1ec98afcc77296f4c484dbc4246d13c9750effaf03db8f0aa7d52b0376ca134456da9c6eb9e0cd386120722ec549d898b0fd56af4b03635dca055624cc68f5933df3544b746584e761f880c0a2f12d8923f58216830d03bc009fe7dcf06946990d2b16fe3dcb6f194605c1d8208de677ffea55369d4638034c58699a16f374be091ea15b6c7354c47a1b17db08e90a4de9c58af5ddceda3e035e17e8a86d4c4963c8828dde1eb7d272e4cb9ae476b4c4f3ca8de473cccd856e03c6de533fa8e803d1591b625dcd24e28089c9932bcc8e0a07b86104d0d9017ccc03f04b8ea4ce62e9765e39bf6075c4ccc365e83eb83c123145c50c297ea0e6aea503770c96dba5fe73e8d9a09da9f11216229e6eb1f1325718e64af8fe1dcc3490880219ffff037900e173711d64a50daedc593adb20834305ccb174bad6a1b30f5f6f9ebbc2350330d8ded446f35e8160bd87c680597f613428871c364a9c4c8f79ec3ebcf94689034cc075a50b8634b21ee37995e8fba8475caa1c4bb622b5f99837cff1b9b25acc03b3b4cd22dd6b240a7f0b3424c70555224b70b0d80f953c8f4b8251e697626bfb0383983ebc17e853a23b7302c8ae07592e915496e509e39ab801e8e9fdf1fe3421034cb2e323a259b2540399a985e0c1fb1afbde275008c492d2e33b6d7e6a931661036ab21ab6e0101e876f74d1537d7dc8f92974c6e1c6a4df7dc1f17790413c416b03b39189ac497a44c7337766f647c713b9403af807804b88e7b12fa5371b154c1803d449bd68ee60693b15f6aa9fbb7733c92b169b8c30f36d54dc676543313c13d90219ffff0354476fa2eb40443365ba6aaebd43a23a76e1e61707a55553e3a82bd5fc97b8470342c0d24898404150da8c4eedf15e53b36304f741e6b3e28908ad460ece3d7e0e033dfcce0671f32ae060937fa37287522e901950d5b290697634baaa8daa446d0803aa199fd7857b1c92eccf8c8f55602517ace85bf49185dd130c2e33cad6e1bfe4037f98df3464b3c137c8ce6ea27667732c0050fbdfe77f38e83bf73fd79edcdb17030213ceb97ded2f6aa5e3acb882fd07df89766e0f6e1bc737f68b9f5f0545faed03ee265daeeefe96f4cc40c17e1bc287d0285d505c6215e88b78fcce92ae6d1b5b0307864a3a39f254442fdb8497805fb3feff8ea8fb498a9057825908f39acf919803bf1a2dc61745941aa7fb9f585d96a4280fa62a5fcf907d75eab09ba2924c3b87030410d4c9be9ade62dde511e95525f60a19d28faaddefc03bc36b8686ad34abfe03b257ed99dbbe821871e505d69803e932c17c1aae58e8ba7d99eba206f9e0edaa03c7daa178a4ee51831d298d6da3bee598ad9947afa192005e0c9d6e982a95e8c5030cb25a034e9f760b6a7f679cfba3f9b8d15d26e0eb5a24b7df2dbd2f611f1b44038a0775258a991250298e085805aee827fc61309f11d3ac34d2bbe9119a56aadb031a7810bfa0fb258d6a2174f10337635d10d48361e1ea9f6e468570669b4fc26a03240dc452bc4706b486ebca8996e836e69c1097182837a84ca0c2efa23abf191800581e02c7147a0789022c899476a7a616ade1e383a45e825273c59ffb1928fa895820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e0270bb6130920b7e3de64233e96ac2464cf45ed42ab19fdfcab7c1d0289c4810879dcb019bd68e00581e02a08e16a4f7ac34f62963570b7d51d49022c4f8338b9f0212688c57e14448036c9fff493fde700219a080037d162c546197069408538bda04ffad292b3762d155602039db5d77baad6d65e2031c098c61b2e837499325f1e316bdd1a94c007db2689295cd9aeddc6f2d0a7bfe00581f0330d38e63fc14f140caad1f74146c8c8faa25444c1f110c879c27b50513a05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff037e7e48d080e5aec741eefcd4bf881aff6f6f425c4d42804e57957f3b24a1a8fc0319b3bb329b7ef8afe95e5dcdca66e8d6bc540bd40f658556f0edf4ff366a20d003fe95a34d049d09da2b72fc7ccef5ace5de1c38ef168d9b0db5cc6e59e2cfa1a900581f038bea4c07bf7078595129aa732ba195f03bb29282dc0a09794a354ca027304648cd1036cd720343956a25a5fabbb70c98609b15389311acfc68ca7b07064c6a076778fecd8735031cabf3d9b30a39efe0e72f794998b3cca6cb65f660f6df3eb8e6170e9ab0c867031af490e71a45f88b0aa47604a406ef139496c35c97ff6a8a4b72fce61f5ee8fd00581f0353ea2eca8dea0356b65e5c390ed2fca04a84587a33f7622cbf8694ca18e05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff030391188753556e4c38006d8d38560940879fdd51e737dc0325dcf344746199fb0219ffff03d8d2550526966798c174dffcadcd88b42fcc886f7ba9f9ff8345f30de6ba45f103a903dfd40cdffb120fca1d4c7b418e30c2e2bc6ed6dd81b4aa4296fd55a8db6e034eb3627aa39fc8daffbda020561bdea7dfaa707661a1294cdad2728fa219707d037bed3e434758a36ced0d118ea5906b4a431f76ea353b698042b68a9e876c9cda03a3a4eac738433912c67f6504b4c1ca668121749cf56474ddf47294b5788009b403ababb01d83f20e4316fccff1e39aaae01a90b7aa8071afeebbf9ef1689b063a4031cb164208893f950d74e06b7adf29794b5e5cf65d965b7a87f40b685b227973203fd62692b3db8d4582e8dca1a0ec9a2469cb6af27f02eda61248b440ab1f85e0f031decb3b98f0bb33ad91b6fb1682e25dc8df0c00db468b15f2de8994f542f63d50361f37042a2587386fbb2d85b5e15e11433817f00b86e0d1803cf02849696221003eae59500f8201c7c647a4226f03796e11ebef1aba03a5b42a4291beff08c941303939ede8e42183c50c236cf0bd6fb88bd21fedbe349bf269ee93002c7796042b10348612e053a12209c11f6bd107a89a4328e3d7afb809cb305e86bbadde3e7b4a80338e1cd81ba6d69ea881017ba289154ae82c0ca5d74b403d9c6c1486326d3973e03ed8d3f78ce1b1bcdf71c5d193092c7a454b4b83f54f48bd92ba2ddbdf255e1620219ffff03410e12b3e537433a995f528fc7dde618a2ff37052b2d0368272b398b0daaa27e036145c12b876706ea099b3ca16815903471ab9194dea3861fa0c91e5181511eee0219ffff037e8e5c2b9fa2f31d015c14470659d1821c79d15c9386b0e924d3737cf9c286430219ffff0348895677416516ea11a8bbc1b8898fbfe1c9feeb9a607deae8e64921da201e790341e7bcc329711efaef00ce10ad720c35fb7bf215b70b1478dc098fb2155c7cd803e8659f0f29aa240c6e53d039d62e2c0fb36d9048780c540e5d8f0c7725f1fcc30333c0c2610886b5e5c4252b28f1037638ff69dfdcaf3fa2dbeea8bd98779b729c031390def807310d2bce05d17a4aad9e328f366fed3ce21cb1f0bea605aaddd78503dccd50d6967df0276628d99b3b38899174936bd8b950948b271ef0a9da5cc73f036c2b157d9e7079bd5d7b9bf55c32d17f4e26232f0b76ad36e94a4a327a39bbb9036b97493a62728a111362ef972086d7df94cd04c7f0aeed3dfc4acfe83e43f8200341fd07d8c8d0341d98555a2879d60efec9e93313c4659c9434a5e1fc17b8380103757a1f087d42d814c121698cf037cd7c30b68773f17bb5cef3c212101d12a63b03432ec5dcca81020a377a3415686bed745a27edff56cb516b00d8552dfa2c715103e9c16c0858a0e7a281cd85f620ddd84328f89c193a7ab8d14541dd9c9c104d3100581f034b2c64d372e60b3deb584c7a735e688064d5472dba43445fe883dc3a9d90423c500367fb8f58870b4d6c24034690ffae360e87fcaefe9c1c1ce7c256d06a488fe81803d9289f911d1bc2e8264e4e3e40e65d8d43a6d44f1fde2ba30b433fe83d9138f003c9f36cea93339355c5b16bc3ed108f851b829cdb5d3cb66065790bfcbaa5c1f603a29ceb75dbdb55aa60bec7a236d8e85f6f9673bf2013dfb6b1b141fdd787a687034db86cec4b566b614c14d223e622296d8c19b1d2077ba14c9457a7d8f5adc4a800581f034d1c8bfa4a97445926fd822a2ead1d6ec09cd234c58e3916929781163fd05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff036d6b75e482c8186329f886bc9c645571d31fc02c6ff4ab4ff4943c806915dc5903cdd938d4ed85d9bcb934431c98ab38acc2c9cf9d4652d7a3a34123d66e1644e700581e0270028ead38d9fd7f9171a618887d4f8576de72527b089b7a129b7affd05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e023ef8cdd718d82e58f2d37179323158c100e8815fb83d9a09270be655085820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e026659b8c90a58f0c6881882d50aa05277ce0d585fcf7750f0bdec8c7d155820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e02b219247b01c8162aa97ca9dad7fe28fe9def6ea389cb95b204325054d74a04d671e9d45d09a23fbc021941b003c3e19b99041442fcb660bfb63d0f6232deabddc898f3f854e97237255ac9e1b00219ffff038ea856e54df40c0cf75d4390e9dcea86288e58c991b4d297bd0033b604282be50391834a439aeb5c9679ffc45e2f172c0d102f20af7fee07142a0b067ae33db38103073400b3269663420f0e8b214cf16aae51b06b862c85363385e76ed82e9a87e203c93cdc1af3de19a8906c3d34f1eda7ef0e9aec45740d69352090a917cc3e4fee03f339543a04a3242c1bc69790d6d9b01fe058d86b99a269e3e75445006a29fc0c03111278dfd3b7f44ef56e9a7bbb3c0897b625a8803d08e2782c845ba9f531e6ee031ad3787fbc3fa4e0267028febd2cb68693773d00743a4360420df43313a9d03b035387016d74e663e4fd5d3807733efcda248d1071325900ea29ab6777ca6fde27038790101908b6a8d44859e1c03555b4e88614d73a96499c64c354c1b2f527a4fc035508d6416b45e7ae100be067f3055e3c62026053303a8719531ae6b0c779ce0b0315c70bcfbe0deedb4fe9bf8c1f9d0589a44fed484c98465e20f82e3f4171ded103bf42f9654b359386c90bfa28621de2f12f5d07a8f6ff2e15b4f8b45ddce1833803f4aa3e01925a76b4e6a008cc2c9621d508e015aeb90adc9ee8574190d3bdb7e20219ffff03a924c3f66432a20af24e2a6d6845994af7767d99e139a7ea7dc8554ef9f9622b03e20f06bdce293ca6f61115267c074aa28bcb609fa315397d772ff827bf73c525038c1afacaf68b977971cfc08f1598baea72165f20742bf8862553bddae85bf04c030578fc023b41e359a204d256938176c92e24b0f8340a5e32b10ce93171147ad40376fd0c507641276b508c167f55b47e55680ed62fb8387eadf9564109791a66c40331ef7238abaad5d8fe71aca593ee4da0dee4a9023f3a2f61e80abfd899b292ce033dee5399f5b2ea924dba5f32bd54b71657cbb0f28176b8f90dceeaf3b0825f78036a9976cf65baef66069f2a9c6573517bd2daf4ffd6cda394ecfb9c44e9d31bbc03e68a768702b60934d9e992cd00936bd17734c3453dcb4e4c4103c8c1d62bf756032b4b79f8a4f153ddf6c48af50f8ef7151ede2b5b9dc1459104973c9d31a2e220032c8a99cbcbf16c939ce1e9058f3c38ff840ef13c5e84425162a09142d6b08ad6038ed4a4c58731ec584a9bd2bd0b55713a1f6ba8ffae2c28fe284e45e7f6078258037e792507da80afb9cb1966c4b4891249a5754478922d3240ed06118ecfac00b003598f7c80a3d8b0b871af7685dfbefc4fa7dc03cd9273cbadda5b2d3ac662622b03f135755931ea2f2555953576630162004de00fecdbb9be11a2847b050c4f61010219ffff032d1d23c62c366521cf551722d186d8063101a32f89e63e50739eda96aa5a4b80037145708b9c31061f6540c9c800cf392ac738dd8540115f3d969229d8ec2f19b7036516cd98c967fe090235097820b53a8510986da56aebb419f8722a07285536bc0392dc6b1b71f2f6d107d2330fc84631f05e7cadfdc4463169dc0aaf6d58ef075f0345b5ad6becf99101e9138e6c884250cc2a7a44ea9165e9ef57a6f4038c8e65d7030a4ba9c187a3de63e7241e3afedaba5b5607ae1fe6876d4fbee40700c6013a6f03a663f3c91c0a06a428b375b8f5b71df40dbb41e48e6296a2770c1b68cc8c106103c52ac0ab6cba6b9a5c5905b80c9fc22b20af8e85aa53d475cbed509af689b95a030594a02c0e4fc4363f5ee918a0898b5c0abe8ffa23418291ee6734c8c3e22dbb031233fcbbd7468c35ec383549febab3a48960f014541b5b4fb1df9abbbdd283d6034d6bb5bfab50cce970a530f8ea83472024829f1740cfcf2825ca85c4a016b0ef034a09b27695f4b7e899cb1273b8e2df566460341bd65520985d92ebc809a4228f0219ffff03460b8107956600a9c69d0dd79b940fdc26f04eb0704f34bdd29024be35311b7703eadc1c47322f3a478c049d0abe2edec04308bff53e9117ef9fafa8e39f5221ef0370e77609739ef27f86d577a385703b83cb1b4a0026988647656903d9c10adc9103dc3b72c558b399b9b2a5a50db7eff99f466a23611d3ea0c7b8e1301d2f4f19ad038c4f6839c54c21d5003a77300c5167380f455a1b14aef6c251aaba47dd430bdc03378cf98600ef88a9562b030e5c2963e4b0c9c4ba2409e86214c638e86a2ecf9603718b723601a055cf3f8161fa1d6089483ed131826849ce63158ca9b50ee249ce03375c270fe698b94b40d71429c5777f4a2778a8a0ebdfc3f74da4c25752c1e953035a00ad6a19523f39fa41e9f82605b1ecb26a615007a3887eb3b660948c938e160306cd16ba555ae1a531a57bdb595b30cca5c215079ca4dd9d312e646994c9e6bc0363ee4e9cd78d9858555b3ecd0db9a64264c4b72196a82db5c2e611eccfa0fea5037108ceba972475e937951839e0240061df784f04e2986b4e62b6f7a0d8868ba303802a00c8be072236f5ef41677419ab16ebdadd2adbc371763ad4bda18ddb915003b8b0a38c5cc0ca4f07f80dae3d66289b1168978674268767d2fbfc83435b51d80311d98c4d19a15aa2b84fd08f4a390ea4cf882fc128d234fd0e7f8a4b22d4713303f3988637473cb24aa2aa0821daf55dccbcb0f529975525538595cf92cdd7930603470ed35dc8514ca8dde794a1964b0a0591dabb56ad7fba451f9885e4700a3d7503eaf7638513e790e643015de695510f1dea0b8461ad8f5aaa660cab92d63ea45103c3b357662b9a2f68dccf5847c22e1d9eacd996a3e652aa456a1555ef1322eb6c030084babec65d302bb566bed91104546585cb799a178fa33488b34895029caba30308282e226b5860f010e6d07888740d84593386bf762a5122a8053b5fe7eabb2f03fae2bf7a45a62bfb6b73a1696e2c1875ad15a044e2c0f9eac4de5c6bc808b70303fc7e3ef1cfde6a680dc08f851710255b4bd910ced4c9571612364209a4b5b35801410f03c04151e22172a02f3994ddf2adf7ac75f363a4530f5e4ce2284c0bc24c7855bc03fd8616a39ffab29ed12fea6ec318c723e2f3fe975c2ffcefb5091484ef123a390356a91a12d2c3161686015921d6740560520db69ee0c8975b32aa0429875efa1500581e02b9c899cbc8d27e98f25813a5f3a94d25566496a4363b9d2796a5d5f1ac5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e0248d06926f3536b2f78aa61f4c65e4a1ac60560c237fc1946e4edbe69225820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e0264d3d3d247e215ffac0b4523255de41efa0d9b367ff2a2d74d4c0ddb924602583b5c8aec00581e02d5ea7fbbc0a0b29210188deebf2a0870d3315d08fbc4d700ed556c9398472386f26fc100000219201303df9dec1696fcd71c498e7e965bd196ffaed8434f189f18d7daac08511841167100581f03e091a2b4b63fd2e06b9f9a632b6eca438156a3b57d8df78f45d074fd74f04641097079d91c00581f0327f6ea78e840c909d2cca44a3f98f1680170fe43d0882128eee3116a26605820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03bc607a5416f84d99e8d263a3a8276d681514a33f26ad3007eac795620794cf1b03c6df7523717d317c72556a25f20ad9dc8e149544244a4e05095aa9e3161cd7dc03af73a8342aa41dbf7afce758326a3351f5844e5ab7bce8152a196023bea0735e034c41f1af52adc6cd3d97e1fc122fcae6730923d821b91c0ffc318f5cdea0df010219ffef0395bdbe8b48e283e8cf93dfc53f038e9077365bc089f7901eec3659dea5678b3c033d800b6e9bbd16a3daea40e691648934de3785c506e0ed9451db5f9253f615b7033df2ac579e9979ba47453117013c327699e201d2d07f29439add69647784746f035f2069e9e49dd9ba8b7ae6a250aae435721da42d1f9824fb68f2939d0692f8b903ae4703482f7c55078ab2049e463925b867a6438a7453b80caf240f24c590a5930371bfe6178d19cd6d8b1b7ac2dbe5d5a129999ca55e0b36c6564aa6bc40ac912d03576065fe41aaad51f62645114d720d3b137523b4599b8110d559ca9bb477f472039bc70b06ebffe41b889b6e36368e496dce03d899fd0b64485fe80c09203f91000312e3d86d5c521510d09ea9f1f4cbcbac7bd72a2260861eaf5b6f5d2b645e76e4036bab120d8d423629e3df9a05f1c224e0924752358b9b21ef042d4b3bbc668e7503e4950becdf875ace3f51181ffa7517b5852fa16d017c983cedd55b590545c09d0324fee56055009ed0c12000b3289de4a6837116efdcf374aca2f12853a5c2daf30312000331af2fbf375aad88951bf6d44859e53b4f6c3920e0d8fcb45ea8f6d0d7032fc4a9749ab9afb7e027a5ce05662acd35a3f1fa9a35751d6799e61d65cfe5310219ffff030bb18f59d92b7859b02e0ce9a9bac8d581f87e6b18a46e433b710ddfd7096ba103ba452be15f07daf63e7f7d171f7efd5e77fc6b8f4045ba75e5c3e38b46537f4f0219ffff030292e938808c94e3948b6f6b0e408736a1d719f049d64dcf023d58cd564debfe03f0ba748b7aebf0f1591c7976759caed7d6bf4d57f257cc74a2ec9618e7879cf30327e0188fdd6d8f9061d0ac866a7690855b11c3a5d6df05e8108e40e6165e7f1303ae722dde3e1493fbd35b26114be51dd1f9984e7589de31905768a9bb09a895f6039baa815ac60f562fcfe5f339eef3c1f1c632c32d03c94d0675b53449e2bdf2880335721b7e877e87abd7253f2b9482211e0e172048ccffb3ff40d21238e2461acb03e1020466e0f757ad3c5b360603437d2538629a056454eab08ca4140c6ed505c703dde60c5987a77e1e2683837e5c3460cf0958b82c5b1cf75baf31554d7746548c031eae9d8a152cc883cec30f4ff7b137ade919cc1ece7acec24ba90b0036d2b3770301dbc256136aa7b67928440941067e609ad6d0eeac252c70e3a14ccfc099ff340219ffff0378f7594c50f6597727fa297a07be2653baf2d58f2323b7ad870561430cb76e0903b3ccfaa6686d7017ffdf1bcff96b59d6eccda77ed42d02e9ee99c3af9c61028703759df184ef6b0c62be3e64be574200e05f6e5d86560ef4fee520a29844fe318303e6f36ccd758dd9cb984856c94526f31be76d1c666b1ab31da5d26b6fe2d2600c03e17b2d1bf4032a66c9f80c4598619355b691138869d42ec77d9f2ed6353eaa8f0338ebdab00d328c0d7736345ff3ac28337ab309b649ccaadd1ab790a401da617603c39115dc072a40818d0442f844891cc0fb203eec2af848226d6957f45ec40b140309f103262bded39db0c4960929141f2fb90d549fe0329040f0b59c6643175d7e0311a73d5463c79d3d46bdf6dd66dbc3d680e992c62721d4d61fc3d0fbf1f618e50333d7146d760dd04a91598cc8710115fe6788171cad797b82d81e92803e86aa6003f00ec633c2ef13537b0e2fcfa5bfeb6fcbb8b6d3316f9dedc30af6492169c288037e8062ef64c85c3ae863235d203235c841eff285f5e0ae5de6721a6a8e968f01038e996a520943b148d34edb14815496cd1b07d5f43c3543fb0a5c9ea5cb173c2003957aa423115ecde6a45e4b26e5978731f01fa00a5ee8f2ce669158275d501939035b70a34ef300c73b28276d778d54113697dd2f73365e77c287a3b6a86b3a9e6203d4def8f38374f0c80f600157c67257161b627eeaae884ef2c60fe3ae53e6194f038c398a0c41b9218a015d54bf7bd78ec3a80d8ead82559ae3710fe4ddfaf6616f032d9b7daa67e695afdfd5ffd21e2ccbdc29b00348c28f03077e10cde7ba11cde4038f669f4fac88afc6ea1baa9f43f0e7cfea6fb742a7644ada66613e99e3825ae3038c3b70cfd2c98c24d7c030cbc35eb02f6155aa7d149470a1cf8a7dbc968c0c9c03b2b363e79c648e13a9659e5210c0726e8f998b3eb4ac4798396d7d7ff005269d039ba38f61895dbbc90d5c18134729ce05c69bfaf7968c2cfaf38fe63ffd113f2e03739b3f99dc008d2228cd053b3708a71b3f6d4fedb950e69b26b640cb4e2d651d0389cb682f051ff7a5e01dbcb05af968aab32c89800778258e97963cb07a10a3b003ef7f4849aaddd8735c0028c66bac7058db41f0a805e6b1de4096950a236526dd034062a675e7c2dcd15d4930223046d54987d407116d0083a3be73d8fd2bfff28103f1a4f09653bae53d394ba374a6605d8aec25c77e17efcca846375e9ea18b1926035881587ffb3a0e01710e2e6881fc5a34e4ea4c86533c354098fac495459cb9a603ca80b505aa62463c9182cfa3e9f0c28292112664d8fd3ea753896c79f6626d4203ef540ba75184e69b0cc004d495f23b89ee474939fee3f25b105c5b04f7a2127a036b0bd3c41610c77409e80cc45c7aabb146a5bf17499e196c111be0face196a8703c83fe45ca5c6bbfae8e8f08f2dd2764fbc69afa3add4f1982b3e1b746963a21603cc7b26e623070ec97b3e645d4a4e32258bff6b0597b13923dba3450501a1e1ba03a72571c1a7548f7e4d279cb5673acc2ce560f0e49b4474e6fc250813a33df499034a589c1ff7c954c05f7551736df580c4e5c1813679fef8b09e13bc2028e6826b037c522072578d99241a8cc402e952bd4bf4e70dcf7f7a30d8eace2f084eac40e40370b6a8b6bd75d2132947f3e05baeabe14238a4ed553307e98f48e69b8e81a82200581e023c8e0285cf1900e405e4bd465e219a261ce68dfc8bb46bae3a7ec61bb25820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e02a8075e9efe61170e277642bc016106cb05fbbf530bb940c05351b2d9e95820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff02030340c3eaa78234b5a8d7b6984550b7aef4dcddf380f80fd41bdd8f57c7a34bccb903f066e73a5ab76f356582aeb52015e96dda876e378db9b82a07ee3c11c92585220342599fdb9b56cfcf70c3a0af8e644d8b32935d475f77150d74900ccb8110ba760307c244a0d9cd3dcf5742468050b60a7fd532b1de2fa60f542f31efeb01d77d350361d9e735c3026a7766ebf912d3aebee68b957be15a2ca91ef96781cd527d0a320219ffbf034cbbc0f85695cfacc3e746c0d3c448e746fd410c73efd2df67252acc9815a97003335be98874939f17502404c12fa25e1491be3398c5109fae930e2ed0776b8c65039a46c6f3750758a94b903ebc0e2f1fb2aebe90cedc7239078745476cc36f4aaf031d383193ae50cc33165af736bca5744a9c0332706d912ea1002211853cffbb750219ffff0323dd4af0add6fca7acfb323e16be1c572e9377ad7772e1d40c9551914012e63803014fb94630ba0ac447c09091633422f8e4be55942f31d3903d13a01f4d09ca570348c84dfaf54ee090897c37fff0ff89b6bdd7b351c6aa1a2669496e589bec43990358f085928c05a9013004526fb9724517ea61e2d7e6c3358fb138ce3a91bbd32f03c26418b2df01e76adf9328ec71c3103b9a73283852c0ea4d6fba7f95bdaa3a42032669da92223b262b55858d142ccb8bfa9bdbe28087b85e9f4dd82d93d7babfab0219ffff0361a9d95143264d77a28682c43ab0f604d04e64bdc2a4926e34321a57591452db03b658d1304c092f2763384a77039b677778560ba456fb98b73be8819e3630efea03c2d99ad1032009f2e3bfd21dd666a2764803b3bec755dbb528a931eefc9bb3e0034b0aa3b6bfb5b79c4f4cce4fa038642a3eaab67659a5166377ce10aa17ba04eb032def192041b0b61907b0f7de46016ce6880f642cc5f9dbc0af3d37acfa9ab82603109e02f812a055e84fa9fa007addec2de6fc4f8e3cc3e6dcd816b2984943a63e0376128f9b6d4edd2045d2b8be90accd625f9717161d313fe444b4d7e0e862e54f0303af19af8483f0d78a21fdebc68a9b7dc413c66985fd0800c5a66bfaabdf4d440219ffff036b3d71c6766011c9cf957a4b4bc82037930a894f6285fe344586fc300a4b87b103f7f9b644dfeb110bfed9368de4cb4dd99b306742b6d80df44bdbd55978a5fe3d031bfbfc2466d347bd82e44d1c45a51eff69eb5c955c1e16fb99f21f95abeec3680336498192c0617aee66e529df16118c3df59cc95e153acf3ad0957cbf46e164850219ffff05581d02a65bd257638cf8cf09b8238888947cc3c0bea2aa2cc3f1c4ac7a30020f014b028dd64a11b84a15281fdf190c34021920020219702b0307134903f4a3fc60a027293a65e45512f0ec24091d5202024098c18f1344064103cf0d9d5167dc306e9c588bd9bc1f554dcc8e8e5e51bfef531e7b1dec1ff48677031c3b349a7ee937b2b4babc92d591b01f6c8296a446b23ca1e9686aedfeac1f5503c55b2981a6fe08260cb6a076c76858d56aafdf255d0a12a2c50abe35d468c7e7035fc60798e8fac6ae35e11c4236223d5067c6564cdb37c8a57c767fc5b7db8968033909e6c87be78bf54588e521ebdded1d467e8104d18958f7dd079010ee22155703e7aef0d8c9d7ac18853b8799630d1f904cbd62db280abf5396a568b84bf92b460219ffff03354b3281fa942b76ace66c6ee41878fee419cda1ae02936da5ca105b3c6004610219ffff03924da55ffa15305d6893846b17b0a47814a7938f68af06aedc437a2f242754870326c16b16c25cba7c92bcd2e9b56bb605430d8fe5481203e15b8595d1a0bee2650316fdb500d03cf52523760e0fd5cbc12639496fc0c413f48a480e717dbd2c432a034fe668ee86bcbcc4e3f135c41627919a93f7a85a85909a3931b6d33b0f813fd1037c0229cdf04ff808953e4b53012610248043fe56aa742d58b2dad85f893befd903966c29c92fac61bee4555ab1ce3a2a018667b75d3079af6dbe9e2e806f5457590219ffff031a304bc48b1174470818d4ee0b9c8fdf163ad7049b79b1131b1f1dfabca37db40391d5dcea1fe0938aa767940bffb1542b77f6a2c89ec4cd28b0e886655089064903e12c105dd84744cf8b47fe8d70c88c6fa16ca99ffe238e8bc3d50378757c81e403a20d2aa221e50d2943e024e84f53043631e69cab315beab59e94a5db0f7e0bc50371986351a43f8225822038fb53137a245ec33cc69fd31a2a6c2ff638cdffeae303f5ca806ec15a9f1f0eddb7110f5d2ae5aace1988dc6113b43d389461780ff7690350c1150d61e2fb4a2081af60cd42a736e84a1a71530857b44ad9e6b254c4bfd803fbd2bb2084c8523ebedfd52595ad17d714c086e37da462c997141b544126b06a0219ffff0394ce008a9c5fac1909a7cdaa71631c0a888c40967f80c3260f13c8c7dda8fb5a0390b8b53fb713f4dfb3d0ee239dc603ee646f79550d1c4e1fb94efb0620682e7f03ad8051f6e6f7db4bcf48f3c22dee67ba019eb3667819e42abbf6b504672df1da030874eb954854f7cd25361fb55ec16a3c3d0345156fa61d641e6462dfd336079c03ca90312bc1bb9377b4bfffb91fb0d1d50972aae675c44616dcf9cf41e8cd5f1a039a16d4fea6acf699cfea70ecae9edd1e9885a6290d1ced29e6b9da9517c2e52a03f860b29b876db755f7166ed17b4c00ed540816add8d804ea72102288660af7a003a081a77c673b3424af44d97cbb6b9556e005dcb25fb69af2acc2320b189d4b7f03ef3eded0f0bbcafe06d84ec07630b5c1d74ea16fdced67c348b735886cbb8e120219ffff031fa89d3a19b1abb13c62ca9449f9fd43a7846473bd9f5838c5b62349e59656fe0339ca649c1c851df0fdcd92162ca44da01ffaa0f3b79137b6da3a82a067353aa4033cf9d4ac267b0df8df74233e4a03d220e265191ee180e950b90ce5bcaac25e1a039c1f565b01853fb21255633fe45c170a7139ee72f0d5e302faf46e36656634e803e7aca9d7a27962fe3f37df7858196f5cef307e4891e640694d4c9f54c559078903f00d998d7d3c74b2766ce3d5fbb5c22e16bc261fadd59f454a36007c5b2b146c036611738bee850dc5bfcc62d9c6fed990d5b12b969a431d2d5996c51e24205a250348c5950b8e4b69565ff9285efdd068ebab913325a05f4c68bb59068c689fb63003a19a4e049b780eea24b79bee56da4f9e57ac73f599918af2ad1fccdd99958e4703f0766d6df8051410147c141b93e256ca938dd150e26fcc1a98bab4d125f632c703b2c94547a2133af864e24a69de9fc446d27b79fa4b99f57512f590b758ccfb5c0328ff1efe218cc67bc7065e3973888a7e0c469f936f6cdaac0c1f31043210dd2903eb3b0ff9280bfab1dab6f286f3645b1b0475683586d218681c2bc4def4dd3368031092dbee68bf6aaaa5a97206210473dc922e6c14ca8c3912ef520c18e5c8953d03106e9f7a8a4ca630b86e6a57ca1cb6a198bfd8be5d854738cd8d2a2efb7dd676035546db03d1cdc0e2257dce93b61c37e62dece3afe1a7cf3c13d9c06556ef829003e69d4d0726e34ca3a8fb159d643ccc988133f7b90339866ad92ff2d46d6361700392ef9391941ae78df861730a47686a6a81c911a19d6e5d614123570d859364c7037584c6a59f6226d885c6d47ed58cfd73d6744ed47a3e82c3764585341125ac8b0349873963179f3ecaad389de81a72297409bdf2867e24bd6194a756a9ea548a8703cddbb8161b899aa7df6ff95eb0528d95c4d9ff1672a9ee412e515570dedecb4503e435461b429da52971234d244c0bf2d2f18955e3860939181c22dc159369a59403943d07322bf0bfec6f644f7198a06496ed9ea58e8b932344d4e67a446fc15a2a03108892167fbc916d48dc178429bdf5a7f1f648fa9f2c99e1b78cc6435e765d7f031bb52f732699e351626931489c638f3040c8142005855159b07bc9c71f0630b103949852bb79a59a87142792be5efc32b6ffa284da4ae4509ca4b7d3f2c74026d103d4e39663108df2e9d5aafa5a95416792432dfc4a788d1fcd4ecff860f4cc439303019a7e106e3e52ce086a0337f5baf9937953c5b6e8ad9fb57114b3068d1ffc6703551f898d59354984f56d4db53b97e4ddf8839858166d5cd5e92eb6fa5188788103d82457438286ed6d71a70872336bf4d5e61a1573f4982da5e17e11ba58feb42c03903f2f84292f0c89a6955fa28e483fbeb449883e8ce8cd783b56fe702ab19e52035d255cb040697c20ff4372ca83681e56eb434da618c3dda0f5e1f8121cce2bd203c7ce299078965bf10577b0a24ebd4fa3036e1734a6f0604a35a99f7dd7e1a13b033ea4fbf34c5ac42fc65af993bce42b75d2b0705e75b6149d4faab46f74eeacec039c10e49992bce2a8eaf5468b60f0031f7d8085a889e3a4c6f2331982ac2dd70003184c253be5bcdaec4394755e2afff465f47a638ccecdc1a040a83f7d05a64d830301a27c97a7dff56353dd893d7b6d4549afe765e8e8dd75cc65bc875ae23885a6037184801c58056bc28c7f9358d71c2580c41c33e40d77b24e89a3943ecb4e34be033087799e0d2507cb10b7f17374c67304b949389aa29fcb8b8b8f261d37254f6c034249495c48341ef12a81fe1d542ab1976243169e351ff71eb8bdb8e8129a483703cf3efc3b248c6222a56012e92895164f41c3e62979fe504bc57d486b2bb9b8810354d717e24b731744ec12822e425079e99774ce643dc4fb50c26acf8e27e9a1af03e7e7cb5186dd32d2dd7754450f3cde21d587ece95e6d23bbac4d53de6a5c4dec03d4ddf2fe6067ba7063ed87953d7cac507e6b181d3f09de058860cbf012fcafd003a27e20ce0c79ff4ccc9660a7cd46e65b20e00c00710b511ffb36b69495d738ce0368d57539080679a7404533a9cb2494f94cfd17e7d6620f4e0c01e93ba95758fb03e21135952f7aa362911d27d0092278b2447ffe9c3f10013fe096f6448a35be47037b51ff8f822e9e5e19264111d1ed1bdf484d77fd2fa13593fc8bab078ae3f0a3036a9774ba04b7384912932efd76b7dcdccb44aa591d95625f76f85dcf08ea92f105581e03ff35d8f124bac6825dc56aa0ddb06ed0b4176305064ccfb542592922a00c034705e6a70b404555035638c95a3214e4699134737250d82b9e1103eea8ee520aecf5e446f7e785d28b03905982d3bc98b3ad75d5ca7a92446cc8923fae39a2352245000f9d3e9dc0f3ee03bbd13d463ce2111713bb0d6ad117b0ba991bc32336d501e872703c4ad6bc3c9005581e0383f749eee7b3af4ab92eae05807aa2b87c171d49569a4c42e20fa7653007011bffffffffffffffff05581e03daea18b99a136dcc10a63007b9a72f0299ddbf88e872ee246d6d2d12e0040205581e03885a281463e7ac124c775221384d850e24cd64b776b2a509fe42cbc7100c0346a40176c6360003e9fec2f19f4b290ea820001f6f8070c13c85171b2fa804cbae15891f17ef4c8203ce33220d5c7f0d09d75ceff76c05863c5e7d6e801c70dfe7d5d45d4c44e806540306b487d15c028b6df56c3ebb9b7086965eba3a240857a647faece2ff13269f2b05581e03a04ee069ae211b6e7fdf04b983cf718d84c1a5292e7f6207d7da5209f007011bffffffffffffffff05581e03e2fc072d5075007b6d716db18e76aec7aa1aee35fbc10f89dc9276a4500c014701d0567516f4a5036b61b5baf495897d0e083bafaefd44c9b30d2187584b0289d99053aef0eb642d05581e03401b1cae6bcd3c75bb7060f78b139f4a5b88cd3c75ea33d330d99a81800c014744e2e46807f36805581e03598881691c31a20b4379d8984d2f42431bb4b0669700bda6fdeca1f990040305581d021789de32c933fd61ae78d893f5c2f5269f128c722bd5dcdb040d332f040105581d0253f09dbef230b15d5cbf9d641a17d48e723bd944a9e0bd71869b1833040105581d0225b869e5d52572858bfdee6128895c52dae6cbeccee22f46ea4428a70c188e481995458b9ecf704d02190422033826722f4f10e7f5593b1ba3d251a7d917da55d61f802745b3df29df550238300219cffd0382bd4d58a6d9223a91497bc5c50a71db4098e4bf56db7b53e2c3f9bcf8911ddf03eda0c2b872a2e8004d912b7b3e3add407fb89c2e1935792b3b1747369761f494036b4389aa833fff43d1200a2b8d10cff80998608fc1cd9727a830faaa0990918c03f25a53d819c2be142584b1afd1baa8cd4bb389f88d938ba051119f47204c0b20031ed4c8beb960985fd9cd4998f0c5f09e259e213b59007591e89993d9e3e972280219ffff03714324c0346974bcbf735158e66793fbb8c7fa526a6afb0c8c29b2ff3d7c2a6c03c071cb10dedbe5705efef507f9e0ce0a8d7da622f425b401717e4d826477a51c036656773f56dacd0e751b322dce3e39a876d95b20d9fd9894ed4aba5646998921032707856409b17fde1be8ac642b20d38e9a0c86e36d92dd99531d358d7a3de1420399d94492bfca6335faa3865938f0b91a4e774e3c569ba6871cacfde5cf4121cf03b6548e9eb142a6a10b627e760cec7fccf00cdfb570e12044049bb545bf8fa26503fd12edee6837c3ad78cf34516f02bd237f8c374373364492917c95f2463c4e4b03e75ab60d63f32db41de0b8c4ae74386e4ac3339aa21f8b4b8dcf09a25277b0140305e3c069b09a5d6f764e825ec0ee8924473b9abb78336cebd31a11a60895b50e036a6075ac55e17a4c53693f1b1b6c932bdbeb58a9672f15bacdbf87ba01e221fa03133a3b4e995005bb215a3af63194f58966e256e760e7b10d6600e6e926473d4703cb484fc7d3f2443d3e613fef6a1635d61015eeb788dd55e78bdc220b1039aa87031e4ab1cefcb4df1ab843a9c99c73bd256f7075bd014f19d34923e0283e810fdc03e73b91d94b5ce180e52f902f3f44c51aa72eded520c5b8ab924f0b7be8aecaa30219ffff038433bda393dbff28847dc3b1c401d09939944bcd6af8855e167ea5d57d5f36f1032f697ce9dc8e87f850a921e7fd318f5e69ba5b8bb7f10ffd7796aefb2cf12b5303786a68e63a44deb121d719756cabf5b870bafe9b174b0040bd45c1a3c2fa80d60219ffff038252aae62902d5a39cead22caad08f18894571c955600d14273c666e808e0fbc0219ffff0397274ba63303faccd7be40da76dd8295ca7ed8b2e028875707ffe3df82812e80031c2729090731c72ce7322c825e0238cb86e438afdfd127b7147c352579dea79b039e1cfeed62ad3f8bdba5bce63d73c0586adebcd09eae52221c9c86bad6783ee30219ffff03e19272ac4d0944de700e54e84bbc6aa82c1c6eb55fbdf72cb1fb3ffb04cead0b03adc76950ec2d7900fa63770ba216225cc60dfc61e7280f230a04c80915b53120033ea1e6b0c1f051d253779f08bd5f78763eb44b94b116f08e83ddc4191f1e661703db7931db2f7c3859ffd14bb399559b830abd33dc932e82fed25a30a91729c1b103d9a057fd11ea43ec4f3a9338ba7bc78c97523322f3780ce9f3aa1f0a55a0c1f7038fc62e2a390dbdaeb9df1d6e25f70883bd1f4079f9ffe7a01145f78e9fcf125a032f2cc32eeb6c15c6cd712d83c93880c0b9d616630deaad8c365138a351a395e703d9f3e79c4ee1eb800049dbd54e8683e36567e70a9f9a1c7d37e7ecc12d44e69e038ddc99093c69494bad8659bfc20a5d10c6c38907a39e79a040676275fb7ad9ce03f86dc389bb91c0b712521c76d4bd40acce707cc771b450a2d51d483dce52ac2f030c9672009c381eee44d4c85135019ac54dfe1a5aef7f6fac6ffe67c9d92595cd0359105eda31ccc4753ab417de14c7f60f62b25a1299cc965ec701d985efe0413103bd6bd60035ffbaabb297761601c55e40c7e6a6cbc71f64e3b907a7f16c8956c5036945df6ddcb3fc549ac0578beeee21d014657778e412507b1682379ce9d3229f037e1aab16b65e2bf56b3a6930857fe2210be08cf780a660a4692eba6fa3264bb703890af9a8ccfc9ae978e47633448ad35710f298d2fffb7d2a2f62bf10098871e20337361355899b501836e7bc93db9cfaa978f0495c66323e93b03f21a2221b0cf20305c1f37a1f32769a9640f8e275cbcb9f062d79ed1d16e0e2000fa2af2bf6c98703c5f7e41ea5ff56cd109e967dc6c6e367d0569e99f30a6483fa9c3b68bab982ce03f72f695c04751effa1788640b6a2e62dc59a3217941bf52cd2fa93689340e88a03e5f779f7027a95e9a650553e67aff9d78e5ce7144b5dba7772835219f13add48036ad41ecb9a2892d127330945bb52eb0951dacee1ec0517c1c138085f841a3d6f03fb7c8abe933abd924fb075551df00ec83d20553680b17847d0b46c1bdabff85b03c0ce4c2867fd7fe0997b2ce82d50dd7333a270eeab086b40d2f2bcf91d25848203311d03a45932503e6f3e786f88174d35069f98ebc2aee4645d5e53d305f84e35033170813983d2a97e427850d58cce3aec7d9d3a39c004e88bee912e3cd61491210334c55de6bcb8667625b51e47db5701e0a4439d04df2e0db2a114556c6f4929c803b0a62636ddea40881f5b630a31070b4b993d899bda48b7c9e6c4816d14cb7b5d034bf2a99b145d78bacdf9dc26ba3cdb049c1e3bdc771a7baa9af1fdbcba39455f03122b099a228f933080c06dfee4ef7564a07699a8adfae533944199143fadb7f6031f6756ccad4047b0f851cefe1f7596e29b6b2fb338b4709e5fa672e86c5cfcdd032763176441c5fbda3110efef38e42565e752981a2933755e82e3a818f1de826d03d84338c4bca31b4f55e96dcda485c38b72c34574d9b983c6ea3a3d71d8c86cb7033c66a651794508b76ecd212fbb82702032cb83543d20a968a9e168b681c28362031957c29a5acd3c0420567b9ebbdc812a03faae5f2da6ad01eff5ddd04d375a4003677313bf4288eed8684d4bad6e1ff659254d239a8785dbea1398135f3841cce403a715c5ce192d5bd421bec8ee0c28d426dcbd7e3feed4983e0313a398ec5a3efd03820f9d9286d73e0364cd01ffbaba5c7a70670a3bf803ffbe6ea95a5e9f817e7303e0bd40096a54dbd890f9c9a4cf4bb06e0f4fa960676d5db2e7f601f8134170030343b03412e798ed51a67f96ff5327e10f7fa1f5ad6a82ba26e3b13cb20fa8cd3d0330397302a3d898044aa5f7aad8de80771600777540a00ca0942abf3b9e7e33990336a0ae182f155e5ae17dd7d82679cfd5430df902bcd1d2eb0ab62adf4de981e603527215f58f40903a68296e3ee96ae0eef922b1f065f8c4560c052cac0768933b05581e03d93aac1412f22d76bbad5dd38f89f405117e29eac41d7620b52eb834700c0245b82b46260005581e03ec2e80e2d2f87d3456c8ba30821592d6e9e90731365fa27eeb227bea70040105581e0360a790a99ad83fbc7ef78a36b399459417f9303aea86115ac63ac30850040205581e03f97dfbf09c53f062c580273f4511c81e434436f3a8992e39f5596ab3d0084703a08a1e097800045959ae6080604052600436106101dc5760003560e01c8063affed0e011610102578063e19a9dd911610095578063f08a032311610064578063f08a032314611647578063f698da2514611698578063f8dc5dd9146116c3578063ffa1ad741461173e57610231565b8063e19a9dd91461139b578063e318b52b146113ec578063e75235b81461147d578063e86637db146114a857610231565b8063cc2f8452116100d1578063cc2f8452146110e8578063d4d9bdcd146111b5578063d8d11f78146111f0578063e009cfde1461132a57610231565b8063affed0e014610d94578063b4faba0914610dbf578063b63e800d14610ea7578063c4ca3a9c1461101757610231565b80635624b25b1161017a5780636a761202116101495780636a761202146109945780637d83297414610b50578063934f3a1114610bbf578063a0e67e2b14610d2857610231565b80635624b25b146107fb5780635ae6bd37146108b9578063610b592514610908578063694e80c31461095957610231565b80632f54bf6e116101b65780632f54bf6e146104d35780633408e4701461053a578063468721a7146105655780635229073f1461067a57610231565b80630d582f131461029e57806312fb68e0146102f95780632d9ad53d1461046c57610231565b36610231573373ffffffffffffffffffffffffffffffffffffffff167f3d0ce9bfc3ed7d6862dbb28b2dea94561fe714a1b4d019aa8af39730d1ad7c3d346040518082815260200191505060405180910390a2005b34801561023d57600080fd5b5060007f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d560001b905080548061027257600080f35b36600080373360601b365260008060143601600080855af13d6000803e80610299573d6000fd5b3d6000f35b3480156102aa57600080fd5b506102f7600480360360408110156102c157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506117ce565b005b34801561030557600080fd5b5061046a6004803603608081101561031c57600080fd5b81019080803590602001909291908035906020019064010000000081111561034357600080fd5b82018360208201111561035557600080fd5b8035906020019184600183028401116401000000008311171561037757600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803590602001906401000000008111156103da57600080fd5b8201836020820111156103ec57600080fd5b8035906020019184600183028401116401000000008311171561040e57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190929190505050611bbe565b005b34801561047857600080fd5b506104bb6004803603602081101561048f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050612440565b60405180821515815260200191505060405180910390f35b3480156104df57600080fd5b50610522600480360360208110156104f657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050612512565b60405180821515815260200191505060405180910390f35b34801561054657600080fd5b5061054f6125e4565b6040518082815260200191505060405180910390f35b34801561057157600080fd5b506106626004803603608081101561058857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156105cf57600080fd5b8201836020820111156105e157600080fd5b8035906020019184600183028401116401000000008311171561060357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803560ff1690602001909291905050506125f1565b60405180821515815260200191505060405180910390f35b34801561068657600080fd5b506107776004803603608081101561069d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156106e457600080fd5b8201836020820111156106f657600080fd5b8035906020019184600183028401116401000000008311171561071857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803560ff1690602001909291905050506127d7565b60405180831515815260200180602001828103825283818151815260200191508051906020019080838360005b838110156107bf5780820151818401526020810190506107a4565b50505050905090810190601f1680156107ec5780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b34801561080757600080fd5b5061083e6004803603604081101561081e57600080fd5b81019080803590602001909291908035906020019092919050505061280d565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561087e578082015181840152602081019050610863565b50505050905090810190601f1680156108ab5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156108c557600080fd5b506108f2600480360360208110156108dc57600080fd5b8101908080359060200190929190505050612894565b6040518082815260200191505060405180910390f35b34801561091457600080fd5b506109576004803603602081101561092b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506128ac565b005b34801561096557600080fd5b506109926004803603602081101561097c57600080fd5b8101908080359060200190929190505050612c3e565b005b610b3860048036036101408110156109ab57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156109f257600080fd5b820183602082011115610a0457600080fd5b80359060200191846001830284011164010000000083111715610a2657600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610ab257600080fd5b820183602082011115610ac457600080fd5b80359060200191846001830284011164010000000083111715610ae657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050612d78565b60405180821515815260200191505060405180910390f35b348015610b5c57600080fd5b50610ba960048036036040811015610b7357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506132b5565b6040518082815260200191505060405180910390f35b348015610bcb57600080fd5b50610d2660048036036060811015610be257600080fd5b810190808035906020019092919080359060200190640100000000811115610c0957600080fd5b820183602082011115610c1b57600080fd5b80359060200191846001830284011164010000000083111715610c3d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190640100000000811115610ca057600080fd5b820183602082011115610cb257600080fd5b80359060200191846001830284011164010000000083111715610cd457600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506132da565b005b348015610d3457600080fd5b50610d3d613369565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b83811015610d80578082015181840152602081019050610d65565b505050509050019250505060405180910390f35b348015610da057600080fd5b50610da9613512565b6040518082815260200191505060405180910390f35b348015610dcb57600080fd5b50610ea560048036036040811015610de257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610e1f57600080fd5b820183602082011115610e3157600080fd5b80359060200191846001830284011164010000000083111715610e5357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050613518565b005b348015610eb357600080fd5b506110156004803603610100811015610ecb57600080fd5b8101908080359060200190640100000000811115610ee857600080fd5b820183602082011115610efa57600080fd5b80359060200191846020830284011164010000000083111715610f1c57600080fd5b909192939192939080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610f6757600080fd5b820183602082011115610f7957600080fd5b80359060200191846001830284011164010000000083111715610f9b57600080fd5b9091929391929390803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061353a565b005b34801561102357600080fd5b506110d26004803603608081101561103a57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561108157600080fd5b82018360208201111561109357600080fd5b803590602001918460018302840111640100000000831117156110b557600080fd5b9091929391929390803560ff1690602001909291905050506136f8565b6040518082815260200191505060405180910390f35b3480156110f457600080fd5b506111416004803603604081101561110b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050613820565b60405180806020018373ffffffffffffffffffffffffffffffffffffffff168152602001828103825284818151815260200191508051906020019060200280838360005b838110156111a0578082015181840152602081019050611185565b50505050905001935050505060405180910390f35b3480156111c157600080fd5b506111ee600480360360208110156111d857600080fd5b8101908080359060200190929190505050613a12565b005b3480156111fc57600080fd5b50611314600480360361014081101561121457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561125b57600080fd5b82018360208201111561126d57600080fd5b8035906020019184600183028401116401000000008311171561128f57600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050613bb1565b6040518082815260200191505060405180910390f35b34801561133657600080fd5b506113996004803603604081101561134d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613bde565b005b3480156113a757600080fd5b506113ea600480360360208110156113be57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613f6f565b005b3480156113f857600080fd5b5061147b6004803603606081101561140f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613ff3565b005b34801561148957600080fd5b50611492614665565b6040518082815260200191505060405180910390f35b3480156114b457600080fd5b506115cc60048036036101408110156114cc57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561151357600080fd5b82018360208201111561152557600080fd5b8035906020019184600183028401116401000000008311171561154757600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061466f565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561160c5780820151818401526020810190506115f1565b50505050905090810190601f1680156116395780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561165357600080fd5b506116966004803603602081101561166a57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050614817565b005b3480156116a457600080fd5b506116ad614878565b6040518082815260200191505060405180910390f35b3480156116cf57600080fd5b5061173c600480360360608110156116e657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506148f6565b005b34801561174a57600080fd5b50611753614d29565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015611793578082015181840152602081019050611778565b50505050905090810190601f1680156117c05780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6117d6614d62565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141580156118405750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b801561187857503073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b6118ea576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146119eb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60026000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508160026000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506003600081548092919060010191905055507f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea2682604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a18060045414611bba57611bb981612c3e565b5b5050565b611bd2604182614e0590919063ffffffff16565b82511015611c48576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6000808060008060005b8681101561243457611c648882614e3f565b80945081955082965050505060008460ff16141561206d578260001c9450611c96604188614e0590919063ffffffff16565b8260001c1015611d0e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8751611d2760208460001c614e6e90919063ffffffff16565b1115611d9b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60006020838a01015190508851611dd182611dc360208760001c614e6e90919063ffffffff16565b614e6e90919063ffffffff16565b1115611e45576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60606020848b010190506320c13b0b60e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168773ffffffffffffffffffffffffffffffffffffffff166320c13b0b8d846040518363ffffffff1660e01b8152600401808060200180602001838103835285818151815260200191508051906020019080838360005b83811015611ee7578082015181840152602081019050611ecc565b50505050905090810190601f168015611f145780820380516001836020036101000a031916815260200191505b50838103825284818151815260200191508051906020019080838360005b83811015611f4d578082015181840152602081019050611f32565b50505050905090810190601f168015611f7a5780820380516001836020036101000a031916815260200191505b5094505050505060206040518083038186803b158015611f9957600080fd5b505afa158015611fad573d6000803e3d6000fd5b505050506040513d6020811015611fc357600080fd5b81019080805190602001909291905050507bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614612066576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b50506122b2565b60018460ff161415612181578260001c94508473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061210a57506000600860008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008c81526020019081526020016000205414155b61217c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6122b1565b601e8460ff1611156122495760018a60405160200180807f19457468657265756d205369676e6564204d6573736167653a0a333200000000815250601c018281526020019150506040516020818303038152906040528051906020012060048603858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015612238573d6000803e3d6000fd5b5050506020604051035194506122b0565b60018a85858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa1580156122a3573d6000803e3d6000fd5b5050506020604051035194505b5b5b8573ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff161180156123795750600073ffffffffffffffffffffffffffffffffffffffff16600260008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b80156123b25750600173ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1614155b612424576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323600000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8495508080600101915050611c52565b50505050505050505050565b60008173ffffffffffffffffffffffffffffffffffffffff16600173ffffffffffffffffffffffffffffffffffffffff161415801561250b5750600073ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b9050919050565b6000600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141580156125dd5750600073ffffffffffffffffffffffffffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b9050919050565b6000804690508091505090565b6000600173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141580156126bc5750600073ffffffffffffffffffffffffffffffffffffffff16600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b61272e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b61273b858585855a614e8d565b9050801561278b573373ffffffffffffffffffffffffffffffffffffffff167f6895c13664aa4f67288b25d7a21d7aaa34916e355fb9b6fae0a139a9085becb860405160405180910390a26127cf565b3373ffffffffffffffffffffffffffffffffffffffff167facd2c8702804128fdb0db2bb49f6d127dd0181c13fd45dbfe16de0930e2bd37560405160405180910390a25b949350505050565b600060606127e7868686866125f1565b915060405160203d0181016040523d81523d6000602083013e8091505094509492505050565b606060006020830267ffffffffffffffff8111801561282b57600080fd5b506040519080825280601f01601f19166020018201604052801561285e5781602001600182028036833780820191505090505b50905060005b8381101561288957808501548060208302602085010152508080600101915050612864565b508091505092915050565b60076020528060005260406000206000915090505481565b6128b4614d62565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415801561291e5750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b612990576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614612a91576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60016000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060016000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507fecdf3a3effea5783a3c4c2140e677577666428d44ed9d474a0b3a4c9943f844081604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a150565b612c46614d62565b600354811115612cbe576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6001811015612d35576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b806004819055507f610f7ff2b304ae8903c3de74c60c6ab1f7d6226b3f52c5161905bb5ad4039c936004546040518082815260200191505060405180910390a150565b6000806000612d928e8e8e8e8e8e8e8e8e8e60055461466f565b905060056000815480929190600101919050555080805190602001209150612dbb8282866132da565b506000612dc6614ed9565b9050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614612fac578073ffffffffffffffffffffffffffffffffffffffff166375f0bb528f8f8f8f8f8f8f8f8f8f8f336040518d63ffffffff1660e01b8152600401808d73ffffffffffffffffffffffffffffffffffffffff1681526020018c8152602001806020018a6001811115612e6957fe5b81526020018981526020018881526020018781526020018673ffffffffffffffffffffffffffffffffffffffff1681526020018573ffffffffffffffffffffffffffffffffffffffff168152602001806020018473ffffffffffffffffffffffffffffffffffffffff16815260200183810383528d8d82818152602001925080828437600081840152601f19601f820116905080830192505050838103825285818151815260200191508051906020019080838360005b83811015612f3b578082015181840152602081019050612f20565b50505050905090810190601f168015612f685780820380516001836020036101000a031916815260200191505b509e505050505050505050505050505050600060405180830381600087803b158015612f9357600080fd5b505af1158015612fa7573d6000803e3d6000fd5b505050505b6101f4612fd36109c48b01603f60408d0281612fc457fe5b04614f0a90919063ffffffff16565b015a1015613049576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60005a90506130b28f8f8f8f8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050508e60008d146130a7578e6130ad565b6109c45a035b614e8d565b93506130c75a82614f2490919063ffffffff16565b905083806130d6575060008a14155b806130e2575060008814155b613154576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60008089111561316e5761316b828b8b8b8b614f44565b90505b84156131b8577f442e715f626346e8c54381002da614f62bee8d27386535b2521ec8540898556e8482604051808381526020018281526020019250505060405180910390a16131f8565b7f23428b18acfb3ea64b08dc0c1d296ea9c09702c09083ca5272e64d115b687d238482604051808381526020018281526020019250505060405180910390a15b5050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16146132a4578073ffffffffffffffffffffffffffffffffffffffff16639327136883856040518363ffffffff1660e01b815260040180838152602001821515815260200192505050600060405180830381600087803b15801561328b57600080fd5b505af115801561329f573d6000803e3d6000fd5b505050505b50509b9a5050505050505050505050565b6008602052816000526040600020602052806000526040600020600091509150505481565b6000600454905060008111613357576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b61336384848484611bbe565b50505050565b6060600060035467ffffffffffffffff8111801561338657600080fd5b506040519080825280602002602001820160405280156133b55781602001602082028036833780820191505090505b50905060008060026000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505b600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614613509578083838151811061346057fe5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050600260008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050818060010192505061341f565b82935050505090565b60055481565b600080825160208401855af4806000523d6020523d600060403e60403d016000fd5b6135858a8a80806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f820116905080830192505050505050508961514a565b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16146135c3576135c28461564a565b5b6136118787878080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050615679565b600082111561362b5761362982600060018685614f44565b505b3373ffffffffffffffffffffffffffffffffffffffff167f141df868a6331af528e38c83b7aa03edc19be66e37ae67f9285bf4f8e3c6a1a88b8b8b8b8960405180806020018581526020018473ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1681526020018281038252878782818152602001925060200280828437600081840152601f19601f820116905080830192505050965050505050505060405180910390a250505050505050505050565b6000805a905061374f878787878080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050865a614e8d565b61375857600080fd5b60005a8203905080604051602001808281526020019150506040516020818303038152906040526040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b838110156137e55780820151818401526020810190506137ca565b50505050905090810190601f1680156138125780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b606060008267ffffffffffffffff8111801561383b57600080fd5b5060405190808252806020026020018201604052801561386a5781602001602082028036833780820191505090505b509150600080600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415801561393d5750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b801561394857508482105b15613a03578084838151811061395a57fe5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905081806001019250506138d3565b80925081845250509250929050565b600073ffffffffffffffffffffffffffffffffffffffff16600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415613b14576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330333000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6001600860003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000838152602001908152602001600020819055503373ffffffffffffffffffffffffffffffffffffffff16817ff2a0eb156472d1440255b0d7c1e19cc07115d1051fe605b0dce69acfec884d9c60405160405180910390a350565b6000613bc68c8c8c8c8c8c8c8c8c8c8c61466f565b8051906020012090509b9a5050505050505050505050565b613be6614d62565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614158015613c505750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b613cc2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614613dc2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507faab4fa2b463f581b2b32cb3b7e3b704b9ce37cc209b5fb4d77e593ace405427681604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a15050565b613f77614d62565b60007f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c860001b90508181557f1151116914515bc0891ff9047a6cb32cf902546f83066499bcf8ba33d2353fa282604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a15050565b613ffb614d62565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580156140655750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b801561409d57503073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b61410f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614614210576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415801561427a5750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b6142ec576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff16600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146143ec576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf82604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a17f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea2681604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a1505050565b6000600454905090565b606060007fbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d860001b8d8d8d8d60405180838380828437808301925050509250505060405180910390208c8c8c8c8c8c8c604051602001808c81526020018b73ffffffffffffffffffffffffffffffffffffffff1681526020018a815260200189815260200188600181111561470057fe5b81526020018781526020018681526020018581526020018473ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019b505050505050505050505050604051602081830303815290604052805190602001209050601960f81b600160f81b61478c614878565b8360405160200180857effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152600101847effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191681526001018381526020018281526020019450505050506040516020818303038152906040529150509b9a5050505050505050505050565b61481f614d62565b6148288161564a565b7f5ac6c46c93c8d0e53714ba3b53db3e7c046da994313d7ed0d192028bc7c228b081604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a150565b60007f47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a7946921860001b6148a66125e4565b30604051602001808481526020018381526020018273ffffffffffffffffffffffffffffffffffffffff168152602001935050505060405160208183030381529060405280519060200120905090565b6148fe614d62565b806001600354031015614979576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141580156149e35750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b614a55576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff16600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614614b55576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600360008154809291906001900391905055507ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf82604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a18060045414614d2457614d2381612c3e565b5b505050565b6040518060400160405280600581526020017f312e332e3000000000000000000000000000000000000000000000000000000081525081565b3073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614614e03576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330333100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b565b600080831415614e185760009050614e39565b6000828402905082848281614e2957fe5b0414614e3457600080fd5b809150505b92915050565b60008060008360410260208101860151925060408101860151915060ff60418201870151169350509250925092565b600080828401905083811015614e8357600080fd5b8091505092915050565b6000600180811115614e9b57fe5b836001811115614ea757fe5b1415614ec0576000808551602087018986f49050614ed0565b600080855160208701888a87f190505b95945050505050565b6000807f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c860001b9050805491505090565b600081831015614f1a5781614f1c565b825b905092915050565b600082821115614f3357600080fd5b600082840390508091505092915050565b600080600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614614f815782614f83565b325b9050600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16141561509b57614fed3a8610614fca573a614fcc565b855b614fdf888a614e6e90919063ffffffff16565b614e0590919063ffffffff16565b91508073ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f19350505050615096576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b615140565b6150c0856150b2888a614e6e90919063ffffffff16565b614e0590919063ffffffff16565b91506150cd8482846158b4565b61513f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b5b5095945050505050565b6000600454146151c2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8151811115615239576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60018110156152b0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60006001905060005b83518110156155b65760008482815181106152d057fe5b60200260200101519050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580156153445750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b801561537c57503073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b80156153b457508073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614155b615426576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614615527576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b80600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508092505080806001019150506152b9565b506001600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550825160038190555081600481905550505050565b60007f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d560001b90508181555050565b600073ffffffffffffffffffffffffffffffffffffffff1660016000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461577b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6001806000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16146158b05761583d8260008360015a614e8d565b6158af576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b5b5050565b60008063a9059cbb8484604051602401808373ffffffffffffffffffffffffffffffffffffffff168152602001828152602001925050506040516020818303038152906040529060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050509050602060008251602084016000896127105a03f13d6000811461595b5760208114615963576000935061596e565b81935061596e565b600051158215171593505b505050939250505056fea26469706673582212203874bcf92e1722cc7bfa0cef1a0985cf0dc3485ba0663db3747ccdf1605df53464736f6c63430007060033005821028a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b410105581e03a2f4a4cbb1b67aa687f36aa61d0631958879d58861d3dea13daecfa6f00f014801edd3c37b642e1d1959ae030d8329487da35d8659e539b873e49a33cf3313fa1c19bd69d0a3e977aabaab0c05581e035547497eab9a72da4aa1aab6d3969904b40c60b1e87ea3c2e5e811fca0040405581e03c5d0f11268cd65335d623dd7e5e2bb1dcc90db4e6689bd66bbbcda3a40040205581e03854a5402ee10b25856de85f60ad0442f88de4cf0cf694b708a3c9b50b0040205581e03e3f6dc65e4447e6a86ddb7c9c96877b095b09b5634d01e3566c83a77e00401031d93f60f105899172f7255c030301c3af4564edd4a48577dbdc448aec7ddb0ac0605581e03fd36dedfa3a6ad18222ea91870b9d2e2da28a043b5c8e10dc17e88ef1007011bffffffffffffffff0219b9f303004ed5e0a028dc92526454d9335bb5dae020ec9903b913a045df183f0329206e03e7c3087443e5e9feed69d364c776586eaaef2f68f9b0a583f0db0e3960aa17ef0219ffff03c9ca73ba388efbfe5a8d71f2f93e0a1baccc3f2f67a829eeefe9bc99e6f5c8b503883f40fbb618e42c0841557a14b661c74a3177b9f3831ffe587783acfa8d421203f1f76a9ac3f97e42a5cba5eb39cee4a1d2f809168811850631107024aa80957c03736b8d5253a17f8a2f58185f7e80b3aae289393c269215d97c48e8727b2d37fe0380fc247e482c485448db036d5b04af0e509162546438d2041af95a1ed530c8370219ffff03d3c9b38e8e1da178fccf9433941eeb6698d2caa6ae9ffa97f3a81851e4a28b800219ffff033000c03b9515874d23d65ab4670c936b8e7cc1b76dbd76a1648c2c8c906beaa403b973cbce4d1450029cfa5bad310ddf63371b7faa23de013568baec4f84fc904603f2e8724e21761190048f3d05ab5a261d55ad32e287c59836c6efd5a087238f26038d0dd165d8777565080305cf0d95f48dac525a254e208323fa2fc859657fa58a0399dd8915a5a9606fc5b5b10b17465f6bba95105dbbcab2b532693da77bd290d203503e2bc44d69fc3610c48612ec44e19a17107695bb919c62a511f45b215bee320337af8d66ca200d6fa2bfdbd00b598d3c62f76c7c55d26163e646ac291d0fe7e803fa89beeba1b278da580c610a6e369898f2f0322f6168b75dcd1e2578c09fb6710358e8ac36cca0de03c25fad084e80275a640b8275f5a0c3e49d98460ec34d39e503218930fa681c2a7db0b95777e5c1c943dd013de905be7998d87ee54c336fcf0303545781fce91c2b85bfd1b4dd6955c7ffb3c3045c95ba59be04437ad5fef6154c0372ca12f11250bf8b4761d411f21f5e8b1b9b384f4abfa8e0cf2fed860af435190219ffff0353072a05b0780d4b829ccc56a191398fabc570b851bbfb96840d63be04000c97035f524e62032fc2be179cf7106ad180e485524cd429555bd8e98dc153c529843d0344d364b01627a8c9fb443cff223767a6963ed84719697b3bab46b24ba89826bc03bfaba6a73ad003970a1d58eb1929d95d74e340defea0258a6e4ed9150326c7e0030f01658194c95866bf92f246c84a3114ac0aa4ebec14e435aa2ff8535b0b006203446f80e8ba2aa0ce93a2f6f0da3c97c2454d1857283fe1d5b7655d7b209a809f03c6e483191da083a48abdaaa0f0a0cc79f2d5a63add3559e1403099140f98189503d251a53ca5846232ef41032368ee6c558ebc2f358e90ace4788a144dd6060c7703f74ddaecaebfc9b0edaa7127460246843ba49283eda7306cd3a42e9ae1e7864403a2e1c6b889ffcf3b139c4d6fc47496cddc3933e956a13e7a5656b4204d12e91e030ce3c3900e9b899efe4688a297272d8d0d0bdf01e5a9a818cc62892c8cae4f1b0321b1cafebec83ee379c6b076f95b1b2a1657425c2d4e77785058acbfeb7652540338a98dab8d6c73d797fa267472a952a356cd33cee76aa586f0b15f7bdb7952e5030817a8a400faf5d7f88199d5d4bb14e9ea3d5fe876fc91fea4caa8641d2125d10367368e6ad0e898a0637ba90c3bad01ccb3a37d176497d1fcf6884d38bdec082103c001a87779c81f5c1a134b8432898aa6dbca5b487efe8d9098055c7ca5e1d74f033d1bafae79f871d9a26e701073deea9110621fb466c23ab1d43d421bc9d2b2640337f5202fffad96baf56a80474eea08f53fe6cefb666527efdd634b95d891b39303d8ea4bbf1b494e77a6bcf8fe952ec3ad95f2a401643ebe78eaac2088867098a403980a9aa66cd5731904e546d5c4375916f0ae4caf2ea0f2b85d2139a1e04c0f3103cbe95ed246e5c2fd56df0107da9ed90b2f7b061141e3eb84fdde2afeaaa95e99039d876cc2292461db34883fde0ba0eff656ea81a93b378db41b627929e20fb6be03284157b4f49b3ba62de4a4595dc797d2f392e4d187666c67d7333df30fb5eb6003041d1da5d30d322e03832543a845465bf521a8b1c4c9c219e581679bf54480de037523135e467f58360f1556fdaa1862a0fd20db52e666956455a1805b030181c90390a2aea049ec967d0f1baacc109a5c1937f096c2440b3dd7b9e170738bbb303a03b6fa2b5629fe373928c9d497787b1b8b69e1359d3294d199325f1ee57da3830203a64132ba06e155fefe2f450925c7d9e3925ed6f0aa93b3bdce77436ff0a13dcc0317a1287711c12496c8caff4c99374a4b7787a49d35d17fb867bef479fec30c9e030d83e012b5ca06c4d5b798a6f520333ec4a4be8127331fe651c038eb3a76aeb703933428dc372dc6b212b653ccfaf3784cade82d14b55692b05ad53e3af5b52d7e035bcafcf808c831e229819f1bf7a7dd812ac032d4a31b6fcad1dab276c4b17add03c4ba858bb009b9a5bd477b6a649667b194cce14d8291583e4351d6c34dbb5d0d03b779bbc0725a3f5bc155061529b941c30165a9ee577a480ef468a1cd5509d3b703f8fe5f7e0ca57e7da21ae9b6d9cfda7433b7f970af26b520a65a36bf4d2dac9703f2e471acf814fb0e35e4f3e3f2287b7fd9197c260ae40962618240846e501660038f297cf12e827a63704cbc0c25a363de07b71ad5691e4c7b08bcbcd40a825df405581e03fd970384e443fdb8179cd5f7742ff3b358b1986aafd07f4c39b81d0fe004020346b82b3c62f8ab111b0f4398bcbf507c1c54c13848a4f3e07e7d47b2573d72580354d266adb42079a47f3b70152c3ef94aac8a425b31f434c3c14019b2ee2b309a0605581e03d3e165c04bbf11ef528cebce7f06b57b04febf249c8122804181c69e4007011bffffffffffffffff05581e0385fdef739bfa66eba33bcdae9db4c30f607f542dc68f3ec2cd4ac7cd000c06470151ce2013ec00035edf13801c825eff9e830686ab8142a516392ebedb879aca5a3f1a3bd74920c005581e03462ddfc2593c772a4ec6128c9172da3c487c88b5812c8b037fca8fafe0040103084840a116a5187ae885cf06d80df1fe9b307382e50366883861342ed5663c790326ced49a9e7cf512581ed12edc93eaa0e46106a0c30bec4e5fabe91c56dfe17405581d0250abdf1f50f9a3e2355b16d535059aa9277c1216aa631ae4f308291f040105581d02c44bafea4be0800c03271865dffb8d7cedabc6410a36bc03900a43dd0847049c9f169d8e0004592c1d608060405234801561001057600080fd5b50600436106101b95760003560e01c80636a627842116100f9578063ba9a7a5611610097578063d21220a711610071578063d21220a7146105da578063d505accf146105e2578063dd62ed3e14610640578063fff6cae91461067b576101b9565b8063ba9a7a5614610597578063bc25cf771461059f578063c45a0155146105d2576101b9565b80637ecebe00116100d35780637ecebe00146104d757806389afcb441461050a57806395d89b4114610556578063a9059cbb1461055e576101b9565b80636a6278421461046957806370a082311461049c5780637464fc3d146104cf576101b9565b806323b872dd116101665780633644e515116101405780633644e51514610416578063485cc9551461041e5780635909c0d5146104595780635a3d549314610461576101b9565b806323b872dd146103ad57806330adf81f146103f0578063313ce567146103f8576101b9565b8063095ea7b311610197578063095ea7b3146103155780630dfe16811461036257806318160ddd14610393576101b9565b8063022c0d9f146101be57806306fdde03146102595780630902f1ac146102d6575b600080fd5b610257600480360360808110156101d457600080fd5b81359160208101359173ffffffffffffffffffffffffffffffffffffffff604083013516919081019060808101606082013564010000000081111561021857600080fd5b82018360208201111561022a57600080fd5b8035906020019184600183028401116401000000008311171561024c57600080fd5b509092509050610683565b005b610261610d57565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561029b578181015183820152602001610283565b50505050905090810190601f1680156102c85780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6102de610d90565b604080516dffffffffffffffffffffffffffff948516815292909316602083015263ffffffff168183015290519081900360600190f35b61034e6004803603604081101561032b57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610de5565b604080519115158252519081900360200190f35b61036a610dfc565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b61039b610e18565b60408051918252519081900360200190f35b61034e600480360360608110156103c357600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060400135610e1e565b61039b610efd565b610400610f21565b6040805160ff9092168252519081900360200190f35b61039b610f26565b6102576004803603604081101561043457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020013516610f2c565b61039b611005565b61039b61100b565b61039b6004803603602081101561047f57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16611011565b61039b600480360360208110156104b257600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113cb565b61039b6113dd565b61039b600480360360208110156104ed57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113e3565b61053d6004803603602081101561052057600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113f5565b6040805192835260208301919091528051918290030190f35b610261611892565b61034e6004803603604081101561057457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81351690602001356118cb565b61039b6118d8565b610257600480360360208110156105b557600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166118de565b61036a611ad4565b61036a611af0565b610257600480360360e08110156105f857600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060408101359060608101359060ff6080820135169060a08101359060c00135611b0c565b61039b6004803603604081101561065657600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020013516611dd8565b610257611df5565b600c546001146106f457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c55841515806107075750600084115b61075c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180612b2f6025913960400191505060405180910390fd5b600080610767610d90565b5091509150816dffffffffffffffffffffffffffff168710801561079a5750806dffffffffffffffffffffffffffff1686105b6107ef576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526021815260200180612b786021913960400191505060405180910390fd5b600654600754600091829173ffffffffffffffffffffffffffffffffffffffff91821691908116908916821480159061085457508073ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff1614155b6108bf57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f556e697377617056323a20494e56414c49445f544f0000000000000000000000604482015290519081900360640190fd5b8a156108d0576108d0828a8d611fdb565b89156108e1576108e1818a8c611fdb565b86156109c3578873ffffffffffffffffffffffffffffffffffffffff166310d1e85c338d8d8c8c6040518663ffffffff1660e01b8152600401808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b1580156109aa57600080fd5b505af11580156109be573d6000803e3d6000fd5b505050505b604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff8416916370a08231916024808301926020929190829003018186803b158015610a2f57600080fd5b505afa158015610a43573d6000803e3d6000fd5b505050506040513d6020811015610a5957600080fd5b5051604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905191955073ffffffffffffffffffffffffffffffffffffffff8316916370a0823191602480820192602092909190829003018186803b158015610acb57600080fd5b505afa158015610adf573d6000803e3d6000fd5b505050506040513d6020811015610af557600080fd5b5051925060009150506dffffffffffffffffffffffffffff85168a90038311610b1f576000610b35565b89856dffffffffffffffffffffffffffff160383035b9050600089856dffffffffffffffffffffffffffff16038311610b59576000610b6f565b89856dffffffffffffffffffffffffffff160383035b90506000821180610b805750600081115b610bd5576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526024815260200180612b546024913960400191505060405180910390fd5b6000610c09610beb84600363ffffffff6121e816565b610bfd876103e863ffffffff6121e816565b9063ffffffff61226e16565b90506000610c21610beb84600363ffffffff6121e816565b9050610c59620f4240610c4d6dffffffffffffffffffffffffffff8b8116908b1663ffffffff6121e816565b9063ffffffff6121e816565b610c69838363ffffffff6121e816565b1015610cd657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f556e697377617056323a204b0000000000000000000000000000000000000000604482015290519081900360640190fd5b5050610ce4848488886122e0565b60408051838152602081018390528082018d9052606081018c9052905173ffffffffffffffffffffffffffffffffffffffff8b169133917fd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d8229181900360800190a350506001600c55505050505050505050565b6040518060400160405280600a81526020017f556e69737761702056320000000000000000000000000000000000000000000081525081565b6008546dffffffffffffffffffffffffffff808216926e0100000000000000000000000000008304909116917c0100000000000000000000000000000000000000000000000000000000900463ffffffff1690565b6000610df233848461259c565b5060015b92915050565b60065473ffffffffffffffffffffffffffffffffffffffff1681565b60005481565b73ffffffffffffffffffffffffffffffffffffffff831660009081526002602090815260408083203384529091528120547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14610ee85773ffffffffffffffffffffffffffffffffffffffff84166000908152600260209081526040808320338452909152902054610eb6908363ffffffff61226e16565b73ffffffffffffffffffffffffffffffffffffffff851660009081526002602090815260408083203384529091529020555b610ef384848461260b565b5060019392505050565b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b601281565b60035481565b60055473ffffffffffffffffffffffffffffffffffffffff163314610fb257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f556e697377617056323a20464f5242494444454e000000000000000000000000604482015290519081900360640190fd5b6006805473ffffffffffffffffffffffffffffffffffffffff9384167fffffffffffffffffffffffff00000000000000000000000000000000000000009182161790915560078054929093169116179055565b60095481565b600a5481565b6000600c5460011461108457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c81905580611094610d90565b50600654604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905193955091935060009273ffffffffffffffffffffffffffffffffffffffff909116916370a08231916024808301926020929190829003018186803b15801561110e57600080fd5b505afa158015611122573d6000803e3d6000fd5b505050506040513d602081101561113857600080fd5b5051600754604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905192935060009273ffffffffffffffffffffffffffffffffffffffff909216916370a0823191602480820192602092909190829003018186803b1580156111b157600080fd5b505afa1580156111c5573d6000803e3d6000fd5b505050506040513d60208110156111db57600080fd5b505190506000611201836dffffffffffffffffffffffffffff871663ffffffff61226e16565b90506000611225836dffffffffffffffffffffffffffff871663ffffffff61226e16565b9050600061123387876126ec565b600054909150806112705761125c6103e8610bfd611257878763ffffffff6121e816565b612878565b985061126b60006103e86128ca565b6112cd565b6112ca6dffffffffffffffffffffffffffff8916611294868463ffffffff6121e816565b8161129b57fe5b046dffffffffffffffffffffffffffff89166112bd868563ffffffff6121e816565b816112c457fe5b0461297a565b98505b60008911611326576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180612bc16028913960400191505060405180910390fd5b6113308a8a6128ca565b61133c86868a8a6122e0565b811561137e5760085461137a906dffffffffffffffffffffffffffff808216916e01000000000000000000000000000090041663ffffffff6121e816565b600b555b6040805185815260208101859052815133927f4c209b5fc8ad50758f13e2e1088ba56a560dff690a1c6fef26394f4c03821c4f928290030190a250506001600c5550949695505050505050565b60016020526000908152604090205481565b600b5481565b60046020526000908152604090205481565b600080600c5460011461146957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c81905580611479610d90565b50600654600754604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905194965092945073ffffffffffffffffffffffffffffffffffffffff9182169391169160009184916370a08231916024808301926020929190829003018186803b1580156114fb57600080fd5b505afa15801561150f573d6000803e3d6000fd5b505050506040513d602081101561152557600080fd5b5051604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905191925060009173ffffffffffffffffffffffffffffffffffffffff8516916370a08231916024808301926020929190829003018186803b15801561159957600080fd5b505afa1580156115ad573d6000803e3d6000fd5b505050506040513d60208110156115c357600080fd5b5051306000908152600160205260408120549192506115e288886126ec565b600054909150806115f9848763ffffffff6121e816565b8161160057fe5b049a5080611614848663ffffffff6121e816565b8161161b57fe5b04995060008b11801561162e575060008a115b611683576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180612b996028913960400191505060405180910390fd5b61168d3084612992565b611698878d8d611fdb565b6116a3868d8c611fdb565b604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff8916916370a08231916024808301926020929190829003018186803b15801561170f57600080fd5b505afa158015611723573d6000803e3d6000fd5b505050506040513d602081101561173957600080fd5b5051604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905191965073ffffffffffffffffffffffffffffffffffffffff8816916370a0823191602480820192602092909190829003018186803b1580156117ab57600080fd5b505afa1580156117bf573d6000803e3d6000fd5b505050506040513d60208110156117d557600080fd5b505193506117e585858b8b6122e0565b811561182757600854611823906dffffffffffffffffffffffffffff808216916e01000000000000000000000000000090041663ffffffff6121e816565b600b555b604080518c8152602081018c9052815173ffffffffffffffffffffffffffffffffffffffff8f169233927fdccd412f0b1252819cb1fd330b93224ca42612892bb3f4f789976e6d81936496929081900390910190a35050505050505050506001600c81905550915091565b6040518060400160405280600681526020017f554e492d5632000000000000000000000000000000000000000000000000000081525081565b6000610df233848461260b565b6103e881565b600c5460011461194f57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c55600654600754600854604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff9485169490931692611a2b9285928792611a26926dffffffffffffffffffffffffffff169185916370a0823191602480820192602092909190829003018186803b1580156119ee57600080fd5b505afa158015611a02573d6000803e3d6000fd5b505050506040513d6020811015611a1857600080fd5b50519063ffffffff61226e16565b611fdb565b600854604080517f70a082310000000000000000000000000000000000000000000000000000000081523060048201529051611aca9284928792611a26926e01000000000000000000000000000090046dffffffffffffffffffffffffffff169173ffffffffffffffffffffffffffffffffffffffff8616916370a0823191602480820192602092909190829003018186803b1580156119ee57600080fd5b50506001600c5550565b60055473ffffffffffffffffffffffffffffffffffffffff1681565b60075473ffffffffffffffffffffffffffffffffffffffff1681565b42841015611b7b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f556e697377617056323a20455850495245440000000000000000000000000000604482015290519081900360640190fd5b60035473ffffffffffffffffffffffffffffffffffffffff80891660008181526004602090815260408083208054600180820190925582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98186015280840196909652958d166060860152608085018c905260a085019590955260c08085018b90528151808603909101815260e0850182528051908301207f19010000000000000000000000000000000000000000000000000000000000006101008601526101028501969096526101228085019690965280518085039096018652610142840180825286519683019690962095839052610162840180825286905260ff89166101828501526101a284018890526101c28401879052519193926101e2808201937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081019281900390910190855afa158015611cdc573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff811615801590611d5757508873ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b611dc257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f556e697377617056323a20494e56414c49445f5349474e415455524500000000604482015290519081900360640190fd5b611dcd89898961259c565b505050505050505050565b600260209081526000928352604080842090915290825290205481565b600c54600114611e6657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c55600654604080517f70a082310000000000000000000000000000000000000000000000000000000081523060048201529051611fd49273ffffffffffffffffffffffffffffffffffffffff16916370a08231916024808301926020929190829003018186803b158015611edd57600080fd5b505afa158015611ef1573d6000803e3d6000fd5b505050506040513d6020811015611f0757600080fd5b5051600754604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff909216916370a0823191602480820192602092909190829003018186803b158015611f7a57600080fd5b505afa158015611f8e573d6000803e3d6000fd5b505050506040513d6020811015611fa457600080fd5b50516008546dffffffffffffffffffffffffffff808216916e0100000000000000000000000000009004166122e0565b6001600c55565b604080518082018252601981527f7472616e7366657228616464726573732c75696e743235362900000000000000602091820152815173ffffffffffffffffffffffffffffffffffffffff85811660248301526044808301869052845180840390910181526064909201845291810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb000000000000000000000000000000000000000000000000000000001781529251815160009460609489169392918291908083835b602083106120e157805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016120a4565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114612143576040519150601f19603f3d011682016040523d82523d6000602084013e612148565b606091505b5091509150818015612176575080511580612176575080806020019051602081101561217357600080fd5b50515b6121e157604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f556e697377617056323a205452414e534645525f4641494c4544000000000000604482015290519081900360640190fd5b5050505050565b60008115806122035750508082028282828161220057fe5b04145b610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f64732d6d6174682d6d756c2d6f766572666c6f77000000000000000000000000604482015290519081900360640190fd5b80820382811115610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f64732d6d6174682d7375622d756e646572666c6f770000000000000000000000604482015290519081900360640190fd5b6dffffffffffffffffffffffffffff841180159061230c57506dffffffffffffffffffffffffffff8311155b61237757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f556e697377617056323a204f564552464c4f5700000000000000000000000000604482015290519081900360640190fd5b60085463ffffffff428116917c0100000000000000000000000000000000000000000000000000000000900481168203908116158015906123c757506dffffffffffffffffffffffffffff841615155b80156123e257506dffffffffffffffffffffffffffff831615155b15612492578063ffffffff16612425856123fb86612a57565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff169063ffffffff612a7b16565b600980547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff929092169290920201905563ffffffff8116612465846123fb87612a57565b600a80547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff92909216929092020190555b600880547fffffffffffffffffffffffffffffffffffff0000000000000000000000000000166dffffffffffffffffffffffffffff888116919091177fffffffff0000000000000000000000000000ffffffffffffffffffffffffffff166e0100000000000000000000000000008883168102919091177bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167c010000000000000000000000000000000000000000000000000000000063ffffffff871602179283905560408051848416815291909304909116602082015281517f1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1929181900390910190a1505050505050565b73ffffffffffffffffffffffffffffffffffffffff808416600081815260026020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b73ffffffffffffffffffffffffffffffffffffffff8316600090815260016020526040902054612641908263ffffffff61226e16565b73ffffffffffffffffffffffffffffffffffffffff8085166000908152600160205260408082209390935590841681522054612683908263ffffffff612abc16565b73ffffffffffffffffffffffffffffffffffffffff80841660008181526001602090815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600080600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663017e7e586040518163ffffffff1660e01b815260040160206040518083038186803b15801561275757600080fd5b505afa15801561276b573d6000803e3d6000fd5b505050506040513d602081101561278157600080fd5b5051600b5473ffffffffffffffffffffffffffffffffffffffff821615801594509192509061286457801561285f5760006127d86112576dffffffffffffffffffffffffffff88811690881663ffffffff6121e816565b905060006127e583612878565b90508082111561285c576000612813612804848463ffffffff61226e16565b6000549063ffffffff6121e816565b905060006128388361282c86600563ffffffff6121e816565b9063ffffffff612abc16565b9050600081838161284557fe5b04905080156128585761285887826128ca565b5050505b50505b612870565b8015612870576000600b555b505092915050565b600060038211156128bb575080600160028204015b818110156128b5578091506002818285816128a457fe5b0401816128ad57fe5b04905061288d565b506128c5565b81156128c5575060015b919050565b6000546128dd908263ffffffff612abc16565b600090815573ffffffffffffffffffffffffffffffffffffffff8316815260016020526040902054612915908263ffffffff612abc16565b73ffffffffffffffffffffffffffffffffffffffff831660008181526001602090815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b6000818310612989578161298b565b825b9392505050565b73ffffffffffffffffffffffffffffffffffffffff82166000908152600160205260409020546129c8908263ffffffff61226e16565b73ffffffffffffffffffffffffffffffffffffffff831660009081526001602052604081209190915554612a02908263ffffffff61226e16565b600090815560408051838152905173ffffffffffffffffffffffffffffffffffffffff8516917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef919081900360200190a35050565b6dffffffffffffffffffffffffffff166e0100000000000000000000000000000290565b60006dffffffffffffffffffffffffffff82167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff841681612ab457fe5b049392505050565b80820182811015610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f64732d6d6174682d6164642d6f766572666c6f77000000000000000000000000604482015290519081900360640190fdfe556e697377617056323a20494e53554646494349454e545f4f55545055545f414d4f554e54556e697377617056323a20494e53554646494349454e545f494e5055545f414d4f554e54556e697377617056323a20494e53554646494349454e545f4c4951554944495459556e697377617056323a20494e53554646494349454e545f4c49515549444954595f4255524e4544556e697377617056323a20494e53554646494349454e545f4c49515549444954595f4d494e544544a265627a7a723158207dca18479e58487606bf70c79e44d8dee62353c9ee6d01f9a9d70885b8765f2264736f6c63430005100032032e2bc0c0ff22609eac8f10e1c8736f3e780dcb85055451e7ac674e2667ce4b570313a4031999f138fd5a2cd6ffb7ab63e1107b95a0389545c40aadc91bddd993fa00582103e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af051cb2aee8a60ca86f8bda208376042d19788005820026cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c68854c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200582002b661198733f53bb9b621ec808effd2e8a3d86db6962103738e13951e49aab0470aa8ddb6865c490219014000582002575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b582082b7b380037e2868c106004fab2c2e17609e3a9fa13840bbf296721c0dd3c0a5005820025a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a84f24bf0960cb159873685c3ddaa1a61002184400582103f6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c704101005821031f25289b5c9db29d46c3566463f71796d2e07c9a7a96a888214082f19288cd0048041f4dbd95f5b05600582002f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee358206638103b0000000000006ce4eb2514608b4f000000000000002abfbcea99faf90058200252222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f548b683c400457ef31f3c27c90acb6ab69304d1b770218480219f44505581d021ec3c811e880dc80a981a5503c30597b49fa4ab1144a8a7f423bb3010701192c1d02181c021917570384a59ac3f31976525b65ab661b72b02bf1fcd35c019ba2d4839d69d9d1f4a5a003555d3a1deea1603addefab3179dc6c0d4a9474c55a7db59a22c15f2d96be5a46039d76bb6f100ce6c89af8bf229d75e5dfef89b17dcdaf6562beae642253966b910359cdaf928aa0094243eecdd88a705298bb96fccf40245caf2827654f28102a0903e2e349c733b734f0bd6cbb14b2476a91752f85e5984909e9a9d0c3e9f346a20a03913179e69808e46ea766db4e7697baea9473ce78179d47862c65b94d8274999c03cd306511fa5054b7d3923ad15479e1c96a92f08ef36e77479e23f347f3c4d9420219ffff030960f818f714050360f6673cfc01ebfe16978b10dee5d8d1250ffa87fbc057520317faada0b2554524cbf0f158ab366854523cd3abc1048475668c620653ab83e7035e31d3f580b3388a11a9e925ee6c701a15e53c0759ff9bbbb440790a795ee291033285dac1afedb332e73b2038c86cb75d99b286cff8a15bdc0d1c428f745f1bd7036b0f57e92ea0ded986ff55bfee8906ea457f937b4d01075701114ae582653f6f0219ffff03d1012b39ca70a0069aa165e552fde8f16eade9315bf29740ec1924edda2f5d9203e714ee21efa38f19d5dd377eaca0a9bfd6e57d8403953573a3091efa67c75f4203624f37476107a5562c070d8aab9cc9e673a7c691233ee4f6209caf819feba8460219ffff0381b8e81238e4a04505742054c12b577bb7526abacd7ae5d0a452ca9e68fe00c703692a51d70221dd95c4fad9be33cd6726af0c72ace00ed5e72eb4d6af1792fe4d0366e2f37e2c5a8ec2a5064e6113d9007f112346aad335e1ba7d3160b6285d2f2c030d8a638e05c837ac5d5c91008da969fc6c102803b16fc16288b5935c7942e05303f369a7114e0e139054bb39a7da90c73af10dc967e135300b1a51ab3834f1513f037642861636360312dd872552a2f5305f921f721c78767285a87cb34dc209b26a030c48efdb98e768c8f29ad854ed8198f64ca85095f56c66188d74be5de2475ee5034db5d7a65715f046a5206f0ee013c9d4385d79f4793b68f2b31342e0f12ede6c03d6dd6ddb2d15eae9c7757764327bd92738cbfe3930c95e0926fb8b911c5edda60219ffff032d970fa441a8ceccf173b3f3583416fdf2449f0d0979b81b303ff052f05d13e60304c508eea3158972c739232daebec044ee3e6c74de4486d304fb5705f116a350039ac7b92c52f57bec422e4bd5d04cd7305dc6f5c199d06b767f7dde98260cce2003086f5ea5b03703666c4e964c11977539ee7f769dc1679e38023a236c5cea1b7c03ab9015181cf9bd4d07ea66fd55eb5fd4e66496ac9a3bf79de4b9696d29f5d71c03ff59793356b794601134356afcae4ce0eb405722c5904f5a9e97034357cd91210368194bed561141a5c1b8e690ccf34ba45ee2986ce9b7f482fab6d276a694604803e4240f626d977518478184087ae0611dfceef30d260e0b5f1549f451b0e15237038a1295b965bcb0217dc2723e69c4218f7a2a87f3aae34950fd4a272a18f95bd403f42e0c256715d1ee3a4a6918149678b09d3d3e0774f35e58b67aef0392cf880703ca95f90fbba770647f79ed35ab45489a669f692df35a5089fe8709b805f119e6032dd21f3d2a7dab53b67c1ebc0500b0839f845e97d084f7e876ead8d1c6ea1843034c0a0f68a78a39af4f96714dff89d0b7988c498313636e977901d5a999d8c84303bf068389eaf23c697e1408deb9a4ea01bd287305616a4ddbb752c1a7c637eb780369621e88944f74018f58e7814d9b6d6aa3e14e89d8790fc31c5d276c6eeb6a4a032cee9909e0c373e751b9a67e6d2c426423c2d60071ef563cb8f257124bbf8b9a03941a99a652b0da2b1442664bb924322bbd09ab4d7aafeda0cd655a25e105317403667c5365f5cb236369b4f0c5acc855da9a7ba5fad565d6bfd4b2cd7cc66cfa13030143e65770620f06dbdd4af525a0b1a64e18b4e0863566e3fc0d068bde81a11603a1062e64b6c0c1a3b115614015a0f389958eaca757a36e62e2beaf4c6074ff3e03542abb1e1fa4a79109a0d73cc3745d546501c2afea0bc907a4393a48e95f147803ba6f19341599e591e41fd0519c876484deb506094134082f0864e9c90f90cc30036ed1d7b297d52b277036171f5e79bf443c613523903d473841a7eb5bc8b1946c03ca6a57dd23e807bbbca35af47076d23a451df1debffcef0a12643b6bed8517a5038720cd405944bbed2ed9c99ac85e5a5aff3588574588e22c83a113ef88966cbf03d33019e4c7af24bce42b4f99ae30ecea23cdb837401598cdcbf363f439108e7c03bb1b72dcc996e76105a95e902b28fffa306563baa068c9a3c118c6a33eb7a03503dbe37297cf24f2f4dedf11a33e12269f0d9fa67b2b0c44b7336340f179a49b3f032433a5c47dbf5e5e31ea74c894bfe9d3cb035453a90fde1f439c7858a04c34d80372f6536a41790269753f6922540475872ab7f3bc4d672e6430221e5c5ec43493035a07e39e3ed1473b0daea069bb9a1d2adbb908d2abf49394cadf5f51bb2d762d031390d695ddf998b7577fa12fb1d6de9afc3906932ed19f47aeb3dce0df6a5d1803f83dfd71604cab3958ddb484ee3e6b8b6e52044f99d535bd84ee2321f547e8f203b49a439c70815a034488f438b88d072612c57dab54f930ca493a2c1bda93e2a903f4ae4c0c57037d3fb1a66deda8859bd075cd1b1b8a27c4db7cac5557effe70a903298138ff149e07bdf9c25f93380a723ddd55a9357ae5a973a9009232597b2a61039bc368cbf373721b248693e3151951a8782d175a9e3b7c1e1e6a4904a2302e590365fa6211e1b492da2c98f1220085e282ed72b579fa743580e6de8b70e84da626038686337b2bd55e8c5033a70fa68ec75122364e509041a1bb9bad8264b378073403fe2e601f7831716635ff577b42df3ee52e2784dc27cdf8f9d6b07a9a4e3d302403ca67e5ad9a0f7657c1cabd8a375b397a50b0f7af217e626ac0d51f44b6b1524b03e23c58c95b44b8fd12895181146a8f586b3c37ca55cd42b7c02697189044bd610380661da0f1a245ce5de68c63e8a1f0c239e8287c63234f96f3c23c70f3ddbe29037d577bac02759343ae457a5340d1f712c33b2e76ab6b7619d44dfee6f8f107cf0386f51603cb09df5a7bb925d88c0350d4d24d1c28598fd380ffe430a602f1151a035b69615a1a4a13d2a355ad8ebef639873b5dd93438f31af82cce511cda941f5403eefff2d897cb203c541aead358162b0d5a6c057941e0348da56dc83e933dd927031f50c6594f3089698e950ec35c3668c10574313715358cecafef3f17c75c7fd8035bb356ea469270bc941971fa5f3f13eebd7182c4d07b1e5936b749524c6a033903f0146a70d2698de30df9bfc84add4bf7824c3f8fc2c13bda287f68de33cb88bc035f5a1117d6968dc818545bf3caa0ec44f7d8f11c5d5bfd1e7303ed597056f19603f5c38af6f709db9cee967585e1c3475c1b2c419a7f025c8551ca0211a787993d036d18433acfe02224d8dbca55b504bac5c832f9d266ed9a338d58ebe2d0226eb8035c5b31895362a7e4b5f826c1f8c285b6ec5f307d83db0fc29849db070028a1660397dc7269c9bc8321eb76c84812b6cfd4bfc227934e113e65ba20262d933bd8c003ffb8203d5d65af0e0d0c11e1c9f688eb67ac46c262291bb5e3f499f21b319fc1031286c01bb09b3cc54808700c2d7ac300eff42defcf47f011d5c3b79e1dc0f8d0038603b9f2e5ca0befcd270cedbb962e2dfdfa5f6566a2bbcfd93fc7529f91782c03e0dc6be0773960cabdc724cec9b09fa9b9e9aff9d76df0455b2bf1d795ca29a3031d93f60f105899172f7255c030301c3af4564edd4a48577dbdc448aec7ddb0ac0605581e0302c2f650d1dabe4467d5038dd0e789abe371f9e6fada398839360fa2a007011bffffffffffffffff03767c4368cc25f0f8d30ce48e9ae71faad89a9509f0088b9a32bb32c64256cea903525e052104f94a17543f0e46570e8465a38e406fbcff6ae7447e93638dabb95f05581d02c55d89f1df9abd2eee3f6e0a8efe53f929d36bcd3a7e134f702da6fb0c03473f6f1420567ef605581d02ddb0dde0d879c2e4144fda652d366909aa742fd9ade130583bcdeaff0c0246622ab6fe13c805581d02c070fc7ed870826db358006f3c0eca572f8aac6c268ac0130805f5f20c19028b4807cb94878c0c4a3305581d02d899c0d4f975f439752f5bada431134b4990a236327a0ef46bbe37d40c0146c2f8959c742b02190d0103b2dff530257219ef8af877b1991b5006303f8ada6c2ab655e88057f2e24e6628038f59510413678d21bce67a691343a22918b9f49fab93e726d395ab19b0ff1b3205581e033fce37f1900820391700fe9d561e194a0505d7e9a2fae48a6ba017656007011bffffffffffffffff03663cad5d90e1c7c8e244b9dfa750ecdd937f6177dc94022a3f84f6cc5e01f36f05581e03c3ce4aa708f9b69e2d04abe8521021eac25dc3e71247b3f6d7a29fb2e00c01470128c3b52bf00003c21a9b4dfbf17303d1461773454a92d7a65aeeec89477b40917c544fdb7a30b305581e0379411149f46fa7df406872f14682ec1b9378577d92d9bb00b1b91df2000c014707132bc2cc801402194bd603c56988604cfe964abf785e74135cd1e1d2a2d1736ca608ccf42d8a5a663e865f03b98c164d99541c19a09eaaaa42ad4c255402961ca54727e73c95dd3f2e3c3b5e03538c5adc584217b6fdd5edd20b5471ced13546b8f7cae0a93a42ea1d741914a503377723c880b302d7a7b353447a4b8282d3a150a7fefe001dc91ff826c38e8da5031f8ff9d61c0b130ed2ae177f6d9f81906f1ae7328441c79e31bc75d2f5f30b7c039d350daceb38a0d75a940136635164832bcba6e631c015753a8a6cc0c64986c50219ffff03ffefe22c9391fe6b210ff1ea3839ff0c3573cfa695b0f2241309a6324df6e1bb0219ffff03a52ee312c27564ec5de33a92ebd25e0ac0806d9c71b2e3af0eab6283fed4d8540219ffff03169629eb79b5dee32abe97e9bdd17d7a361e12b9d47607fa1864699b4d27bc0e0219ffff0323c2256eea45e2659ade6c954eafd18a8cb38a7b6e072279869b756a70fddc6a0219ffff0354de8a7f20ea7c86963380665fcd6c91c606701f7a9717353b03820924a15884037df6def0c81558aafd4b09a5e5abf7e5cb71520ff85f4d18f733cb6440c7af54034d89ac532bed43cdd30c2e6c649bad88af20c8e5363a8378b3324795c138aa880334a29dc51fc0439b0b59dde57221b62b184cc7d81a96dc8ee72ebb30df8d89eb031f70232a64efcadb6156873cb4b48115f3f5f4cb3b0eb45ed496d84bffffa42c03e4ea3f0745592596ced9342a16166b57665bd078e426928161ebe09c17cb6e16039df94c5c71d55874cebd7d87f9fedaf39adc3ce09ca439cec77fc3cda6a45bba03c0be3be853f2ae0372aaa43bb28fbc1ddbfbbaf0a7c0e071aea06fbd6402c51f036a9b4a91548333ca179a6bcc96da6acb6a00c830f9fc54f586cb21894a55da5103c098c29465c3b8d549528d72289259706b25edd6299bf6197c6564ed2355d3e0034fa3d78fa38327dfafd5bfa12105833b15a37fe67d9c8c7612436501ea271deb035d78d22bf3533dc43fba810f32b15e1316cb8b976a31b8c1ed07c1d988962fca0324e2a710f3c13e95cc1882b0a32e55c24c5b8e7eb3cbcdba36c6dd5a46679ba303b5ece83bd6b18170337794d39a16c4f17c7de60be65e471a515028141375b9e5037b3edac7e40d418adfb726d4d7c70380d67508a40dddcb901bc68474cd39433e03dce5b7a67506788298d89b90cf942cc0aff8c26bbfed29d07b543b1be91489790368b696f4437d36244d6f07105571bc3b221977f692aa5a0b3f97191aa920c45b034d1d16c1286d9147f95d48ab693053d47ce3e095ec4af1dad3af3f9d41778e7f0322d04b8d0ca8026323d6377d1743044cd92db9461643171658ca57eb0862f8b603055020a8edabbd75812e9667884af1dbda2de45854efc6d5b988a4404adf5530037e12accbaa7f8379e0674319fb56524c5fde3506685bac6bc9b8985937b011db03a74cadab84310d49684cb8914aa35c1464e42c8081c8051f74d7291de33aaa1a039df5ba3a8dc7835e93b6cb8fe577e439e669eef9d5b3cbbddb904142f9e614f2035fe0158b1c329ab9ea1cf5387213d2a6e258aba5dec083f844216161218dcb560360291c31d3bfe2e5f5588724f50fd9af6f7e45b08bfc7a8d2acdbdaf94f20aa403d3cb20fad8435b09f179989e1bf8f9b91553423417ffca62ded425f76bd552a50361c8aaf37e1f51a3471653b082265f6d14c87b3e5a1f7cc2e869f91f078e39aa03ea4c7bb4bda02e6e154e1dc2151c06398bfc2f615f37bdedcdd6da8e9c45238c038f7186ffcd90c646d1b6805daf33d261e7322b894de7ddd5a9d09c922dee3eb503fe52f136e68e4c7581e83ad5a1998a9c148f93e89280b5ba593b04d166b6f8f703aaf1fe5995efcf21f25ac79c4647ec2e2395830221770b8553ccdd245e79646c039acdbbb39aa484a6e033d5050283fdb64848377a02afa9fd1997aab86f816c7f03672ec410bd8b35bf6c8d7263879ea2561687d0eb6ace8aed4c40b4dc6076745e05581e0316d4bd7dacd97d0c571e0b1d90bc117dad36182b78878703b2205a43a00c0344ee6b280005581e034b2d2decf95050be5986747389e5b75a9a8667f14af23b7ac1f2f78df0040104591b126080604052600436106101395760003560e01c8063751039fc116100ab578063a9059cbb1161006f578063a9059cbb1461034a578063bf474bed1461036a578063c9567bf914610380578063d34628cc14610395578063dd62ed3e146103b5578063ec1f3f63146103fb57600080fd5b8063751039fc146102e15780637d1db4a5146102f65780638da5cb5b1461030c5780638f9a55c01461033457806395d89b411461014557600080fd5b8063313ce567116100fd578063313ce5671461020a57806331c2d847146102265780633bbac5791461024857806351bc3c851461028157806370a0823114610296578063715018a6146102cc57600080fd5b806306fdde0314610145578063095ea7b3146101815780630faee56f146101b157806318160ddd146101d557806323b872dd146101ea57600080fd5b3661014057005b600080fd5b34801561015157600080fd5b506040805180820182526004815263434c415960e01b6020820152905161017891906115da565b60405180910390f35b34801561018d57600080fd5b506101a161019c366004611651565b61041b565b6040519015158152602001610178565b3480156101bd57600080fd5b506101c760115481565b604051908152602001610178565b3480156101e157600080fd5b506101c7610432565b3480156101f657600080fd5b506101a161020536600461167d565b610453565b34801561021657600080fd5b5060405160098152602001610178565b34801561023257600080fd5b506102466102413660046116d4565b6104bc565b005b34801561025457600080fd5b506101a1610263366004611799565b6001600160a01b031660009081526004602052604090205460ff1690565b34801561028d57600080fd5b50610246610551565b3480156102a257600080fd5b506101c76102b1366004611799565b6001600160a01b031660009081526001602052604090205490565b3480156102d857600080fd5b506102466105a0565b3480156102ed57600080fd5b50610246610614565b34801561030257600080fd5b506101c7600e5481565b34801561031857600080fd5b506000546040516001600160a01b039091168152602001610178565b34801561034057600080fd5b506101c7600f5481565b34801561035657600080fd5b506101a1610365366004611651565b6106c6565b34801561037657600080fd5b506101c760105481565b34801561038c57600080fd5b506102466106d3565b3480156103a157600080fd5b506102466103b03660046116d4565b610a8f565b3480156103c157600080fd5b506101c76103d03660046117b6565b6001600160a01b03918216600090815260026020908152604080832093909416825291909152205490565b34801561040757600080fd5b506102466104163660046117ef565b610b17565b6000610428338484610b5e565b5060015b92915050565b60006104406009600a611902565b61044e90633b9aca00611911565b905090565b6000610460848484610c82565b6104b284336104ad85604051806060016040528060288152602001611ab5602891396001600160a01b038a166000908152600260209081526040808320338452909152902054919061123d565b610b5e565b5060019392505050565b6000546001600160a01b031633146104ef5760405162461bcd60e51b81526004016104e690611928565b60405180910390fd5b60005b815181101561054d576000600460008484815181106105135761051361195d565b6020908102919091018101516001600160a01b03168252810191909152604001600020805460ff19169115159190911790556001016104f2565b5050565b6005546001600160a01b0316336001600160a01b03161461057157600080fd5b3060009081526001602052604090205480156105905761059081611277565b47801561054d5761054d816113f1565b6000546001600160a01b031633146105ca5760405162461bcd60e51b81526004016104e690611928565b600080546040516001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600080546001600160a01b0319169055565b6000546001600160a01b0316331461063e5760405162461bcd60e51b81526004016104e690611928565b61064a6009600a611902565b61065890633b9aca00611911565b600e556106676009600a611902565b61067590633b9aca00611911565b600f557f947f344d56e1e8c70dc492fb94c4ddddd490c016aab685f5e7e47b2e85cb44cf6106a56009600a611902565b6106b390633b9aca00611911565b60405190815260200160405180910390a1565b6000610428338484610c82565b6000546001600160a01b031633146106fd5760405162461bcd60e51b81526004016104e690611928565b601354600160a01b900460ff16156107575760405162461bcd60e51b815260206004820152601760248201527f74726164696e6720697320616c7265616479206f70656e00000000000000000060448201526064016104e6565b601280546001600160a01b031916737a250d5630b4cf539739df2c5dacb4c659f2488d9081179091556107a09030906107926009600a611902565b6104ad90633b9aca00611911565b601260009054906101000a90046001600160a01b03166001600160a01b031663c45a01556040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107f3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108179190611973565b6001600160a01b031663c9c6539630601260009054906101000a90046001600160a01b03166001600160a01b031663ad5c46486040518163ffffffff1660e01b8152600401602060405180830381865afa158015610879573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061089d9190611973565b6040516001600160e01b031960e085901b1681526001600160a01b039283166004820152911660248201526044016020604051808303816000875af11580156108ea573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061090e9190611973565b601380546001600160a01b039283166001600160a01b03199091161790556012541663f305d7194730610956816001600160a01b031660009081526001602052604090205490565b60008061096b6000546001600160a01b031690565b60405160e088901b6001600160e01b03191681526001600160a01b03958616600482015260248101949094526044840192909252606483015290911660848201524260a482015260c40160606040518083038185885af11580156109d3573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906109f89190611990565b505060135460125460405163095ea7b360e01b81526001600160a01b03918216600482015260001960248201529116915063095ea7b3906044016020604051808303816000875af1158015610a51573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a7591906119be565b506013805462ff00ff60a01b19166201000160a01b179055565b6000546001600160a01b03163314610ab95760405162461bcd60e51b81526004016104e690611928565b60005b815181101561054d57600160046000848481518110610add57610add61195d565b6020908102919091018101516001600160a01b03168252810191909152604001600020805460ff1916911515919091179055600101610abc565b6005546001600160a01b0316336001600160a01b031614610b3757600080fd5b6008548111158015610b4b57506009548111155b610b5457600080fd5b6008819055600955565b6001600160a01b038316610bc05760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b60648201526084016104e6565b6001600160a01b038216610c215760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b60648201526084016104e6565b6001600160a01b0383811660008181526002602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b6001600160a01b038316610ce65760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b60648201526084016104e6565b6001600160a01b038216610d485760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b60648201526084016104e6565b60008111610daa5760405162461bcd60e51b815260206004820152602960248201527f5472616e7366657220616d6f756e74206d7573742062652067726561746572206044820152687468616e207a65726f60b81b60648201526084016104e6565b600080546001600160a01b03858116911614801590610dd757506000546001600160a01b03848116911614155b156110fa576001600160a01b03841660009081526004602052604090205460ff16158015610e1e57506001600160a01b03831660009081526004602052604090205460ff16155b610e2757600080fd5b610e536064610e4d600a54600d5411610e4257600654610e46565b6008545b859061142b565b906114b4565b6013549091506001600160a01b038581169116148015610e8157506012546001600160a01b03848116911614155b8015610ea657506001600160a01b03831660009081526003602052604090205460ff16155b15610f8e57600e54821115610efd5760405162461bcd60e51b815260206004820152601960248201527f4578636565647320746865205f6d61785478416d6f756e742e0000000000000060448201526064016104e6565b600f5482610f20856001600160a01b031660009081526001602052604090205490565b610f2a91906119e0565b1115610f785760405162461bcd60e51b815260206004820152601a60248201527f4578636565647320746865206d617857616c6c657453697a652e00000000000060448201526064016104e6565b600d8054906000610f88836119f3565b91905055505b6013546001600160a01b038481169116148015610fb457506001600160a01b0384163014155b15610fe157610fde6064610e4d600b54600d5411610fd457600754610e46565b600954859061142b565b90505b30600090815260016020526040902054601354600160a81b900460ff1615801561101857506013546001600160a01b038581169116145b801561102d5750601354600160b01b900460ff165b801561103a575060105481115b80156110495750600c54600d54115b156110f85760155443111561105e5760006014555b6003601454106110b05760405162461bcd60e51b815260206004820152601760248201527f4f6e6c7920332073656c6c732070657220626c6f636b2100000000000000000060448201526064016104e6565b6110cd6110c8846110c3846011546114f6565b6114f6565b611277565b4780156110dd576110dd476113f1565b601480549060006110ed836119f3565b909155505043601555505b505b8015611174573060009081526001602052604090205461111a908261150b565b30600081815260016020526040908190209290925590516001600160a01b038616907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9061116b9085815260200190565b60405180910390a35b6001600160a01b038416600090815260016020526040902054611197908361156a565b6001600160a01b0385166000908152600160205260409020556111dc6111bd838361156a565b6001600160a01b0385166000908152600160205260409020549061150b565b6001600160a01b0380851660008181526001602052604090209290925585167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef611226858561156a565b60405190815260200160405180910390a350505050565b600081848411156112615760405162461bcd60e51b81526004016104e691906115da565b50600061126e8486611a0c565b95945050505050565b6013805460ff60a81b1916600160a81b17905560408051600280825260608201835260009260208301908036833701905050905030816000815181106112bf576112bf61195d565b6001600160a01b03928316602091820292909201810191909152601254604080516315ab88c960e31b81529051919093169263ad5c46489260048083019391928290030181865afa158015611318573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061133c9190611973565b8160018151811061134f5761134f61195d565b6001600160a01b0392831660209182029290920101526012546113759130911684610b5e565b60125460405163791ac94760e01b81526001600160a01b039091169063791ac947906113ae908590600090869030904290600401611a1f565b600060405180830381600087803b1580156113c857600080fd5b505af11580156113dc573d6000803e3d6000fd5b50506013805460ff60a81b1916905550505050565b6005546040516001600160a01b039091169082156108fc029083906000818181858888f1935050505015801561054d573d6000803e3d6000fd5b60008260000361143d5750600061042c565b60006114498385611911565b9050826114568583611a92565b146114ad5760405162461bcd60e51b815260206004820152602160248201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6044820152607760f81b60648201526084016104e6565b9392505050565b60006114ad83836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f0000000000008152506115ac565b600081831161150557826114ad565b50919050565b60008061151883856119e0565b9050838110156114ad5760405162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f77000000000060448201526064016104e6565b60006114ad83836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f77000081525061123d565b600081836115cd5760405162461bcd60e51b81526004016104e691906115da565b50600061126e8486611a92565b60006020808352835180602085015260005b81811015611608578581018301518582016040015282016115ec565b506000604082860101526040601f19601f8301168501019250505092915050565b6001600160a01b038116811461163e57600080fd5b50565b803561164c81611629565b919050565b6000806040838503121561166457600080fd5b823561166f81611629565b946020939093013593505050565b60008060006060848603121561169257600080fd5b833561169d81611629565b925060208401356116ad81611629565b929592945050506040919091013590565b634e487b7160e01b600052604160045260246000fd5b600060208083850312156116e757600080fd5b823567ffffffffffffffff808211156116ff57600080fd5b818501915085601f83011261171357600080fd5b813581811115611725576117256116be565b8060051b604051601f19603f8301168101818110858211171561174a5761174a6116be565b60405291825284820192508381018501918883111561176857600080fd5b938501935b8285101561178d5761177e85611641565b8452938501939285019261176d565b98975050505050505050565b6000602082840312156117ab57600080fd5b81356114ad81611629565b600080604083850312156117c957600080fd5b82356117d481611629565b915060208301356117e481611629565b809150509250929050565b60006020828403121561180157600080fd5b5035919050565b634e487b7160e01b600052601160045260246000fd5b600181815b8085111561185957816000190482111561183f5761183f611808565b8085161561184c57918102915b93841c9390800290611823565b509250929050565b6000826118705750600161042c565b8161187d5750600061042c565b8160018114611893576002811461189d576118b9565b600191505061042c565b60ff8411156118ae576118ae611808565b50506001821b61042c565b5060208310610133831016604e8410600b84101617156118dc575081810a61042c565b6118e6838361181e565b80600019048211156118fa576118fa611808565b029392505050565b60006114ad60ff841683611861565b808202811582820484141761042c5761042c611808565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b634e487b7160e01b600052603260045260246000fd5b60006020828403121561198557600080fd5b81516114ad81611629565b6000806000606084860312156119a557600080fd5b8351925060208401519150604084015190509250925092565b6000602082840312156119d057600080fd5b815180151581146114ad57600080fd5b8082018082111561042c5761042c611808565b600060018201611a0557611a05611808565b5060010190565b8181038181111561042c5761042c611808565b600060a08201878352602087602085015260a0604085015281875180845260c08601915060208901935060005b81811015611a715784516001600160a01b031683529383019391830191600101611a4c565b50506001600160a01b03969096166060850152505050608001529392505050565b600082611aaf57634e487b7160e01b600052601260045260246000fd5b50049056fe45524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e6365a2646970667358221220e85ef9e74da2b18270d1c91ede66b645fd4e18cce2d8c288dfd1bf29e20218df64736f6c6343000817003303d314e2201f63550827bf0cdf911a026e9b0dd35a2f6b042d7983f41d500c286e03afcf42739076459a23904cc6d7653ab8d5c2424c291827d6b2280df99edb949000582002193bfb8a8ac5c70cc4f5bcc93cf05c91f1701d33c7d0cd17fef09b345e0d8f41010376f05ef8fb93243667c474862883c05cf5ae53590f729fd42a29d5ac4c9c013a0369d9b379fbef4e01b3d2080f7c8e8e9f0d26a944db370902843e4359cc3a836b00582003998a60e1e675ae554eb9d43998288aa2d3bbeb74f093b78f5eaac3064f9080471c78a7679b214b00582003c941205aa8032ad63acc7460009b742213fc090c66e575c2e100ad87e19c10470154425fd7470c02188100582002e49eacbde352733e786c0a671dce3b8aadf72e9b68eff921c5041512f0f2175820fffffffffffffffffffffffffffffffffffffffffffffffffffc75045d9bcb5200582002301be158f2cae65e19a9c9e70bb5988a4d312bc95accc8748e3d5e19f0e2ca47046d61b5bb113b0058200222a9ef97d3a9b49658506a3f3bba83fe56420f692bbe4e9298b3e90e20f8d9471907a34f179dfc039716cd352f4b797457d921d9646a23e6f98723b6a53ab0491417703b25efe0bc00582002cabce7b540c95fae520e2fdf48d5a70e5b17c9e0a2ae80044ad2098884e46c5820ffffffffffffffffffffffffffffffffffffffffffffffffffea493b68062e7a03a6572a351d70ebce1425933eb911c7f3bbe62c85db8c4cc09ad943869d263fc003dde05dd943c769a0a17496b2a6697ef3fd5ad69728908d3633ab808e59e283eb00582002ba475fc33bd0f952f33744f1973eeaeec63485e8fff4760187bd9642d9c34e47013d5071b06d9d0219dcef03fc802151fd0071fc346b4fc82b419a635a5c2067d369158ad12f25869500cb23034d837f51f749756d6e8008b65f2fe70c56a7ca7b15393580ac7961b9c93e49ba03353097e71a897b3976ba6e0a1956dd21be53c87815ae6b01173a8e431e39f01c0058200254025d82f000bf5034e08b44c18adb5b631e0e795a8fbc89aeddfc2a018e154101005820021d32db26b0ef514d3aeb96bf78933b46e0a5c534d9559f1e2b24c0383ebaf65820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff032cfb0c2f14542e04ed1a5c4778f78ea17a4416159610b401d8a85c23bac0e97f00582002de8ffda797e3de9c05e8fc57b3bf0ec28a930d40b0d285d93c06501cf6a09057010001d5564d5338168dd3bafc9fcb37d264b451670b8d03058855726a4141e6411ce7d771bd04e0ace97d3c7647a04d0c006d03494cd89a036f1fece6b0a15375a7e4b85f2ad5cd8ccadcc1141f1f9f13a269c787c128b35700582002ca73392a540fe1950d294f2f14a31a1e582a928ae1c6c2dbee4372e6e07892472471ab765335a0037322c4e94923041abe6a2e423d3db076e18392f4ef7ccfc79f76c2d168963c18005820027e606b0852c084f64d8b0d39bc9b68eedc89af1ad47d6115c7baef0750ff3541010058200277db92afcba08cd86adc11425f684e852a0b67fcb6d20825ff37fe798f5d0a47090401e39b918f03f8ffcbb869a654bf93baa89618989e39f259a3b135f44b588652d91b7d894317005820022fccf1163519cc49dd925bf8a76124553ef501cb6be00ca0dd677e20c779f05820ffffffffffffffffffffffffffffffffffffffffffffffffffd6141fe2972c6a0219bfdc030fa652634736d283d246d68c56f24b97fa738f79b82cd455b5c63213fd26455b038fd9a303976d34f8958b1958dfcb7ac5bd0219758551c012cd70a5a972ee5adf00582002f67f7468ab441155363145017ccd7b7999c43b3e8757c251578bf9c9ff6f6b470913773c9750bf0058200250e6b12f97fc6f4ff69d3a9e7aedbfb25fc20d360b3006e6d4213f191fd9324704d874bff04c5a005820028a0e933ee673218feec8be9bfa6ef0e751b366df0de6ea33741babbe95310b5820fffffffffffffffffffffffffffffffffffffffffffffffffffac5d16182eae800582002fa68efb8be33d6fdba6658539618d6b7e037e7898c965052dc3736dd7bbfdd475fc7028b6a9f4203dfb06b42c95f2aac2d8057b7f94a853286db6e20f955f33d9cd1c77ed94a800d0354a7e74443ad5d8764652d19e16b1b1541f110b994870ccb80c227acd784a93b03d5e708fa215fc2d978de65263c654ddbd50fad68a3883d0af087c3405e8cc6bc00582003c74b49237f99c988c0a91e6bdc306d4df93c40f95fa24f8692b0d4edfc99a04805e26e848833dcec0058200374a746b5555b132b48865fb523a014af5828acb326222b172522c43da7cb605820ffffffffffffffffffffffffffffffffffffffffffffffffffb9020661a76b7e0219010100582003108e10bcb7c27dddfc02ed9d693a074039d026cf4ea4240b40f7d581ac8020480de0b6b3a76400000058200313c6e386e363fcf34ac713342b74445f482b86f536e9bb54b9df49b4da4860480dcfa6a6d6779d380058200352eda49633b7bce573f8e90ced5f624fb81c40bcb27970f4eb7cd9f39a19e05820ffffffffffffffffffffffffffffffffffffffffffffffffffb8fd16d69acd1f021930020357d2c137377b10b0d4cab7372ecbf084651da96132f2211bc47d3bfb622f1cbf03afcd9cb0d6eade7fee2aa56ff6937a378a4a29653f9a3ebb55bb78ccee38d1d20219f7e303407c135badaf2a22f8aaf9da359334f1876e09a0caae932686f3dee20a8ed7870305bff418893a396e4c4e7d98c317929a64317a407c780ff32c3964a3c3bfde250058200201d9688706b54d15cda98d5f7769b04166d65fd6637f266423c243e5a4818247024d22b0b21d2303eeec0bbc5981199bcbe9b6320816ac2233c649fb1a7218c571bdaffede88afa3037057e064638c7593e897c10519c5b49e31ef616fd55c519f43ee8bdf23ca3c5503c37d7c7e90d53bbb36dc81351dbf2d493d81673e9e6d94bbfb438c537bf0040200582002b5b113c3362f857a3c03606be4fbe8e1193eca42a2681838ab148addcbf5634704642f856dc1a30058200265d587a2570a145f6b3b698c79f7d0c1225355c4d56576b6e8c541a535fc775820ffffffffffffffffffffffffffffffffffffffffffffffffffb7e0350c71b49a03aca9e2ce90f6174492048ce752dafdc9d9431d9a9069cc48c05795d4ad1dace2005820029a991a3a433167b9a4cc2b7a64ba4eeed205093740df6c08b43f6ab3a37d1c5820ffffffffffffffffffffffffffffffffffffffffffffffffffebec23d1d6d16600582002715a8e73fb23ee049880a077ae86f5d350b9b77384810dff7142edc9ee664d5820ffffffffffffffffffffffffffffffffffffffffffffffffffdb40fb8f6a96d8005820030691a4b1ef5899ea41e271d5970d99438d5e69260cad78161f967c9fdfb9d05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00582003b4a454dc3493923482f07822329ed19e8244eff582cc204f8554c3620c3fd0480de0b6b3a764000000582003a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec34440547a250d5630b4cf539739df2c5dacb4c659f2488d005820037ea67b79476bf871e3fadc49ce522e02a9180dccb7199785715b36ff7504d05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff021921820366ff9444fbaae00373bb481689ce569f714472f08cfba260d8ba033fb67eb7c6038575ef0604f6d6d642f3dda4494e1c80ed094c6bc3d337f760155213de33f0cb0058200201c417d962731257c2644f4fc72bcfc33a95576a151d750f9d26cee5888ea24702c7cdea3f0eaa0219bdef032603306245306e9e95f05c1033ec9fa7ebf5767d73f2fa4fe92a18b0dd244c2700582002165314e5e0eaf3eade136d72cee1f88c700abbe8ce41770bacc9424a54279e5820fffffffffffffffffffffffffffffffffffffffffffffffffff0a221a73715ec005820029d69f86befc55447495ed58448d9d143d70143055d7707e69936fca3a23f7a5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03cc86b07ad687fed0ec099fe283d0d09b9e29a170aa758f4ba6a4de5f0431a158033647c0c8e0a5133b8536e373afb3916ba9d65573446372b5f8d423103f11590b005820031ed3604650b2aeb65f74a72d2bab8f72eeedaac2dde3d554b57f413b8aab4046f8d583657ce70058200338c91d8aa6139f872500e67b257671589abdb59c61fe5fbab728b5ce3175c05820ffffffffffffffffffffffffffffffffffffffffffffffffffd58cc69491ab9600582003a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a8041010218380058200247d87da98715c3ed63046f61010fac43848bf543ced1b76aedf6a50f153b104101005820029de73f4f2a3affd41d2bb6334b4d3c02395f33513b8efb72090ec154a1039b5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff033ceef5d3cffe9831c51792d491e49ac08d3ed093512fbb40ad84df3d866a687c005820025ae2f1a686fc65f52da42bebf0636ae02902bfc01961f1d3e96aaa0dbf49b4470f59523482f14003b4517ad4d74db7e0b23d3a96fc542c7ed07c3ccb9fee240ce443c6f16a93886403e4fdafce77df788d97a16f6ef3c027471cfd7b87634a7157643adcdbfd38260500582002c8d3390450841b4be9c88c87dc9d698b506dadc7cbc90e1c7dcfd7e68412854701fe28a794f2a30219cffe00582002ebdb5d939e01b3922bdf616066d4e78e2328f90424854703bf578f373923484702fb1756aba9cb00582002a568872657d6cf6893b28fb0d9cb1f0cd04d4e0c0421174bf80d5fad48efc35820ffffffffffffffffffffffffffffffffffffffffffffffffffb9004babb3441f00582003003493ab7c00b0199c2ffe97712c2f27fb54cf5f816cc5bb760c2c25b563d0470a1118be62253a00582003d4e2c2659582808fa60c2a272b72920b10f624278260105e694dccbd15e300470ff5a428aea1b30219048000582002279ea4bbc9705e85002e16ea4a8e827319f75361985d73e4a175db464347db4c019d971e4fe0719f9d50860000582002b6990105719101dabeb77144f2a3385c8033acd3af97e9423a695e81ad1eb5420149005820021b32cf224aeb097412df8878e15269f3fd4d5aacdffa807e1084dc55f20e244710987fc355852303528fa3492e17dcab658ca7f04281b1e6759a035c203ceaa1d3665b1c2bc5b3d10058200223f9c54df335fc1b52971f0ab4f37fab3a8d5ed8f19e42ff31a617dbefec774706323e51ffbcb4005820026a41b4d5f30ba17f05b6ca0ba06c8f0f95758b480fcab4a711553983321e445820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005820022d27f3379c5bd21f1c5f287e52837c6f6a1e26502db4c38ae2db24aa1ca1bf5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005820026966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7411a0219cfd3035004a72ba9add33ea65f3bddebd25df291c3ae0c41c9c861e8508b53f3088c4e00582002f29f424eff63b3eb8f64d802c127231e8b44129e3ea8c6f81dd190308576095820ffffffffffffffffffffffffffffffffffffffffffffffffffb184bd9a2c0a9f03121157274bb924e0068597410342ae7ea4edfc7a403018fb2eb2dda003e7643500582002692bbf9f3ee64ad30db6d40f362ff5e9d57370e47547af7040c997239e970c4101005820022dd49cfdc29b0a18041ea928a8c3326123166d51e202a1e5f38a4e788f39cf4701616b3ceff5210058200252222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f4118038d8c73d2bafd80782707cca1103323b3c8d357aa907024a4045cd371ccfa9338005820027b074fc0c7123ca3a0b740fed51125915e19d38c7b882352df2b9eea2c84bf5820ffffffffffffffffffffffffffffffffffffffffffffffffffb8fed8b4fb88a20361eb56a435e8c9abda1648b3a9a9bdaa3b56446729587b5d7f5da19a4a30a79b03b0130aa8da0e8c27af0863d015325a06e5b949295b8c7e121b0f263cc6aae3c00058200286f2b0ee929f4a217491efb5d8707c8cbf903f7d74cd367f77cdbbf6e11c444709ed0b8412e5860322d4c3db87aafeeb2edf3b63d93dec03e623754d2bd2f0df4b2e3e098ba341930312f2ecf66debdb22f78971e2308e00cd2dfccc33c465cb56fac5a7623fe1fbbc0219f7750219ffff05581e03e449b31bde037df0439b78ae02331dd2d40844839e49ef3201484542600701191b12030ba73b7f804a139e6574caec9727cf9c24aea22f7309b0fd35278d704f700dae03c7072fd42d5a6e75d519982f75051f3de632a97deac11f73cbcee54735b5b4cd01410705581e03f10fb68883dfcd964c2d4904a3aca8cbd7ba798320724f02b14ebcb5300c03462ac2b0346208021945e1034ff32c6608391be33fbe55f53c7025b71a582eec94be56ed92a439777d1816d703022bcf0a8e4a2644a6c8a56dce2ad12aacebf3260a53e04c806cbbf5912fc9630356651de917baf1e9ed0325aa485eeb2accbf5fbe7b2650b63f6155532481ff2d03f9103821b601934d482df73e1ae15b570be704e251c702a31ca7ba640fdcaf620305da9e2bfdc7f60bab1cc9470ff36c106a78bdc140a4f081ea2dfc81c90b759403111bbf32bdf2fa4dea0bbf15b2a47894616f4e4e6076ffeac3f7ef9cbab2ff0703955bb65eec57695070904627d9e21d2abb2a1e23531aee64bc2f950791398b590388213da29c4d9b44b1eaed041cca0f9392ed5bcbad8305dcfd0ee2e9f9f593b30219ffff038ec9c904d4270952fa3cb84ce079ecb69cd6d32eebcbcd00ffd190437c57cac90367c777c0d5893adbd3709a1ee34fa0df39aa49929616c9a494d6eb3faba571e203054b25c0dc6be18d19f07eb3f15c279f547a82a10552ad842064ef504361237e03d1da2bad9d8c3e4ae4f50161ef023019262726dd47a84efaf214727d3b6e9a04031fadf9d7c68a470e27a8a9464546ab43eafaa0ff3fe1dc5176c18c4f45e8213f031cbe6362e431f50e1794e2e8e5120543b33d8af786f16d7681f8ff1232cd4cf603708b1b8d171d1209a86da26a396647c9b1d93fec6783009d6ec6960fc1c2a89a030c677b49d4bdce9fe3882aa84b122dbeaef90e5e5b448b6bda5bd35527422a9d036d8241ba8f7f33c9b13c50ae19cd8b47d9278ec48bcf5ef6e4da2261bf2b93e403eab50ec55558e4897800059b38ed13848d9b166257a416da181edadf964f30230321d5b7bb9826ec9ca0d5ddc27c11421a6653b86f2e17253a04601b71331bc2410219ffff03598e6ee1c1125c6b7086fa65fa0f2e713aba867cf3b49ed451389b7e7504845903cdc25193c1aa98d6a00442dcaedbf7f7e766492e4b796f10279ba8a9147869ea0378fcb2afe7a66623f60a3e2c04ea1ed336238af0d3151e1abb94eb4e0f12388b0344ec398832716f3619ac107db0d1e297e6aede4465690a14afeb9ad66537074203edc9f8c6f16ade6b1f6bc2f08879519d59a8c5a90103ad71f856e3ab8347e1090219ffff03d7bd9ece9d45931bcf48331bf731471b6da87ab8a644fa374ce25608fc1e66ff03f772655437195900a4626ba546a7c83604e5bbaebb6f945a8ed24f7ae4476fa2034be0e71357280655400a1b513f4427954bd21f1bd287864e42eb71cd73df62a403db2c577219d228099a0630f21426d768cb7b10d309352e1f7246f152c2b8494803c66572f58383e885ee4b09235ec9587fde7dcd9168d1c3fb713f985fd4d1f748031b08d31d3f43d7d90c45e756a76f14a87f4cb75ff9ae997f91c674f9370b3517035a6ae47fd4c60efcb73c9c718ddd40c1e059870b37649c4ff65531c26c5e46800219ffff030514c9d7736cc160b7826b9ecc4272646b7b98b17e523e0950e21bd3386201d303c274a3c5c9a8ae97b7e7092b064609ddf60a40380c1d032753594e5a5ef2113a03936859c9f4de1e3adc201278a419dade170329afbe4f27d06c85d9143291670f0370510b33e2a85ad15b33ea84e5cbb7beeba921250088f5cf63257914af6129be037e2ec6026974ae7abb85fa76bf01da1305f4c42af012cb375d0906ddefe7034a03ee9aae715b49059635c1b6d59bdd0e19dcce999daf5cb3fbba24cd8c02e216120336c33965bc07409169c1da6eaa004c3243b0b5ade544b4c3cbcf35e390c5906e03bf8732a2dcaf3cb9eed5ce6de518588b722df7b63118ebacedc3acddcca9063203425014f9d3c06ca9b97fabc3e2c7fe195aa67a5abff1de230e2e49187d300a4503ac482db6a1a0e944067823679dea17e21a6b22f2c83ba2abcc26817f2e58c19f0399964d6cf151a56a43a23be84477dc72e8cac768165213f64ec65b995b92a5520308f5320b30c5e4a464d2ea439006f11cdc914e79e8cb0130367767fe31548963039f1b1fe7fa9a3fb5d7d47c800d757ea8b5ad83dbd8ee6ec4da9ac4ccdd8273a703cf84205986bd6f32a4155c35772efe433a1295e1a1446700886e608a5e2c461f030972d1609599b552167c339aa48d6ae5fda15fa5b832ea203ef723d8ad5eb61803451cf700482504e6a8d53661c6309b794d91ffbc1ee53ef04c13b47c4774cf73037e0c07ee1a9de8c5f6d963f8e696af2ddc0c10c247d53e669d4554500fd697b303a6ec326903990f6f7f4159dcd9446894bd3409961025ad530dd5ceac3d52e77a037815a1e906ad3433f8fc81741559d38ffcf91a53d077131276de3b08cba6957f03de9fdf0ed3d91173b4a4e63067566df8b9ffd958dafa64024b4524685e0c5a3e01410b05581e03a5fd656052d58ce697be9dca634eb8be7b38495f2dfb649b95a0bd9410040103562d59a51820d47f520c975e0b2bcffac644a509749a3161f481f57b6e826d210605581e03fb554f06fd3826225ba6152e356ec5bdc21317e4e2ab56e0129ea3d2a007011bffffffffffffffff05581e030185e5ff306ce55884850cd1229e1109cf4815ab48d41c6f583f606bd00c0247033a1ad2d68c5505581e038fafa06790ce29fc48d6d13e1bafe01c072357f372ff1b0dc42804ec500847028ed6103d000005581e030da26dc5b0f7683761a9c2fc03025fef3266f2e899469e214f920c94700c054778727d357361ba05581e03dded7c5b51db1d0c984295bb3ef0767297cbc222a6ba08698986221cf00c186a482337c8dff9c1f60405581e039cf1f3b7c89150dd373d4c6f39cedc666906c3fbc0215ca197b52324100c07470342dcf39088d00373146722a7504a33c86a51be4472996561f73934780d2e94c53b9ad8e1e4d50c038d94b2518d85571711e3760dd6a5ba75d870e044842f88c306fa10e90e8cbc1b0219cf9503d93c1dd782977bdde2f87077c0919b7212175bf22f89d1056e8226de58a32e3503d2d264f49ab5d9bb4eaf0b391cb99563168de9b9576169befcbc9b94c502303103ca71d5363fa100af579ed893894c0ca1dc89248ab5c0255cec482f31d0cac1830301720b28e70f64e0d152bf2702b9bd7710467651d8d203fd35d8b676e2b2bc5e03c1e188c9f6902b2ce916a96774a70e93ae5b06b6d3884171a040b9388bd4ec6d039640a812ba7bb0337f983da10bd7876bbf581086fffb609ccc23b3aee12efa4b0340183b97529cbe1c6d0f9425ffec2a19c4e4cd79a4e19adfae955e252feb3476035d631e9f478533b27356ccb27fb922396196d39f1c3c3cd3ba640eb2e65d7e76039728be4941f7771ccc66ae2112a581c6ea46ca4bec04b47d027ce7e5e1158ec30219ffff03c1db94be6e76446f21dc2a83ed376601f6f88bc6df7c4c75858dfe1a992efaa60308d5413e3a30ac721149a84a97585a713e41d9412650124efdd2719528b86d01036f2b80aa479c5ca9802ca90bf3a3bcb47e53b83f6b24dbb3963aea9ce28620b8034107b09d030b4de5850d369329692abf9e46af3eb87257e355effa031d3ecf3103f747cd819b67fe3740b882c3473066140eb78b7970b4a82647dbf994579120270366837ceceb6072918f4eaf7c2c45a7aba912ab0761cfc588e5325e8d51a8136d03c5e86de81a05b178a874e32ef8ca1cca7d6eac0b8aa842bd20fce320ab4f995f030a7c545321c56ac1d084151c3ec73d19b55d020e9cf7c1c45ac15b818149b39703dad370325e460d8108e08909f527666962fac48d60972d7de862dcb1af6bf0a90303a11a5da8539646736f1398dc8dc2555942d1c79a5bb6f271d149931a415340039cf267ab64111a30ac588bf72e45da9cbdff271259de4a4664bf524f84b1291b0364233ae43c6c06faaf3548d904fc3d4897c473a869ff656db643296841aa413a035e3cdc556ec8b1a652f45d6c09e07135826e0611d9f9d6a98db2036cc41cebd40219ffff031dc43c967546d240c14b5bbb4c589d21da42ad3a2ae350abe6df374c3bdbf7d903190010decd1351ab426935c1cf0d730f8014bd0d4f1395c02e57ca6996d65aca034402cfabb3f1cef1579165e42082fe196207f8c10f72c7dffdfbe7a6172a962a034be10ab6c150a5099d896354efbc5871bcf92cef413c67451ecba7ac8d68d94203e2f37f41b7ca8e69a12701cb9490231ed1624362851208a7b72bd509991d0028039f96962846ce4bf2cd29f1d2214bc3584697900fb75613c16cdd74cdf943166f03a6026a555dc95cb1338fe252d3dff15dfc9a1ffaf2fe179ba2cbc6d97228bad30326b35feebc1552685d57c216c8ce8e3f2211b158f2a4e46158f1decafc96f50c031747fa36f2dd6f5cb529596d52cfa0df8f38efbcf87df871fd8c9f4e20ea4426035db49085e5dfa22dfdd616ca7be9c15269e72b448df38200cfc7816d87b0e4b40348d80ff7d513259cb6f89aec5f5e783e313cde5c96d610ee8fa0147b3ce3e9b80308a3c5cbfd804c6f9c366a148813d4dd11ddaaf92587b4ce5cf43a840680e229034a992d1496d4ac9f79499bac80a745d690a6e67d3c2ad45bc323ca69118018f70219ffff033982baf2f50522e80a7b3d1c282bedfb8906071d569fac6862a0cb48286740ce034e1232fcb88f6de6be4ace419a68cfc62d90a843aa227946ea0c5a9f59e523cc034ccb6a335900fe6e7280c05c406ddd3dc9a010cf4b9577c64071d83126d030ad03d3922c5c17254d83694f713b7201237bd24e54dd36f20eb46e2943fc7a15b56003a85c6b90719992fe6057f90517584dd428fa6797ffa1c0974d5f89b0650c968003f876f581f76ceaab789825caa5662995005cc4768381e8cb0e1df061a02bb9d003e1e8401ae359ae90cffbc4cadf1e9957ae94d51e3df5c9a73ce3140dfa8b4f8c0338359ded6781f6c0bc95f38257b157ec889bedc97bba98b26fdce2ca727fcd0f0219ffff03a96bf98acae212bdc0f60e39271ec2f35c0b356e5e5c96d997d3cfdd27561ff303b85fb4d82e87f8052ca74748bdfa9facedb4a13bd75acc61effaa22e7a5477b4036c557412842e57f9c4ae9a895edf39aecff1d7d0a8c53e945e4d6167edddb1d003e0630833f7112433a49310ef4709391840e77704659a4f36de2134976b954d67033cb2c1d45bd9999a614e02b645cbb81c3b38ab31af6012b0ae34f1b4318fa2e703acb09baffacf5c7f36da9c1efee9ba368e53e2e0b5794469a4778120b4b2496f0303fe9f1a86dcf53106a51acfbd528affc36cf3e0a7eb460f1fbb2f4d7734d4dd034978492150341c1f9ff121f8819f2f080947ced925cd00c0741bcb17ec14dae00310d3838f10cc2f6efc2a5c3a24a7b51e223f299a7249dbe5d8eb0336e6ec5d2103d75af6f76cae885a23b4258cb3b2e73e2b5e6561c9e6fe4f538495901fea9dae03b6361a4a3ef4149b1fd51b991ab8a2b7f1c784107d7982c1677a95827ef0f67903a473ef48b45e2fa99b7eaf656244b107e8f7a8725f59a84a3e3e8fc3783cc36603239052cbe6a46ed6ad785e071a03bf55f0a08a4386b91c9f4277c9b4fb2e070f0383ad7d71594261016c3436561b6cc303237f411932b6c589b3cf743015e5e078035c825d694d8790eea37f34eba25baff93f5f4d01ead1b5299190a6415e60eebe035a75fc6a66f1d79053afeb3cd89e0ef67eb2dad40c610b3c6fbbf65b23fed166036148653e3a1b34d4f2ba4c0108019614116c2c81ff0baccbf6eabf413e8d2cf1035c7e1dbfbeac158146f60b6258d1cf89c9c86d78c27d306e463b8777d74159e203918d2003ca9530802ac4297d4f4f4059cc5f8eefbef5b2c3f1e1a75fb0da7ce80344c7f83de3767527b87808433cea440add1abdc21958ca3a6d8b5ad468cd47670346625c9abb4de3f10bc62e2bb79e2d4b121aace6d70d413d52088eedca8974e703df2234abb2bc67e8633ec6522e623f0cd75e0e624486b9ddc1d6bc6305a9b69a03230ffadd76935253f3c98dd6b74a21fcb6ffd603d010d71109446a4e2ff05c1303b55da95eeb8022ea9f764f6fb9511a82fe26f598c6a73ce36a3f6bd8f31e9be50355284229177eabc51491684a8f1f7d26cbbb0941a07f4fc45c095f58ea51a4ba0390bb17e624e25a0e2cead4a6788eb462122439f171438684b0f9e4137056cad50375a9a2e74df7f4250625a23044de8d3b5a0d5d46554c155839c509bb0f878f1e03dd4972a2ca580b027f6600f7f351bb097a44f3270a77aba938d377ebf95402f603f6c3ddb3f6a942c2f8e5042b7aa03cc975cd940076634f19d59f5509cb934b7e035bb4e28909d32411043ecf7cb2bea9cc537b2adea66afde6bb7a7252521f2a1d03472727fd2abdfd7b7d0522fac1482c24ab11b11a976d0abd8e68d63fc4bbb14e03207fb66c5f6affe897c7d9ed861ebd3feb852bd5f269e77184e22b571bb0128503a79825200ff898579cbdd0d42bb58df3c595311f0d6bb8a5e617d8c459b843b3031e066eaf7ecc8f473937f4023037227150234efa8adb67b9d26968f5b7fdd8d60376e70cec0559fa1a38946c7140d96bcb33bf0c0848fbb7f113211a526ce9dcdf03e5314bca4ea077809e8542a370f980459f673793f956f5273cd525f1288e4918035df371d9fa98784741ce87b2ad3ad4b506e2b8fb9e69c88f069d1849c39c2d9a039592041ba5f9e7e17a95f92c02e009c113ce73baf9f144bc0be681eb8b344ca9037662b51617aaa494a69654105b2f4339d787e2270a294a3c7720bf410c6c5b6b03657c9667cd4c4dc4c68aff1a515e6a0a35b4543bbe5aba1ad97fb53349662d950378047952d2008842519a15e756bba5f128b43977f9b70d273a12b0b33f3475c005581e0361ca3ab37370e999383754a427e9cbb499ef62199ddd8159a120c220c0040205581e03f36e6e211b104002f8aaf2d72fdc720433e5cf906298ad5c4dc9f53d900c014708d952ecf96c0005581d02904891f85119e942ba587da80b9e5cd27a7341d2c8444cf2c483786e0c19bcb0483408438a75babcc405581d021e01aaaf06ed39b3d5769854985240f2d0fe845d3d76d4ee9fa26274040e021902040365a171a68484e34a202e133a8f36edccd88f7b61c02e657522d8a6c536f820e605581e03aa683e486278b5435a79fd0ef67705225d3cf1162001ffdb849c2c7ae00c04464ff19e3f3c6805581e03cc1820b98285efea6cfe007ce81b4da4bd9e8625ff924b9c354c216f900401036f8e7b179163beb6e7be221ecc56e1b32e1862c0c8e65a8178890dee7625e62805581e03043571f01386f6a158211c34a25062042e49c70b164adc814d12914720084708664d80ba82b80219c87b03dc560377a7d58470fa47d28661a49479f0069b73d9baa4de33061f775044c9cf03f2145b18f926809d348de858949f37a13f3e0e640817b121076ec2e83cb6939303be00460d9aa041cb6b2ef05122d8fed24e7e68d232a4d40d6ff3aa86e738373b03d74f734c5b428663b2433480cc4f5bae365d9003acec404632fb916f6150930e03c2b0d0d21dbec9cacc093eebff6eb5d9caac249867952165174c7d31d79f51a40389732185edbed893a74de9e6a189ff0a1c89013144da5873bcd0b8f298fc2b730358f118652c93a1d1473c6dc99ca89f08eac2fecca378ebb496282341abb4ed6703d7a511575ecfa69f4fa822d0baf809dad9be14fcd50cac06b26e985b367a51220219ffff03d5d9d2db79ac183aefd66df21ede7ee86250b452445b6e3aa1b6a2ab442db40603e53552828f75e8bae7e8065e135c606cdbb736d04cecf6e0af1763bd4a945a57032d4cb3bf00dd0de94c0e0f3bba48175bf760d9a72b14d2d23159200f9ee6f5e7034762bdc009549f89daa7249569b2721e5c2c07112d0989ed61c1876ec61e2c6d035f333bf83956beb9154882e9ca95891bb3a2b8661e65c23be7f24446117923db038e992a6c770a20647cfb5a0fdc7a77ae10a8676188b9a18b343e9e6cdcc67e720324d713f83824eb06a5eea6b6d5df778e91d5dd9239b2982722499636a137712b030357051acc71c80c151a686aabcfcb568d87bd635b036f24d866846f25957fda0362c086af6920efff38f3eeafd0dad103bf87e94339b7a3459c2154d8fc3f85bf0219ffff03019e41c0984f181010b8aab74f88319a2ff6f699654b9c64f45971ec86c4aa950219ffff037a27355c1be40e1aa23fa2dbc6244312d852419d73cfe6c2eecaae7d503e2d790394d854bd8df4c9cb282100b7e21fd5019f7c94312e41234a7d2dff49c579acd4037bfe0c4084d843086e9282acdfcd6946d2829bd2e60686bf211583c3ac94a2e1037150db65c3ee5b5efd9c9adedb956fc315aabe293e294645bb8814c368c585ed03fbd2979986c6697b7e37289ba54c2907f108364aa55e2f4ef4b1537521fa36fb034c5e36b900a1e1c2a62d0df591a7d14c16701164dd9654755dd4fecf57635166034abe00cd3bcb2dda0dc2e9e9bba4788e2572c297363fac208914b1dbd6be2e9a033ee14d303475adcc0a49c955b8897e3acbcef6e8f570c2090758e28ff9edded60219ffff038d8c3cdd29ab08a26093442e7859a5cdc6cce1c6a315c90e0fe1297cca2c0bf90337bbdf871cd110ea11a1c69046d1af6c26c94bbadeee834c0bbe349d712e142a0219ffff03e78cbab8c801b528a16a71db904306e88d329d02acaef53a1c60fe1008f420b10328d729800f1f178643992fef5b01e8c87426c47efc733973325b14d2697c1665032ffd3afb9ad7f77130f15ec93d6493af96f42faf98103c8d7dcc9168b040e978037784ec79fdff0ecae589f2edba9d6d5d06882fafcc23c23524dfe10876a9f47703fb1cb142b2b75e310a9b7ceb775cd005af478d65afd099216dc84b1bec3ff78603bf9304b3c63dffcac293f3c423a20d754e842cccf9548cfdc8a14dba8864ebcc03fe3ec4c1da00f18de8cc0dadcb9a067a62fb116721bb40884624657cc55faaad03fd45cf5c9b54e058b05267e509f17a01959298987a8cc48ee8ee320b0f48345303549f3533c303b773c31196973479045edc2d4104617a3e40e8d4843aff033ca903a9dd42473dc2a04461505b505884a9cd7005db84b15b207a6647b09da30aa4e703840a1780a2eb1718544e577809fa319dab62516d9e3c9b22773ba59db81bbfb5039cc3c2a6f408e31e0651ebebfc7d5cc84c1cfb37362667c61b3c3ec81a55642003c7e2025fe4736de432a2b1e2870d98f0b7733cdeafdcd9e231c0efa71c856e0103a3e62675e2d61bf54b380c558127761d4f1ab76c2ad192d0a0c36c3b3c1c9a38030547398548c5c02fb10dfee8cd9dd7b52677308b6c050e22855396c76fbba685037c6f001ad55006533fed297f077c62346b004d9ffe9ebb8cc0f49e318db5499303cc30fd706a6467a3d1ebc03ba1062953e63dd50c0e506edf69bc7e09c64d6fe2033cb5d593b28382d496be9e389aa61d2fda25d5dc8ca33f15c4156898e47f7b0f03b9ac59af1d015529220489afa37a70147e1b4dd00fb36473b6d0b1ac0e76516f03d43bc0e30cfa0e89fec0b0d7011e155c34a2249dd3b6975950811feb5567081d0380dde92769abcf818ca7eb399ece5a72868e34b567030b20255f9571a8a8b9870320ab4b014b71839f151908be8ceb14489c144b063e15148c5387ed89478894b203299e87fa5695364244250d23a8d0248cbfe57ab45ef2ceda90d2022087e33f18039c1a0d0b920462f28f727e19c7e0044e5bfa560d3ca17dbcebc06608d18e8135035acfc2bac14413bd0e2e210e561df4ef00f19beff379536f3ec652ea8baa22d603993add5605371f8429f46b8272b4c67aa0a71e824db4f23b1acd248335c0982f05581d03a0c9cf1688de729324d71300dc9a0d65f9e69245464a6231dc6543900403045957886080604052600436101561001a575b3615610018575f80fd5b005b5f3560e01c806301ffc9a7146101f4578063088890dc146101ef578063150b7a02146101ea578063192128b2146101e55780631f00ca74146101e057806323a69e751461016d57806324856bc3146101db5780632f100e4a146101d65780632f40e62a146101d15780633593564c146101cc5780633d0e3ec5146101c757806349df728c146101c25780634b31e26f146101bd5780634eeca823146101b857806350431ce4146101b3578063547988f9146101ae578063791ac947146101a9578063a0136443146101a4578063b6f9de951461019f578063bb7b9c761461019a578063bc197c8114610195578063d06ca61f14610190578063d1ef92491461018b578063e81dc5c114610186578063eb92db2714610181578063f23a6e611461017c578063f2fde38b14610177578063f9da581d14610172578063fa461e331461016d5763fb3bdb410361000e576116c5565b610895565b611690565b611663565b611609565b6114f6565b61149d565b611443565b61141c565b61138d565b611373565b611314565b61126f565b6111c2565b6110ec565b611083565b61101f565b610fbc565b610ed5565b610d01565b610bf1565b610b2c565b610b14565b6109ff565b61086e565b610815565b610745565b6103ef565b346102625760203660031901126102625760043563ffffffff60e01b811680910361026257602090630271189760e51b8114908115610251575b8115610240575b506040519015158152f35b6301ffc9a760e01b1490505f610235565b630a85bd0160e11b8114915061022e565b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b6001600160401b03811161028d57604052565b610266565b606081019081106001600160401b0382111761028d57604052565b608081019081106001600160401b0382111761028d57604052565b60a081019081106001600160401b0382111761028d57604052565b90601f801991011681019081106001600160401b0382111761028d57604052565b6001600160401b03811161028d5760051b60200190565b6001600160a01b0381160361026257565b929161033782610304565b9161034560405193846102e3565b829481845260208094019160051b810192831161026257905b82821061036b5750505050565b83809183356103798161031b565b81520191019061035e565b9080601f830112156102625781602061039f9335910161032c565b90565b9060a06003198301126102625760043591602435906001600160401b038211610262576103d191600401610384565b906044356103de8161031b565b906064359060843561039f8161031b565b6104066103fb366103a2565b9294904211156116ef565b61041761041283612865565b61173b565b61043061042385611791565b516001600160a01b031690565b6001546001600160a01b03949161045091869081165b16951685146117d7565b833b15610262575f60049460405195868092630d0e30db60e41b825234905af193841561068957610500946106ff575b506001546104a490610498906001600160a01b031681565b6001600160a01b031690565b6104c66104b361042388611791565b6104bf610423896117a3565b90866128d5565b60405163a9059cbb60e01b81526001600160a01b0390911660048201523460248201526020959091869183919082905f9082906044820190565b03925af180156106895761051b915f916106d2575b50611843565b6105376104986104986104236105318951611872565b896117c3565b6040516370a0823160e01b8082526001600160a01b038516600483015292918690829060249082905afa9485156106895787915f966106ab575b5061057d918591612aab565b6105996104986104986104236105938a51611872565b8a6117c3565b6040518381526001600160a01b0385166004820152908690829060249082905afa908115610689576105d49186915f9161068e575b50612c17565b101594856105e6575b610018866118c0565b61063095509061060b610498610498610423856106058a989751611872565b906117c3565b6040519182526001600160a01b03909216600482015294859190829081906024820190565b03915afa80156106895761001893610650935f9261065c575b5050612c17565b1515905f8080806105dd565b61067b9250803d10610682575b61067381836102e3565b8101906118b1565b5f80610649565b503d610669565b611823565b6106a59150883d8a116106825761067381836102e3565b5f6105ce565b85919650916106c961057d93893d8b116106825761067381836102e3565b96915091610571565b6106f29150863d88116106f8575b6106ea81836102e3565b81019061182e565b5f610515565b503d6106e0565b8061070c6107129261027a565b80611079565b5f610480565b9181601f84011215610262578235916001600160401b038311610262576020838186019501011161026257565b346102625760803660031901126102625761076160043561031b565b61076c60243561031b565b6064356001600160401b0381116102625761078b903690600401610718565b5050604051630a85bd0160e11b8152602090f35b6060600319820112610262576004356107b78161031b565b9160243591604435906001600160401b0382116102625761039f91600401610384565b60209060206040818301928281528551809452019301915f5b828110610801575050505090565b8351855293810193928101926001016107f3565b346102625761083b61082f6108293661079f565b916119aa565b604051918291826107da565b0390f35b9060406003198301126102625760043591602435906001600160401b0382116102625761039f91600401610384565b346102625761083b61082f6108823661083f565b5f549091906001600160a01b03166119aa565b34610262576060366003190112610262576024356004356044356001600160401b038111610262576108cb903690600401610718565b925f8313938415806109c5575b6109b357826108ec9161090c94019061280f565b6001600160a01b0390811692610901836143e2565b818398929a93614430565b83339116036109a157156109935750808616908416105b156109355750610018935033916144d1565b9150916042825110155f1461096957610018935061095282614559565b61096461095f33926145f6565b612855565b614605565b919290506009548211610981576100189233916144d1565b6040516339cedf2960e11b8152600490fd5b945080841690861610610923565b6040516332b13d9160e01b8152600490fd5b60405163316cf0eb60e01b8152600490fd5b505f8213156108d8565b9181601f84011215610262578235916001600160401b038311610262576020808501948460051b01011161026257565b604080600319360112610262576001600160401b036004803582811161026257610a2c9036908301610718565b93909260243590811161026257610a4690369084016109cf565b946001956001600c5403610b03576002600c55818103610aef575f5b828110610a73576100186001600c55565b610a8f610a8182858a611abd565b356001600160f81b03191690565b610aad610aa7610aa0848689611ac9565b3691611b25565b826133b7565b159081610ae0575b50610ac1578701610a62565b8451632c4029e960e01b8152908190610adc90828901611b7f565b0390fd5b600160ff1b161590505f610ab5565b6040516001621398b960e31b031981528590fd5b6040516337affdbf60e11b81528590fd5b61083b61082f610b23366103a2565b93929092611bad565b60e036600319011261026257600435610b448161031b565b6001600160401b039060243582811161026257610b65903690600401610384565b91604435908111610262573660238201121561026257806004013592610b8a84610304565b91610b9860405193846102e3565b8483526020946024602085019160051b8301019136831161026257602401905b828210610bd65761001860c43560a43560843560643589898c611d9f565b813562ffffff81168103610262578152908601908601610bb8565b60603660031901126102625760046001600160401b03813581811161026257610c1d9036908401610718565b92909160243590811161026257610c3790369083016109cf565b936044354211610cf0576001946001600c5403610cdf576002600c55818103610ccb575f5b828110610c6d576100186001600c55565b610c7b610a81828589611abd565b610c8c610aa7610aa0848689611ac9565b159081610cbc575b50610ca0578601610c5c565b604051632c4029e960e01b8152908190610adc90828801611b7f565b600160ff1b161590505f610c94565b6040516001621398b960e31b031981528490fd5b6040516337affdbf60e11b81528490fd5b604051632dfb7c8b60e11b81528390fd5b346102625760c0366003190112610262576044356001600160401b03811161026257610d34610e009136906004016109cf565b6064359291610d428461031b565b610dfa60a43591610d528361031b565b610d604260843510156116ef565b610d6c61041284612865565b610db0610d8a610d85610d7e87611872565b8785611f31565b611f41565b600154610d9f906001600160a01b0316610498565b6001600160a01b03909116146117d7565b610df1610dc0610d858684611f19565b610dcd610d858785611f19565b90610de960043592610de2610d858a88611f22565b90886128d5565b9033906140f5565b3093369161032c565b90612aab565b600154610e1790610498906001600160a01b031681565b6040516370a0823160e01b81523060048201529091602082602481865afa918215610689575f92610eb4575b5060243582101580610eab575b610e59906118c0565b823b1561026257604051632e1a7d4d60e01b815260048101839052925f908490602490829084905af19283156106895761001893610e98575b5061376c565b8061070c610ea59261027a565b5f610e92565b50811515610e50565b610ece91925060203d6020116106825761067381836102e3565b905f610e43565b346102625760208060031936011261026257600435610ef38161031b565b610efb6141d1565b6040516370a0823160e01b81523060048201526001600160a01b0391909116908281602481855afa908115610689575f928492610f6f928591610f95575b50610f45811515611f4b565b60405163a9059cbb60e01b8152336004820152602481019190915293849283919082906044820190565b03925af1801561068957610f7f57005b8161001892903d106106f8576106ea81836102e3565b610fac9150843d86116106825761067381836102e3565b5f610f39565b8015150361026257565b60c036600319011261026257600435610fd481610fb2565b602435906001600160401b03821161026257610ff76100189236906004016109cf565b916064359061100582610fb2565b60a435936110128561031b565b6084359360443592611f8f565b60c0366003190112610262576044356001600160401b0381116102625761082f61105061083b923690600401610384565b6064359061105d8261031b565b60a4359161106a8361031b565b6084359160243560043561234b565b5f91031261026257565b34610262575f3660031901126102625761109b6141d1565b4780156110b1575f808080933382f11561068957005b60405162461bcd60e51b81526020600482015260136024820152724e6f7468696e6720746f20776974686472617760681b6044820152606490fd5b6080366003190112610262576001600160401b0360048035828111610262576111189036908301610718565b926024359081116102625761113090369084016109cf565b9290606435936044354211610cf05761114b85341015611e99565b6001956001600c5403610cdf576002600c55808203610ccb575f5b818110611180576100188761117b6001600c55565b614044565b61118e610a81828489611abd565b61119f610aa7610aa0848789611ac9565b1590816111b3575b50610ca0578701611166565b600160ff1b161590505f6111a7565b346102625760a0366003190112610262576044356001600160401b038111610262576111f5610e009136906004016109cf565b60643592916112038461031b565b610dfa42608435101591611216836116ef565b610db060018060a01b0361122d815f5416956116ef565b61123961041286612865565b61124f610d8561124888611872565b8886611f31565b600154909190611267906001600160a01b0316610498565b9116146117d7565b60a03660031901126102625760043561128781610fb2565b602435906001600160401b038211610262576112aa6100189236906004016109cf565b91606435906112b882610fb2565b5f546001600160a01b0316936084359360443592611f8f565b9060806003198301126102625760043591602435906001600160401b0382116102625761130091600401610384565b9060443561130d8161031b565b9060643590565b61131d366112d1565b9291924211159061132d826116ef565b5f546001600160a01b039390841692611345906116ef565b61135161041284612865565b61045061136061042387611791565b60015486906001600160a01b0316610446565b346102625761083b61082f6113873661079f565b916123d3565b346102625760a0366003190112610262576113a960043561031b565b6113b460243561031b565b6001600160401b03604435818111610262576113d49036906004016109cf565b5050606435818111610262576113ee9036906004016109cf565b505060843590811161026257611408903690600401610718565b505060405163bc197c8160e01b8152602090f35b346102625761083b61082f6114303661083f565b5f549091906001600160a01b03166123d3565b60c0366003190112610262576004356001600160401b038111610262576114716100189136906004016109cf565b906044359161147f83610fb2565b60a4359261148c8461031b565b6084359260643592602435916124d1565b60a0366003190112610262576004356001600160401b038111610262576114cb6100189136906004016109cf565b90604435916114d983610fb2565b5f546001600160a01b0316926084359260643592602435916124d1565b34610262576020366003190112610262576004356001600160401b0380821691828103610262575f805160206157338339815191528054928460ff8560401c169182156115fc575b50506115ea577fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2936115e5936001600160401b0319161790556115a55f805160206157338339815191526801000000000000000068ff000000000000000019825416179055565b6115ad61264d565b5f80516020615733833981519152805468ff0000000000000000191690556040516001600160401b0390911681529081906020820190565b0390a1005b60405163f92ee8a960e01b8152600490fd5b851610159050845f61153e565b346102625760a03660031901126102625761162560043561031b565b61163060243561031b565b6084356001600160401b0381116102625761164f903690600401610718565b505060405163f23a6e6160e01b8152602090f35b34610262576020366003190112610262576100186004356116838161031b565b61168b6141d1565b61278b565b5f366003190112610262576116a36141d1565b5f34156116bc575b5f8080809334904190f11561068957005b506108fc6116ab565b6100186116d1366112d1565b916116de428410156116ef565b5f546001600160a01b031693611bad565b156116f657565b60405162461bcd60e51b815260206004820152601860248201527f556e69737761705632526f757465723a204558504952454400000000000000006044820152606490fd5b1561174257565b60405162461bcd60e51b8152602060048201526013602482015272556e737570706f7274656420466163746f727960681b6044820152606490fd5b634e487b7160e01b5f52603260045260245ffd5b80511561179e5760200190565b61177d565b80516001101561179e5760400190565b80516002101561179e5760600190565b805182101561179e5760209160051b010190565b156117de57565b60405162461bcd60e51b815260206004820152601d60248201527f556e69737761705632526f757465723a20494e56414c49445f504154480000006044820152606490fd5b6040513d5f823e3d90fd5b90816020910312610262575161039f81610fb2565b1561184a57565b634e487b7160e01b5f52600160045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b5f1981019190821161188057565b61185e565b60011981019190821161188057565b9061271091820391821161188057565b9190820391821161188057565b90816020910312610262575190565b156118c757565b60405162461bcd60e51b815260206004820152602b60248201527f556e69737761705632526f757465723a20494e53554646494349454e545f4f5560448201526a1514155517d05353d5539560aa1b6064820152608490fd5b1561192757565b60405162461bcd60e51b815260206004820152601e60248201527f556e697377617056324c6962726172793a20494e56414c49445f5041544800006044820152606490fd5b9061197682610304565b61198360405191826102e3565b8281528092611994601f1991610304565b0190602036910137565b8015611880575f190190565b929190926119bc600283511015611920565b6119c6825161196c565b936119da6119d48651611872565b866117c3565b526119e481612c24565b906119ef8351611872565b805b6119fb5750505050565b80611a29611a15610423611a0f8795611872565b886117c3565b611a2261042384896117c3565b9085612d0d565b50909391905f9083611a9757505090611a7f91611a9194611a6c611a58610423611a5288611872565b8c6117c3565b611a65610423888d6117c3565b90896128d5565b915b611a78868d6117c3565b5188612f41565b611a8b61053183611872565b5261199e565b806119f1565b809194959350611ab3575b50611a919392611a7f928792611a6e565b9550611a91611aa2565b9082101561179e570190565b919081101561179e5760051b81013590601e19813603018212156102625701908135916001600160401b038311610262576020018236038113610262579190565b6001600160401b03811161028d57601f01601f191660200190565b929192611b3182611b0a565b91611b3f60405193846102e3565b829481845281830111610262578281602093845f960137010152565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b60609181526040602082015281518060408301528060808484015e5f828201840152601f01601f1916010190565b92611bba904211156116ef565b611bc661041285612865565b611bf881611bd661042382611791565b6001546001600160a01b039081169691611bf2911687146117d7565b866119aa565b93611c0e611c0586611791565b51341015611d43565b611c1785611791565b5193803b15610262575f90600460405180978193630d0e30db60e41b83525af193841561068957611cc394611d30575b50600154602090611c6290610498906001600160a01b031681565b611c84611c7161042386611791565b611c7d610423876117a3565b90856128d5565b90611c8e88611791565b5160405163a9059cbb60e01b81526001600160a01b0390931660048401526024830152909586919082905f9082906044820190565b03925af190811561068957611ce38692611ce8965f91611d175750611843565b613617565b611cf181611791565b513411611cfb5790565b61039f611d11611d0a83611791565b51346118a4565b3361376c565b6106f2915060203d6020116106f8576106ea81836102e3565b8061070c611d3d9261027a565b5f611c47565b15611d4a57565b60405162461bcd60e51b815260206004820152602760248201527f556e69737761705632526f757465723a204558434553534956455f494e50555460448201526617d05353d5539560ca1b6064820152608490fd5b91909395944211611e87576001600c5403611e75576002600c55611dc585341015611e99565b8351815190600182018092116118805703611e405783611dff93611e1897611ded88346118a4565b91611df883306137e3565b30956139d8565b611e0833613df5565b6003815114611e24575b50614044565b611e226001600c55565b565b611e3a90611e3561042333926117a3565b613f2c565b5f611e12565b60405162461bcd60e51b815260206004820152600d60248201526c1a5b9d985b1a59081a5b9c1d5d609a1b6044820152606490fd5b6040516337affdbf60e11b8152600490fd5b604051632dfb7c8b60e11b8152600490fd5b15611ea057565b60405162461bcd60e51b815260206004820152602160248201527f5469702063616e277420626520626967676572207468616e2074782076616c756044820152606560f81b6064820152608490fd5b906001820180921161188057565b906002820180921161188057565b906064820180921161188057565b901561179e5790565b906001101561179e5760200190565b919081101561179e5760051b0190565b3561039f8161031b565b15611f5257565b60405162461bcd60e51b81526020600482015260156024820152744e6f20746f6b656e7320746f20776974686472617760581b6044820152606490fd5b9293949091611fa085341015611e99565b8434039234841161188057611fb691369161032c565b9215611fdd575093611fd791611e2295611fcf42611f0b565b913391612160565b506140a0565b9490916064420180421161188057611ff7904211156116ef565b61200361041283612865565b6120358161201361042382611791565b6001546001600160a01b03908116999161202f91168a146117d7565b846119aa565b9161204b8461204385611791565b511115611d43565b61205483611791565b5196803b15610262575f906004604051809a8193630d0e30db60e41b83525af1968715610689576120ed9761214d575b5060015460209061209f90610498906001600160a01b031681565b6120ae611c7161042386611791565b906120b886611791565b5160405163a9059cbb60e01b81526001600160a01b0390931660048401526024830152909889919082905f9082906044820190565b03925af19182156106895761210f61211793611e22995f91611d175750611843565b833392613617565b61212081611791565b51821161212f575b50506140a0565b6121469161213f611d1192611791565b51906118a4565b5f80612128565b8061070c61215a9261027a565b5f612084565b93919092612170904211156116ef565b61217c61041283612865565b6121ae8361218c61042382611791565b6001546001600160a01b0390811697916121a8911688146117d7565b846123d3565b936121bc6119d48651611872565b506121db6121d36121cd8751611872565b876117c3565b5115156118c0565b6121e485611791565b5190803b15610262575f90600460405180948193630d0e30db60e41b83525af1801561068957612338575b5060015461222790610498906001600160a01b031681565b90612237611c7161042386611791565b9361224186611791565b5160405163a9059cbb60e01b81526001600160a01b039690961660048701526024860152602094928590849060449082905f905af1801561068957610498610423836122a8866122b2968c6122df9b611ce3610498998f9d8e5f9261231b575b5050611843565b6106058151611872565b6040516370a0823160e01b81526001600160a01b0390921660048301529092839190829081906024820190565b03915afa9081156106895761039f925f926122fe575b505015156118c0565b6123149250803d106106825761067381836102e3565b5f806122f5565b6123319250803d106106f8576106ea81836102e3565b5f8e6122a1565b8061070c6123459261027a565b5f61220f565b909492919361235c904211156116ef565b61236861041284612865565b6123988461237861042382611791565b6001546001600160a01b0394916123929186908116610446565b856123d3565b946123a66121cd8751611872565b511015806123b8575b6121db906118c0565b506121db6123c96121cd8751611872565b51151590506123af565b929190926123e5600283511015611920565b6123ef825161196c565b936123f985611791565b5261240381612c24565b915f5b6124108251611872565b8110156124cb578061243f6124296104238794866117c3565b6124386104236121cd85611eef565b9086612d0d565b50909391905f90836124a75750509061249491600194612481612465610423878a6117c3565b61247a61042361247489611eef565b8b6117c3565b908a6128d5565b915b61248d868d6117c3565b51896142bf565b6124a061053183611eef565b5201612406565b8091949593506124c2575b5060019392612494928892612483565b965060016124b2565b50505050565b969594939291906124e482341015611e99565b81340390348211611880576124fa36828b61032c565b94612509600287511015611920565b612513865161196c565b95816125226105318951611872565b5261252c89612c24565b906125378151611872565b90818315925b612576575050505050612570611e2298998361256661255f6105938a51611872565b5198611791565b511192369161032c565b9161421c565b806125e285858f948e6125f4966125a9612595610423611a5286611872565b6125a2610423868d6117c3565b9083612d0d565b50509390928d856125ba8d51611872565b84149182612642575b5050612629575b5f956125fa575b906125db916117c3565b5190612f41565b611a8b6125ee83611872565b8d6117c3565b8061253d565b9450806126208b611c7d6104238461261a6104236119d46125db99611872565b936117c3565b959091506125d1565b61263285611872565b61263c83836117c3565b526125ca565b10159050858f6125c3565b73bdeb498e872e36f899f237fd1b93673ed6c14474330361275a57612670615060565b612678615060565b6126813361278b565b612689615060565b6001600160601b0360a01b735c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f815f5416175f5573c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2808260015416176001556126d7615060565b6e22d473030f116ddee9f6b43ac78ba3826003541617600355816002541617600255731f98431c8ad98523631ae4a59f267346ea31f9848160045416176004557fe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b546005556006541660065561274a614379565b612752614389565b611e226143d3565b60405162461bcd60e51b81526020600482015260096024820152682737ba1027bbb732b960b91b6044820152606490fd5b6001600160a01b039081169081156127f7577f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981168417909155167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a3565b604051631e4fbdf760e01b81525f6004820152602490fd5b91906040838203126102625782356001600160401b03811161026257830181601f8201121561026257602091818361284993359101611b25565b92013561039f8161031b565b600160ff1b8114611880575f0390565b60018060a01b0316735c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f81149081156128cc575b81156128c3575b81156128ba575b81156128b1575b816128aa575090565b9050151590565b801591506128a1565b8015915061289a565b80159150612893565b8015915061288c565b916128df9161471a565b6128ea839293612c24565b6129a65761039f9261293c61294a612907610498610498876147e1565b93604051928391602083019586906029926001600160601b0319809260601b16835260601b1660148201525f60288201520190565b03601f1981018352826102e3565b51902090916043916055936040519260388401526f5af43d82803e903d91602b57fd5bf3ff60248401526014830152733d602d80600a3d3981f3363d3d373d3d3d363d73825260588201526037600c8201206078820152012090565b906129e3612a4b61039f9461049894604051938491602083019384906028926001600160601b0319809260601b16835260601b1660148201520190565b03926129f7601f19948581018352826102e3565b51902091612a04846147e1565b6040516001600160f81b03196020820190815260609690961b6bffffffffffffffffffffffff191660218201526035810194909452605584015260759081018352826102e3565b5190206001600160a01b031690565b604051602081018181106001600160401b0382111761028d576040525f8152905f368137565b909260809261039f95948352602083015260018060a01b031660408201528160608201520190611b5b565b9092915f5b612aba8551611872565b811015612c1057612ace61042382876117c3565b90612ade610423611a0f83611eef565b91612ae9838261471a565b5092612af68183886128d5565b8094612b0383858a612d0d565b50604080516370a0823160e01b81526001600160a01b03968716600480830191909152919891969491851693909260209290918385602481895afa918215610689578f612b608f9483908b99612b66995f9261065c575050612c17565b906142bf565b931603612c08578a5f92945b612b7c8251611885565b881015612bff57612b96610423612b9d936106058b611efd565b908b6128d5565b965b1691612ba9612a5a565b90833b1561026257612bd25f9692879351998a978896879563022c0d9f60e01b87528601612a80565b03925af191821561068957600192612bec575b5001612ab0565b8061070c612bf99261027a565b5f612be5565b50508796612b9f565b8a5f94612b72565b5050509050565b9081039081116118805790565b5f906001600160a01b0316735c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8103612c525750506126f290565b61039f57505f90565b51906001600160701b038216820361026257565b9081606091031261026257612c8381612c5b565b916040612c9260208401612c5b565b92015163ffffffff811681036102625790565b519061ffff8216820361026257565b919082608091031261026257612cc982612c5b565b91612cd660208201612c5b565b9161039f6060612ce860408501612ca5565b9301612ca5565b8115612cf9570490565b634e487b7160e01b5f52601260045260245ffd5b9091612d19818461471a565b5091612d2481612c24565b60018103612dfd5750612d42610498610498600494876080956128d5565b604051630240bc6b60e21b815292839182905afa908115610689575f935f80925f94612dbb575b50600a612d9691612d8e8261ffff80936001600160701b038091169b16971604611894565b951604611894565b92935b6001600160a01b03918216911603612db5579291905b90919293565b91612daf565b9050612d969550600a9350612de991925060803d608011612df6575b612de181836102e3565b810190612cb4565b9296509193919291612d69565b503d612dd7565b91612e1461049861049860609388600499966128d5565b604051630240bc6b60e21b815295869182905afa8015610689575f945f91612e4d575b506001600160701b038091169416908293612d99565b9050612e7291945060603d606011612e7c575b612e6a81836102e3565b810190612c6f565b509390935f612e37565b503d612e60565b15612e8a57565b60405162461bcd60e51b815260206004820152602c60248201527f556e697377617056324c6962726172793a20494e53554646494349454e545f4f60448201526b155514155517d05353d5539560a21b6064820152608490fd5b15612eeb57565b60405162461bcd60e51b815260206004820152602860248201527f556e697377617056324c6962726172793a20494e53554646494349454e545f4c604482015267495155494449545960c01b6064820152608490fd5b909394612f4f851515612e83565b82151580613023575b612f6190612ee4565b80613005575060405163cc56b2c560e01b81526001600160a01b0395861660048201525f60248201529460209186916044918391165afa90811561068957612fd684612fd0612fcb612fe197612fc5612fdb9761039f9b5f91612fe6575b50611894565b95614888565b61482d565b94612c17565b614888565b90612cef565b6148c4565b612fff915060203d6020116106825761067381836102e3565b5f612fbf565b905061039f9450612fd684612fd0612fcb612fe197612fdb96614888565b50831515612f58565b5190611e228261031b565b9080601f830112156102625781519060209161305281610304565b9361306060405195866102e3565b81855260208086019260051b82010192831161026257602001905b828210613089575050505090565b83809183516130978161031b565b81520191019061307b565b9080601f83011215610262578151906020916130bd81610304565b936130cb60405195866102e3565b81855260208086019260051b82010192831161026257602001905b8282106130f4575050505090565b815181529083019083016130e6565b919060a083820312610262578251926020810151926040820151926001600160401b0393848111610262578161313a918501613037565b936060840151908111610262576080916131559185016130a2565b92015161039f8161031b565b608081830312610262578051926020820151926001600160401b03938481116102625781613190918501613037565b936040840151908111610262576060916131559185016130a2565b919082604091031261026257602082516131c48161031b565b92015190565b519065ffffffffffff8216820361026257565b81601f82011215610262578051906131f482611b0a565b9261320260405194856102e3565b8284526020838301011161026257815f9260208093018386015e8301015290565b91909180830360e081126102625760c081126102625760806040519161324883610292565b1261026257604051613259816102ad565b82516132648161031b565b815260208301516132748161031b565b6020820152613285604084016131ca565b6040820152613296606084016131ca565b606082015281526132a96080830161302c565b602082015260a082015160408201529260c08201516001600160401b0381116102625761039f92016131dd565b604061039f94936101009360018060a01b038091168452815181815116602086015281602082015116848601526060848201519165ffffffffffff80931682880152015116608085015260208201511660a0840152015160c08201528160e08201520190611b5b565b908160609103126102625780516133558161031b565b91604060208301516131c48161031b565b919060a08382031261026257825161337d8161031b565b9260208101519260408201519260608301516001600160401b038111610262576080916133ab9185016131dd565b92015161039f81610fb2565b600192919060f81c601f16601081106133ce575050565b60088110156134a5578061341e57506133f381602080611e2294518301019101613366565b909290156134145761340f33945b6001600160a01b0316614d5f565b614f0c565b61340f3094613401565b60018103613467575061343d81602080611e2294518301019101613366565b9092901561345d5761345833946001600160a01b0316614d5f565b614d83565b6134583094613401565b600414613472575b50565b61348881602080611e229451830101910161333f565b91906001600160a01b039061349e908216614d5f565b9116613faf565b600a81036135235750806020806134c193518301019101613223565b6003549091906134d9906001600160a01b0316610498565b91823b1561026257613505925f92836040518096819582946302b67b5760e41b845233600485016132d6565b03925af18015610689576135165750565b8061070c611e229261027a565b600b810361355a575061355561354582602080611e22955183010191016131ab565b91906001600160a01b0316614d5f565b6137e3565b600c8103613581575061357c61354582602080611e22955183010191016131ab565b613eb4565b600d81036135b357506135a081602080611e2294518301019101613161565b6001600160a01b03169290919034614c2c565b600e81036135e557506135d28160208061346f94518301019101613161565b6001600160a01b03169290919034614ae6565b600f146135ef5750565b61360581602080611e2294518301019101613103565b6001600160a01b031693909290614965565b90919392935f5b6136288251611872565b8110156137355761363c61042382846117c3565b9061365261042361364c83611eef565b856117c3565b9161365d838261471a565b509061366b611a0f84611eef565b51916001600160a01b03828116911603613725576136be6104986104985f94965b6136968951611885565b87101561371e576136b66136af610423611a528a611efd565b828c6128d5565b945b8a6128d5565b6136c6612a5a565b94813b15610262575f80946136f16040519889968795869463022c0d9f60e01b865260048601612a80565b03925af19182156106895760019261370b575b500161361e565b8061070c6137189261027a565b5f613704565b8c946136b8565b6136be6104986104985f9661368c565b505050509050565b3d15613767573d9061374e82611b0a565b9161375c60405193846102e3565b82523d5f602084013e565b606090565b5f918291613778612a5a565b91602083519301915af161378a61373d565b501561379257565b60405162461bcd60e51b815260206004820152602360248201527f5472616e7366657248656c7065723a204554485f5452414e534645525f46414960448201526213115160ea1b6064820152608490fd5b90600160ff1b81036138cb575047905b816137fc575050565b600254613811906001600160a01b0316610498565b803b15610262575f8391600460405180968193630d0e30db60e41b83525af19182156106895761388f936020936138b8575b50600254613859906001600160a01b0316610498565b60405163a9059cbb60e01b81526001600160a01b03909216600483015260248201929092529283919082905f9082906044820190565b03925af18015610689576138a05750565b61346f9060203d6020116106f8576106ea81836102e3565b8061070c6138c59261027a565b5f613843565b90478211156137f3575b604051631a84bc4160e21b8152600490fd5b9360429592916001600160601b03199485809260601b16875262ffffff60e81b809460e81b16601488015260601b16601786015260e81b16602b84015260601b16602e8201520190565b6001600160a01b039081165f19019190821161188057565b6001600160a01b039081166001019190821161188057565b90613979602091949394604084526040840190611b5b565b6001600160a01b03909416910152565b9190826040910312610262576020825192015190565b6001600160a01b039182168152911515602083015260408201929092529116606082015260a06080820181905261039f92910190611b5b565b93959491926139e690600955565b613a636139fb6104986104986104238a611791565b91613a32613a0b6104238a611791565b613a176104238b6117a3565b613a2c613a2388611791565b5162ffffff1690565b91614430565b604080516370a0823160e01b81526001600160a01b0390921660048301529360209384918391829081906024820190565b03915afa801561068957600a915f91613dd8575b50048110613b70575b613a97575b50505050505050611e22600854600955565b613b139660018451145f14613b1f57613b0d9281613ad2610423613acc613a23613ac661042361293c986117a3565b99611791565b92611791565b915195869485019192602b936001600160601b0319809360601b16845262ffffff60e81b9060e81b16601484015260601b1660178201520190565b91614e1b565b5f808080808080613a85565b613b0d9281613b3361042361293c946117b3565b95613b40613a23826117a3565b613b64610423613b5e613a23613b58610423886117a3565b95611791565b94611791565b935197889687016138e7565b8351600103613d7f57878784613b8861042384611791565b613be3613b97613a238a611791565b9161293c613ba7610423886117a3565b85519485938b85019192602b936001600160601b0319809360601b16845262ffffff60e81b9060e81b16601484015260601b1660178201520190565b613c31610498610498613bf8610423886117a3565b613c076104986104238a611791565b6001600160a01b0390911610968b613a2c613a23613acc610423613c2b8187611791565b956117a3565b845f8c613c94613c4361095f8b6145f6565b95848414613d5a57600a54613c78908d9061293c90613c6a906001600160a01b0316613949565b9b5b8b519485938401613961565b8751630251596160e31b8152988997889687956004870161399f565b03925af191825f925f94613d25575b50613ceb57505050848103613a8057825162461bcd60e51b8152602060048201526012602482015271151bdbc81b5d58da081c995c5d595cdd195960721b6044820152606490fd5b15613d165750613cfa90612855565b03613d05575f613a80565b8151636a70124760e11b8152600490fd5b613d209150612855565b613cfa565b909350613d49919250863d8811613d53575b613d4181836102e3565b810190613989565b919091925f613ca3565b503d613d37565b600b54613c78908d9061293c90613d79906001600160a01b0316613931565b9b613c6c565b878784613d8e61042384611791565b613dd3613d9d613a238a611791565b9161293c613dad610423886117a3565b613db9613a238d6117a3565b613dc56104238a6117b3565b9187519687958d87016138e7565b613be3565b613def9150843d86116106825761067381836102e3565b5f613a77565b600254613e0a906001600160a01b0316610498565b6040516370a0823160e01b81523060048201529091602082602481865afa918215610689575f92613e93575b5081613e4157505050565b823b1561026257604051632e1a7d4d60e01b815260048101839052925f908490602490829084905af192831561068957611e2293613e80575b50614fa7565b8061070c613e8d9261027a565b5f613e7a565b613ead91925060203d6020116106825761067381836102e3565b905f613e36565b600254909190613ecc906001600160a01b0316610498565b6040516370a0823160e01b815230600482015290929091602083602481875afa928315610689575f93613f0b575b5082106138d55781613e4157505050565b613f2591935060203d6020116106825761067381836102e3565b915f613efa565b6001600160a01b031680613f4e57504780613f45575050565b611e2291614fa7565b6040516370a0823160e01b81523060048201529091602082602481865afa918215610689575f92613f8e575b5081613f8557505050565b611e2292614ff1565b613fa891925060203d6020116106825761067381836102e3565b905f613f7a565b9091906001600160a01b031680613fd25750479081106138d55780613f45575050565b6040516370a0823160e01b815230600482015290929091602083602481875afa928315610689575f93614023575b5082106140115781613f8557505050565b604051630ceb95c760e31b8152600490fd5b61403d91935060203d6020116106825761067381836102e3565b915f614000565b80614078575b504780158015614058575050565b5f8080938193829061406f575b3390f11561068957565b506108fc614065565b5f8091614083612a5a565b90602082519201904161c350f15061409961373d565b505f61404a565b90816140b6575b50504780158015614058575050565b5f806140e6936140c4612a5a565b90602082519201904161c350f16140d961373d565b5081156140ed5750611843565b5f806140a7565b90505f610515565b90915f80949381946040519160208301946323b872dd60e01b865260018060a01b038092166024850152166044830152606482015260648152614137816102c8565b51925af161414361373d565b816141a2575b501561415157565b60405162461bcd60e51b8152602060048201526024808201527f5472616e7366657248656c7065723a205452414e534645525f46524f4d5f46416044820152631253115160e21b6064820152608490fd5b80518015925082156141b7575b50505f614149565b6141ca925060208091830101910161182e565b5f806141af565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b0316330361420457565b60405163118cdaa760e01b8152336004820152602490fd5b9294909593955f14614244575091611e229591611fd79361423c42611f0b565b92339261234b565b959192506064420180421161188057611ff7904211156116ef565b1561426657565b60405162461bcd60e51b815260206004820152602b60248201527f556e697377617056324c6962726172793a20494e53554646494349454e545f4960448201526a1394155517d05353d5539560aa1b6064820152608490fd5b93909291936142cf84151561425f565b84151580614370575b6142e190612ee4565b856143585760405163cc56b2c560e01b81526001600160a01b0392831660048201525f6024820152955060209186916044918391165afa9182156106895761434d61434661435392614340612fdb9661039f995f91612fe65750611894565b90614888565b9283614888565b9361482d565b614915565b505061435361434d61434661039f96612fdb95614888565b508215156142d8565b614381615060565b612710600755565b614391615060565b5f198060085560095573fffd8963efd1fc6a506488495d951d5263988d266001600160601b0360a01b6401000276a381600a541617600a55600b541617600b55565b6143db615060565b6001600c55565b9081516143ef818461508e565b926017821061441e57602b6017820151921061440c57602b015191565b60405163a78aa27f60e01b8152600490fd5b604051636c84b51f60e11b8152600490fd5b6001600160a01b039291838116848316116144cb575b62ffffff846004541693856040519381602086019616865216604084015216606082015260608152614477816102ad565b5190206005546040516001600160f81b03196020820190815260609490941b6bffffffffffffffffffffffff19166021820152603581019290925260558201526144c4816075810161293c565b5190201690565b90614446565b6001600160a01b0393929184163081036144f05750611e22935061509d565b848492941161454757846003541693843b15610262575f94868692816084966040519a8b998a98631b63c28b60e11b8a5260048a01521660248801521660448601521660648401525af18015610689576135165750565b60405163c4bd89a960e01b8152600490fd5b8051601619808201929190818411611880578360088301106145e457601782106145e457818351106145d257601782146145c057601f8416801560051b0183019182010160178201915b8181106145b05750505052565b82518152602092830192016145a3565b60405163664a531d60e11b8152600490fd5b604051633b99b53d60e01b8152600490fd5b6040516323d5783d60e11b8152600490fd5b600160ff1b8110156102625790565b61049892936104985f60409461468c61463c6146208a6143e2565b6001600160a01b03808416908316109b8c989093909290614430565b948484146146c457600a546146709061465d906001600160a01b0316613949565b9a5b61293c8a5193849260208401613961565b8751630251596160e31b8152998a97889687956004870161399f565b03925af18015610689575f925f916146a357509192565b90506146bf91925060403d604011613d5357613d4181836102e3565b919092565b600b54614670906146dd906001600160a01b0316613931565b9a61465f565b61049892936104985f60409461468c61463c6146fe8a6143e2565b6001600160a01b03808316908416109b8c989093909290614430565b90916001600160a01b039182841683821680821461478e57101561478957925b9183161561474457565b60405162461bcd60e51b815260206004820152601e60248201527f556e697377617056324c6962726172793a205a45524f5f4144445245535300006044820152606490fd5b61473a565b60405162461bcd60e51b815260206004820152602560248201527f556e697377617056324c6962726172793a204944454e544943414c5f41444452604482015264455353455360d81b6064820152608490fd5b5f906001600160a01b0316735c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8103612c525750507f96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f90565b9061271091828102928184048114821517156118805783040361484c57565b60405162461bcd60e51b815260206004820152601460248201527364732d6d6174682d6d756c2d6f766572666c6f7760601b6044820152606490fd5b5f9291801591821561489e575b50501561484c57565b80820294509150811582850482141715611880576148bc9084612cef565b145f80614895565b9060018201918281116118805782106148d957565b60405162461bcd60e51b815260206004820152601460248201527364732d6d6174682d6164642d6f766572666c6f7760601b6044820152606490fd5b91908201918281116118805782106148d957565b1561493057565b60405162461bcd60e51b815260206004820152600d60248201526c139bdd081cdd5c1c1bdc9d1959609a1b6044820152606490fd5b909392916149c1816149e4946149bc60018060a01b0361498a81600654161515614929565b61499a6104236119d48651611872565b6002549091906149b490610498906001600160a01b031681565b911614614a5d565b615186565b6149dd836149d161042385611791565b610de961042385611791565b3092615436565b506002546149fa906001600160a01b0316610498565b6040516370a0823160e01b815230600482015290602090829060249082905afa801561068957611e2293614a37925f92614a3c575b501115614a9a565b613df5565b614a5691925060203d6020116106825761067381836102e3565b905f614a2f565b15614a6457565b60405162461bcd60e51b815260206004820152600e60248201526d496e76616c696420496e7075747360901b6044820152606490fd5b15614aa157565b60405162461bcd60e51b815260206004820152601a60248201527f494e53554646494349454e545f4f55545055545f414d4f554e540000000000006044820152606490fd5b92919360018060a01b0390614b0082600654161515614929565b85511561179e57614b3986614b86956149bc614b7f95602084015116614b3361049861049860025460018060a01b031690565b14614a5d565b95614b45848289615599565b96614b5b87614b538a611791565b511115614baa565b614b7a614b6a61042383611791565b614b738a611791565b51906137e3565b6152c3565b1015614bef565b614b8f82611791565b518111614b9a575090565b611d1161039f9161213f84611791565b15614bb157565b60405162461bcd60e51b8152602060048201526016602482015275115610d154d4d2559157d25394155517d05353d5539560521b6044820152606490fd5b15614bf657565b60405162461bcd60e51b815260206004820152600e60248201526d1253959053125117d3d55514155560921b6044820152606490fd5b93909282614c59916149bc60018060a01b03614c4d81600654161515614929565b61499a61042385611791565b90614c6e6104986104986104238551876117c3565b6040516370a0823160e01b8082526001600160a01b0384166004830152602095919392918685602481865afa95861561068957614cf29988965f98614d36575b50918183614cc6614ccb956135556104238b97611791565b615436565b506040519081526001600160a01b0390921660048301529095869190829081906024820190565b03915afa801561068957611e2294614d12935f92614d19575b50506118a4565b1015614a9a565b614d2f9250803d106106825761067381836102e3565b5f80614d0b565b614ccb9391985091614d558793893d8b116106825761067381836102e3565b9891935091614cae565b6001600160a01b03811660018103614d775750503390565b60020361039f57503090565b614d999391949260095561096461095f866145f6565b90919015614dca5750614dab90612855565b03614db857600854600955565b604051636a70124760e11b8152600490fd5b614dd49150612855565b614dab565b15614de057565b60405162461bcd60e51b8152602060048201526013602482015272151bdbd7d31a5d1d1b1957d49958d95a5d9959606a1b6044820152606490fd5b909192614e2f61049861049886518761508e565b6040516370a0823160e01b815230600482015290602090829060249082905afa91821561068957614e9f92614e91925f91614eed575b50939291905b614e7a604288511015956145f6565b8515614ee757305b614e8b89615704565b916146e3565b90919015614ee05750612855565b9115614ebf57614e91614e9f913090614eb787614559565b929190614e6b565b50611e229250908110159081614ed6575b50614dd9565b905015155f614ed0565b9050612855565b84614e82565b614f06915060203d6020116106825761067381836102e3565b5f614e65565b9390919293600160ff1b8314614f34575b90614e91614e9f91614e7a604288511015956145f6565b9150614f4761049861049886518761508e565b6040516370a0823160e01b815230600482015290602090829060249082905afa91821561068957614e9f92614e91925f91614f88575b509391509150614f1d565b614fa1915060203d6020116106825761067381836102e3565b5f614f7d565b5f80809381935af115614fb657565b60405162461bcd60e51b815260206004820152601360248201527211551217d514905394d1915497d19052531151606a1b6044820152606490fd5b5f91826044926020956040519363a9059cbb60e01b8552600485015260248401525af13d15601f3d1160015f51141617161561502957565b60405162461bcd60e51b815260206004820152600f60248201526e1514905394d1915497d19052531151608a1b6044820152606490fd5b60ff5f805160206157338339815191525460401c161561507c57565b604051631afcd79f60e31b8152600490fd5b9060141161440c576014015190565b9091906001600160a01b0316806150b85750611e2291614fa7565b600160ff1b82146150ce575b91611e2292614ff1565b6040516370a0823160e01b815230600482015292909150602083602481855afa801561068957611e22935f91615109575b50919092506150c4565b615122915060203d6020116106825761067381836102e3565b5f6150ff565b9081608091031261026257606060405191615142836102ad565b61514b81612ca5565b8352602081015161515b8161031b565b6020840152604081015161516e81610fb2565b6040840152015161517e81610fb2565b606082015290565b9091615192825161196c565b6151a161049861042386611791565b6006545f9591906151ba906001600160a01b0316610498565b905b83518710156152a55761522690966151dd61049861042360018401876117c3565b80986151e9838a6117c3565b5160405163704037bd60e01b81526001600160a01b0392831660048201529290911660248301526044820152916080908190849081906064820190565b0381875afa801561068957610498602061525892600196615271955f92615278575b505001516001600160a01b031690565b61526283886117c3565b6001600160a01b039091169052565b01956151bc565b6152979250803d1061529e575b61528f81836102e3565b810190615128565b5f80615248565b503d615285565b50505092509050565b90816020910312610262575161039f8161031b565b9092916152cf84611791565b505f805b835182101561542f57506152ea61042382856117c3565b600182016152fb61042382896117c3565b90855181145f1461541d5750835b60408051633684184360e21b81526001600160a01b03946004946020949293918716919085858881865afa9081156106895788615383995f93899885916153f0575b50945163029e02cd60e51b815294169116149682018781526001600160a01b0394909416602085015290968793849291839160400190565b03925af18015610689576001936153af925f926153d3575b5050906001600160801b0382169160801c90565b906001600160801b03925f146153ca575016915b01906152d3565b905016916153c3565b6153e99250803d106106825761067381836102e3565b5f8061539b565b6154109150893d8b11615416575b61540881836102e3565b8101906152ae565b5f61534b565b503d6153fe565b61042361542a91876117c3565b615309565b9450505050565b939290919361544485611791565b505f905b835182101561542f575061545f61042382856117c3565b6001820161547061042382896117c3565b90855181145f146155465750835b60408051633684184360e21b81526001600160a01b03946004946020949293918716919085858881865afa90811561068957886154f7995f93899885916153f05750945163029e02cd60e51b815294169116149682018781526001600160a01b0394909416602085015290968793849291839160400190565b03925af1801561068957600193615522925f926153d3575050906001600160801b0382169160801c90565b906001600160801b03925f1461553d575016915b0190615448565b90501691615536565b61042361555391876117c3565b61547e565b51906001600160801b038216820361026257565b908160609103126102625761558081615558565b9161039f604061559260208501615558565b9301615558565b909291926155a7815161196c565b936155b38351866117c3565b528151805b6155c157505050565b6155d66104236155d083611872565b846117c3565b906155ec610498610498610423611a0f85611872565b916156076155fa83896117c3565b516001600160801b031690565b604080516305e8746d60e01b815290946020939260049285818581865afa9081156106895761567d965f926156e7575b505060018060a01b03975195869485938493630abcd78360e41b85528b60609c8d9a169116149184019092916020906001600160801b0360408401951683521515910152565b03915afa908115610689576156b2935f926156b8575b5050611a8b6001600160801b036156a984611872565b921691886117c3565b806155b8565b6156d79250803d106156e0575b6156cf81836102e3565b81019061556c565b50505f80615693565b503d6156c5565b6156fd9250803d106154165761540881836102e3565b5f80615637565b90602b8251106145d257602b60405192600b810151600b8501520151602b830152602b82526060820160405256fef0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00a2646970667358221220acaf04ce472be82b53e96d88f0fb25b098e6c53619924b6a8555e62e55080e5d64736f6c6343000819003300582103175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9054fffd8963efd1fc6a506488495d951d5263988d2600582103e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0058210366cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c68804227100058210365a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a804501000276a300582103f6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c704101005821033f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee305820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0219b44105581d03639c64228440f4bd2ae46d5b506813ab36fd9137777c62afe5d42f1007011957880219040205581d02876caffad5ef4c51b4d7ef6ea839513ae3eeee534ef47110ec28049504050219400805581e03f47119f7136b6a1def201d85e900331cd238747bc8c56c3c6e6463e7f0040105581e03311fc32da51f90af9ee044ac2e8c9c108c54017da6dd8b9e59106bc2a0040103ce33220d5c7f0d09d75ceff76c05863c5e7d6e801c70dfe7d5d45d4c44e806540306b487d15c028b6df56c3ebb9b7086965eba3a240857a647faece2ff13269f2b05581e03f3dd9015331d033b99d51b3f932aa7d589333cffd3d5c3281757b40b9007011bffffffffffffffff05581e03498ad85465a6d9a87a248ecba19ca612a0bc6e9eaae5da1c26f895fdc00c014709865bab2c88db0219a980039e26cdfb2a9ef661c97ac577b5207d516c184ae68b63335b6d3aaea2a72fecb50304e047bddcc986b3e4762c0b8f8a30b1c2c1bd53d7c959886b27d01c14c5957903ca665c98bdf67c1125f0bedd8d8fd52675bde6f425d81e4d230df5613f775ca703d49702deef497ce5e14f1897dec381593ecd0ee7550f73dc9e69d7eff813644d03457919256d8fd435855f2f60a1b6d5c0c602cb85b7dbeb1bb5793b9001e5b6fc03fd9b6e85a3740c518b284ce1c741f50380f55d7e834f652157495486cfe2ba4003cbe85ca4262ccf78f464d42fe901642b5bb184d1d7f88e69d137178ab9e69dbd03ab9c6524b81a15e0a09ceade91a4d7906800fe4651a203dd36398a44bbfd362d03c6485d885a74cceb4aa4805afe93b9ec4fac2e7dec21ea6618174c100e3dd62003af9497edc61cb8bbf853ac85e79dbdd55239cb7dac9440159d685b462b2ee58303adf802caed32458eecb623c177f75a4684ddafae4f24940323d04b7898dddf7a030881ae0052e0b541d7ee6705433d1a3a943692cc1fb773f54261e6fe25c2b27803725840d94797b3d2448b9513cd102ef6ba6d97a9d6c346311f39dbd5e4aeb45e030e007fd342e7c666da57b5a55e785a48b3e7f54346c807bf4131d18963bd5b700219ffff03dbb2743e1bf3bc70ec684efe001f85c977d856e3f6a407d871f1ad87b3427b7103a6dd7d23742fc177717871d75110b6bc48a4c5007a65ef9a978a6f7ea4e7bd810353c8e4f984c85f15cc08a12ff9dff4ff696c9049e3e3a31283a10c361c6c650c03820e7d036d5c74cc931cdf9bdc4662679fee6bcc9c36a9ec949edfbe708735bd03c2b111442e029426523cd89ead01bfcadf50af753cdef5b369fff72116a7289a0315ec50a03cba12c11b8283b52bc0a3382c94d90ef45268c9109cc46096683094032f7cfc2cfa8cbcec6764b6361d8cb03f92f4d385ad5075e338bf0cc0d767e33403ec9f544fd94a9f85057cdb333a5ef60bba2c00741b236392b85879ef7dce8a000377df763b2775ab1a67a1c2cce7abc97ead79f630416f12af3966fc1af62bc51f0390820b770c5afe59f8ab4997a135077d606718156a330b96b938c8e1bddad9c60219ffff0351d06600fc48aa1cd8ccf461876591ed92572f5964e3a794906cd5a5a736211903346824faa70f9045d88a7a340f4e1fdab02df5564c33dc3cbee2fff33a198d2a03b4920daaccf096345811a281e9d8f1ceddc1f72c59fadfada053dad0eb43ffcf038838b0e1e40bdbb5a40ac9330ff369f3ba3ce1da08f02fc5e8c1a9c3a06ad95c03be603f93cb67235b0ce40ed9e09d63516e5fa4c84529b552aa1c3baeacbfa08b03cb23c6b8674d6201f953d7f57495d4ac36d5711d55b06588d43f57e5c97290c2031d340b453d99e40ede024baffab1b3b32d672e8cfafd6a401aa5e872cbbbed4d0219ffff031cb713d5b98e1fd9c1c7d0457ed03cff2dd309134fc30bc8cc69e9f393084d9d0385be28acd8e5a9ac187b3f46965870433b710cf2fd00e78f4cfdf0e4886e3fa2030f2904bcfcd2e5b65894e512aab57a9633ff6715aa0233c6be0a20f5a70e846a03209df946dae28f680b7fe35f69b0a206f873dc893ce7818063c24e8d39625ee5033808177eb7739f2a0d707a72341461c975b76bbc7ac89b4b3993a940482aceb6033f09cbe6400a6156d36256fcaf08a3c450fed265a9aa9c252cc651e5398497ea0351dcde93059a13be750adcfaeb86176c0cd7a1cf4f00de3b0b067b938c3131550219ffff036f4ba5041ec34e5ad20f27343109bdab7e03181a84f94d4f44b4b66a1177bdc803578a82a14b10cc29aafba9da1ef8327d17b608a8d510c169a1bff286ab632b710382da591b4c27bdc67233a56e852f47d5c0d4adeb12dd559102e6aa521bc6691803d1eccfc39d03774334ee2c11cbddf526ecad8efa1ece517f490e30685df431e3037e886dcd44962845861636005fe9774ebe38db969fe11b802e56dbcb22f921f003f851851f31bd96d86410a5b5fbc002d49c8557d0b4171cb5b2a3d74540d05453035b494d9fa637b4c8ea2d0244da4c4ef964a3ce23faf4aeb344ead56fe6872e450331df86174d19d4f7a64b9feea449f19c426a4076b9e72652ac4ef8be5923b5d40325b24749d38072352205cad7c630332903e58e756168fe462f7cd0f10d1e6ec8037abc4bc4286b5bfe55d54a4cac58aa665ff4c70a8566a22d13dc3e3bf200689203f00096d9fd850394917bc6dea78f91d7601842300ecbaf870d2ea548c8364b0603d8c4aa053640ac090fe91eb500c8d979ff8658c78d5659f52df9ba2d52d742d8034b0771f7765d87278dc4243edae494339a3211ba0d6f11452a95bce27499038003b4771969c58afb00b26c2fd23d3a04e22404ea4d643ff920dd5852aefd35286903038958194ff4630a736dd175b0c460e04e30410a1d6b947062c500b390fdad4e036b002fa1a4ecc0c112680eb6d11f538f5f02dcedcdf8a1fa01b8357dda386e77033e3f07336025fd4faedd4d09468c2281ad5810c403517ec9c30ab7c8bbbf75d803fcad8c671df6139a9b2af47a7ad69f047853c1e7a77a4990fdbe9eb53c7f776703ce158ad80e548c8b6f1d7846737ea80f113648bd73f24c173b4ec7dea3fbf4ea03e5b8b5b1176af1d8f6f0348c563c6ba6e4a4cd1bd905aeef0febfee5e1b7463f0328d401030f733cc3ec4c649749033a6d406faa768de36df982bb0e2b6383c5830398655926d7ec1914d9e02e5fd3fb4436eae06ed55d9d5bda4600a5a3c5819d5f03e4da7a497d1a5d6f2824d5de0783a0600453332912f016e2a60c1afed965ccbe03cfad1eb775484093d3914942ed25151ef7f4e67a0261e3f465f339412d38ca900311628d4726a3f4be18ddd5e6d05949603b84a606dafd51f00a1a9facc14c2065033543d40fe32c46898ef35a743f3eccdeab1dc211a23d30349e922350e738f11803ff4cfd8df78f09b601b5032fafdf54f3a6010c03c4024e180874a24fe63a5aef0308e646de1b8558bc5846be79f3c190dc2f593cdb3161f4f3a28784955db6771d035c6720265557f1d838e27be1d70a2f950b60baaf30b6da4eba698d03c34b224803a3a2fe05c989e1ca1062377d9e3f4d56c452a9e9aa2e88e16710b5355fdcbbcb0326258930d31c13a1ed017f9b332840e789d87f6209b3585a784297e86c55d12d037515a29bf2dc47f757037ba92bdaedb6f85aeea0a5e9d0ebaa7afbbf17ad938b03842e936ef2394185320732470844ce9546c1935c32f29bab348a71b5b8fbf0b003193cbc5748bddbb8a3a364da1b75ac7d0e75dc1438ab8e14ecfd834fe263c4f403ffd76237e5d0435f54c7d362aac2a7be8f4933100b377b4205dd07771ed98578032ee739d643abf9b49d0ddcde51b77162eb24bd02f4d8b617230f3ab71c3913eb032c5dc9bf165128100881a37ad4f837cf2c145d9a98c29c4be1391bfece277f2e0353b4fde41e241e84b23a7072a31be33cd65252095bf7518f752ea6e606ce3a880391656aa50dac0aa20b0dcc02e5e99ee0bcfe24c38a618478526cecab21720da903836fc9c38c897e40706b47461eba74da50399967facda16b47e1e7ec51fca68803b8b6b9c1e9f10008e1d55ae7a37244c2557a929428431c7e737ac3cdc232fc8a03361df2be3bb3ebe5a894fc6d1632b0e468ce16a7de2e9999bde2e99a054e462d05581e03305b479751ece2c9cc059886acbc618492e01d5438e8b490a6502322c00c1830480449f29074f6a3ab05581e0370f02829d676e57d162950596ed9ab0ffa4802c4cb4e65f13a73ec35b0040603cd43772fe60435cccc44d0b0152be3399b3cbb95ae3dc6b4658d5951e95fa3e8031e71c9a529b6857d946cd4e6ec6ea3b9c22022dc8be98c1eae00747edec6499503b236bd75c9c21bddb287d6a6925dc0d658aed590da7301e4e11ad7e815d9f6f4045920a26080604052600436106101635760003560e01c80637ecac20b116100d1578063b9b4aacd1161008a578063e98d8c3a11610064578063e98d8c3a1461040e578063ed83cd0b1461042e578063fa461e331461044e578063ff04e9b41461046e57600080fd5b8063b9b4aacd146103ae578063c7137f5e146103ce578063e1477062146103ee57600080fd5b80637ecac20b14610313578063824a811d146103335780638d690bab1461033b578063999895db1461035b578063a00000001461037b578063b8443aa71461038e57600080fd5b80632de6ca25116101235780632de6ca251461025b5780633c0000001461027b5780633ccfd60b1461028e5780634b849261146102a357806353b03a83146102d35780635ca7ab59146102f357600080fd5b8060021461016f5780606914610184578062f750e2146101975780630b93a9de146101db57806315e2b6cd1461020b5780631fce50961461022b57600080fd5b3661016a57005b600080fd5b61018261017d366004611926565b61048e565b005b61018261019236600461198d565b610538565b3480156101a357600080fd5b506101c76101b23660046119ea565b60046020526000908152604090205460ff1681565b604051901515815260200160405180910390f35b3480156101e757600080fd5b506101c76101f63660046119ea565b60026020526000908152604090205460ff1681565b34801561021757600080fd5b506101826102263660046119ea565b6105cd565b34801561023757600080fd5b506101c76102463660046119ea565b60036020526000908152604090205460ff1681565b34801561026757600080fd5b50610182610276366004611a0e565b610618565b610182610289366004611a27565b6106aa565b34801561029a57600080fd5b5061018261073f565b3480156102af57600080fd5b506101c76102be3660046119ea565b60016020526000908152604090205460ff1681565b3480156102df57600080fd5b506101826102ee3660046119ea565b6107df565b3480156102ff57600080fd5b5061018261030e366004611ace565b61082a565b34801561031f57600080fd5b5061018261032e3660046119ea565b6109c5565b610182610a10565b34801561034757600080fd5b506101826103563660046119ea565b610a7a565b34801561036757600080fd5b506101826103763660046119ea565b610acc565b61018261038936600461198d565b610b17565b34801561039a57600080fd5b506101826103a93660046119ea565b610bae565b3480156103ba57600080fd5b506101826103c93660046119ea565b610be1565b3480156103da57600080fd5b506101826103e9366004611b3a565b610c14565b3480156103fa57600080fd5b50610182610409366004611b66565b610d6f565b34801561041a57600080fd5b506101826104293660046119ea565b610e54565b34801561043a57600080fd5b506101826104493660046119ea565b610ea6565b34801561045a57600080fd5b50610182610469366004611ba7565b610ed9565b34801561047a57600080fd5b50610182610489366004611c27565b610ee7565b3443146104b65760405162461bcd60e51b81526004016104ad90611cc1565b60405180910390fd5b33600090815260016020526040902054606086901c9060ff166104eb5760405162461bcd60e51b81526004016104ad90611cdc565b6001600160a01b03811660009081526003602052604090205460ff166105235760405162461bcd60e51b81526004016104ad90611cf7565b6105308686868686610fe2565b505050505050565b3443146105575760405162461bcd60e51b81526004016104ad90611cc1565b33600090815260016020526040902054859060ff166105885760405162461bcd60e51b81526004016104ad90611cdc565b6001600160a01b03811660009081526003602052604090205460ff166105c05760405162461bcd60e51b81526004016104ad90611cf7565b6105308686868686611151565b6000546001600160a01b031633146105f75760405162461bcd60e51b81526004016104ad90611d12565b6001600160a01b03166000908152600260205260409020805460ff19169055565b3360009081526002602052604090205460ff166106475760405162461bcd60e51b81526004016104ad90611d2d565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b031663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b15801561069657600080fd5b505af1158015610530573d6000803e3d6000fd5b3443146106c95760405162461bcd60e51b81526004016104ad90611cc1565b33600090815260016020526040902054859060ff166106fa5760405162461bcd60e51b81526004016104ad90611cdc565b6001600160a01b03811660009081526003602052604090205460ff166107325760405162461bcd60e51b81526004016104ad90611cf7565b6105308686868686611399565b6000546001600160a01b031633146107695760405162461bcd60e51b81526004016104ad90611d12565b600080546040516001600160a01b039091169047908381818185875af1925050503d80600081146107b6576040519150601f19603f3d011682016040523d82523d6000602084013e6107bb565b606091505b50509050806107dc5760405162461bcd60e51b81526004016104ad90611d48565b50565b6000546001600160a01b031633146108095760405162461bcd60e51b81526004016104ad90611d12565b6001600160a01b03166000908152600160205260409020805460ff19169055565b6000546001600160a01b031633146108545760405162461bcd60e51b81526004016104ad90611d12565b82811461086057600080fd5b6000805b8281101561089a5783838281811061087e5761087e611d63565b90506020020135826108909190611d8f565b9150600101610864565b50604051632e1a7d4d60e01b81526004810182905273c02aaa39b223fe8d0a0e5c4f27ead9083c756cc290632e1a7d4d90602401600060405180830381600087803b1580156108e857600080fd5b505af11580156108fc573d6000803e3d6000fd5b5050505060005b8281101561053057600086868381811061091f5761091f611d63565b905060200201602081019061093491906119ea565b6001600160a01b031685858481811061094f5761094f611d63565b9050602002013560405160006040518083038185875af1925050503d8060008114610996576040519150601f19603f3d011682016040523d82523d6000602084013e61099b565b606091505b50509050806109bc5760405162461bcd60e51b81526004016104ad90611d48565b50600101610903565b6000546001600160a01b031633146109ef5760405162461bcd60e51b81526004016104ad90611d12565b6001600160a01b03166000908152600360205260409020805460ff19169055565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b031663d0e30db0346040518263ffffffff1660e01b81526004016000604051808303818588803b158015610a5f57600080fd5b505af1158015610a73573d6000803e3d6000fd5b5050505050565b6000546001600160a01b03163314610aa45760405162461bcd60e51b81526004016104ad90611d12565b6107dc816001600160a01b03166000908152600460205260409020805460ff19166001179055565b6000546001600160a01b03163314610af65760405162461bcd60e51b81526004016104ad90611d12565b6001600160a01b03166000908152600460205260409020805460ff19169055565b344314610b365760405162461bcd60e51b81526004016104ad90611cc1565b33600090815260016020526040902054859060ff16610b675760405162461bcd60e51b81526004016104ad90611cdc565b6001600160a01b03811660009081526003602052604090205460ff16610b9f5760405162461bcd60e51b81526004016104ad90611cf7565b61053060058787878787611492565b6000546001600160a01b03163314610bd85760405162461bcd60e51b81526004016104ad90611d12565b6107dc81611675565b6000546001600160a01b03163314610c0b5760405162461bcd60e51b81526004016104ad90611d12565b6107dc81611699565b33600090815260026020526040902054829060ff16610c455760405162461bcd60e51b81526004016104ad90611d2d565b6001600160a01b03811660009081526004602052604090205460ff16610c915760405162461bcd60e51b81526020600482015260016024820152601d60fa1b60448201526064016104ad565b604051632e1a7d4d60e01b81526004810183905273c02aaa39b223fe8d0a0e5c4f27ead9083c756cc290632e1a7d4d90602401600060405180830381600087803b158015610cde57600080fd5b505af1158015610cf2573d6000803e3d6000fd5b505050506000836001600160a01b03168360405160006040518083038185875af1925050503d8060008114610d43576040519150601f19603f3d011682016040523d82523d6000602084013e610d48565b606091505b5050905080610d695760405162461bcd60e51b81526004016104ad90611d48565b50505050565b33600090815260026020526040902054839060ff16610da05760405162461bcd60e51b81526004016104ad90611d2d565b6001600160a01b03811660009081526004602052604090205460ff16610dec5760405162461bcd60e51b81526020600482015260016024820152601d60fa1b60448201526064016104ad565b60405163a9059cbb60e01b81526001600160a01b0385811660048301526024820184905284169063a9059cbb90604401600060405180830381600087803b158015610e3657600080fd5b505af1158015610e4a573d6000803e3d6000fd5b5050505050505050565b6000546001600160a01b03163314610e7e5760405162461bcd60e51b81526004016104ad90611d12565b6107dc816001600160a01b03166000908152600260205260409020805460ff19166001179055565b6000546001600160a01b03163314610ed05760405162461bcd60e51b81526004016104ad90611d12565b6107dc816116e8565b610d6960058585858561170f565b6000546001600160a01b03163314610f115760405162461bcd60e51b81526004016104ad90611d12565b60005b85811015610f5357610f4b878783818110610f3157610f31611d63565b9050602002016020810190610f4691906119ea565b611699565b600101610f14565b5060005b83811015610f9657610f8e858583818110610f7457610f74611d63565b9050602002016020810190610f8991906119ea565b611675565b600101610f57565b5060005b81811015610fd957610fd1838383818110610fb757610fb7611d63565b9050602002016020810190610fcc91906119ea565b6116e8565b600101610f9a565b50505050505050565b6001600160a01b03841663095ea7b373ba12222222228d8ba445958a75a0704d566bf2c8611011856001611d8f565b6040516001600160e01b031960e085901b1681526001600160a01b0390921660048301526024820152604401600060405180830381600087803b15801561105757600080fd5b505af115801561106b573d6000803e3d6000fd5b50506040805160c081018252888152600060208083018290526001600160a01b03808b1684860152891660608085019190915260808085018a90528551808401875284815260a086015285519081018652308082529281018490528086019290925281019190915291516352bbbe2960e01b815273ba12222222228d8ba445958a75a0704d566bf2c894506352bbbe29935061110e929086904290600401611dee565b6020604051808303816000875af115801561112d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105309190611ecc565b600080869050600080826001600160a01b0316630902f1ac6040518163ffffffff1660e01b8152600401606060405180830381865afa158015611198573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111bc9190611efc565b509150915087156111eb576111e487826001600160701b0316846001600160701b03166118c2565b935061120b565b61120887836001600160701b0316836001600160701b03166118c2565b93505b8584101561123f5760405162461bcd60e51b81526020600482015260016024820152603760f91b60448201526064016104ad565b60405163a9059cbb60e01b81526001600160a01b038a811660048301526024820189905286919082169063a9059cbb90604401600060405180830381600087803b15801561128c57600080fd5b505af11580156112a0573d6000803e3d6000fd5b50505050881561131e57604080516020810182526000808252915163022c0d9f60e01b81526001600160a01b0387169263022c0d9f926112e7928a92913091600401611f4c565b600060405180830381600087803b15801561130157600080fd5b505af1158015611315573d6000803e3d6000fd5b5050505061138d565b604080516020810182526000808252915163022c0d9f60e01b81526001600160a01b0387169263022c0d9f9261135a928a913091600401611f4c565b600060405180830381600087803b15801561137457600080fd5b505af1158015611388573d6000803e3d6000fd5b505050505b50505050505050505050565b6001600160a01b03841663095ea7b3866113b4866001611d8f565b6040516001600160e01b031960e085901b1681526001600160a01b0390921660048301526024820152604401600060405180830381600087803b1580156113fa57600080fd5b505af115801561140e573d6000803e3d6000fd5b5050604051638201aa3f60e01b81526001600160a01b03878116600483015260248201879052858116604483015260648201859052600019608483015288169250638201aa3f915060a401600060405180830381600087803b15801561147357600080fd5b505af1158015611487573d6000803e3d6000fd5b505050505050505050565b6001860180546001600160a01b0319166001600160a01b0387169081179091558387558590600090819063128acb08308989816114e35773fffd8963efd1fc6a506488495d951d5263988d256114ea565b6401000276a45b604080516001600160a01b038c1660208201528e15158183015281518082038301815260608201928390526001600160e01b031960e089901b16909252611538959493929190606401611f83565b60408051808303816000875af1158015611556573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061157a9190611fbe565b60018b015491935091506001600160a01b031630146115bf5760405162461bcd60e51b81526020600482015260016024820152600f60fb1b60448201526064016104ad565b8854156115f25760405162461bcd60e51b81526020600482015260016024820152607960f81b60448201526064016104ad565b8615611639578461160282611fe2565b12156116345760405162461bcd60e51b81526020600482015260016024820152606d60f81b60448201526064016104ad565b611487565b8461164383611fe2565b12156114875760405162461bcd60e51b81526020600482015260016024820152604d60f81b60448201526064016104ad565b6001600160a01b03166000908152600360205260409020805460ff19166001179055565b60005460405163095ea7b360e01b81526001600160a01b03918216600482015260001960248201529082169063095ea7b390604401600060405180830381600087803b158015610a5f57600080fd5b6001600160a01b03166000908152600160208190526040909120805460ff19169091179055565b60018501546001600160a01b031633146117515760405162461bcd60e51b81526020600482015260036024820152623bba3360e91b60448201526064016104ad565b6001850180546001600160a01b0319163017905560008061177483850185611ffe565b91509150801561181e57865486146117b25760405162461bcd60e51b81526020600482015260016024820152606f60f81b60448201526064016104ad565b6000875560405163a9059cbb60e01b81523360048201526024810187905282906001600160a01b0382169063a9059cbb90604401600060405180830381600087803b15801561180057600080fd5b505af1158015611814573d6000803e3d6000fd5b5050505050610fd9565b865485146118525760405162461bcd60e51b81526020600482015260016024820152606f60f81b60448201526064016104ad565b6000875560405163a9059cbb60e01b81523360048201526024810186905282906001600160a01b0382169063a9059cbb90604401600060405180830381600087803b1580156118a057600080fd5b505af11580156118b4573d6000803e3d6000fd5b505050505050505050505050565b6000806118d1856103e5612033565b905060006118df8483612033565b90506000826118f0876103e8612033565b6118fa9190611d8f565b9050611906818361204a565b979650505050505050565b6001600160a01b03811681146107dc57600080fd5b600080600080600060a0868803121561193e57600080fd5b85359450602086013561195081611911565b9350604086013561196081611911565b94979396509394606081013594506080013592915050565b8035801515811461198857600080fd5b919050565b600080600080600060a086880312156119a557600080fd5b85356119b081611911565b94506119be60208701611978565b9350604086013592506060860135915060808601356119dc81611911565b809150509295509295909350565b6000602082840312156119fc57600080fd5b8135611a0781611911565b9392505050565b600060208284031215611a2057600080fd5b5035919050565b600080600080600060a08688031215611a3f57600080fd5b8535611a4a81611911565b94506020860135611a5a81611911565b9350604086013592506060860135611a7181611911565b949793965091946080013592915050565b60008083601f840112611a9457600080fd5b50813567ffffffffffffffff811115611aac57600080fd5b6020830191508360208260051b8501011115611ac757600080fd5b9250929050565b60008060008060408587031215611ae457600080fd5b843567ffffffffffffffff80821115611afc57600080fd5b611b0888838901611a82565b90965094506020870135915080821115611b2157600080fd5b50611b2e87828801611a82565b95989497509550505050565b60008060408385031215611b4d57600080fd5b8235611b5881611911565b946020939093013593505050565b600080600060608486031215611b7b57600080fd5b8335611b8681611911565b92506020840135611b9681611911565b929592945050506040919091013590565b60008060008060608587031215611bbd57600080fd5b8435935060208501359250604085013567ffffffffffffffff80821115611be357600080fd5b818701915087601f830112611bf757600080fd5b813581811115611c0657600080fd5b886020828501011115611c1857600080fd5b95989497505060200194505050565b60008060008060008060608789031215611c4057600080fd5b863567ffffffffffffffff80821115611c5857600080fd5b611c648a838b01611a82565b90985096506020890135915080821115611c7d57600080fd5b611c898a838b01611a82565b90965094506040890135915080821115611ca257600080fd5b50611caf89828a01611a82565b979a9699509497509295939492505050565b6020808252600190820152603160f91b604082015260600190565b6020808252600190820152603f60f81b604082015260600190565b6020808252600190820152606160f81b604082015260600190565b6020808252600190820152602160f81b604082015260600190565b6020808252600190820152603b60f81b604082015260600190565b6020808252600190820152603360f91b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b80820180821115611da257611da2611d79565b92915050565b6000815180845260005b81811015611dce57602081850181015186830182015201611db2565b506000602082860101526020601f19601f83011685010191505092915050565b60e08152845160e08201526000602086015160028110611e1e57634e487b7160e01b600052602160045260246000fd5b61010083015260408601516001600160a01b03166101208301526060860151611e536101408401826001600160a01b03169052565b50608086015161016083015260a086015160c0610180840152611e7a6101a0840182611da8565b915050611eba602083018680516001600160a01b039081168352602080830151151590840152604080830151909116908301526060908101511515910152565b60a082019390935260c0015292915050565b600060208284031215611ede57600080fd5b5051919050565b80516001600160701b038116811461198857600080fd5b600080600060608486031215611f1157600080fd5b611f1a84611ee5565b9250611f2860208501611ee5565b9150604084015163ffffffff81168114611f4157600080fd5b809150509250925092565b84815283602082015260018060a01b0383166040820152608060608201526000611f796080830184611da8565b9695505050505050565b6001600160a01b0386811682528515156020830152604082018590528316606082015260a06080820181905260009061190690830184611da8565b60008060408385031215611fd157600080fd5b505080516020909101519092909150565b6000600160ff1b8201611ff757611ff7611d79565b5060000390565b6000806040838503121561201157600080fd5b823561201c81611911565b915061202a60208401611978565b90509250929050565b8082028115828204841417611da257611da2611d79565b60008261206757634e487b7160e01b600052601260045260246000fd5b50049056fea2646970667358221220ef166859aad688c15a88e7b99652e092c91187ac153848039b87fb8c8e2ecfc164736f6c63430008180033005820026004cfce3b37ce513505e385c405a0515ee2f102a9e81b6ee5d7917596f3df4101031fc2ceb0c5a6717734fbdf09556818a7c300f926f50b0d34516f89f6fb8b174100582002a6dcd8587eef6abc082ae820eaf38e7fb33bb7f97add8191795a4c6b03f5d34101005820020e12aa90c5cf8a7ac579a1f2ee089db91c28b07224f1874f5ff480367a4235410100582002cc33c39bd122732ec380f9d24244a3448640b3c45c025f4b51d453d2db48e441010219326003f112aa86d947066596cdaf666e6cc4b939536e3d050c114c5b291864246016b103c1ae8d2b41def7f89cd1fe0f66830adcd5f3177e32acb65dd802c02f7f940a9903eece07e57ceb3321dd0c23f26582069c7748279ba48f4cfbdf3cc5af9b119cc503618b138891c164ca992f56a9cfe0c2fc4f1badb2cd4f9f616d5818a1d852c91a03fd352e447b7b7c66c3751a5aa2818e8f3f71f1d9d44fe7c99ab9c3857e1285b1005820021f2fae2bc1ccdd9dc943ff851fe8f85b2eea0899206cca942a2aa37b6651eb4101030be603ef75e68a5639064ec61f827a0256ddc6d5ee71ece143c0dbc6a1820dc20058200247d6771a3ec964564328eb0da7eb6220a120cb587f3a3bba9546fc97152bb0410100582003cdfb8b1afd2d22a55e76d3848c15db11fde0e7bf5d02600d7fdf983d7a7ed0410100582003517f7a2c9dff9056bcf0bae4fa15190b64d93b3c3b93241fe115a65698ab504101021830021910e003b92aa65ab50ce38cde2f5b8dca3fc97560b799691106b2b22c9dd05714fd25ba0058200202eeb219247b01c8162aa97ca9dad7fe28fe9def6ea389cb95b204325054d7410100582002421fc8d3399966045baaf47840fd4c9f889317118e2854843b51fb97ddbfd641010355e7e561b544a8f8312f83e92090b8af73510e4183f802b04fd09512adb2c6da005820026152fb80a5ed863adbf060e4d5adb4fb9f3f98aa726e5bdcf56461fd496cd3410103c0f2e1f0bc31085dc36008763b89e5466628f3e0be4e9d6c69c539dd249a47b3005820027cc3c3fd447f826218757cb48b4d1040a96e311003892e111ee5c53cf5a61841010219e2280332bdf4218a4603811ebadacd2833dc301d3850f3551cdf8e75d85ba7e23e05b003724ef44f4c56ede21026656715d5bfa79131a5b797fd65e0980196f68590ff8d03434da0408135d605bf70377c51ad2e76d34f746ac17442b0571d6b628abd68ac035d97b561ece55f9ef1609eace62f2941e2f92aa416246a4b57a5f1924b4c5dc403c45402b1843bee9c58e1836226c72152427d97e690d84773afeb222656944b2f03924e180db1059bafdbd296a7ebf839e54b302f7008e09c2f959fc32eb1b1f44600582002007d7329d500a1815ec60ae0ae8e7ad6796315fe2cc01046c31d6ea88683074101005820026d05a2b6acaedd4a24cfeaa8b351889e42ea768bd2396a4469c98e278d49b94101005820025318c4196a78ed62dd2a2d34ea5b6dc281c0e40ecf64137ec13ece332d6ca7410100582002c9938aa8c4853e9c4548a522f8409d0e629279fceb9a0676b98472cae7fa6341010058200252222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f549def7cde171841a9f0724124ca0b01a622d749e400582002355fbf3a861b29216af7c8b04d84b9a3aeb037dd215373a85a067ac488de9e410103f8b1af8c17be110ac6c57e4e44e458f84e13e8f2a6f8dc9ac6de43eda3626041005820023eecbf72238d4044508e4efbdc3e136491cae256a5fe6152f3785e02ff5e77410102190b760219ffff05581e03461caee77f54e513b75a4f22cac1f7ee05fb0864b0338411bd59100ed00f0146016a7e824a4f1920a20382d260f1773676c4984396308ac5564ae6774151a959dfdb2208dfbeb4c0232203be329ec0855c67a5c1d2e01b58817a72ad206aeb046eaa96ce891bdae415b8f803d406fa363d0ff4124598dfeefee08adda4c560e48f1ca64a236dd9630d79d7d102196e5c039913fe4d9543dc01c7e0be3ed08f41b6c8d4cf2df2d8ec9af3c056883d7c2c5d03b26a30d352d51e27bdd2e33ea52a809839b7180d2e77e2c4c686436af736200c03aa01603d0139f29720acb399eb6bd30498d5429240f8ba44df63689e7bb1b0ee0315e8a6fa84bfd2acb8eaaea996aa4a46f5de253a0ad609c7f0277ecd5b7d9ec9030db1b444fb36bd1b36ecb2c5be61f74fde9ba73348a2932cd59d6b5fa51af9c503e524d098d04850bd610cf78c96af74626f752666103b0ff686a912722a2821810219ffff034ab005e656dd7ee732bdda28ed82dc2fbf913b11ee168f711f3910a5e101ea850374468738d14c619e33326b3d60427f61e70e73df25c1c85f497c3b0e643c7487034e6b33247da26c37b793e07ab5e073144ff3aa78b59cc322177c1347ccd9011b0219ffff03d6151fe3141aab3766abfd8fb64f188228ab3a01860f94e5f3dae386759b05dd03b83b3078b341fc98d7e941955281275ee677b8ffce66db59a26316465ec016ab03d14ea7cbe8b9aea666c03cf632ae8fb5c6388563ee195c218fe16d32843f76fc03bd60b8c124f03a06a8d176bd2f5422afa15aaa90490c7c49148352d3e64046c10219ffff032b27f0606aea732db89223c448ef50051f7bcdacb533eff5931dfee79f4601af0384412a5b06bc923b7204cd68d0af8f2598fc1e6c90b33d58acdd25d3c747308b039dc97041e8fd84aa338de1fe436aec129d3aa5c0ec11f244775451e3896f3a770378f173b8a4d30a29eee9203ce1e1e54a6106dd9ed5ed4c17c2eae9b68dbd752a030306203e35608f1ae9e70be93c153097e81fa74bc30b2d4572a5300aa33c528b03d519e78944c791a6b7290e3615220d6180f5f3b4f988e74af50a6819b304ad0f03f13c094428bb853ec68e8805360b7bb054a9ef83e884875f09713245a2b9925c03f3adc317e7a0528ad864ef7449ddca3c709b121225f5bdb318043a76596b21fa03e998862be9e44d9272eb6ee0cd0245f6b290a0e68fec23df273ecf18aeecc2600219ffff03653cf990b312b48d3129a8e667459075cae0de8e23543a2a8f04af38a9cbd117038c69f7638729c2b2c4fd1579bb7c450a2e945b05ed09c4d5249e7709e66d92df033fa9f17da0dac9876f9aff16e95dfe253d755448e4b4f133f41349d4827853bb031fb9c175a369a789c26791dd78840a981848810e0d606590e26318e2dba6c38603e92dc4c06f0c8a2f57c0e636b1aef6c3c6b95ce37d2b022597e6d2c29865ea910366417b85fbf96957da56d9a89c5a56a5350800ae61a58765beacb5e7bfb074900219ffff03fcc036779558111dad829df80bdbf8e7ed86971f307eddb5faef9aa2001af1b603fdd49d71c61e97c8cc0eef9254598a4cbd34a908f3375712a90abfb6eaeb30a7037e392fad3f87ac3072039da5cbea2b99f2d10a01aa4d46cb1c81bd8c9bf77994037dab82c751f31533cec6a7f680f545f8bd290d1e62f1b78e3652f2c16c68c43d034413df840cbf29eab30fdd6c5925cf38775cc308be825933f0a4457e5bd8190003a6a403f1d17d616dc28a8fc1653934563fa05637e92170eaf48b3517f636cf4003dc70f577ad37b64c94b215e6cacc6bfc9b76d2069d31c06542739b67f639cf16032e7471c5bc98fbe799ca31f6e82685707e5c6dbd89de69dc16e102adfc152be8036d0931f6981b42fc72d928e9237be1f2817d88cd821dbfe1a67cb0c10f65b0eb03a605521d3ea657b84908918a62052fbb844c539ac5e1904c08746befea6ab877032b0aec36736378b182af8aa6cd22a57d90d9cda61259009045b3ce03a21f2b6f035f096515ad4d65439ea7972903d400af2ed81d8e9a03256ea82db5b186b18d5003cb4db9c3f9db9defe8097d397457551863ac85b7e57c1b2c6fe2f00f0f4761ef03b890b1d684bb97a7977117ba6e78bf90133399ca98a1469720c020d428a8fc61037838d9c0c5980c413be285b7a88c47d58965a4bdf13d6df39c793b8572f2804a0322d287c04885e4ae3b0c002c66fab5cab6c761c8e77f1edd85a19d7db20bd9890351d6789efc868d8d91a24e3199915266b865b02c9ccdaafbdb5adf131bfba228030009af2241f7a17fa0125b532b7e72bd234ab52ab142b16e48b6b4c2518908a3033d51ef1aae5244f85b1fd2d604d2384ffaa7f389f195ab60d86d7dcd0899ab57035b03faa8d3677bfe305dabae8a83dcf83d98ef31f57547fe27a7a500e862283003478aa63c14b12d7af2a5ea2cfbb0af6ebdb0717093bb82001ee5b17314506a7703469d53d574fdf8216720ecf1c26bc457b9f2eaac36c422333e6da374a5be39470316b4eab8973ba9c94d090c4fd40db16d269e32c4b02bfcf1318dcca6ff9c47560316c32aea618f4a35b9bacd455d0a305b960eb28b9795389ee85cd5ea9d18772f03f702531b8b946381db1496a22f719d224543c603ff44234a20f4095e2f95e2c703467c473c8c47f5d82542447d79eb8e60381bc8da41a35cc685cafb4c5e754243034938d492e7906f4db03687b5f21832b35d4df44bd658ed3b0d7ab61cac0bf8fd03a97b9a724ff5b1a1e90032b29559e5da78e98ae90a150b138bdb6685491caaef03a2d5d8f07c83ca3edb8ad79711009dff87c3369ef21c8744c7068c993a48300103aab9764f7d79b8bf52560f5511668cdee2004377a1ea8426701cfa57f05b712803f8d2c48807f0302e324a2ff68d95c5c068789c6ffa8cd022af4bc8704b162e72034fef6d2e108904e11c75633ac94c56ea2c34abd415af6321c9ddd9a79bfb4d1003a2b2260c7dcbc720ed260c92c78e6fd942cd64c323268de67352a5f8de28476b03b03f7536efeaec7d03c85344bd05b42863ffb786acfaedf0e8590a0ecefc952003b611868c281d92a921c9163f1f46adac9e6145f2fa830f494821d676aa9964ef0378803bbbd997283c5c1fc15dc2c5e4dbbd79bac491d993e1621c5358fc169bc603cde17225753c3b37029c26c020f00192de4316e059214e960daff2f16f8653b7037712d6452e2663dbf635f9e788c40977fa07dc920b9dcca7815d5c628bc3bd8e03900975d26c0b3d9a55e84b5100d675acff2b4bcc96fd7cededa6ae810a9043b905581e0331b9077f63e20374836787589d20ab56d308768baf6feb1b2dcb34d7600c024605774fea440003282c5a650368e9b0e7d24d5cdf495dbddf26f77f85fdd6edcaed272af7b35db9035fffd5d88254ce0e08c2302c2c557e343e6cef958b526ab8128e1a20f7ee041605581e0385609eb13c8028dfa4852fb063cfb8e9ae6dd4d43778d540f79800b0600c0b471136f25b00215c05581e030857a6b3d0e9d021a7e24cd58fd0180f851308386471fe7c0fa1131df00c0147177cfd930cba0005581e03f7b7f46b544bcdb050ae1cb9e4843c0c52f99f99213fffdd72c2bc1920040105581e03bded25abb9d9062e93b4b938e4342e381cdd0a9b160c1b6756e896b0200c0f472e0ee90acbfe0003941a28b98d7e7eb2c9d4860a46b4a1f0e3d8d7f5b8e7d66380aaa00245738eb20605581e037a692a51bd2764381b710aa6ee14f3b10a03c47a86d246d05dc0f4ef1007011bffffffffffffffff033fe61504a88f3baa02a7be9ed4728a11da5d0f028d0b73db886f87a9c482ec0105581e0364c13f862dadef046f86fd496b9b1360e13a3a65342770f96cbc2acce0040405581d02a7c06f685821c187259b57fa7d198c295f55a8fb9fb0f44704f3780f0c014704137d1495f02405581d02301e91dad0a99625ab7bac95e9e45e2e6c0681478c0559064f4789400c190ba34802c110f3cc0dbcac05581d025a3ac47fb477a04756ae6d09e974e9e149c86df42e72defac9deab540c034502540be40005581d022dad32b67f3b3ce215ea703599b5d2589505a10e3b92c0c4009de3c50c024701a14eb204aeb802191422036087c726410bace6cb756c82f9b320bf010d071292d2b80cce2bc0aee01728b505581e03ea2ccf104aa3c3bb8a661bb9bd6968b012c3094ac820522c26671449f00c01472e732851988bc00219fff503050c9d1709afb49f78147db39e560425f0f72da7f569698702d0d5a581548ae403c179e7694d3be61fa1f9319dcbf263c998adb8f36b519e1839e79862431ce20d037db25c6d5f953628d47d48c24582e72045d247fe69feab537e1a7d843fdca3b40383cf2c90e85255a4e09fbd9a658d3ec16407a2dfd86ff3746da792b8f6590b7603c23cc926ef7fa26faaf9688523547afdeb8893c5fb0f9d8d89e36b1f0caf29f103fb4a689725f146d33ab055975bf0c61e04e055ec367ee0cfe6e379be40c303a9034514fb4047115ea4be94dcff5c8631ef7bf69378de573168456d8d33a835410d03966ab5264ab6b1ee79f1ed13fd1932330e34dbb18c6046756e2aa0ba2b9e67be03528f9c0eb3ccabd7c5cab313c3d06216a88621f5d6cd8a3a2d697940ca53e79d038ff13f1dda9496f7520e2f12323102141a64c2df069e80548454033cdba2e8fa0219ffff034f7ed5868b4ee23d06f4589c1cf34ca1de570161402012a2a43a4929236b1bed038c462403accfc96a7dfae94395ca85df4ccdce971f5e400afb30679f71c7bf0503f032d0e0ef52bee499f897361f94d8c6c7a846a4cb90275c6e79c8447bc5d484032fba07432177953683b07cd48bdfc75c40619773ea628e2ab245260ca408f2fc03f635269f46f53f6c9f16d08a9011fa081b1bb8c6357cd6eeac1716c40895f5a00219ffff03be49b0a81eb9e4dfe5a6ee51c833400888b4d5aa20b4234ca3f3089192fe978d038cda71049b37ec7e57b9a3b6284d704309f284ec3ce61efc5c41edb161203f7a03b047f3417a84face987743331e0ac2c103c5874895c681a41da8edeeb8d36bb803f53e6adde8d814671ace27379317467e2b7bd7d978c5165387fb115d34e371f6034b6ee7d46f63af3adbeb98b2ab40ec52885cc22d8b1f180c1f1e37e8d7f7526d03d435ba4db19baa93bc2aa261861cb69de4302d2d8ae457df6de4b11938c8faaf0219ffff0314a43490ce217930ade1a451851a8309c9bb06327e03a532e968e6b8d252de7803e1d9bcf8dce50d1689556db5b80a475cfa4cf0612de71b16879e5486df480f9b03bc5f36bf257796f5abaa7058281a3b4f2befe46c05d83cefeba121b8d226ff69036ace07466894ed823acd5dcbb63ef8e697e33d512f3088bfd30e1406bb0c37580363ccacb83d51f763d50cdf67c5873146e208e269a66a4c33e9a59c64aee17ba403848ee16e47f93e687feb50e503df1df655e9667a38004b252c57ee2a28bc81e0031590fc8ff97576fae28a5df42747196765da859d1b5113024f27ba2b04ed963d03046651a3fdae9bf24c346ee2491878a1662d39f37c9a5131866e3a06c7ad851b0369b35143e0f448a43832ca39d2f700e63d22ee0fad299aeeb10d0e4d3b7943c403175f10d40bca2dacfc856d038d1be1af8c4b220f37be556e38856f252e2044db0219ffff0324c6aeba386774d1e8989c95fe3a60742c9fb86190dae4d47ac1544a70b43d3f03b1008494b77f7fcc99181392b436873fb9f07aabf3b4b3bd128fc4309c554d4e0375fbcbfd8e098ffb654c8e4b666ba80cf32d4795fd82ce91dd4cffc4716802f40306b9fff347dfab6cb1084ede3b2a700e9d00b1dcef9bd9fbc4903ec2fa7697af039b6d58df5642346489ea38635f9151ddd4c6dabd0557d19141d9e9a474081f67032d21d533193aedd2a1c01adfc3e399d49270fe20140fe4d3a2a1bf465efd56d403c14018960d14de84efa601675e3c2a0dbdb72c3e1fb92057c5b6e2d6548cd0320385c0c288c0a94c653707d7527cec75ed8cc11f134b350c948af794f8568d2dd0030f7c5648780a7124770847e449c0598caeb5ad572a7856a0c2bb95d275cfb2540396e4316287fc1f3cb7f8d7f7ebaa9728ff7c3c6b5ebfbbf166d41240fbad772c035a3e040b77814795da5ae702b278345bcc58e7d3b6c741ad0b0d0331c46bc131033969b52a1610f1fb5413da159cd22d4c3efea54cfead34b98bc5fec53e03553d033c4936346aa09ac8f98b55000b9b26e1da30e616c6f732571230f20e35d4e0f6036c77bd63a45e913f9234b8181f7a75eab2958453c5a323526620a91646247a2e030c5918294d7821de3a74259acdbe6a6ce26749d520173960fe395374d4c7e899034fcf99c4a7818ebaa24ee9b03e46034624c46d6a7f3e273cd6b548db09bc3bbe0364bf4cc4fbd2dc79c85377c5679c9e8e24cd4abdd5807241715510f967c49fea03cd1efb4e0b1e8d04c2201c552a9cf11f7ef2f68d211a61be4b62efce5711155f037c05a1c086ebeff7d183efc5aa604f8b652127afa0b484d66f399f286b4c912e03e732b3d9645534f9a841df82be813fb28bb5a1b31d2ec72340b37100cbeb784603382909c11c72d4be673a9fd382b3636970f02c29c40badd44308656a4e449bf2035831e6c55de3bb602dfa63a06052f0718e648df163f1bdeab689dacd766e20090368c77dd73eae27c406283eac6e4c539e59079e4c80c83f2b7251167ba0adef0e030bed0e3b7ee979159bb15671267fd5b4a8f081b250f46b90efb68b5c2c861f3d032ccf6367990e901e7bf3ad92b8e47785ab37eef984d2674a40d880d2b92d16c7031fff886985a52e320df2fc94e77707b276e915252a16a4a3cffdaefd8ccf799e03124400534e8615dd46750904ff389d07ef4a078ea9b9b27b5be7bdd8dab34c8803a455fda28dfaccecf3eb7d6c5dbeca72b0bf19c6248f9bb38feaa2ccf2bfa4c603738d99b87e044cfc305c3926e803efe71a1fa29a632bc762f53d6ea60fe8b5f403f8b127665029ea44bd5e1001ea4bc0da6610f6cb16071f7fdd077d2c0ea09f4a034f808bef87afee94999253ff211b2937e2c0015819a663358b4a851e81727fe7038cad1aca5edfdb2c23831ec6945f4bd90847a32150544a8a3b5fab8266e52b4303db1edd88da31b9173b3a01a4bcb70736f1fb7d3d7c4ff994859c9853af8d13ad03368a3deceddbf642196ed53eccf27173a264a7676dd248d202620423db656dba03dcaddf110217ee48bfb8eeff146af24219a51ac93ca67f522482e2a3564b460103f4f3faad37bea3e205267b20d5a04787281af8a7fb40e7e25abd16b1fb673d980351b04acdc59d6d2676cb376513e6ef4e0e457fb7667841912288d0e20a2a80b303cf603aa4de6a946d150a191044c72b182176fb4021c6b2d18a383855a8f0234e031b5526d099906496a3a33d60306caefad770cd03a0881fe26d7f1a0de66b43ac035e59a8277f34b835258b96b4508bec0ef3922515e4142f0490770b3b2175569f03293dce157508c58129da9fdebc65200c1c1d983a8107e0f00d93b0cfe25af3e005581e03e61bc331fa2bcfdfbcbaaffe43953dd29f5dae150f901483658d263be00c044707b05cbe758c7d05581e03e98f02ec287a7bc81664c3bd3547c9c43fc30dd780deb035cc572ed0200c044683c5a2e4fd4005581e0301b4c5ac518015a0bce436357c6ffae80bb8291afc971ebe35544f24400c024501aa2450d005581e03fe83da1ccaef9d9bd78ce53543b6368d6df3947d410bf2d34eb594c300040205581e03ec48afe9bed2ccb5532d8a32fc0cbb2e1ba61c7556de5f51e397c4be200c01463d1ec5ffb1f0032df39c0062882ba634fb24c89e7f9871ecef10a6e6a6e79a336f6ed463d5c94203576d247b864a9c9c193245be5262a86aceeb51240a250d7b4354c293d83a678d05581e0397fc5d38badeb2417fc4af69d36e4965c61f33bb84f646b7d58a9da68007011bffffffffffffffff033fb0fa6f5661fde6378c4db73cf7f1243b922e4b55a3435735c010e477f1c24405581e037a436184a5fc4ce62ad4b2daaf8a45e35f2947e33aeb11416da640405004060219e48e03026455994a7764223a128fa757f29d61c41ac8e28bbab1f879c66e232fddb035036c4372246d91297c5eca3d8d1d450153f9522df23de517957182e29d8416b60c039c41360797c1aa7097a0a9af04c1f817bb5c454d0f772e8ebe2fd352ca7e0d8003ba5c25d3ffc47ad414f2443245ed620a381ce9d9f0d1ee70505a1db59120de1403c0b160d239ce0c71812917d0798c6964edb234491c2f32eb7574c8d7a16270d1034424e96e0a1731300a2559039dea0424a1e764dd8d7d15d03aeaad0ad4f3c4510219ffff031607ad0a1ba305ef701797730c019c3b185c0dbb0f482f10386db3446a8f7a540312287bc29f2baed4b1fc930f52489532b4fec922a36b4a91a91f7783402c380a037ec73b2e9bcef8facbe2637ff6111e1ef07153186e00f47d729f0690dc004b4503055d30bc306d96ab0cb9aedaff495f7afb4730446c7a1f96f785c05004135f200398418a681397427f906b8e034f61b8c96f509d367affb82a452f268302c94de103d152c22b71f6c3039c0923a13d58ba41db780be6dbee23526315a0a7cf0431f1037d07fa5723febf58b7b064f66fb51ccd33542f1733fd5095655fdb1cb52ed61003d757e18731ae954d17ba2b37aef6316225c955be31a5610b87e9454be425325803753b37ff3abfbe6254417acdccdbc7792897f9a69ca3519f33db8e1f0a06f71d035d0f2edb7053f1faccf65af86e531d22c3cc2949b8deb3160c91914b639d5bcb03a103f6ad9e92cdc19712b1a2c2520868d48838c6ae9841b5b6598facc92964630219ffff03d8b89977962e191ae00a471cfc8d04ab27004105aabea3f20a3514118c6362a40342228b2ce4d7af8913be27e5d33f74ac1156e79ce6b215261a0f340150aed5990219ffff034f61ea35d70737235a7524be89070d8a1b4312a463edd647598925fad5dd27ef0367356218f3ac808868159402784456d665ec6a3aa0a41585abfbb65d98f699280338604a343bc9aecc1935981e153cfe89177e98d6a52749241e3474fb37d3062d0379e57d3018566ef607f70739871a70e22f250c7073514ba45ebf0b623e2150310219ffff03ff41096042448d60e51cf90c3f4d32c484f2075dcf75997b73cb5132fa14cee303d55330cfd7fa4095c67d97d6e226d5f746dfa699344068ff850c9cd661a7237e0388185dc33be1752e598f422b3ca36b88f72978624ac5d3c52bca8f1aa5df907b0331efd165c257b035e9d310972a721a568efbe1497c9d6cadc31cdc141092b0dd033175ca26ae0b78e24f241d6d59350f393f513d386f76caa4dc01eec584c9683703c72b5eb847b4637591afae256fd03a1e652300ee869b73b9a744d5260fda709b03ee694228acfb8ca4132d1854640a984b7526b6dc1d8d1d2f7388559769b83fd403772906b1418f536fd3438952d8a507c574f872fecd6527c4d4f4a544f111f742039c81737ae5a38baeb1726a1555e57d6c70cdd7ff0277bba013842f992fe3d414031ad3fe9c07099f58f9840ce63f5cb56a2131a661ce47e279620513c287bccf980353da96fa6b3cf8379071046658960fe9f2e5583b7371aa00bc0b74a6e78a92a1037146604f75eae7c004c649e88055dbdaad5f11c9d75a1c070b80cdd59069fb9b03ea5c7eca43d22257270df496f2de21a4c2ecaab6fd1cfb16d9dfa4d47900295903a009fcb0a43b723b5e57d134fcf69fea087ca706f9821a47c2d9b4078e848a6a0385cd59308c0ba41b68662aa4c4ca5dc43ccecbfe34a0bb758961f960b6c08f76032277366223dc4bd673248b35fdbffb6d962fccfde4417a1b5ee1c25988bc06f803edf621f6da6f85e3543611cb792e072dec8da2d3136176b5ae6477665df090f80320c0a600fde72ac1545c1dcea40ee003c6ce25bbfbe5a425ca100ddac6bb8fc50326c500f83376c53ec000ce4b6de712524fc6cd3f212eef3691502404f29858cd03fffe27ae5abbc45140db8c52c20fa03e4240a35c6c91f37d38279ba9a3127a160302f261fe22ba2bda212b7d6ce8d7a5a6c1e8821417a12a758c3f6aa13317d2e903468dc4cd595e8792cf5d3f757e548396f5d871043b32bbfa37c8abe3143ef6dc03163eab7b6ba3a1f8eba01c69bbbb81c0d5534ba8feab7c1bb11d201e06f7d53b034f5a5f6706dc853cb3ae2279729e0d7e24dda128a77358144e4c0fd3e5d60e980605581e033dadb7653588841a2087764475e0b140094c5573ad02852325c0ee44f007011bffffffffffffffff0399bec80f747a7f4d8c90241f2b4687e87d876ab2a1e786184b774bc9a3b314bc036260dd7e446b60f4c765ee6fa242dd88c0bb4d8ea47dddc956c771e85948b10c05581e03676cdbe01ce54cbf6c6a6c5c2dd9bc2972134e73cf653233f83a9523800405038d6fe41665cfb4d58918bbf3a81688da1ca3f67e529dc7f4cc040b0f75102fa60301337e4413f01c345b614ccdf8788466933f1a0def558c37a19ce0bfd6101d4605581e033431dd3c678cf6bb4c09f0cf8292eb4f7d82052f7363f6f952caf1ea600c0147011f667bbfc00005581d0258edc64491e8dd578d51f1c192eae6265a18ba613588a2056b86e4ed0c02470207288b40948205581d0268f24ae2fad81ed2dca702123aed6f0af6a60f88dbb12d3d35ae93630c02470804bac3420ffe05581d02e390288ece92f3c6fbb890464690c53d3e01772dd97046a8daff4f900c04478034e7a51a2a2804592c1d608060405234801561001057600080fd5b50600436106101b95760003560e01c80636a627842116100f9578063ba9a7a5611610097578063d21220a711610071578063d21220a7146105da578063d505accf146105e2578063dd62ed3e14610640578063fff6cae91461067b576101b9565b8063ba9a7a5614610597578063bc25cf771461059f578063c45a0155146105d2576101b9565b80637ecebe00116100d35780637ecebe00146104d757806389afcb441461050a57806395d89b4114610556578063a9059cbb1461055e576101b9565b80636a6278421461046957806370a082311461049c5780637464fc3d146104cf576101b9565b806323b872dd116101665780633644e515116101405780633644e51514610416578063485cc9551461041e5780635909c0d5146104595780635a3d549314610461576101b9565b806323b872dd146103ad57806330adf81f146103f0578063313ce567146103f8576101b9565b8063095ea7b311610197578063095ea7b3146103155780630dfe16811461036257806318160ddd14610393576101b9565b8063022c0d9f146101be57806306fdde03146102595780630902f1ac146102d6575b600080fd5b610257600480360360808110156101d457600080fd5b81359160208101359173ffffffffffffffffffffffffffffffffffffffff604083013516919081019060808101606082013564010000000081111561021857600080fd5b82018360208201111561022a57600080fd5b8035906020019184600183028401116401000000008311171561024c57600080fd5b509092509050610683565b005b610261610d57565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561029b578181015183820152602001610283565b50505050905090810190601f1680156102c85780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6102de610d90565b604080516dffffffffffffffffffffffffffff948516815292909316602083015263ffffffff168183015290519081900360600190f35b61034e6004803603604081101561032b57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610de5565b604080519115158252519081900360200190f35b61036a610dfc565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b61039b610e18565b60408051918252519081900360200190f35b61034e600480360360608110156103c357600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060400135610e1e565b61039b610efd565b610400610f21565b6040805160ff9092168252519081900360200190f35b61039b610f26565b6102576004803603604081101561043457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020013516610f2c565b61039b611005565b61039b61100b565b61039b6004803603602081101561047f57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16611011565b61039b600480360360208110156104b257600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113cb565b61039b6113dd565b61039b600480360360208110156104ed57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113e3565b61053d6004803603602081101561052057600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113f5565b6040805192835260208301919091528051918290030190f35b610261611892565b61034e6004803603604081101561057457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81351690602001356118cb565b61039b6118d8565b610257600480360360208110156105b557600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166118de565b61036a611ad4565b61036a611af0565b610257600480360360e08110156105f857600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060408101359060608101359060ff6080820135169060a08101359060c00135611b0c565b61039b6004803603604081101561065657600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020013516611dd8565b610257611df5565b600c546001146106f457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c55841515806107075750600084115b61075c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180612b2f6025913960400191505060405180910390fd5b600080610767610d90565b5091509150816dffffffffffffffffffffffffffff168710801561079a5750806dffffffffffffffffffffffffffff1686105b6107ef576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526021815260200180612b786021913960400191505060405180910390fd5b600654600754600091829173ffffffffffffffffffffffffffffffffffffffff91821691908116908916821480159061085457508073ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff1614155b6108bf57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f556e697377617056323a20494e56414c49445f544f0000000000000000000000604482015290519081900360640190fd5b8a156108d0576108d0828a8d611fdb565b89156108e1576108e1818a8c611fdb565b86156109c3578873ffffffffffffffffffffffffffffffffffffffff166310d1e85c338d8d8c8c6040518663ffffffff1660e01b8152600401808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b1580156109aa57600080fd5b505af11580156109be573d6000803e3d6000fd5b505050505b604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff8416916370a08231916024808301926020929190829003018186803b158015610a2f57600080fd5b505afa158015610a43573d6000803e3d6000fd5b505050506040513d6020811015610a5957600080fd5b5051604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905191955073ffffffffffffffffffffffffffffffffffffffff8316916370a0823191602480820192602092909190829003018186803b158015610acb57600080fd5b505afa158015610adf573d6000803e3d6000fd5b505050506040513d6020811015610af557600080fd5b5051925060009150506dffffffffffffffffffffffffffff85168a90038311610b1f576000610b35565b89856dffffffffffffffffffffffffffff160383035b9050600089856dffffffffffffffffffffffffffff16038311610b59576000610b6f565b89856dffffffffffffffffffffffffffff160383035b90506000821180610b805750600081115b610bd5576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526024815260200180612b546024913960400191505060405180910390fd5b6000610c09610beb84600363ffffffff6121e816565b610bfd876103e863ffffffff6121e816565b9063ffffffff61226e16565b90506000610c21610beb84600363ffffffff6121e816565b9050610c59620f4240610c4d6dffffffffffffffffffffffffffff8b8116908b1663ffffffff6121e816565b9063ffffffff6121e816565b610c69838363ffffffff6121e816565b1015610cd657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f556e697377617056323a204b0000000000000000000000000000000000000000604482015290519081900360640190fd5b5050610ce4848488886122e0565b60408051838152602081018390528082018d9052606081018c9052905173ffffffffffffffffffffffffffffffffffffffff8b169133917fd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d8229181900360800190a350506001600c55505050505050505050565b6040518060400160405280600a81526020017f556e69737761702056320000000000000000000000000000000000000000000081525081565b6008546dffffffffffffffffffffffffffff808216926e0100000000000000000000000000008304909116917c0100000000000000000000000000000000000000000000000000000000900463ffffffff1690565b6000610df233848461259c565b5060015b92915050565b60065473ffffffffffffffffffffffffffffffffffffffff1681565b60005481565b73ffffffffffffffffffffffffffffffffffffffff831660009081526002602090815260408083203384529091528120547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14610ee85773ffffffffffffffffffffffffffffffffffffffff84166000908152600260209081526040808320338452909152902054610eb6908363ffffffff61226e16565b73ffffffffffffffffffffffffffffffffffffffff851660009081526002602090815260408083203384529091529020555b610ef384848461260b565b5060019392505050565b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b601281565b60035481565b60055473ffffffffffffffffffffffffffffffffffffffff163314610fb257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f556e697377617056323a20464f5242494444454e000000000000000000000000604482015290519081900360640190fd5b6006805473ffffffffffffffffffffffffffffffffffffffff9384167fffffffffffffffffffffffff00000000000000000000000000000000000000009182161790915560078054929093169116179055565b60095481565b600a5481565b6000600c5460011461108457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c81905580611094610d90565b50600654604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905193955091935060009273ffffffffffffffffffffffffffffffffffffffff909116916370a08231916024808301926020929190829003018186803b15801561110e57600080fd5b505afa158015611122573d6000803e3d6000fd5b505050506040513d602081101561113857600080fd5b5051600754604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905192935060009273ffffffffffffffffffffffffffffffffffffffff909216916370a0823191602480820192602092909190829003018186803b1580156111b157600080fd5b505afa1580156111c5573d6000803e3d6000fd5b505050506040513d60208110156111db57600080fd5b505190506000611201836dffffffffffffffffffffffffffff871663ffffffff61226e16565b90506000611225836dffffffffffffffffffffffffffff871663ffffffff61226e16565b9050600061123387876126ec565b600054909150806112705761125c6103e8610bfd611257878763ffffffff6121e816565b612878565b985061126b60006103e86128ca565b6112cd565b6112ca6dffffffffffffffffffffffffffff8916611294868463ffffffff6121e816565b8161129b57fe5b046dffffffffffffffffffffffffffff89166112bd868563ffffffff6121e816565b816112c457fe5b0461297a565b98505b60008911611326576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180612bc16028913960400191505060405180910390fd5b6113308a8a6128ca565b61133c86868a8a6122e0565b811561137e5760085461137a906dffffffffffffffffffffffffffff808216916e01000000000000000000000000000090041663ffffffff6121e816565b600b555b6040805185815260208101859052815133927f4c209b5fc8ad50758f13e2e1088ba56a560dff690a1c6fef26394f4c03821c4f928290030190a250506001600c5550949695505050505050565b60016020526000908152604090205481565b600b5481565b60046020526000908152604090205481565b600080600c5460011461146957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c81905580611479610d90565b50600654600754604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905194965092945073ffffffffffffffffffffffffffffffffffffffff9182169391169160009184916370a08231916024808301926020929190829003018186803b1580156114fb57600080fd5b505afa15801561150f573d6000803e3d6000fd5b505050506040513d602081101561152557600080fd5b5051604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905191925060009173ffffffffffffffffffffffffffffffffffffffff8516916370a08231916024808301926020929190829003018186803b15801561159957600080fd5b505afa1580156115ad573d6000803e3d6000fd5b505050506040513d60208110156115c357600080fd5b5051306000908152600160205260408120549192506115e288886126ec565b600054909150806115f9848763ffffffff6121e816565b8161160057fe5b049a5080611614848663ffffffff6121e816565b8161161b57fe5b04995060008b11801561162e575060008a115b611683576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180612b996028913960400191505060405180910390fd5b61168d3084612992565b611698878d8d611fdb565b6116a3868d8c611fdb565b604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff8916916370a08231916024808301926020929190829003018186803b15801561170f57600080fd5b505afa158015611723573d6000803e3d6000fd5b505050506040513d602081101561173957600080fd5b5051604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905191965073ffffffffffffffffffffffffffffffffffffffff8816916370a0823191602480820192602092909190829003018186803b1580156117ab57600080fd5b505afa1580156117bf573d6000803e3d6000fd5b505050506040513d60208110156117d557600080fd5b505193506117e585858b8b6122e0565b811561182757600854611823906dffffffffffffffffffffffffffff808216916e01000000000000000000000000000090041663ffffffff6121e816565b600b555b604080518c8152602081018c9052815173ffffffffffffffffffffffffffffffffffffffff8f169233927fdccd412f0b1252819cb1fd330b93224ca42612892bb3f4f789976e6d81936496929081900390910190a35050505050505050506001600c81905550915091565b6040518060400160405280600681526020017f554e492d5632000000000000000000000000000000000000000000000000000081525081565b6000610df233848461260b565b6103e881565b600c5460011461194f57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c55600654600754600854604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff9485169490931692611a2b9285928792611a26926dffffffffffffffffffffffffffff169185916370a0823191602480820192602092909190829003018186803b1580156119ee57600080fd5b505afa158015611a02573d6000803e3d6000fd5b505050506040513d6020811015611a1857600080fd5b50519063ffffffff61226e16565b611fdb565b600854604080517f70a082310000000000000000000000000000000000000000000000000000000081523060048201529051611aca9284928792611a26926e01000000000000000000000000000090046dffffffffffffffffffffffffffff169173ffffffffffffffffffffffffffffffffffffffff8616916370a0823191602480820192602092909190829003018186803b1580156119ee57600080fd5b50506001600c5550565b60055473ffffffffffffffffffffffffffffffffffffffff1681565b60075473ffffffffffffffffffffffffffffffffffffffff1681565b42841015611b7b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f556e697377617056323a20455850495245440000000000000000000000000000604482015290519081900360640190fd5b60035473ffffffffffffffffffffffffffffffffffffffff80891660008181526004602090815260408083208054600180820190925582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98186015280840196909652958d166060860152608085018c905260a085019590955260c08085018b90528151808603909101815260e0850182528051908301207f19010000000000000000000000000000000000000000000000000000000000006101008601526101028501969096526101228085019690965280518085039096018652610142840180825286519683019690962095839052610162840180825286905260ff89166101828501526101a284018890526101c28401879052519193926101e2808201937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081019281900390910190855afa158015611cdc573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff811615801590611d5757508873ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b611dc257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f556e697377617056323a20494e56414c49445f5349474e415455524500000000604482015290519081900360640190fd5b611dcd89898961259c565b505050505050505050565b600260209081526000928352604080842090915290825290205481565b600c54600114611e6657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c55600654604080517f70a082310000000000000000000000000000000000000000000000000000000081523060048201529051611fd49273ffffffffffffffffffffffffffffffffffffffff16916370a08231916024808301926020929190829003018186803b158015611edd57600080fd5b505afa158015611ef1573d6000803e3d6000fd5b505050506040513d6020811015611f0757600080fd5b5051600754604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff909216916370a0823191602480820192602092909190829003018186803b158015611f7a57600080fd5b505afa158015611f8e573d6000803e3d6000fd5b505050506040513d6020811015611fa457600080fd5b50516008546dffffffffffffffffffffffffffff808216916e0100000000000000000000000000009004166122e0565b6001600c55565b604080518082018252601981527f7472616e7366657228616464726573732c75696e743235362900000000000000602091820152815173ffffffffffffffffffffffffffffffffffffffff85811660248301526044808301869052845180840390910181526064909201845291810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb000000000000000000000000000000000000000000000000000000001781529251815160009460609489169392918291908083835b602083106120e157805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016120a4565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114612143576040519150601f19603f3d011682016040523d82523d6000602084013e612148565b606091505b5091509150818015612176575080511580612176575080806020019051602081101561217357600080fd5b50515b6121e157604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f556e697377617056323a205452414e534645525f4641494c4544000000000000604482015290519081900360640190fd5b5050505050565b60008115806122035750508082028282828161220057fe5b04145b610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f64732d6d6174682d6d756c2d6f766572666c6f77000000000000000000000000604482015290519081900360640190fd5b80820382811115610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f64732d6d6174682d7375622d756e646572666c6f770000000000000000000000604482015290519081900360640190fd5b6dffffffffffffffffffffffffffff841180159061230c57506dffffffffffffffffffffffffffff8311155b61237757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f556e697377617056323a204f564552464c4f5700000000000000000000000000604482015290519081900360640190fd5b60085463ffffffff428116917c0100000000000000000000000000000000000000000000000000000000900481168203908116158015906123c757506dffffffffffffffffffffffffffff841615155b80156123e257506dffffffffffffffffffffffffffff831615155b15612492578063ffffffff16612425856123fb86612a57565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff169063ffffffff612a7b16565b600980547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff929092169290920201905563ffffffff8116612465846123fb87612a57565b600a80547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff92909216929092020190555b600880547fffffffffffffffffffffffffffffffffffff0000000000000000000000000000166dffffffffffffffffffffffffffff888116919091177fffffffff0000000000000000000000000000ffffffffffffffffffffffffffff166e0100000000000000000000000000008883168102919091177bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167c010000000000000000000000000000000000000000000000000000000063ffffffff871602179283905560408051848416815291909304909116602082015281517f1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1929181900390910190a1505050505050565b73ffffffffffffffffffffffffffffffffffffffff808416600081815260026020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b73ffffffffffffffffffffffffffffffffffffffff8316600090815260016020526040902054612641908263ffffffff61226e16565b73ffffffffffffffffffffffffffffffffffffffff8085166000908152600160205260408082209390935590841681522054612683908263ffffffff612abc16565b73ffffffffffffffffffffffffffffffffffffffff80841660008181526001602090815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600080600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663017e7e586040518163ffffffff1660e01b815260040160206040518083038186803b15801561275757600080fd5b505afa15801561276b573d6000803e3d6000fd5b505050506040513d602081101561278157600080fd5b5051600b5473ffffffffffffffffffffffffffffffffffffffff821615801594509192509061286457801561285f5760006127d86112576dffffffffffffffffffffffffffff88811690881663ffffffff6121e816565b905060006127e583612878565b90508082111561285c576000612813612804848463ffffffff61226e16565b6000549063ffffffff6121e816565b905060006128388361282c86600563ffffffff6121e816565b9063ffffffff612abc16565b9050600081838161284557fe5b04905080156128585761285887826128ca565b5050505b50505b612870565b8015612870576000600b555b505092915050565b600060038211156128bb575080600160028204015b818110156128b5578091506002818285816128a457fe5b0401816128ad57fe5b04905061288d565b506128c5565b81156128c5575060015b919050565b6000546128dd908263ffffffff612abc16565b600090815573ffffffffffffffffffffffffffffffffffffffff8316815260016020526040902054612915908263ffffffff612abc16565b73ffffffffffffffffffffffffffffffffffffffff831660008181526001602090815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b6000818310612989578161298b565b825b9392505050565b73ffffffffffffffffffffffffffffffffffffffff82166000908152600160205260409020546129c8908263ffffffff61226e16565b73ffffffffffffffffffffffffffffffffffffffff831660009081526001602052604081209190915554612a02908263ffffffff61226e16565b600090815560408051838152905173ffffffffffffffffffffffffffffffffffffffff8516917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef919081900360200190a35050565b6dffffffffffffffffffffffffffff166e0100000000000000000000000000000290565b60006dffffffffffffffffffffffffffff82167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff841681612ab457fe5b049392505050565b80820182811015610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f64732d6d6174682d6164642d6f766572666c6f77000000000000000000000000604482015290519081900360640190fdfe556e697377617056323a20494e53554646494349454e545f4f55545055545f414d4f554e54556e697377617056323a20494e53554646494349454e545f494e5055545f414d4f554e54556e697377617056323a20494e53554646494349454e545f4c4951554944495459556e697377617056323a20494e53554646494349454e545f4c49515549444954595f4255524e4544556e697377617056323a20494e53554646494349454e545f4c49515549444954595f4d494e544544a265627a7a723158207dca18479e58487606bf70c79e44d8dee62353c9ee6d01f9a9d70885b8765f2264736f6c634300051000320384b2573251bfe2e94ba79c0b98c656bf64322ed350b0879a0ee59d5ec30128200058210390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5630480c192f1954503f3600582103e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af050010374865282d34b9409d4255f0b455c005820026cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c68854f1001ad1ee7743e0f6715f50cdaeb1397d5cb4fa00582002b661198733f53bb9b621ec808effd2e8a3d86db6962103738e13951e49aab0471ef8ca7e487b790219014000582002575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5820eb94db55691873fd8f4d937239039c663e5f6abd98c065fcfe316a94084248f7005820025a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a850d579019250d11fd5122a90a86965d7bc02184400582103f6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c704101005821031f25289b5c9db29d46c3566463f71796d2e07c9a7a96a888214082f19288cd00480bfa364ed607bfd500582002f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee35820663811bb00000000000005e26e848833dcec0000000000001a0ce48769dbdff200582002aa68fd0b9ce5597a6b852e7cf6a582dad59ebc65cf9a27520b304170361e9c5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0058200252222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f54c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20218680219f44505581d02a2f0a757c767f2f1f00959b54bb0afc795dbe5a347ff3575e897fb610701192c1d0219c01203217db0427a9a7df644a99dd99bf1ac77d3b430bc74730438ccd55a2a8d7588aa0219da66034d6198727d3f428a3cb210a1b6a8881848ef6b9991490fe3d0fa63f5aac8e468039575845ef29c5c4f5c59d52ccf6a166fbfa745b5ea4f3eff8f35a36592a5a854030bdbc2e6c85207aa334c5439c72db9811d5a27cb403d501e259b7b8211d0482c03fd8f29530748b96c609c2963458b556bce3969a75b37fc96b3ec2f9fe724dd5a03db69853cb602c5c1d6f733e14ca347c37aafb0b625672e219254fd1540b79ec80373b63d0fb6ed7ffde952aa3122f60eaa97e27e71b3b6eb02a094c1f0483e9480033d90703c6ebb918568ef318aa3f6e37ca739916c5219f9cdbd18b22e4d61dc7b03fb32801dbc0d7c9f6fdaccec9071ffde5f68694c6e2bb2869b15b48eaca0968f03a144dfc4f3b1e2fee3dc40999a2217d2da947a61f860d7c9949e19a62afcb8660219ffff035b7997205673de73c19fa9f04c68d5ee94ab57f998c0451aac528c46f1e8566a038a5aea6593fe8e5eabf7d70e5e2af861358713fe6ddbc6a80a05fae6aa571076036766860aed3876af5e53c0d11777eaa443d3639f23258cbbb2013d7ca8a27bdd0376557c3d8ec75e27a501c5a50f204a29391196c5d9dfd264f017315ecbe590830385e066bb492ded45b73a7e0052b0065170d19946f4049f34a6e5646a5eb39b5c0385d59bbaea0f6912608a5ad65f23e8922d4bebebd6dc89f3c395c9b7604187070330b3be6521dcd9f23aa6ccdaee41133c186e71dbd3d8f623bf9108cea020c9fc03b88e987b529e4542dc89df56a59a3e793bf14327347262732745e977a8607f830219ffff03bae8e9a8c6b14ade72932393307cf9f4faa48e3f0d52da0ad02899ebc22993b7030015fd996455b178383a9b571118ddfc72a6bfec4c68748637b22d68ba3d1dfd0306372ec8ab675c393e78f1640f859fb02b1e8553dfd0d9fe86cddf0542b5e939032927ceda284800ad13f2671e078252d3a6067898d2692612555e723693ca7c010364630014871611d84e932a7cd483c2226cf50cb23ddd5c9cdef68b1adcfae9ca03cddd35c2520efd78f95aa0efb1bcfe359ff3d6f2926a220673f33fcb20724b9503c2050126de32364fd49b91c7f710a981853f1b19cfe9a398e16dd63ef616a14c03e4eb94a23ff2beae29a2b3b2cda59f58b2c8081a012d542a2dbc8446474192150390fd2713d204ec02c657eda837d9b903649ca7c21d0bea883560c44a5840cf4f03bad1715db45f58203a1a422cfa3a825aec95f336e5f629ceff505a53b96139980351e45346ddebcd8c95122999b00c0bb21bc3555e46e27df61a54e6556eeb9d0403c32070cbdb4ff193f3ee4659b55f3b2ac1a0ca6e52412ab75d426ce0051fbc990219ffff03a4036d7fde0eea3dd44ad9b1c511f36c5301a5071fd6cee7858bc1f95da35b45030a2fd5e9f19dde84be4f4f8cfead9fd18996d6e0fa39c61eca67049dfd4ec449030e5ea0ca316e81e91bb649fdc161477629da3ecb7e0c762c88dacdc4e332da25038bf68ee483ab94728260a8b47e9ea31366c2e060ed0a35f8253a79e197800910035f3e6ab6e55998003d7d8aa47f2e59965c07e4aecce263a74862460ce3f92fd90365a409f32023f587e6ac8ae13f17021a5b23c8fb39e237ba9cfc763d537f726f03e8e73209210e16df3f423bed3182ea79da5163ee8afc1977c1561bdd1fde9ffc032cb6b42a4667a84d4e42197f5166acb157f1d8edbdfb221d66b68d6016dde21503cde372eb381408caeb10376665bc157307f24506c2491f20ea4c89a7989233e60398cd574d16a61cf4e468e83e89d979ad0c391ea8d422733dc61c173c4f094cd50219ffff0219ffff0219ffff" + } + }, + "txn_info": [ + { + "traces": { + "0x46affe1b4f3fc41581fd20fbaf055daeab80a8b5": { + "code_usage": { + "read": "0xfa39a7947e23c58a7ac305011e8a8ea07439af13ba7588c779761c91858a1127" + } + }, + "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { + "balance": "0x5d9d8b208ea1b6cc" + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "0x28dd64a7658410c285153", + "storage_read": [ + "0x6e11b2ad3f4a126fa523dbca759473ee7a6cebd4efb81c3015eabee05d9bea1f", + "0x3532f2f644d8a7080f8db547c7b002c7f5c206478ebcc15036b3c74f89a6943b" + ], + "storage_written": { + "0x6e11b2ad3f4a126fa523dbca759473ee7a6cebd4efb81c3015eabee05d9bea1f": "0x0", + "0x3532f2f644d8a7080f8db547c7b002c7f5c206478ebcc15036b3c74f89a6943b": "0x10ec3dc1f89c0802" + }, + "code_usage": { + "read": "0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23" + } + }, + "0xd95fc8448cea732810bfa312a3ebaaafa2c6a3cb": { + "storage_read": [ + "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x000000000000000000000000000000000000000000000000000000000000000c", + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000007", + "0x0000000000000000000000000000000000000000000000000000000000000009", + "0x000000000000000000000000000000000000000000000000000000000000000a" + ], + "storage_written": { + "0x000000000000000000000000000000000000000000000000000000000000000c": "0x1", + "0x000000000000000000000000000000000000000000000000000000000000000a": "0xd8a5da9077d5c5ec8b806a3ac3c8", + "0x0000000000000000000000000000000000000000000000000000000000000008": "0x663811c700000000000010ec3dc1f89c0802000000000000012384455dc21a01", + "0x0000000000000000000000000000000000000000000000000000000000000009": "0xaa28037adae5827cce516b9dd7dfd8" + }, + "code_usage": { + "read": "0x5b83bdbcc56b2e630f2807bbadd2b0c21619108066b92a58de081261089e9ce5" + } + }, + "0x71f755898886f79efa73334450f05a824faeae02": { + "balance": "0x190705b0d47776e3", + "nonce": "0x8f" + }, + "0x80a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e": { + "storage_read": [ + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "code_usage": { + "read": "0x0e42165348c9fef8f8381bd60d5276087423604d3f51cabec442610b09b1f5ae" + } + }, + "0x3339474491b13ac638f163d634fbbc722cd27916": { + "storage_read": [ + "0x000000000000000000000000000000000000000000000000000000000000000e", + "0x0000000000000000000000000000000000000000000000000000000000000014", + "0xa959202a47a24885beeb7de07cfd01f9ec6e5a393f1d515f40a6ff4cc8322d41", + "0x1ddb94459ffc40c2677b8a9322a30bbcd8865572dffd5aba3e2181b5ee3bbf20", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x9bacabca323d73597c4482cd7d164b0ba76e976ed396b897fece57202fe17e3e", + "0xb30d0c34c10c130ac92a1de37e54ac7bdb90a70d224fdd92cba743574dc18743", + "0x0000000000000000000000000000000000000000000000000000000000000013", + "0x666a1242978ca79d65d7cbfd5a9f5216d93c4785e10f7e5374786925fe8ccf53", + "0x000000000000000000000000000000000000000000000000000000000000000b", + "0x0000000000000000000000000000000000000000000000000000000000000009", + "0xbb900bdeb39d5848d51385674f626e4a5f449ab5742c2f63f7ae5f91877fb474", + "0x000000000000000000000000000000000000000000000000000000000000000f", + "0x0000000000000000000000000000000000000000000000000000000000000010", + "0x0000000000000000000000000000000000000000000000000000000000000006" + ], + "storage_written": { + "0x1ddb94459ffc40c2677b8a9322a30bbcd8865572dffd5aba3e2181b5ee3bbf20": "0x6e948b35fe5ff", + "0x666a1242978ca79d65d7cbfd5a9f5216d93c4785e10f7e5374786925fe8ccf53": "0x12384455dc21a01", + "0x000000000000000000000000000000000000000000000000000000000000000e": "0x9" + }, + "code_usage": { + "read": "0x3db32b9d0f5a762bfcd05f5d0cbafe91fca8b67a0e2834e13c8bb0531a8125a6" + } + } + }, + "meta": { + "byte_code": "0x02f9017b01818e850c570bd200850dc778ba13830549a19480a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e87649ff6f7003174b90104088890dc000000000000000000000000000000000000000000000000000011b15df428b300000000000000000000000000000000000000000000000000000000000000a000000000000000000000000071f755898886f79efa73334450f05a824faeae0200000000000000000000000000000000000000000000000000000000663811c80000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000003339474491b13ac638f163d634fbbc722cd27916c001a03efde0059a9e20313ee5a466273604b3e79d199a60b2c44e753f5f2fe56eb987a040eca86c1d87c685a3024af4a0224dab92758dc1990b8f8ecb6040f88498ab1e", + "new_txn_trie_node_byte": "0x02f9017b01818e850c570bd200850dc778ba13830549a19480a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e87649ff6f7003174b90104088890dc000000000000000000000000000000000000000000000000000011b15df428b300000000000000000000000000000000000000000000000000000000000000a000000000000000000000000071f755898886f79efa73334450f05a824faeae0200000000000000000000000000000000000000000000000000000000663811c80000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000003339474491b13ac638f163d634fbbc722cd27916c001a03efde0059a9e20313ee5a466273604b3e79d199a60b2c44e753f5f2fe56eb987a040eca86c1d87c685a3024af4a0224dab92758dc1990b8f8ecb6040f88498ab1e", + "new_receipt_trie_node_byte": "0xb9043e02f9043a01830316aab9010000200000000000000000000080000000000000000000000000000000000001000800000000000000000000000000000002000000080000000000000000000000000000000000000000000008000000200000000100000000000000008000000000000000000000000000400000000000000000000000000100000010000080000000000000000000000000000000008100000001000008080000004000000000000400000000000000000008000000000000000000000000000008000000000000000002000000000000200000000000000000000000001000000000000000000040200000000000000000000000000000000000000000400000000000000000f9032ff87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a0e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109ca000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9ea000000000000000000000000000000000000000000000000000649ff6f7003174f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9ea0000000000000000000000000d95fc8448cea732810bfa312a3ebaaafa2c6a3cba000000000000000000000000000000000000000000000000000649ff6f7003174f89b943339474491b13ac638f163d634fbbc722cd27916f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000d95fc8448cea732810bfa312a3ebaaafa2c6a3cba000000000000000000000000071f755898886f79efa73334450f05a824faeae02a00000000000000000000000000000000000000000000000000006e948b35fe5fff87994d95fc8448cea732810bfa312a3ebaaafa2c6a3cbe1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b840000000000000000000000000000000000000000000000000012384455dc21a0100000000000000000000000000000000000000000000000010ec3dc1f89c0802f8fc94d95fc8448cea732810bfa312a3ebaaafa2c6a3cbf863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9ea000000000000000000000000071f755898886f79efa73334450f05a824faeae02b880000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000649ff6f70031740000000000000000000000000000000000000000000000000006e948b35fe5ff0000000000000000000000000000000000000000000000000000000000000000", + "gas_used": 202410 + } + }, + { + "traces": { + "0x80a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e": { + "storage_read": [ + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "code_usage": { + "read": "0x0e42165348c9fef8f8381bd60d5276087423604d3f51cabec442610b09b1f5ae" + } + }, + "0x46affe1b4f3fc41581fd20fbaf055daeab80a8b5": { + "code_usage": { + "read": "0xfa39a7947e23c58a7ac305011e8a8ea07439af13ba7588c779761c91858a1127" + } + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "0x28dd64adaf838032882c7", + "storage_read": [ + "0x6e11b2ad3f4a126fa523dbca759473ee7a6cebd4efb81c3015eabee05d9bea1f", + "0x3532f2f644d8a7080f8db547c7b002c7f5c206478ebcc15036b3c74f89a6943b" + ], + "storage_written": { + "0x3532f2f644d8a7080f8db547c7b002c7f5c206478ebcc15036b3c74f89a6943b": "0x1150ddb8ef9c3976", + "0x6e11b2ad3f4a126fa523dbca759473ee7a6cebd4efb81c3015eabee05d9bea1f": "0x0" + }, + "code_usage": { + "read": "0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23" + } + }, + "0xa890e6b83ac95c7de4ffd4549d4cd282b407b495": { + "balance": "0x22b2b5d53ed342d2", + "nonce": "0x6b" + }, + "0x3339474491b13ac638f163d634fbbc722cd27916": { + "storage_read": [ + "0x2f896bfc9c9e71c6886136cc9d4cdf6a0dad4fb0087b5adb73d7f2333184fdb9", + "0x9bacabca323d73597c4482cd7d164b0ba76e976ed396b897fece57202fe17e3e", + "0x000000000000000000000000000000000000000000000000000000000000000b", + "0x21ef1d37618698fd5eab9f9249a314a31323bb90142688cae6c8c2ab595693dc", + "0xa959202a47a24885beeb7de07cfd01f9ec6e5a393f1d515f40a6ff4cc8322d41", + "0xe02dfcb26e74ceff6d1d546e4ebc866a3afeeb82714de1afc3c7783c51bf5ba8", + "0x0000000000000000000000000000000000000000000000000000000000000013", + "0x0000000000000000000000000000000000000000000000000000000000000010", + "0x666a1242978ca79d65d7cbfd5a9f5216d93c4785e10f7e5374786925fe8ccf53", + "0x000000000000000000000000000000000000000000000000000000000000000e", + "0x0000000000000000000000000000000000000000000000000000000000000014", + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000009", + "0x000000000000000000000000000000000000000000000000000000000000000f" + ], + "storage_written": { + "0x666a1242978ca79d65d7cbfd5a9f5216d93c4785e10f7e5374786925fe8ccf53": "0x11ceb324cf27726", + "0x2f896bfc9c9e71c6886136cc9d4cdf6a0dad4fb0087b5adb73d7f2333184fdb9": "0x6991310cfa2db", + "0x000000000000000000000000000000000000000000000000000000000000000e": "0xa" + }, + "code_usage": { + "read": "0x3db32b9d0f5a762bfcd05f5d0cbafe91fca8b67a0e2834e13c8bb0531a8125a6" + } + }, + "0xd95fc8448cea732810bfa312a3ebaaafa2c6a3cb": { + "storage_read": [ + "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x000000000000000000000000000000000000000000000000000000000000000c", + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ], + "storage_written": { + "0x000000000000000000000000000000000000000000000000000000000000000c": "0x1", + "0x0000000000000000000000000000000000000000000000000000000000000008": "0x663811c70000000000001150ddb8ef9c3976000000000000011ceb324cf27726" + }, + "code_usage": { + "read": "0x5b83bdbcc56b2e630f2807bbadd2b0c21619108066b92a58de081261089e9ce5" + } + }, + "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { + "balance": "0x5dbb414e52781acc" + } + }, + "meta": { + "byte_code": "0x02f9017a016a850c570bd200850dc778ba13830549a19480a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e87649ff6f7003174b90104088890dc000000000000000000000000000000000000000000000000000011b15df428b300000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000a890e6b83ac95c7de4ffd4549d4cd282b407b49500000000000000000000000000000000000000000000000000000000663811c80000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000003339474491b13ac638f163d634fbbc722cd27916c001a0a884ab9e5c1057e2e741e5ad4c6335f655a0dd248b3a6b7d7d0eb450ac2cff8ea07befa7f3480ddc589444e0d4498e0fd68fa765747676bafd9a128c6dcd91d5a1", + "new_txn_trie_node_byte": "0x02f9017a016a850c570bd200850dc778ba13830549a19480a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e87649ff6f7003174b90104088890dc000000000000000000000000000000000000000000000000000011b15df428b300000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000a890e6b83ac95c7de4ffd4549d4cd282b407b49500000000000000000000000000000000000000000000000000000000663811c80000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000003339474491b13ac638f163d634fbbc722cd27916c001a0a884ab9e5c1057e2e741e5ad4c6335f655a0dd248b3a6b7d7d0eb450ac2cff8ea07befa7f3480ddc589444e0d4498e0fd68fa765747676bafd9a128c6dcd91d5a1", + "new_receipt_trie_node_byte": "0xb9043e02f9043a0183057f0cb9010000200000000000000000000080000000000000000000000080000000000001000800000000000000000000000000000002000000080000000000000000000000000000000000000000000008000000200000000100000000000000008000000000000000000000000000400000000000000000000000000100000010000080000000000000000000000000000000008100000001000008080000004000400000000000000000000000000008000000000000000000000000000000000000000000000002000000000000200000000000000000000000001000000000000000000040200000000000000000000000000000000000000000400000000000000000f9032ff87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a0e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109ca000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9ea000000000000000000000000000000000000000000000000000649ff6f7003174f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9ea0000000000000000000000000d95fc8448cea732810bfa312a3ebaaafa2c6a3cba000000000000000000000000000000000000000000000000000649ff6f7003174f89b943339474491b13ac638f163d634fbbc722cd27916f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000d95fc8448cea732810bfa312a3ebaaafa2c6a3cba0000000000000000000000000a890e6b83ac95c7de4ffd4549d4cd282b407b495a00000000000000000000000000000000000000000000000000006991310cfa2dbf87994d95fc8448cea732810bfa312a3ebaaafa2c6a3cbe1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b840000000000000000000000000000000000000000000000000011ceb324cf277260000000000000000000000000000000000000000000000001150ddb8ef9c3976f8fc94d95fc8448cea732810bfa312a3ebaaafa2c6a3cbf863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9ea0000000000000000000000000a890e6b83ac95c7de4ffd4549d4cd282b407b495b880000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000649ff6f70031740000000000000000000000000000000000000000000000000006991310cfa2db0000000000000000000000000000000000000000000000000000000000000000", + "gas_used": 157794 + } + }, + { + "traces": { + "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { + "balance": "0x5dd52155a6ae57cc" + }, + "0xd95fc8448cea732810bfa312a3ebaaafa2c6a3cb": { + "storage_read": [ + "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x000000000000000000000000000000000000000000000000000000000000000c", + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ], + "storage_written": { + "0x0000000000000000000000000000000000000000000000000000000000000008": "0x663811c700000000000011c16aded0b6a2f30000000000000115e265a04ab726", + "0x000000000000000000000000000000000000000000000000000000000000000c": "0x1" + }, + "code_usage": { + "read": "0x5b83bdbcc56b2e630f2807bbadd2b0c21619108066b92a58de081261089e9ce5" + } + }, + "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": { + "code_usage": { + "read": "0xa324bc7db3d091b6f1a2d526e48a9c7039e03b3cc35f7d44b15ac7a1544c11d2" + } + }, + "0xdee90fc88ef3aceace9a22d2b2921906b7244552": { + "balance": "0x8a9d9dbb9e5f0d3", + "nonce": "0xa19" + }, + "0x3339474491b13ac638f163d634fbbc722cd27916": { + "storage_read": [ + "0x0000000000000000000000000000000000000000000000000000000000000013", + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x08f55d8b98c4bc7e11f2e039154ef30538af6b9937232ca209892c45871b15fb", + "0x000000000000000000000000000000000000000000000000000000000000000b", + "0x000000000000000000000000000000000000000000000000000000000000000e", + "0x0000000000000000000000000000000000000000000000000000000000000009", + "0x0000000000000000000000000000000000000000000000000000000000000014", + "0x9bacabca323d73597c4482cd7d164b0ba76e976ed396b897fece57202fe17e3e", + "0x0de215f63bfff4fe7883d192cea3721d7ad4060ea680c272f62f5c5faf4d8402", + "0x000000000000000000000000000000000000000000000000000000000000000f", + "0xad854c85d5b9e7470859152ad53c772f27f17f8dee52d4d3ff9c85f2b46f27b1", + "0xa959202a47a24885beeb7de07cfd01f9ec6e5a393f1d515f40a6ff4cc8322d41", + "0x666a1242978ca79d65d7cbfd5a9f5216d93c4785e10f7e5374786925fe8ccf53", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000010" + ], + "storage_written": { + "0x666a1242978ca79d65d7cbfd5a9f5216d93c4785e10f7e5374786925fe8ccf53": "0x115e265a04ab726", + "0xad854c85d5b9e7470859152ad53c772f27f17f8dee52d4d3ff9c85f2b46f27b1": "0x708ccaca7c000", + "0x000000000000000000000000000000000000000000000000000000000000000e": "0xb" + }, + "code_usage": { + "read": "0x3db32b9d0f5a762bfcd05f5d0cbafe91fca8b67a0e2834e13c8bb0531a8125a6" + } + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "0x28dd64b4b855de442ec44", + "storage_read": [ + "0xfb19a963956c9cb662dd3ae48988c4b90766df71ea130109840abe0a1b23dba8", + "0x3532f2f644d8a7080f8db547c7b002c7f5c206478ebcc15036b3c74f89a6943b" + ], + "storage_written": { + "0x3532f2f644d8a7080f8db547c7b002c7f5c206478ebcc15036b3c74f89a6943b": "0x11c16aded0b6a2f3", + "0xfb19a963956c9cb662dd3ae48988c4b90766df71ea130109840abe0a1b23dba8": "0x0" + }, + "code_usage": { + "read": "0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23" + } + } + }, + "meta": { + "byte_code": "0x02f9015c01820a18850b4ad345008515c508c70083035b60947a250d5630b4cf539739df2c5dacb4c659f2488d8801cd918d39380020b8e4fb3bdb41000000000000000000000000000000000000000000000000000708ccaca7c0000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000dee90fc88ef3aceace9a22d2b2921906b724455200000000000000000000000000000000000000000000000000000000663811fc0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000003339474491b13ac638f163d634fbbc722cd27916c001a0d244c53d24ffc3b2fcdfaf692d82593c0bb8cf3369b2a94c91c2c0249c4510a7a0751ca6879dce7d240443728d6e5756fa78be470b7ebd94cf984a1cb94f9f2c30", + "new_txn_trie_node_byte": "0x02f9015c01820a18850b4ad345008515c508c70083035b60947a250d5630b4cf539739df2c5dacb4c659f2488d8801cd918d39380020b8e4fb3bdb41000000000000000000000000000000000000000000000000000708ccaca7c0000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000dee90fc88ef3aceace9a22d2b2921906b724455200000000000000000000000000000000000000000000000000000000663811fc0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000003339474491b13ac638f163d634fbbc722cd27916c001a0d244c53d24ffc3b2fcdfaf692d82593c0bb8cf3369b2a94c91c2c0249c4510a7a0751ca6879dce7d240443728d6e5756fa78be470b7ebd94cf984a1cb94f9f2c30", + "new_receipt_trie_node_byte": "0xb9043e02f9043a018307c9a5b9010000200000000000000000000080000000000000000000000000010000000001000800000000000000000000000000000002000000080000000000000000000000000000000000000000000008000000200000000000000000000000008000000000000000020000000000000000000000000000000000000100000010000080000000000000000000004000000000008100000001000008080000004000000000000000000000000000000008000000000000000000000000000000000000000000000002000000000000200000000200000000000000001000000000000020000000200000000000000000000000000000000000000000400008000000000000f9032ff87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a0e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109ca00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da000000000000000000000000000000000000000000000000000708d25e11a697df89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da0000000000000000000000000d95fc8448cea732810bfa312a3ebaaafa2c6a3cba000000000000000000000000000000000000000000000000000708d25e11a697df89b943339474491b13ac638f163d634fbbc722cd27916f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000d95fc8448cea732810bfa312a3ebaaafa2c6a3cba0000000000000000000000000dee90fc88ef3aceace9a22d2b2921906b7244552a0000000000000000000000000000000000000000000000000000708ccaca7c000f87994d95fc8448cea732810bfa312a3ebaaafa2c6a3cbe1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b8400000000000000000000000000000000000000000000000000115e265a04ab72600000000000000000000000000000000000000000000000011c16aded0b6a2f3f8fc94d95fc8448cea732810bfa312a3ebaaafa2c6a3cbf863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da0000000000000000000000000dee90fc88ef3aceace9a22d2b2921906b7244552b880000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000708d25e11a697d000000000000000000000000000000000000000000000000000708ccaca7c0000000000000000000000000000000000000000000000000000000000000000000", + "gas_used": 150169 + } + }, + { + "traces": { + "0xd95fc8448cea732810bfa312a3ebaaafa2c6a3cb": { + "storage_read": [ + "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x000000000000000000000000000000000000000000000000000000000000000c", + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ], + "storage_written": { + "0x000000000000000000000000000000000000000000000000000000000000000c": "0x1", + "0x0000000000000000000000000000000000000000000000000000000000000008": "0x663811c70000000000001237d2e8e9b68a47000000000000010ed998f3a2f726" + }, + "code_usage": { + "read": "0x5b83bdbcc56b2e630f2807bbadd2b0c21619108066b92a58de081261089e9ce5" + } + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "0x28dd64bc1ed67fd42d398", + "storage_read": [ + "0xfb19a963956c9cb662dd3ae48988c4b90766df71ea130109840abe0a1b23dba8", + "0x3532f2f644d8a7080f8db547c7b002c7f5c206478ebcc15036b3c74f89a6943b" + ], + "storage_written": { + "0x3532f2f644d8a7080f8db547c7b002c7f5c206478ebcc15036b3c74f89a6943b": "0x1237d2e8e9b68a47", + "0xfb19a963956c9cb662dd3ae48988c4b90766df71ea130109840abe0a1b23dba8": "0x0" + }, + "code_usage": { + "read": "0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23" + } + }, + "0xfe6cf59162f319fb2c536f63a6121eb88dc85a34": { + "balance": "0x73a223146e589e8", + "nonce": "0x28c" + }, + "0x3339474491b13ac638f163d634fbbc722cd27916": { + "storage_read": [ + "0x0000000000000000000000000000000000000000000000000000000000000010", + "0x9bacabca323d73597c4482cd7d164b0ba76e976ed396b897fece57202fe17e3e", + "0x932f5dddf61dcab2b7f81a97907cab7ab3afa53fb02eaa509ecf46d078a590ad", + "0x0000000000000000000000000000000000000000000000000000000000000009", + "0x0000000000000000000000000000000000000000000000000000000000000014", + "0x7e5308f712c73bb1d1fdf32173d487dce249d64f2aed871db408acd1809c3c8e", + "0x666a1242978ca79d65d7cbfd5a9f5216d93c4785e10f7e5374786925fe8ccf53", + "0xa959202a47a24885beeb7de07cfd01f9ec6e5a393f1d515f40a6ff4cc8322d41", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000000000b", + "0x000000000000000000000000000000000000000000000000000000000000000e", + "0x0000000000000000000000000000000000000000000000000000000000000013", + "0x2f2974bd4e0f611fb6e95d73ca9161f61dd52ac4d1e50f1933fbd0faaa911374", + "0x000000000000000000000000000000000000000000000000000000000000000f", + "0x0000000000000000000000000000000000000000000000000000000000000006" + ], + "storage_written": { + "0x7e5308f712c73bb1d1fdf32173d487dce249d64f2aed871db408acd1809c3c8e": "0x708ccaca7c000", + "0x666a1242978ca79d65d7cbfd5a9f5216d93c4785e10f7e5374786925fe8ccf53": "0x10ed998f3a2f726", + "0x000000000000000000000000000000000000000000000000000000000000000e": "0xc" + }, + "code_usage": { + "read": "0x3db32b9d0f5a762bfcd05f5d0cbafe91fca8b67a0e2834e13c8bb0531a8125a6" + } + }, + "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { + "balance": "0x5ded909a5428d1cc" + }, + "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": { + "code_usage": { + "read": "0xa324bc7db3d091b6f1a2d526e48a9c7039e03b3cc35f7d44b15ac7a1544c11d2" + } + } + }, + "meta": { + "byte_code": "0x02f9015c0182028b850aa9e48a008515c508c70083035b60947a250d5630b4cf539739df2c5dacb4c659f2488d880102a336dba60000b8e4fb3bdb41000000000000000000000000000000000000000000000000000708ccaca7c0000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000fe6cf59162f319fb2c536f63a6121eb88dc85a3400000000000000000000000000000000000000000000000000000000663811fc0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000003339474491b13ac638f163d634fbbc722cd27916c080a0b66d674b17cb04d44900455636b496131fdae9b8e4c81f790854bda351130e83a00db241292520a7c135d381b0fd18788c9490aff4b756d397453a6454da4e0a3c", + "new_txn_trie_node_byte": "0x02f9015c0182028b850aa9e48a008515c508c70083035b60947a250d5630b4cf539739df2c5dacb4c659f2488d880102a336dba60000b8e4fb3bdb41000000000000000000000000000000000000000000000000000708ccaca7c0000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000fe6cf59162f319fb2c536f63a6121eb88dc85a3400000000000000000000000000000000000000000000000000000000663811fc0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000003339474491b13ac638f163d634fbbc722cd27916c080a0b66d674b17cb04d44900455636b496131fdae9b8e4c81f790854bda351130e83a00db241292520a7c135d381b0fd18788c9490aff4b756d397453a6454da4e0a3c", + "new_receipt_trie_node_byte": "0xb9043e02f9043a01830a143eb9010000200000000000000000000080100004000000000000000000010000000001000800000000000000000000000000000002000000080000000000000000000000000000000000000000000008000000200000000000000000000000008000000000001000000000000000000000000000000000000000000100000010000080000000000000000000004000000000008100000001000008080000004000000000000000000000000000000008000000000000000000000000000000000000000000000002000000000000200000000000000000000000001000000000000020000000200000000000000000000000000000000000000000400000000000000000f9032ff87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a0e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109ca00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da00000000000000000000000000000000000000000000000000076680a18ffe754f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da0000000000000000000000000d95fc8448cea732810bfa312a3ebaaafa2c6a3cba00000000000000000000000000000000000000000000000000076680a18ffe754f89b943339474491b13ac638f163d634fbbc722cd27916f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000d95fc8448cea732810bfa312a3ebaaafa2c6a3cba0000000000000000000000000fe6cf59162f319fb2c536f63a6121eb88dc85a34a0000000000000000000000000000000000000000000000000000708ccaca7c000f87994d95fc8448cea732810bfa312a3ebaaafa2c6a3cbe1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b840000000000000000000000000000000000000000000000000010ed998f3a2f7260000000000000000000000000000000000000000000000001237d2e8e9b68a47f8fc94d95fc8448cea732810bfa312a3ebaaafa2c6a3cbf863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da0000000000000000000000000fe6cf59162f319fb2c536f63a6121eb88dc85a34b88000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000076680a18ffe754000000000000000000000000000000000000000000000000000708ccaca7c0000000000000000000000000000000000000000000000000000000000000000000", + "gas_used": 150169 + } + }, + { + "traces": { + "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { + "balance": "0x5def439c4b5641cc" + }, + "0x80a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e": { + "storage_read": [ + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "code_usage": { + "read": "0x0e42165348c9fef8f8381bd60d5276087423604d3f51cabec442610b09b1f5ae" + } + }, + "0x46affe1b4f3fc41581fd20fbaf055daeab80a8b5": { + "code_usage": { + "read": "0xfa39a7947e23c58a7ac305011e8a8ea07439af13ba7588c779761c91858a1127" + } + }, + "0x4fadefe2a5eb1bf9e1d3dc4a9fc85daa5c5c660b": { + "storage_read": [ + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000007", + "0x0000000000000000000000000000000000000000000000000000000000000009", + "0x000000000000000000000000000000000000000000000000000000000000000a", + "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x000000000000000000000000000000000000000000000000000000000000000c" + ], + "storage_written": { + "0x0000000000000000000000000000000000000000000000000000000000000009": "0xcf1ba905921432e18b85621f8cf12637a0", + "0x000000000000000000000000000000000000000000000000000000000000000a": "0x255a7ee758811e1b4264e80e0c47e8", + "0x0000000000000000000000000000000000000000000000000000000000000008": "0x663811c700000000000068a4699375205d36000000000000002c7dc20fc759b8", + "0x000000000000000000000000000000000000000000000000000000000000000c": "0x1" + }, + "code_usage": { + "read": "0x5b83bdbcc56b2e630f2807bbadd2b0c21619108066b92a58de081261089e9ce5" + } + }, + "0x8b683c400457ef31f3c27c90acb6ab69304d1b77": { + "storage_read": [ + "0x000000000000000000000000000000000000000000000000000000000000000c", + "0x000000000000000000000000000000000000000000000000000000000000000a", + "0x83e1437aab4333b6030f364dc9caee9c0c897f2db6337f02ce233d75b273b1fa", + "0x0000000000000000000000000000000000000000000000000000000000000011", + "0x675bf37819e10c16f976a50d9f656ba9812a6d5a65c8e7d60705c35921eb77be", + "0x000000000000000000000000000000000000000000000000000000000000000b", + "0x0000000000000000000000000000000000000000000000000000000000000009", + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000014", + "0xfb1d4442566123dd7c0fe5cd59628ac4451e1b9577fd6e20474cca2443e1b531", + "0xddc732c826b289315f15077a83d3fe3859af931e5164604edd58e7607cacb5f5", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000000000e" + ], + "storage_written": { + "0xddc732c826b289315f15077a83d3fe3859af931e5164604edd58e7607cacb5f5": "0xfffffffffffffffffffffffffffffffffffffffffffffffffffe2a8152211587", + "0x83e1437aab4333b6030f364dc9caee9c0c897f2db6337f02ce233d75b273b1fa": "0xa26c347416cf", + "0x675bf37819e10c16f976a50d9f656ba9812a6d5a65c8e7d60705c35921eb77be": "0x2c7dc20fc759b8", + "0xfb1d4442566123dd7c0fe5cd59628ac4451e1b9577fd6e20474cca2443e1b531": "0x1" + }, + "code_usage": { + "read": "0xe7ade98613d60bcf57bf6f63bdc0ba142056f211f922f48e2e16a049c0ba5048" + } + }, + "0xc58e4d309c4f5c88afe355c260ee9a1a699ed1dd": { + "balance": "0x6fd1b56dbabfd0d", + "nonce": "0xba4" + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "0x28dd647816bd65e02a57f", + "storage_read": [ + "0x76cd8662b2bfb43bcc04aa9280535bff21a0556a979e6d40014a513d17cd257e", + "0x6e11b2ad3f4a126fa523dbca759473ee7a6cebd4efb81c3015eabee05d9bea1f" + ], + "storage_written": { + "0x6e11b2ad3f4a126fa523dbca759473ee7a6cebd4efb81c3015eabee05d9bea1f": "0x0", + "0x76cd8662b2bfb43bcc04aa9280535bff21a0556a979e6d40014a513d17cd257e": "0x68a4699375205d36" + }, + "code_usage": { + "read": "0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23" + } + } + }, + "meta": { + "byte_code": "0x02f9019401820ba384b2d05e008502233d46138304dc2f9480a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e80b901243d0e3ec50000000000000000000000000000000000000000000000000001d57eaddeea78000000000000000000000000000000000000000000000000023bb9178b0192be00000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000c58e4d309c4f5c88afe355c260ee9a1a699ed1dd00000000000000000000000000000000000000000000000000000000663811c80000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f00000000000000000000000000000000000000000000000000000000000000020000000000000000000000008b683c400457ef31f3c27c90acb6ab69304d1b77000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2c080a0dd70145d23e041328d8242858ca9815cbb03bf408dd7d81e30532932497838fca0496bf86926d61a533fa5824c9d21271b8b1597b48014282168a48575f8cf417c", + "new_txn_trie_node_byte": "0x02f9019401820ba384b2d05e008502233d46138304dc2f9480a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e80b901243d0e3ec50000000000000000000000000000000000000000000000000001d57eaddeea78000000000000000000000000000000000000000000000000023bb9178b0192be00000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000c58e4d309c4f5c88afe355c260ee9a1a699ed1dd00000000000000000000000000000000000000000000000000000000663811c80000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f00000000000000000000000000000000000000000000000000000000000000020000000000000000000000008b683c400457ef31f3c27c90acb6ab69304d1b77000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2c080a0dd70145d23e041328d8242858ca9815cbb03bf408dd7d81e30532932497838fca0496bf86926d61a533fa5824c9d21271b8b1597b48014282168a48575f8cf417c", + "new_receipt_trie_node_byte": "0xb9057802f9057401830c8306b9010000200000000000000000000080000000000000000000000000000000000100000000000000000008000000000000000002000000080080001000000000200000000000000000000000000008000000200000100100400000000000000000000000000020000000000000400000000000000000000000040000000010000000000000000000080000000000000000000000000000000000090000004000000000020000000000000000000000000000000000000000000000000000000080000020000002000000000000000000000800000000000000001000000042000000000050200000000810000000000000000000000000000000000000000000000000f90469f89b948b683c400457ef31f3c27c90acb6ab69304d1b77f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000c58e4d309c4f5c88afe355c260ee9a1a699ed1dda00000000000000000000000008b683c400457ef31f3c27c90acb6ab69304d1b77a00000000000000000000000000000000000000000000000000000177988b18bb9f89b948b683c400457ef31f3c27c90acb6ab69304d1b77f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000c58e4d309c4f5c88afe355c260ee9a1a699ed1dda00000000000000000000000004fadefe2a5eb1bf9e1d3dc4a9fc85daa5c5c660ba00000000000000000000000000000000000000000000000000001be05252d5ebff89b948b683c400457ef31f3c27c90acb6ab69304d1b77f863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a0000000000000000000000000c58e4d309c4f5c88afe355c260ee9a1a699ed1dda000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9ea0fffffffffffffffffffffffffffffffffffffffffffffffffffe2a8152211587f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000004fadefe2a5eb1bf9e1d3dc4a9fc85daa5c5c660ba000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9ea0000000000000000000000000000000000000000000000000044081919f402e19f879944fadefe2a5eb1bf9e1d3dc4a9fc85daa5c5c660be1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b840000000000000000000000000000000000000000000000000002c7dc20fc759b800000000000000000000000000000000000000000000000068a4699375205d36f8fc944fadefe2a5eb1bf9e1d3dc4a9fc85daa5c5c660bf863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9ea000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9eb8800000000000000000000000000000000000000000000000000001be05252d5ebf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044081919f402e19f87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a07fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65a000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9ea0000000000000000000000000000000000000000000000000044081919f402e19", + "gas_used": 159432 + } + }, + { + "traces": { + "0x46affe1b4f3fc41581fd20fbaf055daeab80a8b5": { + "code_usage": { + "read": "0xfa39a7947e23c58a7ac305011e8a8ea07439af13ba7588c779761c91858a1127" + } + }, + "0x80a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e": { + "storage_read": [ + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "code_usage": { + "read": "0x0e42165348c9fef8f8381bd60d5276087423604d3f51cabec442610b09b1f5ae" + } + }, + "0x8693ca3483daf299aad023eae75221436d6c11a0": { + "balance": "0x8521330e10f264f", + "nonce": "0x162d" + }, + "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { + "balance": "0x5df1064f497363cc" + }, + "0xd5564d5338168dd3bafc9fcb37d264b451670b8d": { + "storage_read": [ + "0x000000000000000000000000000000000000000000000000000000000000000c", + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000007", + "0x0000000000000000000000000000000000000000000000000000000000000009", + "0x000000000000000000000000000000000000000000000000000000000000000a", + "0x0000000000000000000000000000000000000000000000000000000000000008" + ], + "storage_written": { + "0x0000000000000000000000000000000000000000000000000000000000000009": "0x1062a74ebba592da735f3bf72a0ee64", + "0x000000000000000000000000000000000000000000000000000000000000000a": "0xd5ae2126c2d0a7ceefb92f3f99b89aac", + "0x0000000000000000000000000000000000000000000000000000000000000008": "0x663811c70000000000000551c685d5c01ae60000000000001cd36f7824efdff2", + "0x000000000000000000000000000000000000000000000000000000000000000c": "0x1" + }, + "code_usage": { + "read": "0x5b83bdbcc56b2e630f2807bbadd2b0c21619108066b92a58de081261089e9ce5" + } + }, + "0xf1001ad1ee7743e0f6715f50cdaeb1397d5cb4fa": { + "storage_read": [ + "0xba6fabe7c1b33f9faa35c25ff04fe055f58509edaa961f522e7d05ea5b41d704", + "0x000000000000000000000000000000000000000000000000000000000000000a", + "0x0d6f85cf373209cd6fd4c2738b51bed4e72c574868b2d1789fc613f6de974283", + "0x0000000000000000000000000000000000000000000000000000000000000012", + "0x000000000000000000000000000000000000000000000000000000000000000e", + "0x42839f559ed51fc754e4cff021a3d0fb278fa5e9d01911adb8c98803f7d2c47e", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000013", + "0x32bf1e57b2f3d6c52d6033622253e59c320f4f31dd907f1da3ac1c2fc891a4b6", + "0x8b364a1dd167da933f7abc9cb2ca26d32b828ba52ee73f18dd43ac7813dfdc93", + "0x000000000000000000000000000000000000000000000000000000000000000d", + "0x0000000000000000000000000000000000000000000000000000000000000008", + "0xcaab888c23d12df622bf5e4d33f8b1b243f92f6f0e47f8ff75a68c6247893ae9", + "0x000000000000000000000000000000000000000000000000000000000000000f" + ], + "storage_written": { + "0x42839f559ed51fc754e4cff021a3d0fb278fa5e9d01911adb8c98803f7d2c47e": "0x90a7feb273c206", + "0x0d6f85cf373209cd6fd4c2738b51bed4e72c574868b2d1789fc613f6de974283": "0x551c685d5c01ae6", + "0x000000000000000000000000000000000000000000000000000000000000000d": "0x14a" + }, + "code_usage": { + "read": "0x7d443041f5f212485af211c85b7fb6fcf1550f959a66bf997e2c4951782dc525" + } + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "0x28dd64a47f6c71916a57f", + "storage_read": [ + "0x6e11b2ad3f4a126fa523dbca759473ee7a6cebd4efb81c3015eabee05d9bea1f", + "0x793b794d76a0a454c7b10e10121915a5212de57e0d7d94cceacd9abd22330ccc" + ], + "storage_written": { + "0x6e11b2ad3f4a126fa523dbca759473ee7a6cebd4efb81c3015eabee05d9bea1f": "0x0", + "0x793b794d76a0a454c7b10e10121915a5212de57e0d7d94cceacd9abd22330ccc": "0x1cd36f7824efdff2" + }, + "code_usage": { + "read": "0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23" + } + } + }, + "meta": { + "byte_code": "0x02f9017c0182162c84b2d05e008502233d4613830461c69480a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e8802c68af0bb140000b90104088890dc000000000000000000000000000000000000000000000000007af53217af31b800000000000000000000000000000000000000000000000000000000000000a00000000000000000000000008693ca3483daf299aad023eae75221436d6c11a000000000000000000000000000000000000000000000000000000000663811c80000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000f1001ad1ee7743e0f6715f50cdaeb1397d5cb4fac001a02c9a9ab0d7eb2503a5ebbfe1a66c2f08a9170e1da5b14cdb142fce831522a43aa0360ecd3be1f224118862ddb78787cf31ad7600994b6c7a4a947ae3a071619a8b", + "new_txn_trie_node_byte": "0x02f9017c0182162c84b2d05e008502233d4613830461c69480a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e8802c68af0bb140000b90104088890dc000000000000000000000000000000000000000000000000007af53217af31b800000000000000000000000000000000000000000000000000000000000000a00000000000000000000000008693ca3483daf299aad023eae75221436d6c11a000000000000000000000000000000000000000000000000000000000663811c80000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000f1001ad1ee7743e0f6715f50cdaeb1397d5cb4fac001a02c9a9ab0d7eb2503a5ebbfe1a66c2f08a9170e1da5b14cdb142fce831522a43aa0360ecd3be1f224118862ddb78787cf31ad7600994b6c7a4a947ae3a071619a8b", + "new_receipt_trie_node_byte": "0xb9043e02f9043a01830f0845b9010000200000000000000000000080000000400000000008000000000000000000000000800000000000000000000008000002000000080000000000000000000000000000000000000000000008000000200000000100000000000000008000000000000000000000000000400008000000000000000000000000000010000000000000000000000000000000000000040040000001000000080800004000000000000100000000000000000000000008000000000001000000000000000000000000000002000000000000000000000000000000000000001000000000000000000040200000000000800000000000000000000000000000400000000000000000f9032ff87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a0e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109ca000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9ea000000000000000000000000000000000000000000000000002c68af0bb140000f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9ea0000000000000000000000000d5564d5338168dd3bafc9fcb37d264b451670b8da000000000000000000000000000000000000000000000000002c68af0bb140000f89b94f1001ad1ee7743e0f6715f50cdaeb1397d5cb4faf863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000d5564d5338168dd3bafc9fcb37d264b451670b8da00000000000000000000000008693ca3483daf299aad023eae75221436d6c11a0a00000000000000000000000000000000000000000000000000090a7feb273c206f87994d5564d5338168dd3bafc9fcb37d264b451670b8de1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b8400000000000000000000000000000000000000000000000001cd36f7824efdff20000000000000000000000000000000000000000000000000551c685d5c01ae6f8fc94d5564d5338168dd3bafc9fcb37d264b451670b8df863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9ea00000000000000000000000008693ca3483daf299aad023eae75221436d6c11a0b88000000000000000000000000000000000000000000000000002c68af0bb140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090a7feb273c206", + "gas_used": 165183 + } + }, + { + "traces": { + "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { + "balance": "0x5df12eb9f766c554" + }, + "0x17032783b5f29c8a68e0fafba4db021f248409e4": { + "balance": "0x0", + "nonce": "0x1" + }, + "0xb27b03580c193d324931acb143f6a1f3477e702e": { + "balance": "0x1257cf59bdce9000" + } + }, + "meta": { + "byte_code": "0xf86c808501a13b860082520894b27b03580c193d324931acb143f6a1f3477e702e8801ff118a94dfd0008026a06b6a8c636721916ee32404201135745eeb2bfad873e662f5a59c331cb6647a95a0452ae97ab3fb5bd8e4cc7890b28876a662430c40bf643239303e0c86770c96bf", + "new_txn_trie_node_byte": "0xf86c808501a13b860082520894b27b03580c193d324931acb143f6a1f3477e702e8801ff118a94dfd0008026a06b6a8c636721916ee32404201135745eeb2bfad873e662f5a59c331cb6647a95a0452ae97ab3fb5bd8e4cc7890b28876a662430c40bf643239303e0c86770c96bf", + "new_receipt_trie_node_byte": "0xf9010901830f5a4db9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", + "gas_used": 21000 + } + }, + { + "traces": { + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "storage_read": [ + "0x390f6178407c9b8e95802b8659e6df8e34c1e3d4f8d6a49e6132bbcdd937b63a", + "0xbedcb5df52f588f61ff32320302f7849d95752554baca3aee643459d2813fab3" + ], + "storage_written": { + "0xbedcb5df52f588f61ff32320302f7849d95752554baca3aee643459d2813fab3": "0x1497511bb6b1f16f2", + "0x390f6178407c9b8e95802b8659e6df8e34c1e3d4f8d6a49e6132bbcdd937b63a": "0x4d5baffcde6306e76a7" + }, + "code_usage": { + "read": "0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23" + } + }, + "0x43506849d7c04f9138d1a2050bbf3a0c054402dd": { + "code_usage": { + "read": "0xcdfb7d322961af3acae7a8f7ee8b69c205b36f576cc5b077f170c7eb8ecbe3ea" + } + }, + "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { + "balance": "0x5df1aadbe3850878" + }, + "0x93793bd1f3e35a0efd098c30e486a860a0ef7551": { + "balance": "0x34051d7a04031aac", + "nonce": "0xbcb1" + }, + "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640": { + "storage_read": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000004", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x00000000000000000000000000000000000000000000000000000000000002b2", + "0xda4f87b75ea46b0bcb01699726cbe92f07b59ae026875105518c234a010fe63a", + "0xda4f87b75ea46b0bcb01699726cbe92f07b59ae026875105518c234a010fe639", + "0xad9dde667637e023f1ffee9137c14ca72fd0a457bea73c3e79e176283357a6c3", + "0x0000000000000000000000000000000000000000000000000000000000000002", + "0xda4f87b75ea46b0bcb01699726cbe92f07b59ae026875105518c234a010fe63b", + "0xda4f87b75ea46b0bcb01699726cbe92f07b59ae026875105518c234a010fe63c", + "0x00000000000000000000000000000000000000000000000000000000000002b3" + ], + "storage_written": { + "0xda4f87b75ea46b0bcb01699726cbe92f07b59ae026875105518c234a010fe63c": "0x1000ed33000000000000000000020ce00fa821636b2d952f000002c5cb2601c", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x8e9b198ababa2f7fe6a21112ad04", + "0x00000000000000000000000000000000000000000000000000000000000002b3": "0x10000000000000001ec39445981969ea7d3b54b3200112eb4a4fef7663811c7", + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x10002d302d302ab02fceb00000000000045c6a16d265bb9fcae89d55c6498", + "0xda4f87b75ea46b0bcb01699726cbe92f07b59ae026875105518c234a010fe63b": "0x22d49e52321c6f90afad5a369abbdcbfa6", + "0xda4f87b75ea46b0bcb01699726cbe92f07b59ae026875105518c234a010fe63a": "0x1c24849ab98c7905f71913f3039", + "0x0000000000000000000000000000000000000000000000000000000000000004": "0x9336b8b0a431b7d8" + }, + "code_usage": { + "read": "0xa981b66c747a3d9fa29d7e200d5faaa2826960523d0e5a0df8148e8868c480b4" + } + }, + "0x9def7cde171841a9f0724124ca0b01a622d749e4": { + "balance": "0x16a7fb085b7", + "storage_read": [ + "0x5f5a09b54e538f9aa6a9bd36e4e1a5370b6d3b889be76cfa9f33bf6e1d909fe7", + "0x390f6178407c9b8e95802b8659e6df8e34c1e3d4f8d6a49e6132bbcdd937b63a", + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000005" + ], + "storage_written": { + "0x0000000000000000000000000000000000000000000000000000000000000005": "0x0", + "0x0000000000000000000000000000000000000000000000000000000000000006": "0x9def7cde171841a9f0724124ca0b01a622d749e4" + }, + "code_usage": { + "read": "0xa85d7ec4ffa81da8f494f1e81b70a200d3f75a414e3a803943ecd868aff78ab5" + } + }, + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { + "storage_read": [ + "0x10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b", + "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", + "0x1f21a62c4538bacf2aabeca410f0fe63151869f172e03c0e00357ba26a341eff", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0xe6695488b0655a1b1fdfee55f87e57d940b86639032942a6c8ac0cc373a938f8" + ], + "storage_written": { + "0x1f21a62c4538bacf2aabeca410f0fe63151869f172e03c0e00357ba26a341eff": "0x49b731ca328c", + "0xe6695488b0655a1b1fdfee55f87e57d940b86639032942a6c8ac0cc373a938f8": "0xd6adb5d897" + }, + "code_usage": { + "read": "0xd80d4b7c890cb9d6a4893e6b52bc34b56b25335cb13716e0d1d31383e6b41505" + } + } + }, + "meta": { + "byte_code": "0x02f901170182bcb08434fd4acd850158174adc83061a80949def7cde171841a9f0724124ca0b01a622d749e484012e3b68b8a4a000000000000000000000000000000088e6a0c2ddd26feeb64f039a2c41296fcb3f56400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000099f388994000000000000000000000000000000000000000000000000b6ea0676d933c915000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c001a0269fa6d550e1ce6f76884a87645bd2a462c303dca06423f2f0638a572109f0f7a0776dab3e59d703ff24cc45be1b856a73f66facbd0c5d6c41ecc940ca7e02416f", + "new_txn_trie_node_byte": "0x02f901170182bcb08434fd4acd850158174adc83061a80949def7cde171841a9f0724124ca0b01a622d749e484012e3b68b8a4a000000000000000000000000000000088e6a0c2ddd26feeb64f039a2c41296fcb3f56400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000099f388994000000000000000000000000000000000000000000000000b6ea0676d933c915000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c001a0269fa6d550e1ce6f76884a87645bd2a462c303dca06423f2f0638a572109f0f7a0776dab3e59d703ff24cc45be1b856a73f66facbd0c5d6c41ecc940ca7e02416f", + "new_receipt_trie_node_byte": "0xb9036802f90364018311b201b9010000000000010000000000000000000000000000000000000000000000040000000000000000000000000008400000000002000000080020000000000000800000000000000000000808000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000800000000000000000000000000000000000000000000010000000000000000000000000000000000200000000000000000000000000000000000002000000008000000000002000000000000000000000000000000000000000000000000000000000000200000000000000010000000000000000000800000000000000000000000f90259f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa000000000000000000000000088e6a0c2ddd26feeb64f039a2c41296fcb3f5640a00000000000000000000000009def7cde171841a9f0724124ca0b01a622d749e4a0000000000000000000000000000000000000000000000000b6ea0676d933c915f89b94a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000009def7cde171841a9f0724124ca0b01a622d749e4a000000000000000000000000088e6a0c2ddd26feeb64f039a2c41296fcb3f5640a0000000000000000000000000000000000000000000000000000000099f388994f9011c9488e6a0c2ddd26feeb64f039a2c41296fcb3f5640f863a0c42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67a00000000000000000000000009def7cde171841a9f0724124ca0b01a622d749e4a00000000000000000000000009def7cde171841a9f0724124ca0b01a622d749e4b8a0000000000000000000000000000000000000000000000000000000099f388994ffffffffffffffffffffffffffffffffffffffffffffffff4915f98926cc36eb00000000000000000000000000000000000045c6a16d265bb9fcae89d55c64980000000000000000000000000000000000000000000000009336b8b0a431b7d8000000000000000000000000000000000000000000000000000000000002fceb", + "gas_used": 153524 + } + }, + { + "traces": { + "0xd9db270c1b5e3bd161e8c8503c55ceabee709552": { + "code_usage": { + "read": "0xbba688fbdb21ad2bb58bc320638b43d94e7d100f6f3ebaab0a4e4de6304b1c2e" + } + }, + "0x617c8de5bde54ffbb8d92716cc947858ca38f582": { + "balance": "0x2863bce4f1dad386ab", + "storage_read": [ + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "code_usage": { + "read": "0xb89c1b3bdf2cf8827818646bce9a8f6e372885f8c55e5c07acbd307cb133b000" + } + }, + "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { + "balance": "0x5d848c31830be0ba", + "nonce": "0xe5ad8" + } + }, + "meta": { + "byte_code": "0x02f87101830e5ad7808501231a000f826ac194617c8de5bde54ffbb8d92716cc947858ca38f582876ca54625d8e66f80c001a0e0c1666544ae41c9fecaba65277c51af8755838a43c6e5bdc15c83970af18461a0405b701cfa1a3b22fc547c21955d55210f7fdd87b576466a2cf30cb27e9fb442", + "new_txn_trie_node_byte": "0x02f87101830e5ad7808501231a000f826ac194617c8de5bde54ffbb8d92716cc947858ca38f582876ca54625d8e66f80c001a0e0c1666544ae41c9fecaba65277c51af8755838a43c6e5bdc15c83970af18461a0405b701cfa1a3b22fc547c21955d55210f7fdd87b576466a2cf30cb27e9fb442", + "new_receipt_trie_node_byte": "0xb9018a02f901860183121cc2b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000002000000000000000000000000000000000000000000040000000000000000000000000000000001000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000800000000000000000000000000000000000000000f87cf87a94617c8de5bde54ffbb8d92716cc947858ca38f582f842a03d0ce9bfc3ed7d6862dbb28b2dea94561fe714a1b4d019aa8af39730d1ad7c3da000000000000000000000000095222290dd7278aa3ddd389cc1e1d165cc4bafe5a0000000000000000000000000000000000000000000000000006ca54625d8e66f", + "gas_used": 27329 + } + } + ] + }, + "other_data": { + "b_data": { + "b_meta": { + "block_beneficiary": "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5", + "block_timestamp": "0x663811c7", + "block_number": "0x12e3b68", + "block_difficulty": "0x0", + "block_random": "0x253d73a8035e2de53f82869fbdd8a700c96604da0485d0a02b05eebc53f615e3", + "block_gaslimit": "0x1c9c380", + "block_chain_id": "0x1", + "block_base_fee": "0x1231a000f", + "block_gas_used": "0x121cc2", + "block_blob_gas_used": "0x0", + "block_excess_blob_gas": "0x0", + "parent_beacon_block_root": "0xb2fdced42678407cdb560649f70178a2a0e4095543930548bf164b7b936eff36", + "block_bloom": [ + "0x20000001000000000000008010000440000000000800008001000004010100", + "0x8008000000000080000084000080000020000000800a0005000000000a00000", + "0x80a0000080000002000001001004000000000000080000400", + "0x102002000000000040000800000100000000000004110000001000088000", + "0x80000004000000000048140000001010008090800004000400000", + "0x205000000002000000000480000080000000000010000000020080000880000", + "0x20000002000000000000200000000a0000000000000040100000004200002000", + "0x50200000000810800010800000000000000000800000400008000000000000" + ] + }, + "b_hashes": { + "prev_hashes": [ + "0x684d6da309f7c8a0471b18617c4458fea2ebfec17d4f0a5e678f1adc7d5e9741", + "0xe9f1efd5c5d0eeda512ab8cfaa72a1e9289c1b7a1283080532c8bf75b78289ea", + "0xf1f4005a05d16db3af62e164efecbf1b244dfbdd4ecaa7787db627be3dc65b6c", + "0xb4a6b84696480f6864d1662abbdf9bf74d8c58d6831f0d7ebfb89c9527fd1a06", + "0x8aa706c69b4efb973104f4e3a32e95d0961cee806ac934c0b8d0f315a498fc1b", + "0x444a9b5d65aad376d8aad458454c8ad90e7ea3846e9a5bc88ce3838d25159a11", + "0x1be1d7933a44de4622ab864125ee3235446ec8090ae5ad96c5f4828ecefa7ce2", + "0x36608be1e2611f8248650a94ed0758bc3c884feb9c0182cef5b7a63b7cc13779", + "0xa018694b69aeead7c9707f8c4cc7087744b18f2697f5c0e8a72149f86472c3c4", + "0x42b9f09fc79a85c6dbc70e2580c57750a85e32aa34a83eef14f300d348d80549", + "0x037a1779222ab6b0c5bc9bf100ee568481169909eddd00b72d91cf43bd3b9450", + "0xecd72414a715469a622e9efd685e27bc0441d39bc3d1b900f9754ec88f9801dc", + "0x8e052b282793944721886849be4cf06bb9e2775bbecc017e62dda26fb0f167a2", + "0x57aa3978290d109b379f9622cb7bd3004b31e97e1d6c5fc9aef813b26a12417a", + "0xeb29dd370b5de7729819c006f19d3914245e72d3256b9c995bfc821c9244a782", + "0x62c4a05472424c89303241223787d704f5315bb16b5944adb8d950cc16d75ec6", + "0x10a0e4f10d854bb43c754b7cb0d027189f85db84c7da3f7533d1bf739bb89226", + "0x6ca125813a1af466dd481a16c6f008fe7301984f9caae0a16930609a1ceb43c0", + "0x18408b483e1a38b76caa9dc51e6c7dd759983b35d486954d2c50c9b90fffad01", + "0x5b1c83b40fda62cb008946b4884c1b95b1768c7a9a8a1e7dc20b34a4d8c403c7", + "0x06bee782c257495049d7a554a608a8f3acce1a09e6f844a8ddc9f723d93f83ce", + "0x72ea26252912b77f3b227e529337cf14b0971ea20e4f186ea969746a424def3f", + "0xb0b4482691ab89cbcfc2afdfc38fe972122598ef6db45c03684dc8c3ce63c4d2", + "0xcf7024250378017427243922eed81ee9088d32bf4867417e260cc11c71777519", + "0xe49ea52092a071f276c7b306d0f8d40c038ce49b3f8a45e03040c59e96c0354c", + "0xe4ef8d83b4e6e621c0b573d7209a364d87c763b840b572148b2006c6d346667e", + "0x5a06ccbd9311095490a4efbc9b358c85b5f3a1317841a83e7c36257e3827d16a", + "0x8dfbe7aa136575b5e3ed1659e1eee974f48d427717965748faec651425946931", + "0xab7ab2503be5139555349596c5059bc7fe1baa4f16222717f18023d7c8beceec", + "0x35c18135a8a4fb0c195df12feb2bdde3ae41fd2a5e68de71b2b8a8668cf01f50", + "0x265ac74569927d3827fe9a690f8b1dcd909e9400253da565681b0b6c3254b1be", + "0x5150ceb40dcc6656b079be8c4b5d0fdd0ec4c03dc56a07ded8bb4958f59f4cbe", + "0x131dce8de367b3afccb61ee1111ecbcc26b6c8cc011858def61749257822475b", + "0xcba6e3e9371fc2428e0d64458c1ea38247bc2402c35512699acd9e98c791bf4a", + "0x11d1ff0fba9724e35b61aa790e030eb87dfa4ae5f3eb32c562dfe73301a0e538", + "0x177c10d79e45f97825d1aee005daac2f5b878dae2afbeb077d701031eb8f5fdc", + "0x369df7c07465d4ce309973349ff656ec13dcdfed6bb6003a0e276257d2c804de", + "0xef7ceb8248b175f4f2ebd3ab1d3e9f716b7066d771728afc13ce26582001864f", + "0x612c72dc848b6752e0c59a08a69aad3195c94cf15b2be1ea04b8de949bbd8a62", + "0xc7810d75f85d4affc277425e3cd8016fa5fe5fbcebbc2f313f78dc733627c764", + "0x58f26bc1fc04a3d64c035bba57ccf0a11bd85643ca25a12c2872a379807fb223", + "0x06672d0ef05ad36ad7cbe4a6b0f8973d9fcf2f4948f3faad186eceab128f033b", + "0xcc7cf48db08ffbd8b3424b5d657f50f805cb7669da9f94b9ce708bcfe6ff60ab", + "0xf7afed969af520af3135ab06f591b98a657e6ed456cae4a8cf9ff74692a89ec6", + "0x2b5a83bdb1420eaaa036b871302aa8bd6f0591353932b9716ae6f5d9fa4960f7", + "0x17da50dc5a57ddfc357fd76dd2c6d844ab86f00c7c169f672160d94d29e0cfbd", + "0x901fcc4fd29455ab7e86debd1c0e590be42ed9a32eaca646491db2c0b28e1de9", + "0x0a46152ca0428fee39b130485849e10aee553f1d20af050610786b0c435076a5", + "0x3cda416406d979ad59f6bc3e924b619413a0431cecd4305a0cc0a5d7f72b1abb", + "0x669e1c9b20f56963eedff200b3287a89ecbff1d729d72150e9df36f0500f40f4", + "0x47e065cbecfdf242a8348899b50d34ab1136fcf1286219cb5865168112de1af8", + "0x01692e1363a33e509152e33cf5683b8ec0b778fd056ba37ee9b14e78dbcdc23d", + "0xc3b4f5f75a81c39f9f89c67c8d3c6e22dabfe55ec84e400f71095471478b22ce", + "0xf800724be14e70f9d21d7298e5cd85accfb1b5993b9641dd7d18f84e3fdf139f", + "0xc78c8f4aef3560226da9e906f818326914099cf7281311269fb3f185dad4026d", + "0x2d1d7a03d4eb6a453acbc98e7de60b9964c2dbabe8f364c64ce09713954816d6", + "0x319eddfeb21e1b4ac4eec6e0ab207f96bfd39fce8ad357412244a7a204cd15de", + "0x9267223f50a0670bad1af619be27538967dc3ffb4187427b8718dda635b0646f", + "0xf4fdc676ba397e28ef91298691083e12cfada760c6212d42d9d8bfc203b2e9c4", + "0xbbca762d5f9b9afece0c0c4b1b6d39c696101f0d380a059d42de63ee58a7b4a7", + "0x0caf0d6547aad36aef5058f772589929611795961e4c11ff6e4f0054350f124d", + "0x6e765d61eb9936842a77969a46b3ec1e8e63f8217c9a38dc1ae03fc1373e244e", + "0x074ad7e8b9a67df3be4ee365ca4d0ab0f21ac9681b206d8cd2e81d63999ab540", + "0xabe2d66e6ca7b273d15bdcc9569b73d69a7ce40ac4c7e0bca15233a0b466c48d", + "0xa559298ce71269e041845b239a2192380f19a0df3e986c9f7b88c52dfbb68b72", + "0x7a4474570b63e6e68f692ef9a2018faac14f83b2d5fe714dc8c28ac55ac5905e", + "0x6ad9f2d6af5853591ca84cf8c8641c40ea800ed17d0a93293df66964801d03bc", + "0xb2d224381f1b031be102dbb9d37bff9401c970648af6099de354dba9c6ffdc9e", + "0x920acddab483a67476079786ac02506ac2cb329acf707110cf4fc08be0946bce", + "0xbf74a03ed288773ab3d8ad7d6c7e45702d3679aa685d44d8b4ed31d25df0ee64", + "0x22fd1691c5b5c5d7e0f25a25045131cebb54e96418ac33f6b033dae571f0e1d8", + "0x584f88b1cc9ca5825aa734b30c1fb88947f7b29fa5d389f9d375c52c89be0095", + "0x1dd49abeeb34f3a389166da6f7efa1d7949b4bdee5d013696b53e1d3e0b9a1a5", + "0xceff63f333df8bf4dea68dd443b195b5d53234d4f3f14316949c2de4b2e51c86", + "0x1ee213255d77233e366e55a248c9c524fa15f218a439cad211c52d08bc09845b", + "0xc66fff1c5b7c88b856f8ece31c2f1323b54633a83674d87dfffe4a039adc5440", + "0x6968799fc7f27cd33e698332e67b85305583909934aa9ba5d2d503c722b808d3", + "0x489aba839fa83f28484a016bfea5657d9fee72f0bea757f0ac9a525bc9d4e0c4", + "0x8011b25fa321e68cbffce47bb81ec56e944834e9319323b6d99242b1decc258d", + "0x034004ec7113ffbaabdcf03e27f06cfc4b51fca60f79d9f1c78689eddde9ff0f", + "0xf1ca1b925a4666b8156d842c61b4d8d5a8dadd1493c1deeebcec06607f1c57c3", + "0x1c336bfc7570cb6baea4f653a5ae9ba9e6b2966d9662d544a50dafa3f80dc8a5", + "0x994795c6a5db487bc99d50a5c47ca4b0a31f0c14e266c75699ed998e418b0273", + "0xa3e8dc9d9b954be9e365913c0274dca50600b157ef2a66b480f464fc60ac2582", + "0x7cb8907a3c885ffafd56d399ffbdfc77647d589029466556ef8f0a875768cac8", + "0x685efa6d0c4ef05758e0d4cc60d8bc30c5b4d09aa595b293802bff4a14130d6d", + "0xd53ee0bac8d4f6afc15d9187d8959e047551b286607e98abc71af4a276e5533d", + "0x9c94b7cad6eed497f33a8cc13c23be945cfe59b5fc88ebf6e5a78b63139eb416", + "0x746593942f1a4e89157f04ebe3b5d19178964d6b358d03422340314b431a30eb", + "0x8f00f2b21786fef3908e708b2f9a9646dedf89570e52487c359d6237a10b2332", + "0xaccfffd17acfa905ea0882f1329d9419e5c6f8a914ce79ea8bde3168fc93bc43", + "0x382e627dc9457a8b97ead189afe4209b1301ef73409198ba1d03296eb0573c10", + "0x663c5109a960127c5f4461650b9ac0ec98b441af6a5c813ac394a4a54174863b", + "0x19bdaf621cb61a7e39ed537392459a8fc6fe593e80ac043b0541e3aa49db0083", + "0x258b4555385f82af59c3297d6809bdee140a627a72108fa2ccd7a467b7f9cf77", + "0xd2f5ec45869a2227af4789029b17d79e0f7465a3f8491ef8263f65fe9ee67863", + "0xb7429f2486335edee7b97c68bf21746516eb3edc2421b0af31cc0981edc71274", + "0x8532734773de15bd7612ef289467d82ecdb38f97f721842329c032baad97797e", + "0xfa196478a8d98d9e5a29d49e5dae3a0e8af0bd81bb1d171cdd0793511de497ee", + "0xabfa52d45f2b64ba8da63779cb8c0da104c6e4085a94a12e4c01e888d0eff5b0", + "0x79af7c7f2df5704e1ea35e336dcb50d6e1c4644beb770c4b737f92279b7cea4a", + "0x8bc16add47e2bffc84ae2cafaefd162c81d9377c9628e0e7947f05424374341b", + "0xcc2ff38b0b787f201208bec3c7ba38ae0a9ecf9c6ff3bb4bb91f07c5260b9f0d", + "0xe6bd50d7a43e80dc310283c8d28b6057de35dab665fc389ccf2934ef522a923e", + "0x976b800be89a4615209eeafe260f290b961f8f35f243ae15caaf16dfb2e8afdc", + "0x61a02bc6d0e17ea710151c4fe838cf5bdfe39ee7c341dc8c819e3090e8995218", + "0xaf4ee6ee6f0c42d3890e4924e9643e91b32c5aceec89f065f19c45abcc154afa", + "0x0f7199378a78513d389e381668ab838bc87df917e5e754834ad1f5b4ba46a6af", + "0x66cfabbb497ae08a4886bdd98e29ec79fb1f3faf1d1ec0a70f216512003f77c7", + "0x50897dda2b2a63b1cc8b51dffe4639b10e5914b650b91be064539c56782c2669", + "0x65f708a418a87d3ba9358abcb3000aa267c384be3e817691e59f6e2abfb7e383", + "0xc0b3bc8a33b5990a67385736bf5f3bf1f93d7f5d88b61f8e587da89741e58a78", + "0x3aa019da243d763fc2d4aedd82a42dd62088970f41df5b4701cddbc95ec80d1b", + "0x563be0cef26a96fe96d2458a459c16a1cfd98ff30de359be1ef0fc8148dcacd9", + "0x53d17e0d0919d7d07e8aece7825a7a898b4b0b4d0fce1514aeca0ff467fe359e", + "0xb37bb8db446e1a68ddb111e983aeae19443e6a8320d8c067b96b7e0e1a99872f", + "0x711a0ef39ec7f508ef2f5518d2d2b572d9af583cdd0c5b8d1b0aad720887ff8f", + "0x8f6223a484539e961de50a72916b5c37eacd50fc874cc9cb57cce7f3a7fbc768", + "0x78adee7ef19e71b90686be4672ade5996a705ab3676ecf56aa12c5fc7e31232a", + "0x66a7b68e630b5c23fe3a8c1ebaf2f71d65cce803acece13e7f6e04164b391170", + "0x49998c072fd731b40d077d1a8b908f79732ffe4ec4e2da0fbf213fd862d0c4b3", + "0x3e5c71e123e0b9ca51c5ed7b564993541002f89bbf50899c493aeae139bbfa73", + "0xce955633621fa117e4f462ac09a82e957eb8875e9157f5a4519db7dd9f3b3db8", + "0xccf8c2816119cf226e69262ee6189843884fbb9193c83f43ad112593c579a97b", + "0x9b38788917cf5046229a4763fc45d527b56734f07c93c9975192e1053590676d", + "0x3f5312d1efed17763afe2c2b773a7587614aba32c00f88ffc0d698494894ffc1", + "0xaab17205746a6a5daa4eb3d0466ec46e0c8d04b3c623b6fe14cc2fd82645d040", + "0x31c1f4e27e27c122a6a14ab18c6fd88362bc2b429d235845039bad13ff9ed249", + "0x5a6375471b27ee726cb0ba9e003429feeb43e2bd7f4e3fc96efe82f8f33684ba", + "0x6f5d262d1320c05cb93ca7d3d992a25ae9a6fe11b9651cabaedd420744253fe9", + "0x067b51b1902fa5927c5e23fba472434f29b062f91e0a8f6f03d5e4b4181cd769", + "0x9abcf7ba8d6a8803ac56d28d05cb9953df3fe3ea131bfcb1458f13ab5ab002f1", + "0x4898c61d3980d568e8d9869f9640be08595bba2097d3670d8a9f4cb531c83cd4", + "0xbec84066edad2a85a68054ce63fa0b03a8bdefc2c2921e09bf39e231e90393d9", + "0xe7531552af7303e818ae3ee063ff0e744cf71cea8169ccccdf303dad279ae6bf", + "0x25e0066d1dd0caf08b7affa7efe80264d46531196e281472a2b980e75697f6ee", + "0x6d5382548b060f3a60ae44a588df9338105f4e81f2de512aa36e6bf097292b75", + "0xadc1f0377233d6a5b656285306196cc7d8812f6513c2a9e92129ad261b7bba64", + "0x48a56c398dd7f128b06cf43f48cf8101c73cad5089622805af90db35a3a2426e", + "0x796ba441d23ff8350f8dd21c95df5bff4be0f3d873aceb329870c9d93e4b4f6a", + "0xaae31869bdfe85b02cfc3730b048b9e8e867879eed841bc020f272e893a4c0e1", + "0xeda5bdda98493d39a7204389c01006b9f116d3b568b0a4b58760d1d50f10c43b", + "0xc69cdb8b8d9b86fd9189c4dfbaaa542ff2eb9ee1f966d74e0936eb61162a9440", + "0xe138f6ab0383a803777aacf019c008232d49e3938cb80755d91f94b8c1df9157", + "0x05c166f7b3e46160fb1f4ac911ed3d4c787d422417b0a3548eeb3ec05c000899", + "0x5715789eb35a14b9f2594183f5e1b2636286ab0eedfc6f865af792d3329e3932", + "0x06fdc5cdb25a18fdaace2028efcb17c70dc6dccb6836d67e6c4db9d19e4b30a8", + "0x436ca87dbaf5000546a1158bba2679e093644214021c55bf7a18938bcbc8c26b", + "0x06894a55e5217c96c84691b662bb5882090290e3d2b8fb815d289bd43f401053", + "0x26c1f7ee90e8bd920401242821aa78bd2e76cd8acfb5fe42e8bc26215b52db55", + "0x4d17f9ff3941f0445607712ebebe2503ee90c31256324860d6e671a31dfc8553", + "0x7020b4039cb2fed36bd801861b72ffb52ea3692fb0de287cdc4555647691de3e", + "0xe142905c102278068d91f1305da98fd3aed0f0bcdcb79ae84f737b74e542687d", + "0x0b60248cf3eaff8acbdef8407bae6ea515c4f7277aa50e7bf0f3a13dc9838d64", + "0xd41fdec7606d96e22c817f96dbf17e5e13bb4e78212650e91b7400902e778bce", + "0xcb6628fc75d77664c234f1d54c51a7cdf15cc8c3b7ad42249755f5e5dab163ad", + "0x3fed5b830f088274b00699b5099deca1c39c0c184fc5c8d1759127430b799c68", + "0x6c81efc467bdadf6699b8b50d87b2b3f073b1d298080fa487edec7678bbcdd7e", + "0x0a5681394def8105618c8c229de40baed1deb5ecd36ac9f5f7d9f82f7dd8d951", + "0xad9fc5133f35f8c368921d614a6fd24e959ea43b5e3ed28ad744d776312c912a", + "0xb2c1ee02dd75e9ffb9d569d3c94152e51c9a92e119f9569a556413fe9283669d", + "0xb7587fe5feead89ea8bc2beded08ce896657010c6b7d3e1da556a028dd60aa48", + "0xffb8497fce9f9951f155f22cb21c88a8346cce183cadfcd36a797909b8399335", + "0x27227cf2b8fe94edb031f9f4b120e877776b7e71ae9f1a7b691027418768a512", + "0x8cafc271f1243f94fa188ce39e1ba9739d80f86bdcf7f0c4010906a0d7090669", + "0x68f1869e7e8f5764068759080c8b0ff0286d37a8c29586fa85ac9fff73c4d6f5", + "0x347203b050cb378a6b9eeb3550a773ed54f50bd58173d4e11e3775f17d66f159", + "0x55b8e9795919367fabbffa613d1be39b3ccf550e3d950255579d3d7d71836f4e", + "0x637439c6d5433a874591bcb7da0ce781f4c25a874f62c818315c5830a1aba7ae", + "0x2de673eb74141f4ffbdaf7ebdda72bfcea0feaf9a142d063f3076c830515fc4b", + "0x6db7840198ae6647f0396b0cffb0f0e3a5f19311b75f6165470bed13a5ec491d", + "0xe20f94a3cdb5b9583962ba1f91574d92d3d8ec2ffc342352c818cbfb1817520a", + "0x63e84cd9b5d9feb635bffccfdde923321b2228637b15f4c3140efdbefab2db2b", + "0x5dfb69849e6108ac90ec5a1dad1c02272e2749d63d7bdaf23e5f64a47b29d4f6", + "0x4a0eebd96c61d60db69c4b5ad5db67b5fb12b5ad82b97806123278e11f8e9ea7", + "0xb3b11a0d126c5a8b4e17ed69992814241a1b878eebc7881f746afbbf314f3948", + "0x8a43a231547f106b5327611db49e34d6e4ef973dab099027812efb441e377e68", + "0xe2d8e65eceab5214cc111762bf93b3f28bc4df80537772391b90c4b0f64dc79a", + "0x37fd13e02c213bf88b8c936ae156da419085ec4784501c856068e4e5701690d0", + "0x43ff3058e2be0dc50c23e8dd30d2222626d8f33055bba0e54bc039438439844f", + "0x218808f95657312a6048a0ef5ab932797271a031a17f0c9ea3c4ce800b223602", + "0x176aa1cc1879ea31d77f16e7f676357a071a6251a73a4fe102ea9e5a4a225d01", + "0xfaa14244b79614b82d7032d9268ea13c99da4784866e3bcb8261ba64eb4e3061", + "0x6c59f62d885febe6a62a95bc4d4193371717f6e371a8d0f99afc8d0c453eba2c", + "0xceac1b9b4fe4ecc2abb129a577ad3418ae76bacaee0795df90b72efcb6593f80", + "0x5f7a826a06c7edbab0eb5684a9774ffaf820340e296a6852384d4379e28c77f2", + "0x144fc77d47ab1ba0d034429cce70860e427f22e30a43405a8f428c0da78aa7e5", + "0x9c998912ed306b9a8e384c0e8afdca281557d1808e68ae2ebbf0ed2344b3a9b2", + "0x7ff6f8bb85a0dc93aed009e3a967fff69c97952b18bf1042ec4cc4b89d3569e2", + "0xee6783172418d7984b42267c1612bcddb7d38e23375793000c092b1205eb2b81", + "0x50ef73d80aa69de4640d47bf47012ad9bc6fe971d38f32200fe6df4ea0ed8fcc", + "0xcb90a6a4c4189787842e3330fab531daf96f240c9bb77b9ef4a9a7bc5a7dceac", + "0x891884d140c8ada4d7af76bf1f1e4e7085fcdaab482d3f093ee9ef4c3e2b5955", + "0xcacfbc3c46f96bcbd1c93915883c9af2b32078c383f65eebfccf8e2736f331ce", + "0x352b23629786a33d536df0957c32984c19e1b13d338a1dd0cffeef6a0c9c063f", + "0x6d7cad0c54976b20f02db2ba8926a59d51c95be9e5dac6d3a7f5f13c06e3c207", + "0x0a7eb23ff876af6025848afd5be18ea20b29d98bc58f659eac3ff66127199613", + "0x5933e5991f50c1693833f19f0d9059ca1d5daba6fe8d37620f397bee7ff02ea8", + "0x9c20dd8216a07e1952ac1ca48277c615d1a0fc0d4d3833df76c5a2de231dd7d4", + "0x4821c85f5615995b87747f6ef3022532677d4e86fdf010412dc2a3df7bef94c0", + "0xae021e27142fc325181ef3521e9cc29883d845f96a971f3dd4bb9eed8ae1aa92", + "0x78d29fda96832395048b73d9e023958767457069a1718ec5e27740798eeadb74", + "0x25724cc81bf397890f2bdd8f35a3756b3386f333ed55fc7e51bbc2f1fd2bf6a2", + "0x824762d03b42d475150a9fda08ff9285f62f2c0bf6401d7375d602bda1f3bea6", + "0x9333ebdf48f884bef84e204c826dc6677ea35bb496ae9738e5e4e0f47df8988b", + "0x70b1e57107c22010c865f93f7f733c043e56a38faa8ec3781ee7036ed0e309d6", + "0x1aa6351752aa54bb2130bd05beaedd3512fb362e69fd4a44663fd698e013ce71", + "0x1b656e01797575847ba4c952c0c056d1470d690eb65f6515def1d25db71b414e", + "0xeac3bf46591d9d4f20321cd484fdea8273ac34a5253266f1df57fdd983bce73b", + "0xc376b24cb0a5528dc24776b2fb7b065cf69ae303d0a643a980e5531e2ff0dee4", + "0x0c5d2c7315c9ea69050ad21982e373768d0a3526db617f33da18e6bd1b63c373", + "0xeb06cdc3f738bb8a8669b37cae56d51bf3ec47ed7fd0f93825a42f1007909042", + "0xcc349f38dcd62fd9c5fbd781a080fe1571aa415ad5907eb370844f54e2dc820c", + "0x7e7b0e1c580a5745ac78bc733d960d43f34f16d7f124dcb6d7fba5403fe4c006", + "0x1519a24ed04d8572dc9b6b050b3856fcf11fcf5a1dce1dbc22bf15fd2ac1d156", + "0x086764bcad0624385326103f4960c64e4fe8426883e81145713d118d16a8303d", + "0x621f91b175ad126f9b503e1bedb24e0752b88eec86c8e7efd30435a1ab9a4a52", + "0x56f59bce52ed19a2791690f7917aca8bd2ae2f412eae82dde2ed0d89c1645f5b", + "0x8560130fe131d33f84e58cd33e4157b5a3ccc0f8a0952bc8dbc02984e2b0a68a", + "0xf66c3c244b672ef8df24cdd6f81021342d8d354f5b5e88ef55f7a791cc39a1bb", + "0x6b7f194a9a191bea25a5f988bfcde8461a661ff0cd084042b716a34cb3d6bf54", + "0x32f1c5949c9448345947a59d5c5104d1bc90f7b702fdca4f521dcc4b5c0df0d5", + "0x3f8391d7ed4f166d44d21a9f6357608855320ca5fb50e89894635ad48e0e6604", + "0xd6390cb566f7a9ff98913fc33ede46108b29759a3f819a42eff90766e6f6435d", + "0xd06f278cceb875552dec0ac684f30e906b9e9e26c81891803c0e332ac92eec21", + "0x47fc7b6ab4869079bc2059199df9ef6db2b1d9c38fa5dc73f482a4ee367d0560", + "0xd0016d3ad15aa07661db1756fdfd7d586e245fc0f15269c65370feac1485b406", + "0x5adb9f0abcf8622847c66176355b549742614156c4fa62b54dd853ae1d4e8ed3", + "0x3bad795c74f5e233fe973cf09b6a7f680bbb62fe8788feb2cc4949285d1bc916", + "0x42dc8204f8822efa30913242c0c7b41bcd1707e790cc4d609e67412cff634e64", + "0x66e8171b0ddf974391efb526e2b7ccebd85f137094225e2627b8c7f7149e8e37", + "0xcf186ed9125bca4d3bf513d3c324d72290b55b334052e43effc9c01880108dca", + "0xaf95f87155fd1bdeb3c85f003a4b232950f08abd700e5f198dff81e7fe28a90b", + "0xf18e69bdaf8bb5c5e43959d6d7e9ca57b391ff18112197b3615dafbce0c96eba", + "0xc6ab386f906a7f4bfed294e143d87b0e3a33a2270cd36642605e162bf9b13dfe", + "0xcfda661e48ad92890b2d8598d5375baffd31657ab88917966338caf29729f848", + "0x69decb9aa1175f5783c608242cc366907b07917cffbedaa434786aa2cc1f25fe", + "0x80f9b7b74df1d29ba5d29559255f20096c7419f8fb5f25a9a52c4f6bda0bdb06", + "0x89d751062d8782376d3c187aa726620d694aea101ed9562fbbae5586b2a5ddd9", + "0x554a7df6055887e4e3f23dabf665219d49c49aec890467ffc39394de053f8be9", + "0xe6a6717093a4d6d61ae2548f18c023b916fea722fe301b234c2065968eba8896", + "0xfbe85151ebf2b992e6db11d6e5c0957fa11c3ffc924cfc7912101c5a1c8b7e3a", + "0x21b315e2a4fd2cacd402fa338518800ca7d215dbb63ccdb83c044a0bf63146eb", + "0x39065ef91631dfc82f937b0f834e674d37326d6e8a23410d27cdef7c4d52e5a2", + "0x6b720dc652a51931dd7a8dc86b7cd30c12185a39db1cbc6dcc7323d8355627c7", + "0xc6bfd1c8c7222bf522882ff5c3c86c7b6f17196b79afd7d39b700fc3526b6bcb", + "0x5725adf9c722d78ef5e43192465eeca06b4f8753ed584f9eb5eb3dbf0ed9b129", + "0x92c5c278bb28520f283325dbd8a2773644a13cbba491933ba9be7a3c6169daba", + "0xcfa791094b86177ae57ccc0be478cbf8c6c09b79b698af50bcacd239997cf329", + "0x43acd4f1ae62e9e5c1c87284abc8f7d0d0aef0f70cd934a788ab566573e2f5a8", + "0xef4b796bda5a978f28ab810df3f2ee6ba0d646bb35ecc59d4a8aa1a11011d8e5", + "0xd804abc861a77340c0556e21650cd1f8e1e4a3fa4d83ab19862c83fbe0ab6b9f", + "0x633167a2ee64b2820d9557bd0bc540255f9379ef0802e7385c70c90bc7f3c57c", + "0x2c32447d713a9859b680e9da9f634341c159c0ae1f94f35d47f6ef384f7e69b6", + "0xbf5087bda3e2f18942e922e2e0aa27e88b23d65f33009049dfde50400fa1c7ad", + "0x27c8003b8d5ed3a0dfb4313cf8e969478176df308cb8ff7590f86323469046e2" + ], + "cur_hash": "0x0b4c5b498320e5858e7445a5af10658ffee39c9a39de22c81452de5421f63464" + }, + "withdrawals": [ + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x11a0834" + ], + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x11a27fc" + ], + [ + "0xc9234d5606e02a0acdb7682fe36adb588cae60d8", + "0x11b88e3" + ], + [ + "0xc9234d5606e02a0acdb7682fe36adb588cae60d8", + "0x11be16e" + ], + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x11a1416" + ], + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x11a2dc0" + ], + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x11a42a8" + ], + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x119940e" + ], + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x1199000" + ], + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x11a046e" + ], + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x119d364" + ], + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x3bc7b17" + ], + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x1192ee6" + ], + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x119bcc9" + ], + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x118f663" + ], + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x119b5ce" + ] + ] + }, + "checkpoint_state_trie_root": "0xbbd66174555d27c88e285ff4797de401470d8d2486d15513ab36e491e864bca2" + } +} \ No newline at end of file diff --git a/trace_decoder/tests/test_b19807080.rs b/trace_decoder/tests/test_b19807080.rs new file mode 100644 index 000000000..62a4588c5 --- /dev/null +++ b/trace_decoder/tests/test_b19807080.rs @@ -0,0 +1,66 @@ +//! This test aims at ensuring that the decoder can properly parse a block trace +//! received from Jerigon into zkEVM `GenerationInputs`, which the prover can +//! then pick to prove each transaction in the block independently. +//! +//! This test only `simulates` the zkEVM CPU, i.e. does not generate STARK +//! traces nor generates proofs, as its purpose is to be runnable easily in the +//! CI even in `debug` mode. +//! +//! The tested block is this one: . + +use std::time::Duration; + +use evm_arithmetization::prover::testing::simulate_execution; +use plonky2::field::goldilocks_field::GoldilocksField; +use plonky2::util::timing::TimingTree; +use pretty_env_logger::env_logger::{try_init_from_env, Env, DEFAULT_FILTER_ENV}; +use serde::{Deserialize, Serialize}; +use trace_decoder::{ + processed_block_trace::ProcessingMeta, + trace_protocol::BlockTrace, + types::{CodeHash, OtherBlockData}, +}; + +fn resolve_code_hash_fn(_: &CodeHash) -> Vec { + todo!() +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct ProverInput { + pub block_trace: BlockTrace, + pub other_data: OtherBlockData, +} + +type F = GoldilocksField; + +#[test] +fn test_block_19807080() { + init_logger(); + + let bytes = std::fs::read("tests/b19807080_trace.json").unwrap(); + let prover_input: ProverInput = serde_json::from_slice(&bytes).unwrap(); + + let tx_inputs = prover_input + .block_trace + .into_txn_proof_gen_ir( + &ProcessingMeta::new(resolve_code_hash_fn), + prover_input.other_data.clone(), + ) + .unwrap(); + + for tx_input in tx_inputs { + let timing = TimingTree::new( + &format!( + "Simulating zkEVM CPU for txn {:?}", + tx_input.txn_number_before + ), + log::Level::Info, + ); + simulate_execution::(tx_input).unwrap(); + timing.filter(Duration::from_millis(100)).print(); + } +} + +pub fn init_logger() { + let _ = try_init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info")); +} From a5f54212c8fb94969422779b75baeca914ac67f3 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Mon, 6 May 2024 23:41:06 +0900 Subject: [PATCH 20/40] Fix KZG precompile I/O (#213) * Fix parsing of calldata for KZG precompile * Fix returndata * Pacify mighty clippy * Fix versioned_hash check * Update test accordingly --- evm_arithmetization/Cargo.toml | 2 +- .../kernel/asm/core/precompiles/kzg_peval.asm | 4 +- .../src/cpu/kernel/tests/bls381.rs | 21 ++++--- .../src/cpu/kernel/tests/hash.rs | 9 +-- .../src/generation/prover_input.rs | 55 ++++++++++++------- evm_arithmetization/src/util.rs | 8 +++ 6 files changed, 59 insertions(+), 40 deletions(-) diff --git a/evm_arithmetization/Cargo.toml b/evm_arithmetization/Cargo.toml index 43e75b902..9cbeb1aee 100644 --- a/evm_arithmetization/Cargo.toml +++ b/evm_arithmetization/Cargo.toml @@ -35,6 +35,7 @@ rand_chacha = "0.3.1" rlp = { workspace = true } rlp-derive = { workspace = true } serde = { workspace = true, features = ["derive"] } +sha2 = "0.10.6" static_assertions = "1.1.0" hashbrown = { version = "0.14.0" } tiny-keccak = "2.0.2" @@ -50,7 +51,6 @@ jemallocator = "0.5.0" criterion = "0.5.1" hex = { workspace = true } ripemd = "0.1.3" -sha2 = "0.10.6" [features] default = ["parallel"] diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/kzg_peval.asm b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/kzg_peval.asm index adf87534d..796f5b390 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/kzg_peval.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/kzg_peval.asm @@ -66,10 +66,8 @@ global store_kzg_verification: PUSH @SEGMENT_RETURNDATA %build_address_no_offset // stack: addr, res_lo, res_hi, kexit_info - DUP1 %add_const(32) - %stack (addr_hi, addr_lo, res_lo, res_hi) - -> (addr_lo, res_lo, addr_hi, res_hi) MSTORE_32BYTES_32 + // stack: addr', res_hi, kexit_info MSTORE_32BYTES_32 // stack: kexit_info diff --git a/evm_arithmetization/src/cpu/kernel/tests/bls381.rs b/evm_arithmetization/src/cpu/kernel/tests/bls381.rs index 6f13a8a13..56d6c1f7a 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/bls381.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/bls381.rs @@ -13,6 +13,7 @@ use crate::cpu::kernel::constants::context_metadata::ContextMetadata; use crate::cpu::kernel::interpreter::Interpreter; use crate::extension_tower::{Fp2, Stack, BLS381}; use crate::memory::segments::Segment::{self, KernelGeneral}; +use crate::util::sha2; use crate::witness::errors::ProgramError; #[test] @@ -116,16 +117,22 @@ const KZG_PRECOMPILE_TEST_SEQUENCES: [TestSequence; 10] = [ fn test_kzg_peval_precompile() -> Result<()> { for (bytes, is_correct) in KZG_PRECOMPILE_TEST_SEQUENCES.iter() { let commitment_bytes = bytes.0; - let comm_hi = U256::from_big_endian(&commitment_bytes[0..16]); - let comm_lo = U256::from_big_endian(&commitment_bytes[16..48]); - let mut versioned_hash = keccak(commitment_bytes).0; - versioned_hash[0] = KZG_VERSIONED_HASH; - let versioned_hash = U256::from_big_endian(&versioned_hash); + let comm_hi = U256::from_big_endian(&commitment_bytes[0..32]); + let comm_lo = U256::from_big_endian(&commitment_bytes[32..48]); + let mut versioned_hash = sha2(commitment_bytes.to_vec()); + const KZG_HASH_MASK: U256 = U256([ + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0x00ffffffffffffff, + ]); + versioned_hash &= KZG_HASH_MASK; // erase most significant byte + versioned_hash |= U256::from(KZG_VERSIONED_HASH) << 248; // append 1 let z = U256::from_big_endian(&bytes.1); let y = U256::from_big_endian(&bytes.2); let proof_bytes = bytes.3; - let proof_hi = U256::from_big_endian(&proof_bytes[0..16]); - let proof_lo = U256::from_big_endian(&proof_bytes[16..48]); + let proof_hi = U256::from_big_endian(&proof_bytes[0..32]); + let proof_lo = U256::from_big_endian(&proof_bytes[32..48]); let mut stack = vec![ versioned_hash, diff --git a/evm_arithmetization/src/cpu/kernel/tests/hash.rs b/evm_arithmetization/src/cpu/kernel/tests/hash.rs index 8e3e62d6f..305a705ad 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/hash.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/hash.rs @@ -4,11 +4,11 @@ use ethereum_types::U256; use plonky2::field::goldilocks_field::GoldilocksField as F; use rand::{thread_rng, Rng}; use ripemd::{Digest, Ripemd160}; -use sha2::Sha256; use super::{run_interpreter_with_memory, InterpreterMemoryInitialization}; use crate::cpu::kernel::interpreter::Interpreter; use crate::memory::segments::Segment::KernelGeneral; +use crate::util::sha2; /// Standard RipeMD implementation. fn ripemd(input: Vec) -> U256 { @@ -17,13 +17,6 @@ fn ripemd(input: Vec) -> U256 { U256::from(&hasher.finalize()[..]) } -/// Standard Sha2 implementation. -fn sha2(input: Vec) -> U256 { - let mut hasher = Sha256::new(); - hasher.update(input); - U256::from(&hasher.finalize()[..]) -} - fn make_random_input() -> Vec { // Generate a random message, between 0 and 9999 bytes. let mut rng = thread_rng(); diff --git a/evm_arithmetization/src/generation/prover_input.rs b/evm_arithmetization/src/generation/prover_input.rs index 774d493b7..2e05f8a2f 100644 --- a/evm_arithmetization/src/generation/prover_input.rs +++ b/evm_arithmetization/src/generation/prover_input.rs @@ -29,7 +29,7 @@ use crate::generation::prover_input::FieldOp::{Inverse, Sqrt}; use crate::generation::state::GenerationState; use crate::memory::segments::Segment; use crate::memory::segments::Segment::BnPairing; -use crate::util::{biguint_to_mem_vec, h2u, mem_vec_to_biguint, u256_to_u8, u256_to_usize}; +use crate::util::{biguint_to_mem_vec, h2u, mem_vec_to_biguint, sha2, u256_to_u8, u256_to_usize}; use crate::witness::errors::ProverInputError::*; use crate::witness::errors::{ProgramError, ProverInputError}; use crate::witness::memory::MemoryAddress; @@ -429,18 +429,40 @@ impl GenerationState { )); } - let mut comm_bytes = [0u8; 64]; + let mut comm_bytes = [0u8; 48]; + comm_lo.to_big_endian(&mut comm_bytes[16..48]); // only actually 16 bytes + if comm_bytes[16..32] != [0; 16] { + // Commitments must fit in 48 bytes. + return Err(ProgramError::ProverInputError( + ProverInputError::KzgEvalFailure( + "Commitment does not fit in 48 bytes.".to_string(), + ), + )); + } comm_hi.to_big_endian(&mut comm_bytes[0..32]); - comm_lo.to_big_endian(&mut comm_bytes[32..64]); // only actually 16 bits - let mut proof_bytes = [0u8; 64]; + let mut proof_bytes = [0u8; 48]; + proof_lo.to_big_endian(&mut proof_bytes[16..48]); // only actually 16 bytes + if proof_bytes[16..32] != [0; 16] { + // Proofs must fit in 48 bytes. + return Err(ProgramError::ProverInputError( + ProverInputError::KzgEvalFailure("Proof does not fit in 48 bytes.".to_string()), + )); + } proof_hi.to_big_endian(&mut proof_bytes[0..32]); - proof_lo.to_big_endian(&mut proof_bytes[32..64]); // only actually 16 bits - let mut expected_versioned_hash = keccak(&comm_bytes[16..64]).0; - expected_versioned_hash[0] = KZG_VERSIONED_HASH; + let mut expected_versioned_hash = sha2(comm_bytes.to_vec()); - if versioned_hash != U256::from_big_endian(&expected_versioned_hash) { + const KZG_HASH_MASK: U256 = U256([ + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0x00ffffffffffffff, + ]); + expected_versioned_hash &= KZG_HASH_MASK; // erase most significant byte + expected_versioned_hash |= U256::from(KZG_VERSIONED_HASH) << 248; // append 1 + + if versioned_hash != expected_versioned_hash { return Err(ProgramError::ProverInputError( ProverInputError::KzgEvalFailure( "Versioned hash does not match expected value.".to_string(), @@ -475,21 +497,12 @@ impl GenerationState { /// Verifies a KZG proof, i.e. that the commitment opens to y at z. fn verify_kzg_proof( &self, - comm_bytes: &[u8; 64], + comm_bytes: &[u8; 48], z: U256, y: U256, - proof_bytes: &[u8; 64], + proof_bytes: &[u8; 48], ) -> Result { - if comm_bytes[0..16] != [0; 16] || proof_bytes[0..16] != [0; 16] { - // Proofs and commitments must fit in 48 bytes to be deserializable. - return Err(ProgramError::ProverInputError( - ProverInputError::KzgEvalFailure( - "Proof or commitment do not fit in 48 bytes.".to_string(), - ), - )); - } - - let comm = if let Ok(c) = bls381::g1_from_bytes(comm_bytes[16..64].try_into().unwrap()) { + let comm = if let Ok(c) = bls381::g1_from_bytes(comm_bytes) { c } else { return Err(ProgramError::ProverInputError( @@ -499,7 +512,7 @@ impl GenerationState { )); }; - let proof = if let Ok(p) = bls381::g1_from_bytes(proof_bytes[16..64].try_into().unwrap()) { + let proof = if let Ok(p) = bls381::g1_from_bytes(proof_bytes) { p } else { return Err(ProgramError::ProverInputError( diff --git a/evm_arithmetization/src/util.rs b/evm_arithmetization/src/util.rs index 15a93c0af..63adf7103 100644 --- a/evm_arithmetization/src/util.rs +++ b/evm_arithmetization/src/util.rs @@ -8,6 +8,7 @@ use plonky2::field::packed::PackedField; use plonky2::field::types::Field; use plonky2::hash::hash_types::RichField; use plonky2::iop::ext_target::ExtensionTarget; +use sha2::{Digest, Sha256}; use crate::witness::errors::ProgramError; @@ -255,3 +256,10 @@ pub(crate) fn get_h256(slice: &[F]) -> H256 { .collect_vec(), ) } + +/// Standard Sha2 implementation. +pub(crate) fn sha2(input: Vec) -> U256 { + let mut hasher = Sha256::new(); + hasher.update(input); + U256::from(&hasher.finalize()[..]) +} From 23ee01b8d84fbad0a8cd7f051199fe9b01f9a3a6 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Tue, 7 May 2024 06:25:08 +0900 Subject: [PATCH 21/40] Fix selfdestruct address listing (#225) --- .../src/cpu/kernel/asm/core/create_contract_account.asm | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/create_contract_account.asm b/evm_arithmetization/src/cpu/kernel/asm/core/create_contract_account.asm index 051540e17..a614f9fa8 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/create_contract_account.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/create_contract_account.asm @@ -4,6 +4,7 @@ %macro create_contract_account // stack: address DUP1 %insert_touched_addresses + DUP1 %append_created_contracts DUP1 %mpt_read_state_trie // stack: existing_account_ptr, address // If the account doesn't exist, there's no need to check its balance or nonce, @@ -28,13 +29,10 @@ %%add_account: // stack: existing_balance, address DUP2 PUSH 1 - // stack: is_contract, address, existing_balance, addr + // stack: is_contract, address, existing_balance, address %journal_add_account_created - // stack: existing_balance, addr - DUP2 - %append_created_contracts %%do_insert: - // stack: new_acct_value, address + // stack: new_acct_value=existing_balance, address // Write the new account's data to MPT data, and get a pointer to it. %get_trie_data_size // stack: account_ptr, new_acct_value, address From afb9633a4c4270f8d7b6538deb23d84d4d3682f6 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Thu, 16 May 2024 16:45:33 +0900 Subject: [PATCH 22/40] Fix withdrawals without txns and add test for empty block (#228) --- trace_decoder/src/decoding.rs | 2 +- trace_decoder/tests/b19840104_trace.json | 366 ++++++++++++++++++ ...9807080.rs => test_parsing_and_proving.rs} | 21 +- 3 files changed, 382 insertions(+), 7 deletions(-) create mode 100644 trace_decoder/tests/b19840104_trace.json rename trace_decoder/tests/{test_b19807080.rs => test_parsing_and_proving.rs} (82%) diff --git a/trace_decoder/src/decoding.rs b/trace_decoder/src/decoding.rs index 69b2192d7..fda6a9c76 100644 --- a/trace_decoder/src/decoding.rs +++ b/trace_decoder/src/decoding.rs @@ -646,7 +646,7 @@ impl ProcessedBlockTrace { let withdrawal_addrs = withdrawals_with_hashed_addrs_iter().map(|(_, h_addr, _)| h_addr); last_inputs.tries.state_trie = create_minimal_state_partial_trie( - &last_inputs.tries.state_trie, + &final_trie_state.state, withdrawal_addrs, iter::empty(), )?; diff --git a/trace_decoder/tests/b19840104_trace.json b/trace_decoder/tests/b19840104_trace.json new file mode 100644 index 000000000..d5d698878 --- /dev/null +++ b/trace_decoder/tests/b19840104_trace.json @@ -0,0 +1,366 @@ +{ + "block_trace": { + "trie_pre_images": { + "combined": { + "compact": "0x0103e13c370f7ce6f9de81dd4068775d7c39d1e19051063dea7b4f0dc570ab06868103940aa0f44732cfe96e03cc53d65cc93839531689de2370a6bc12eb515a75f6ea03cce25a8e8a0b65fddd641e8856e8baec7cd1a012b7470e92aea3d4f6fd02c1c80386d98f1b6d13f0446b302efc1197100dc2d2d10c9e7db46ec2a34d55b2f0bc9f0345a634828040159ea3390d453145c5e97f3406575308f3803fc63944db067cbf03cb91ecd916f16b6227a60215cab43c4b9a409b19cb9af242375517af5841ac5d0329a14ef57d4d2282d4f7479ea6d37f728b45af8ac42a621a953369de0b54535503c4c441ce0b6d427b5577cd15b9cd890ff95d1434d101165be1623b9cd1ef67b6035ffdb8ef07117acdf909af168d381bf8e503dba36547fe5895671e9d495add1c03324e1dcc50950c11240379740d5b383f879719bb1d477d04592d65471d973e8203f355ecc89a4b76dfdd7fa0f0318293854c502c03a74c0c4ce388fcd79835e0bd03bb455a53b6153bf3fb7e0fc6db20d7ef3661942e9e957af3b9120f91f755984103be0773cd56d7c9df1c8062a9c35b731b1359b4b9ef8c31a5c74c7286d96fcac103f7dde717fe02b995df87659a9cf25cc95f994360e2b8cba31db4183c14c9670203c669e29de38188ac1e9b8662b0d995e31e500883d08affadd4eaf71cb7ae43b603dbe9990a2e3a1b78a7e86924142e73d32e6a5fe8ff15f9d923df57f5f5667c02036a42738b24621de70cf0a5975e4e0de90c279602ed12d99c46e9951927348d1f03476ce5c4306a2f9875011e1b52bfad9af7f9eaa53a0c09725b641957f6e5abd1033a6eee3a707b54e089427a7df5f4d780cc9bf727b8527a2670b3e558fc580ebd037c900a244c50a273971a28d09b3e5448a2d3b92248efb447d5e914c83885d94c03cbb3c52e38138f069c39c31753b3a1e333713637c57dc2f38dade06d65833c4e032e9e786d65c8c00c3053a66aafa6d738b6029bd9b44ca911fadbb1b22bbccd3803f23a75947392a97153c9fabd3ebe5d109892d7ae8a4d3b64458d60fb5b0cdef0034c0924eddf164fd85e99748a48284ca73c4b2dc83edb9d3072551b3a6be0f94703bcf4e068ce6001b29bd880451c3388ef1c144cd79c727000046ef06b8b6beaa403b615536da0f1386f5dd58645a0955875bf7e4c00c8bf8397054268b30a5ca536038f67227b47f06ade1eb106d7b50bd823a327fc6d04556d011b98e4e695a6578e0328a17cd0c678af3871f0a5d3dfe41ff24967fbb7f11726dbf53707b5c27a5961036ce3286ec2d725cfeb234d4a29386ac1b057ea5109e381b59f5816430e82386c03a2d833f5221678f65bb919bab928e1ac2a21fb16c768c802385743c1f20f33bd0394ef837d0a9420521c9b61374d3433c534a22365b1ef1d34c305b1d4d508b17803245d8e881b569e0f2859d441687596d69a1f13172b721b798abdaa13cd61acf10331b17ef9b060f53595553fba5298848848045635c1ddd4867fb8834193531efb03b254caa5808fc83395b676f33b88eb741b88fbd627dcfbe735a4924fb3a7ef47032c53e4633f67dd8f1cd3b97857e2fa1888a08113bd4ab0ddd9f3f0667b75f31d03ebfe5a0154be1c4918681bb0b84185583e2381f1c034dbfeed740abe5a18a73e03e0414af533edb23b377e58da622436a7e1bc72da8005d38e7403e9c796fc8d7d0354fdd1dcf7c140089c99e10173a00bde1620974e76caa85b89c61386854d2e1e03a4fe5ae7bb248ad34f6d6c3cc546f3945bcabb9ea6832e5b2c126fb2948d37b0038436e7414d7bd51873cf6d66cd7100548bc241391940c4596cb8fd8318c9eb43033ef1123618d733594d9bde1b472f2695835b273426ef0e82d2901eced8db7402036499aa00f5cc368850094bfc120b9b888f16d1b2882fd29bae671a5c415cb83c03d27584f834991d48dd9d39eaf146fd21299f67af62a7f0f8c56f316b58c812f503c22ffb33a73ace056ec28dccf8f19920c8075a47151913e13bb76de334454350035bef61b2be3a91a5d66d92b259cb46bbe3a8a5866efac22f59f73f70174791b503c892127d3f6cceb7c41af2f17aa67450342f091879adb9acf3a7b5a8c1035ddd03556d3ec12e5343a2ef72db2e7ef381104279b2476ce4ae4a56b660b50347595203835ea527241d3ff19f15c1d172c75245fc94e1e2680fe5d2551b6225417ecdbb03fffa2540a1573b228956fa86248745aed31ae6e2c983714f32fd9dd7fc58c36a05581e0329328c87be9836183768908f30171daf16714c1f39a360b617bd23df400c0a4629546746ae0805581e03934cc196d443b1d2c5588f9fe55fa70f673923ac0eef128d9203835d500c0147c7a8357a0fa80005581e038bc60869ca403f3d41ddf8575cfa5be7785783dfb7dccc395b1ad999b00c01470d35cd2acec40005581e03d4abd7a5e810b20841e6aaf7fdba78949602e371cc5ff9aac8f74876200c054702c0246afc0c000458613373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff0155000358c5239b1dd310f0537a237bc27563369523c442d801ec8818db3671c74674540365826e2e7ab17befd18111567901f4d1c040051467d927e308ce4b248822a0cb03b1c225aa33368624bda2174d75e18cf62636a5b76c4c6e1b2e418d4f5b2a355f03f9f0c35958ba855fe3b67c2d3e281098a811faf015ff970c2ac1ed7519b7a9f90340d8abd4aea2178e472a6fb5d9a54ab7d9da7b4cec41de15657ae7d7ee47ee9203b4b2017ac478ec2fecab17810a12138dd142aed069ddb547bc6ca75f30d6edbe0306ea5daf16f46213f499aabb9937cce13f5fef58bb32f6e0cb3e9a06a0b51e5e035f11d8c8b2a04750588c9cb0a75e46b7e6e954dd62adfd624acf8da2643082d903a0eed321da78a0ebdd5fe9921d733518413a2cb187ca6c1105f7abd1cb2fbb1b0311b7c5e1e609f177f216cde5180864e5b646e33cccdacb581e3f588664f2aa29037d0990dcadece1c27990f6006dfb1b06731a2e38f30a794da8bad6323491555b03fe92a1e51e25e90d509a1d38bc6f2a9fe43e4f72047a2a24b870c8d30fc64b6703bcde60f65dd3d2ca3a2f2f4b0fea9276db48ca7062d6b0b6aaa3a0510d812fc003df6748c404401faeb51fffb775298fa91f0dd2b737b0f8595ddd8053103833c50367ba887e07b40051afda724bac2d9295be9135f1fabf12fcb95ef8762bbc5e54005820035b0fcf3bd800db1425c3b5cea82894f4bcd6c6e3f3681233ecaa3a21a22a3044663cc98f03a6b7d063b82dec0e83c94ca4a3eb6519dad637b47c2321d17ec61548a784c95703fbf5999fcabba0e2ed169e657630905ddaa06e1ff8ded70eec12494da396b04f039ebc1b67747b6a172ee99df6e8debb1b98d1bb2af2fd4d7d9233c6f86fb8bb9a03262f082f2f808c68b5102bd0ca743333881bcdf56c5b0caf594c71733ed0c34700582003cddb82bd7d6f65fe1dc37f3f335825dafeac204521b498c3035fca9d24671044663d7ae3034ccdf84f270e9fb07171b769d1737ca3cb47be3d33be2cd32aab896647a32da0031f8901f34af3b4a8bac1e2b5d52650022a64650558a0c04c5fb4599c094dd08f03f52e9a5bf4207d9cd90239f7199782d58a55fceb3895fa2cb4cc9542d2a5872303e3af39c59d84c6dd4bbb43417edf1e939070570d233bb5a1b7198c4d67b1567100581f02b50381a466ad9b3f5753ae9d99dc1961955a9450f39a54fe5ed20f23703944663cfcbf00581f02d7283d573d576507b2896b999a1774e22aadc14d202294c19a0f513a12fb44663ca90700581f022d34ba1eaa86355c866834b1f968ae22db024991e5f42a9a675196c9459d44663de78b02198410035714c53a00c63f7abf88010b7d8641a6f7c834c8c8b6ab07d1304165dd133e2603fb951dd6719d0846becbb4d55276d92a7a77f6687eba589ff57bdfdb799fe4ce0328f4625c2a4b7929cbf0ea4f0b0e3459896a55de82446c8997a5856856abffd90391340802d5194304e8a2f4323c84da7647cf985a3c6d2a1ef795dbd8a53817800219bfff03576e9c67f4913b6dec689d445a1d0cd79a90fb1c9a2f0c87a2ba84d25171980603d924cd2dd4d5362c29bcbb0fc4f61e17558e556aea838c8d09ab96901ba11fe3033517c66a20bd6abd4b1ee2e5cd45298563b00fd979ad371af28dfe95b868322a033e0ce59b58426d4eb6806b518e0a18ab4d6ebeda65951a616eb9c9c917b983520219ffff03387f24a9b1c6be19fad5f890ca5c502479f798fa7a51ebdf492ec7752bb784ba03244183a300b82435242d6e0b450a90dd10e49846e00e2cda1481f2cd6893f04b03356f09c737f039614e52e2e0c37fb7a70313ace29b2154d9114db788f0ae541a03ad4ee9196fcc075e9a4dc9fd2bb8c75338dab17003f968d73480b3db389bf9d7031d739c09b9667fbd3f6ab1ef093a08e2c5366e1d6acda970be8d1c3fddedc2a2031afcc8c217d91826795f263a1f09fe4f6130bbd58deca5fd7c33077375fa1cf5032a8efece59d3d07314f21a48b9d4e15cb7c42c0780b4da74b5713427a941530403f0804f578891545342627171f5a9f2262c76551c8dff1d87e1d4f600e0dba569034e67010819e15c986e4fc147d9d699936155d18e88644fabd15bd556521a9b7203b6fd210aee5e046b5053ad7132b8c705eaeeee963dd7c5efe7209d356667b6e803b5c9b2a4290b6130b89889f84edc7688e2d8893cf6671a82834411fe9bf6f27003ec93db40b8df894e80edcf0202cd39373099967eb1e75f285e6cc998e7b5b26803692dcb502f53043296639d68228f69c17e83663caf8ab3cae97b1674befdb8e103d5c209b220a62d28e6f24c89d7d6a37de20b3fe53afa4e39f71b5759f4d3e4c4032a92f5ace6d6fe31425c6282bbda8e84e2db0345cf5662433266e1bf343a12b403e12845abeacbb0beb485495bef7d65bf834364233fece312a49b576ff3c340bc03e6a66e06863ede15f9f2aa3ade52cec2ca242e3f806c40699a40d9284bd88b94037d6889baaf1cc4d81c4acd0d0e884c685ffb3a2d29cae5b0727b2ecd3e8d8dd2035982174780f06ddfdd84d88987dcbdc69ae1279de27976eb62d2e5abcfa1c18b03e13a3a9a9b2880112b3171f114eed30dcbc533ff7d56980c8898a82d1f4482c7030e65acda5fde29d1cc76bbd3ffc17d8acbbde66d3e68b6a5d9dabe5204525aa103c5a998297fe5c2766bf6dcaa34f57328a1961bbb6140e34f22155c4904bcdcb203f53af6a0b865c4f515acc6d3c93000e2cddc6f8e8d961c2672a7629d1907994303a7385e81588f6c99b53022f433eb8c00efc96fa67eb6331327cac4b1a07d7585037f98a226ecdb56c0d8651302d8b22d2acff8bc0eecd3e3213753028839c0d92903cf753d039db5e738d1ee967356555d047215a4bb82aab79373c359ff70a4478b0379a542cab81cfd9d454470df1e42547fa517c5d3120e5bd3730d98934b64e62603a05dfba906e040db50a0afa79a425201f74146354d88c2754c5821eb719b5190037b2a32ea2c5f26a8552a7872f3ce840aa447e7d9c0efb0b03c99d131161b810903f7b2d22dd98c0f407dc59ac30c13260cec6e6565f54b9d9b09c4bf1bff9829e50371a35ec266c88aed1b2897e3f36a14cbb329816629742399d38ccf05c541a55700582003c8baf22843559d0eb809346dcfbcc9cae4248ce96c61ee9416d0767869af1044663d262f0395451063870cbebd1a1c414c042a67a269e123a33eefbe12795ca4533cbee4210377696199292b63a284658963a209f97c039eabfad945fd77a3e3d3a2431baf2f03c62ede3a485a67f19ceee383b691bd1579d726b72f36bd25966b245d2658c99f0362f50e33577871564dd39157ff97bca77d58296e10eaf771487a741d7c032be10364f74730c91f576090ca0ba7b1e956e4003c70aa7dd5bfc978ce3110ff013e5b00581f0238787dd0e1f5d8a1615fa968dea23acfd3d692e6fc565b123f7caf145eff5820fd7d812e8f33936363ce5cc165cab273cc02af6aee8da3d42370f4d343c6f1fd00581f031906aedc23fbbf5f6acc171555cde2fc54aafcf7c2b796b6c9407434c47058201dd3ed11c47ea3f75e3c5ca68e5ab9e0bbb7ecda2318917c262c27a9af24af1900581f03c2e6f82dfc83b58e2df5c82f2a4b3a3d5a9f68fbfa24b96adf46c00deeb044663cb0330206021928000219ffff0219ffff03db912c435e75072736adad652ecfc4673017ec25347326549992227d294d856b0309e3878fa9a66b2f5cc86044daab843416ab5977bcf6dc06b760060f09ec7ca2037c1fe9f58ca2408965a47d402563a15f2fcba83a4a6eb0df1b030da50ba63a4b0219ffff05581e03a92c6bc4c13a5ec45527f0c18ea8932588728769ec7aecfe6d9f32e4200701186103efc4a95eba2b0e8ca1e7050522d539fcaee456a9baaed13b74846ce6e9f5284f05581e0387d430445038d44dfe5f67a6a29f0e61137f74004f19eb39df5f6507100c134622a41c687260035a6395100cee14b8619641123268781a56f8d9fad4ec4d364c15d99ab81fd8650219d595035d3b51473708235f94b7786ec3d9e1141cffde44d1b3c2f8f50735bc89406bdc0219ffff03bb6dce5781ee1b12b6667ebff1c6a7e00b27f644760beec12fb305c5eca6748703c2659bef399996280795da7d7c6cc8380980b8ed448477279fce86cb1439815903e45da9fe37e969afed21a06e65aab4b8219e0be940cf6c2d96a3312dde77c4ba03cbeb42d91012dfa7755211b9ebebc4e6481daa53edeaceb78a26478589a5171c0355e8cbfb54708ecacf62e4d8e82d3ac23e130e81a0a767b06f854fc5a76a803203d82d3c30bfa72b176d2285d259d03f43e6dccdc50cc03dff946f850414c15e6103b84ba827a5ef576ce6db5eeb236b0b05db5fa57d18448dfa5685297e76ae072103494925526b1d70068072e28e02df4d43941cfb627cfba3d4a148a8e857bee15e03cd19d837ba2dea15c076f66e441a92c62e7c353901568a45ba5f71e66e88d278038ba8c6e5027ea75be5cc7cb96300e7201167a07c4c58fb06c272593502d8b9dd0219ffff0301b8e895dbf6e8f0d41d8a44ae0ad8221319b0b3232425ee213ed801d9eb9ddb03c15636c31d015e3f4a306e5ca113823cf9bcf4422ae44ce6f9833e1a4c92a87b03e4b068e19c0ce1a9cde31af1072d04cdbe5f101a86ab598ee61a8ccb7a5160e3035131433214d21d38997d1bd4d2eaf4a69ee15f096d86344b920532b298824fc303152393ebcf209df305d93fcdb3ee0d6c9c28dd5fed398bdaf44f0e32a1253e5b03c28ccc74e893b02e05d6d5447b79d210a80b3799c658e3b2cae91e64f250d52d03b663e80603c7f9bb86ac127aa3726183321274f5675178b609500bd338fe6689034a4d70349a6c431a3edaefc60a1dfa6f6151ad5d23e0028b924ea72676af2798033122fd4a0193f4e18ca6f08a697c0af1c8141e2659da5c6bf0f87f5ed86ed8a70219ffff0372acd27b85056450bebd86a5a917527908636a4c781fa8535f6801d52643cbe60313561846f55baa646d462f32f65378782aeb0a36ec4396cb1dd95ff2e32a73cb0219ffff03ab23c03eb82757b5599c6b1b720f61171157020454f78fb6fba98f50b444103603d21ed651d18455a8a402320838db0c0ee34b4fd81fa20fdd9fe9bc10a3f6756f03f215f60497fcb591efea4098f8e6d1af4260aa34dd0143154ccca2ff7a41371103390f008988bcb2194db38af1ae0c5b450fc67aa63847c3333631236ff160ddca0392da5bcac8b88fa85dd08f6c0a2c54c8049826621e9de17655a5ac61c896422e03361866a4d5b1c4ae521a022b8d0bf3f956cce9f4558293865b4e2e09b679f5fc03c1204c89e187aa54d5bc785652301c858d3b8a3a271e3b29970f5e35b03f03b003e5d334dae613ef68f86ecc885179c64542cbe18c5b4aa3bd929eb6d2bf5d9e430219ffff03001cc050182fb06ae5d5cb21738d6a93d6acec33ffbada6bb24116265a0d418203eec4298676ca64e441552ff83659d2575ad601c6fc53e98344ea7882a0fe1c56039d6b1ff6c754633d01894c26d7ec56ad635490dfb291b08544cfd1b5dcb0cec3039a6d2abc2c326ca7b11f03d687495f40353542ae9b674bb6c59bcc6956657d8b03fb6d16aa3f68791b386410f1c544051aef6b48a87517f2c2f60be6c45290bc0b0364b38a393150f0aeb5df8ae532cc8017202732b532046e192a385a1df530a1db03cbb43ac36287287ae24670a0c4214587495798add9fbd6124993f2c6629d4236035826a6a848d733844dd5987c128e28517f21bc7c06fd3742853a2d94c29cca9203f49b340fc1b72e277463376094dbbb5ab76de329e2984dffaa0324bff1cf9c6b03c61f7518e360642bc12298499dea86e091bad02e3082654be4ab16ade8be481303f3cc663d1119c0e755090ff50e657071c1b7aaf76f5049f8f68efd2fb3e2a76c034f1fc53876be73fe021b3da55b1b841bb1e1d6ca99a8687da7557638ec4021cc03152f541cf3ee990146f63fea1d9b53abaf06a58b00a2ab68fce1c0f5b0c489120387f624a5927e67b0a59632a5850f1c011737bfa8f3a51c937ea14a3988e066b20323c3238b822b325fe11ec12c14e024ddf5710d8802e5c85d1ce7c47fb02a52b303fc2eab2d511cbc973f0716322261116d8b81f804ad73d6d380caf1c42989ecea03067c67cd91a2747c892fd1605d2a38c8b7409376e9684323b7694d359eabf9ff03097bfa2378565be66df31046bbd59bd5d4c2c14299553cc58d7ffb2ecf27699603ddbec2457d43fcf01730e63a612c370b56bb911de1147bd597396fc1a116484e0390b76a69782f11b0cbea81d1398b41e2927e5edb66688c6c728806bce71839d103a1d7ebab554c080cb2fb2cb17667e3058fb3537ed64855620f7305c9c45973e5039c2e37751faff09ddd740b02db8f0ee80d92547b41e50ef9a3d2113bdd2f18be0330ae1b86a3326ef4be28d5c2470d8808df51f32e5399ba760da826f46905959f0362cad7ebe4f2c9f7e283ebe4f1ca1dc9e8e7c8bedc52ae4bde537a19beea1f1d03d51b9c0f3ccf30ef1a3754573521f05d4981b3c7a0eb635ddc81684ab75fdc9b03766ce30501987085efd915e492dc68eb6fb728a13070605c762c2dd8fc65c9e503d2be7794bc7b7ac3eff3e2bf8fca6721b9d7304e19263153d566066109a5fab505581e0329e4cb84257f3a85c60d49d690d86c942f7d8521e57336a24bd99719500846f84ac406100005581d0231e95b8419eb826136a480dd3e995ed8b16c389a83abf73d262522a80c18434a049d41dbc0f641387da905581d021defeb7d022491798adb92c6a0e0bbd3e028c60b710678fcfc65bdc804030219804003f0a45890bea7520a3246b1d46241f5163e20df1f78ed7ae79c9d2c9c8491ce5003c33fc0d0e68fbd9b43795a1d49f372864646e80a64b2ae991aafe78cf1a6b8bc05581e031342fc8a455f0bb4cf1cf5c8d538e0fc58be64fa5515ece1f52ab69a9007011bffffffffffffffff05581e0377c6fb8536ebf253b0a73a57f2d3dd389ca68dd09a55ca7c9402700640040105581e038548cf6f40bc75e6b2075230a9b50dab25ae567bbff331319892bb76200c0847207fdc31f0253705581e03e43b5623140b81b5788ab27a9b607435c29e998d62d7041c02aa5497b00c01461e55d5526400035cc05631b99585c07c74357703c6536ee955b7fc282ec27b997ee3cfc7db376305581e033e96d0303c74a5f280436b54c6cb65c305ca1432bce3657a886fa885600c0147c9ac605151e00005581e037248f8cba499143afad86bc4846e956fc50ef032d10256e551b3342c900c0346a2c1a8265fb805581e03448abfbdc1f8a978d1266c4e652cfe22c375ac38e7fec717c83e4464700c03472cde779ad41d6c036657955b7ec60ccd679d744e3007ebed64a1c193b1f5e6764e5fd5098f5621a4039be7f2f51b6926d91717387c7d931554000322f401f33bae238c59d16d1bc25005581e0304099b4cfbfbef7c9241d66211f3db31de111d250cb83214371a03052007011bffffffffffffffff02197f550306084be24dad54cc280b3bf8ec4aaabe90ef39d4a1d3de01361e7978f2333e6203d070f1709a1cee0cb0f2dc904d4a97ba0bb0899e7c44573daf0c4481dd0acdef037256332e427d6fbea337f43cf2cdfff5aff4243af95cea9f189e5215b4da468003a8eba973b3479f7730e2da45137f7025dabe1729a91367de311f71e2a554b9d2032388595cefb05ecc623b4644216fa13da46bf66e0af30c3b6e61e980e47f2f4103a78c06eacefde35ab475592d485471fd7c9e2ed061a66683db9e0f938c6eeea103943cf313af4d005afba941eaf012b10bfeba6c27bab6a5b500e7a463beb4ce8603cba2737f18889d6a5f490126166eccf6eeedf56294b6983e211caddd1e958bde03e70c0538d263e7153e98080dd8651340c6a16c61ccac8e72faf62b3ce0035e6c032c7f02e20d33992e4a756861c214ea04fd1fa5b7d7b77054043d909012ce7ca6033ddebcaaabe1a9f3fc9f1433bd43c275272753ecffc73f4dc4d147106d060bac03501228901ee23e51b629d809fe86ca91b857ebc264348fa6711d52ec799e49790380856eaf2c16d41b7c5851c8b8be074e2796ef9fc5a51af35abf82e7064d8ce70219ffff0373457bb1c2d5a37570f05f98d0da6ab88337ebb1dc5e69d8905424c0d25f3ff5035ceaa6bbae7d847a6b71ed0fd91751f23a221217cae0cca560eca0ed46641fe203baefa85ed25f86ec38ffeb1dce60e649268ec9253f9ea2e937177cac4a32c02103deb8799b4ebd27039932274a35b805633be4c4f9e316424c0da29cfeee1413500319e279bdc8e991271380be21083446fef85aaae12b0ee658bd0a4472344497c80379866774e2f225b8895e3de8a034c7cda76505fe5a0bbf4cee28bcf03a50889103f207db476fd76b340723f6806bf05778064c1b8c03ea762913042a6deaa83bf00333ab1293f95b00e0f5ae9eb29d2b2914f25a9e927ff18e077fabb6ed05166bc00219ffff030f435593b37c6e7e90456965f90baccca95ec22f18726dba77f969586563319a0374f3bf4b37dfba97d3bb836784a582d5387f1825e37bae15cef044e4a400ee10034224592b2898cf17dc3412815aa5d8306aa68fd1897203f32a00fde6614829db03b141e7b53041cedc0726c3eb62ccf7ef8594064490e280a085412683871b3de0039ad73dd089132dd4797a0dbd3bd6fe1b098c46f06c517ef1e3797ef8c51dd21003c0ccc062063d494691863a58cffb0498c3ecf32b97fe6bafbfa4f91a3aadbd3403ca29993c9ab4769f55b1668e25e86366c58315e90c52fbfbbdaeb966f0d8213a035444b46af6e3c94f584c4dc0c7cee1e3e9750071fa4f648b452633be27cee3d103dd0ef771e03aa7643e5ebc78ad7ba2ba6e3029f1f54503f575b2c1db3a50484e032fb5287c04e5b0440901877a095a4a433cb704c875ab4faf5d92b890f83a368d0342182d0d0b8b8f6888219bb5441e71ed5247061f247b3e5beb05256fd437446703057fea1a0b7e2ad1b0dac40108d98219a77820023ae54f6aa1ccd599dbbfaf90030bcd00ac7b07c9a993662985753fd20a5ad28aac1cad7cafef8550f76d905bf003fdc6ae80d23be595b425ec6bf90d701c20aa537c992ae517519c1c9132cd35b00219ffff0300b3538138548d8e7cdfe3ec6a0649346cab821a2c12759a2d5c735ca1e2476b0397f9be41801a5e5cfb6d5aa5fdffc062a433b8403c000683a3eff412dceb69d303ae5583703a0d55910f9e00556ed1e99f16b07e48d8063abec2fc81036899731f03468233cd70fdd2f93ca606aacf821aa5b529aa0b97ad5fe3e01834e23fd2f26f0380dbd6361c810ebf3dd1e1d37a2708bacdbfa655f6f65184e4c77d428d18dc65030fa56fdff3c568c91864f230f49dcd3071208ac2c6f2e9f436777e5f8a3600060394e5b49d3a8c2f88390491996babb83300f840ef28c6dca64d89405e3a39151d03a8be77041b3cc39fd155bb8c1634cbc4f642485cc1bf74edd32f8097e2b9d1d00219ffff038ee69e0c947fa4b09f8d38dde182267b4c4a16ea0242c041369619c16b2f69500392065172c22a81711d3803081e2c66a408e0727400ad2d83ef13c570633d5fea03ec289b0aa52aff14cdb4d22bb62e330c7e4ca2aa611ebae50902c190e9d0c99703b5288a3d3514fb46b592e754af10a92ec8a7f724ce0c27d8406956ac2b8f5b8b032f969988b3bb0cc7bbc443189f47d5900025e8f77ac5500ca95baf449e774c7103c4154482e9e26c9bc32112b211d4717775216f694bfddfc8b269ab56a46205d80219ffff03b86aaf4643a9522fbbae67b172edc7c77393325dc9c31c9a7abd8d3994c847c20389ee3ccc7222da174ba639ecac039602c3e717952af7296b84ed11b7e6f2309e037361d147efae615395c158f88bca05f3945005fc43a8196e1bbc97e710e13cd10342525c1620a81a4b104fc85e3694cb32589489ce2ee5b01cd9e6ab14a5cebace0343e3b06cddcce5e0f06baed99ecd0a20775a5ff0e74cc5a76fc9c61010739764038b6c262adfe371830f62289777185cf8f52217c9f95282c67923a377eaaf86ed035dda29994853c06889e9e46917a2a0f79400940167b9aef146ab347b55d664e703559ca936f787bfb77440659de1c59ad30eeb28eebc23817b843ae74f761b283e036bb1e58c1e30f46e3e5a17c627b36840ce672a1b4576340c39a5ab5614686d62036d4da0172d96c64cf89c5e12666fff5bae46b070b11ff34d32f8cd1e16c31de903b51b58d1619a46dcfc5d4137a3842bbb299e2fc846ce6f87bbb6b89c2b0b9bdf03d913f2df7f405954f7f3ddec0f6b6fb418ebc3eb0748d9195474e98518ae658a034f042a8a0a48b51cab19b6ff5b512e2e54ff0bdfb317f28c999791cb204411d203e5d907601a73120bae4431e4d05d07deb1eb429c219bd2219dcb26e71fd9affe03cb44b7d70efc4351355d73d14d0022521f1b259212653ad83e8baa6cec3c23d3031dc993b12dc23a0d67244bf463405d99e8000baa1fbd040708ceeebc4fdbad8203d0d7e8ea93789f0857b17f2698c99009e8d53b35eaa1e292ffc9714d45adc00d0395d70f0441aafcca4c87ba0a8e565313ea2d2b242fb3cad8d04d6fb8483fadbf0355534b75d3c88e148d52a6870f57d19ba0ac801caa76e08476b90b48d7245a4003baa59f2afa6444c81244b86b7c9dd17b6edca4ac623a93762aa2db74658760e6035b375faf8e6b683046c729754d73b5625199502802051d1b437d400cdcb0cf3603600009c54247532fa59301cf9015172f85b4dc83cc238f70df51f73262af9457039fa254060036f7f90b710bc3b17f2d866d5db4c06b175506c911246f6d0b50f5036d5b2af4df1e2f292bf9b5cb4e13e1ceb528bcd8ac00fdb041d777a456ae4d1b03e7f4e78e600c3126f4691665f1a867963f504bf7e5f272f7e269369d693885370390d6c43e3dede80b4c6759b42c5d330666f3fd1146f164889637c446b1673469038dd25b7adccd1a9cc378ec8a4c419b593e30f6c853535e3f3c3a7675d9f5948a03b6c688039c4174b06ad78c703fba69d3145fde491a9ea057e0ca4689ce6f083b03b5591bb5ba5bd4c45ac3827151cc5af7614e1bfed7809196971af8362c19490403c07c7fc3f92e6ebff32d0fce3961aefd7ee2e0c5090158ee8578d094fdc5b2e203e91b66637cde3244ee7a4f3c0c6f2d41e5d3d51f2fb161fde3b3e46a7be5d2e403ca6f9f545f835eb201a5cb3a64e0c6a3d382ec7bf5bd18d1ee4128de0d5c8fb303d2ff40753b8f0eec2e458c6c092129ecdf4da77d7b95645ca1ef6a47af7135db0342225c8b5558e35ea88dec62a89b5dddca727ca654cd826c19ea4bacc6fbeb6703b0de12445e9e9b542fa6214e604fc40e069fb71044af40d50d4b741d3a3791eb037d577156ee5867430b360d6a12c062c7557e0fe2cfdc2c23c24675676109820603e4abd4b3b402bf6ca99131af823b1f1e3dc564cd30eb6b83aeb4e6295678639c033c1f38bb537c6eb904d7f5068a749092053dfb9fd8445dd54829d6f8c96024650380fecf187fecb501dd15f7452fb9a5b4030af29bbb400caab89d9fd1f6b32d0603bdedc66f85c076c34ddaa82d7f153bf1940a7a05e35f70ee5078634ff0d9529f038c72a5acd831a0cdfd5fa323c3d53bd217dd034aa9adea20652ed7fd52458f230337e3badcb7453ce9eda24b15ef374242c727263e5f5d002190cd5723f3fa6c180380ffb85a7a5817f873341fb95fadb78e688522800706a26f5721c9cc29cd9a2005581e03eb820fd1bffc460d328d2689d7ffe13c4443877bebf25bc3502cd345900c02472427ac4344f80003a31f886c62f676187a889179625c86a313cef7b2e41693325ff5e023fba049f203508d78823e3c33ea99a6bf0627dce396125c96e0f94f2752a9ff05880c38b1dc03e886b0f9abe462c549e7b057256e86d99e81713aa80dcddbafc8f00a0c3fb71605581e0371534472d82ce1ddcefa8a9a081057fa05952073b437c3327c830078f00402031f8b67be329f6419c9282095843235301b6b3475e42bc9e3262b646aba8072060605581e03f53b77d7a78435e2976275d2001631a331bf18d7cd453410c7e13373f007011bffffffffffffffff031d93f60f105899172f7255c030301c3af4564edd4a48577dbdc448aec7ddb0ac0605581e03ae96e94082f9c665a9a9b7f6b303fa097b51f1de5b42723c5fb44dad7007011bffffffffffffffff05581e0339df67ea70629138640dd9da3fc27fd679261a5ed2042474aa5feaa5400c084712cd0bc426c00005581e03cbf8b6bbea8c53f00e0fb58e1ca24db29869fdfa1a0b1ca084233002100c02471b5fd6500abfbf021973b80219ffff03e7428ac36fde822f7b83a928647fe466714ccf84913a6c5aaab7ac82d2b59c7b03d5f0df5e6a2783e918a037a9ee7fa2685115ba141d07037d7b5d46e2054dd5e7039f79a9182ba3b1d9aae33023b9070a638797d05e7f4a44729aa17849d5223a24035fdcd59a931ec1e91064d844c649073f657e4203d36eacb2351521e9e4954ac603af08685cc91bca4266afab1357e6e9f2f219f11bdd5e1261f3d98327009541ab03f8593ca6ef3232bf440abe37ff28edeba31191f213e5d4b2c3b5e1439decbc4b037730aa436fa2aacac76aec54eb6182c0ca79133e33d8131d4554eefe4d75ef1403916342879007e01d97b956f624ce44c84248f89bab906ad9c5b606db31c8588a0372587fa7fb363fd94c739e3143d15247587726f61c87b0d21223c25021bba3ed039481046e71013bc6b2a66cd833a256a6b6463c9fe29691a062792221433402d80356ec3acc0a875b7d4d1c3d8fad6fd9368ea37b473ec6f1e19b78bfe9688e599f0362eca503aebc0ffd7ebf4011dd3b5a9c0d801d14f9ffccdc1d19bfcfca7200c8039a28638fa0c05db0d14a30bc9ad3c7098402739c7e17b7da6b480c83e70b3734030aaa978382ea98c6ad771b344bb123f56ee7d5411df29e368eb8c0b4f38423d103059c72e2ba055b1657fd968572d1f163a911c55b7e10b7098e5394e265cbd79a0219ffff03edc2850f10888801ac80c00884047f5f1f9544044abfa9ab59823ad741a2400403f51b244fbd3f59b9a76bb6bd4e26ce99a5fb4a0356949d052bf8e1a61212e8c8035085eab9c1ea2fd2a560bb213b6d65267407f6db09f61e2dbaf4e4a7008279ee03540d382e5593c6ae6206d3279c3eadb38dca924833afdc4ca438a9937b4c27520337f218ee8dd919d2e018d8c42eb15735b7d2bbe0a74839a890c46788319fbcfd033ac886856797396af4c01108aa53ea9372d271d2b9acbc1330f74a8677e4c2030219ffff032299a13f697369a2b8c8cd261698d2c8787129b582643aa4a927107a7660802803bc910df7c9217db39175a9572de2d6e7b40223794d53ba7c243144bc16d2847a034c997b100bd7d9daeb847ef6a709d188e83d39df005cb3680a1b86ff656d718e035f53bbfea1cbd32e1cb555de9e02c8670a100a58dc33bd879f6ae31c83623c530379a28106c797704a0effe2026865167c4da61f6f36350cecacdbb4a552d223e203e92c82c1edb4bf407b8d4c88e76983f82c06025827a8448d940953d3e344ab9803415a787536042dcce97a33582d622b3c234fc3830a69c4c4a69f6cb5b747ffe303204e8dca3471264a5d4a52f7fc903b258b615b152237ea048a368e60801c8ee803379d91c811e24844e170e2a6b7eeb60f0bc974dd7770e1658ec9093fb561912a03478c6781a8b905bd91ba3277ad73fd8f441063ad8b4ceb471630d8f3ed66721303031a7750b7920766c5a83f2f2536cebac67ed4ac864e67e19b983b125711b4530219ffff037bf29d067ad70cfe53cfaca684a37229afe02b269a966eed4ed1468e28150b7403493e70d29d51289a7d856c5f354a06e786c281d154b3400e808102badc2388850302ff93a3d24cbb140307b0eef2cd8f54c657b10eafb610840855e336ff7d506603f4fe654f6a1f9d54fa6ded013deceb6bd263f60aa743472d43d0847671421b6603b79fdfee4d00a18b9b2bf952e6e3bf6e8c58143ff6bd625b85910f82c989e9a0037d6bab9c0422fcb453d57c21d8d125f8111f44e0e77e5b85292d811e9b1b49bd0308f33ccd920808d75225fdcc533b87d62202c3c57695853bb328da416ccef01303564a099ace8ddd129f8fa6ffb1424cf467b5c8d3e42ff0a86006db8c76ebf2fc0219ffff039f6c2732d7b95617cb8197e7e4002cc002c64b144b5845e9daecc07fd3339b5003bd2bcf581ad903637e4b18193b7f9be573ac1cc2937acab93bf51d6e988a42e50386494bbfb0aa70413b277552286c6f29bdefcd7272de4e0157a9b0941dfae682035b5b82d8a2aab2799f37814d9c86295de5931fcfcfdd704f8aec87c1a138b7950306f1364261d9ebf275893b908531ea5b3a477fab09954a136e867b35f1d2f0d303a488dc121ff72c6a93a87347962f56baa5d7ca73fe9d843b61bea8ad6abac63a033743dbd1d0c596f2a960a19a9828f1804b0f7b7bb2ea5e0cde398adb8ea475a603338a53de165699f747dbca896213f929be1829790e8375571f32bea3b9dcf80b037a23d8955e02626c2d9fd140ff89ecb66065fa37307d13a2e091a88d50be0da503fce5604a841794dd825a8230ac71ac0b39e20d50e06a76c5e01054fe236e7b9c039c401fe781b9c7c3a3c5404c3c4534e7efbf6814bf59ffbade4f69191cb801da033ddbe85c6065e70c8607c2b56a55c611d921fc7254fa6c20e3b0cd1d34e3e6f003cd236c04aae3f2c1d68481eb9bc358740d4bafb2ae08006ccfcd011d6afed38f031bbb02dd05d10628864a88112243e6894c305e535f73e727a4d52f403e143ca103819e66518a3856d1d846aa1d5dc35dc477febcf8bc0907ceecbf8cf79aa27fae03d2cf80a073783fae139933f1118b3464f00a14500d42dde47448e74100c6e77203de3c2a255b9b8bf74ee7b19feea80661889633fd0276b9628aec54d3d205b5ea03a7e6f134ee27e789afd55d643954b0eb2eebe8c7a371156db10b438eea837175039491a0c87ccc44b8836fab4b9759c5ccbf54c64f9a79aaa5b17d8c998631aa59036c9a03b3ecf60fef21257cdf8c1e29218b5803c7af18ed5144c1d117df3472f203933976cd2b0d1b8aa5f90a0f05897ea5cfef9719e26b44c045a45ba9c31f008a031d040db431c1fb7f7f1d03948d9460864f252e74802469844850a06bcbb9427e034cca4db733293a24d9ac26f272ba20d508028a6bd06f11de91ef40863e7e69b70303f8de524306c156a570cc25d8338c395c1d3f250c6f99895dcc60cdd2f682e3035cadbcd3d4e5e0653750a7b95e0b23a8d99528089bad926e1eefd78e42f6f5f3035e38338aecc7ac3cde429b7e3ca376145c5f47b227675c83aeddfa37730e7bbe0396af2dd1fcea069b5d7ec3c83208498da143d91ddca8b76a026484ed0a6c675f03a00b51ba6358fe3421a49355bce187b96bc2ee92bb024717c9308964ba93b1d60353f300e70b2a63b7c0d61d497bde74e0fd2dd87ba270bb896db24248e9f18752032e32d8311e84f7669859ab7142d248057171786d538b72b3b224f99390f5487303d3586623ba49d4a46a0d22a44c5d21975812461e0b98198bf602f91fc600bd1703ffe1b3a1a75a68ea426847a428f6abc359c7fc3f2254410c0b18bfdfdb2df636031b290c8af2c6b40f4413944c55645e780c6bd37db2397a369694b03519eb1b5f0374470a5751c368eff94a85f83d58ca0deb060f16bbcc115cd0f3bc5ca1c1c79103d0117d2b9071d72802a9a798fc10d91517b8e153afd593a7766ee3c6fe5a50aa038a461051b15d2b75df6c9895928735cc2614bad3c8fa997cea767b7f245491da031147cc7e1da5fc3970ff386d6e178e39e2e3f256ed9c5234312439ccc09bb7770381a1aa551415e356b298808eff24d7cc13f0e4a4e042531b704d0978dd348d9e033d8d47d0b3ab8f53caa5462ec26e18ffd435793ce80d454d3a0de3a66b8f4dd3037cc67dd0f9e0b3c1dec99b0a939a542f806924dd609dc79b15e879e374c2ddd603db1edd88da31b9173b3a01a4bcb70736f1fb7d3d7c4ff994859c9853af8d13ad03368a3deceddbf642196ed53eccf27173a264a7676dd248d202620423db656dba03dcaddf110217ee48bfb8eeff146af24219a51ac93ca67f522482e2a3564b460103304bf37f15689fcfc3757c0cb8a9c73e888edf17c9ae9b058175ae200c1a49ae0351b04acdc59d6d2676cb376513e6ef4e0e457fb7667841912288d0e20a2a80b303cf603aa4de6a946d150a191044c72b182176fb4021c6b2d18a383855a8f0234e031b5526d099906496a3a33d60306caefad770cd03a0881fe26d7f1a0de66b43ac035e59a8277f34b835258b96b4508bec0ef3922515e4142f0490770b3b2175569f03293dce157508c58129da9fdebc65200c1c1d983a8107e0f00d93b0cfe25af3e005581e03e61bc331fa2bcfdfbcbaaffe43953dd29f5dae150f901483658d263be00c044707b05cbe758c7d05581e03e98f02ec287a7bc81664c3bd3547c9c43fc30dd780deb035cc572ed0200c044683c5a2e4fd4005581e0301b4c5ac518015a0bce436357c6ffae80bb8291afc971ebe35544f24400c024501aa2450d005581e03fe83da1ccaef9d9bd78ce53543b6368d6df3947d410bf2d34eb594c300040205581e03ec48afe9bed2ccb5532d8a32fc0cbb2e1ba61c7556de5f51e397c4be200c01463d1ec5ffb1f0032df39c0062882ba634fb24c89e7f9871ecef10a6e6a6e79a336f6ed463d5c94203576d247b864a9c9c193245be5262a86aceeb51240a250d7b4354c293d83a678d05581e0397fc5d38badeb2417fc4af69d36e4965c61f33bb84f646b7d58a9da68007011bffffffffffffffff033fb0fa6f5661fde6378c4db73cf7f1243b922e4b55a3435735c010e477f1c24405581e037a436184a5fc4ce62ad4b2daaf8a45e35f2947e33aeb11416da640405004060219e48e03026455994a7764223a128fa757f29d61c41ac8e28bbab1f879c66e232fddb035036c4372246d91297c5eca3d8d1d450153f9522df23de517957182e29d8416b60c039c41360797c1aa7097a0a9af04c1f817bb5c454d0f772e8ebe2fd352ca7e0d8003ba5c25d3ffc47ad414f2443245ed620a381ce9d9f0d1ee70505a1db59120de1403c0b160d239ce0c71812917d0798c6964edb234491c2f32eb7574c8d7a16270d1034424e96e0a1731300a2559039dea0424a1e764dd8d7d15d03aeaad0ad4f3c4510219ffff036d14a8da010d275acfb59254cdcb9eae8cf5fe7b6ab4a71a2a36194c74b9a3bc03731ac71e9d419e6d1673e09527d61c4f7b9d634dec4cf33946a8ea03f67fa57e03a916837a533d8f341b1bb7fb0e55bcc6b3331f628dde4d08d5ac4d72b8da516703c20aaaece2b73a3a9adc8e7a8806aca1e659dcab0526d1437690766ff0b4452d03d21f43153c769cbbc6c78b5dcf8b9a5d5f139ab0d0f24fd215618da92a34226a03d152c22b71f6c3039c0923a13d58ba41db780be6dbee23526315a0a7cf0431f103c90bb142fa9e97c9d16aab891708825684b56324f90029d149c20ad2d236a30703401ae904b5b28a574e99197b56de5407bb0eef555f36f65acfe9f1819fa6a2dd03966249ae6fb9ba9de1c890a1de3ecf7da07fa01d7602346d1d2a3b0f829cbb750379694c8599c0f3eb1711dfed98e2e4ff393e83f1c8132f830c1298d0a76c182603355149d946dfa2ad5f816899256dbb60ca00eecff995689c2bbd7370215729a80219ffff03fa4bc73fd4b3e5ef71f2079c8c7d0367d6c5e067eaed4b34656b1926863401bc03aef7db107436b3c50134ad9bdba4ab448c0b3cf02a7e2b8c0c88fbdb4b0c2cb50219ffff035193acd8f798b1956536dcf7fe595d15a5dd7454a103659708015d2297bdbcef03c5619d369433bf9fcfb86dbc288cde781cab61a13890d23d8e35269f21df280603173e6adb834c681b932bab9925c8017b62ee314a1a96d5785219e76ed6e6fff903e34b9c0599f8c2ef3b96a881ffdcaec6e123973792fd3b5afa5ef14a69a289880219ffff036d601f7967997e14f0b8ddec3cd67d8c4758d5849ca7356bce8860758ee9c848036b190a0ac12310f42571c2af0c442bac657e635a7e06b05aa0139febf8aef0a003c8825581763941270cb3e17f04c73fd4484788da4848a24a6b4ac5c87f1e31840219ffff0219ffff" + } + }, + "txn_info": [] + }, + "other_data": { + "b_data": { + "b_meta": { + "block_beneficiary": "0x0000000000000000000000000000000000000000", + "block_timestamp": "0x663e28fb", + "block_number": "0x12ebc68", + "block_difficulty": "0x0", + "block_random": "0x2e5e4c5b459345002e2597c5b8b556f0fb51fd9c6c45f7892eb98cb4de973795", + "block_gaslimit": "0x1c9c380", + "block_chain_id": "0x1", + "block_base_fee": "0x1aafac7f2", + "block_gas_used": "0x0", + "block_blob_gas_used": "0x0", + "block_excess_blob_gas": "0x60000", + "parent_beacon_block_root": "0x5ab8b5a8f53fb6e010ee24d46334a9ce1051a5ba1691b6383ce8b2a03c10f158", + "block_bloom": [ + "0x0", + "0x0", + "0x0", + "0x0", + "0x0", + "0x0", + "0x0", + "0x0" + ] + }, + "b_hashes": { + "prev_hashes": [ + "0x55c186b1bbde8e25b506461db3948d0ee3420042b5fe5a7410af110c1de0554c", + "0xaeed1603c512f372a2edadeb3d4cbd206835a22b25cd9d39c3e144804b26c16f", + "0x8dca61b2f49865cc3effd9e925230c787baf87e30919bd49b738ea4f9dd036b2", + "0x784d858125b4d78a6e0dba2b7ff74bbdc3d6cdc5ef978d08ab3c7ce504229394", + "0x5bb4ba2e0ac463c24628bdd92b74e7a47e0502c8c96089de64f98fd35521dfcd", + "0xcadf72a2755406bb8059cd8dba4c72f97994e51e5ea12b8133bf7f525a428169", + "0xec80579f0f3ffb6658e493e6f1483bb0806356edc411dcaaec825724a3e85e55", + "0xcda45eab952474a2adda67ba2b623716d391bae8d5b2fd81941dd84eeafe83bf", + "0x43155a9903b32ed536bff6788430cd522a275e94c405f619da62c9f41b018a56", + "0x1dda2c3bd2b5216008e68146df9d8cc1caece12dcb2ff13f2d66c16ec1f56aee", + "0x5bcd9b6bfa39e238cef925d01df0d26cd670e0ba714825862f9cb850284774d4", + "0x3b3c0b38b57668e82873490e17ca1ef17b6dad0601c34f2972d7fc8b8d79d22e", + "0x61b483cd38bcf09fd561812bba39b3e0575e93cba262fb454dee0a36e26ab024", + "0x5ff6964818c36655c7482419013438b32b7bf43dc1d0bd80afefc563849d4cd6", + "0x9d06161a501ffd95aa500f5df003b02fdab30b429c3c6de48b7cd32a367fc9c3", + "0x5e1b8c54af7fdaee9c27c67ed4990b609922c54e5fdfadfe928183304ff8cd45", + "0xafd7f313fb970bde5669de90b24b56ff010a3485ffa4229746d6d8000f892f5c", + "0xd10b7f3f17f6a0b00f9947380b0f509e595d3c52059e7bd52a2294fcd5003d4e", + "0x708d167c7dc11778cb2016d12caadb0c38c4c9f173f499c71f4030b87a7b7b28", + "0x29ba6bbb96afc0058f9ab3fe3f22a70a94a91adba5a671798fac8569abf7ba17", + "0x7ed2fa00a64618717df2375d8eeca6eb00b26a69772891d29d53bcc4eb97d7c0", + "0xa2031bdafa03772d8e8504c380ca08269e89761519083027c64bd27108650bc3", + "0x4cbb35640dae5ac34a0aaf0e22f68266ae98b4edeb0906781e24cfc0d2c1228e", + "0x58f159fbfea30bd5848f382e8cefe0df41c355f2000f0f67ed1c38ed41844b24", + "0xe09a822d4bf4cfb20b9fc45e996b452668920a22f397cd02d5ee1f13cae4d641", + "0x9e6ab278fcace59150124fe6472e7bd931674e6370f240abcb38fde46d01cc22", + "0x1b69a6375b4c78ffa61e023a2c3ffb8f21e59f567ab40c647e5248d2763ddcae", + "0x5d2d637fec24b5fa7b72709c699b0605f20c4f77d02bf7e7450a2f845054e472", + "0xa1086afa9e6aff4921a7f3744b7eb28c3441f4e4e359b3a25e7007c65c640531", + "0x8f2cb0870c743235582ca9cae7a5948af500bc1b374f93df3a574e32fbc38f3f", + "0x1a7094e313b8c766f54be0f5da29c29e43b05d6ae42ba517ac1b487bc5c96b29", + "0xcc819e929bede7df0a415f55c1ab062970f815326af426de9f5fbfb992e7e437", + "0x2f051da3ca994b22fb131a27d68bcb2c9726154744a10d46298f40140f6a56fe", + "0x6c853ce7a1da5258cd093e6cd033391a68d0a459038ce37a67d9d17cb88036c1", + "0xc8b0a654e358d7ea93973e2de403d258fdf63da30d6d4034c6827d63fb0edc94", + "0x079cd237218aeaf9a0806360b659ca28762c81f635a3cc501004bb285da58d45", + "0xce1a58a6ed09e666cb0079d48bed5325d161b1c9799f1376a6e4f5822d56800e", + "0xc2136e2aaabd540ca25376c74fb0f675f74eaf47dfe8338a55bfe7ca09ebf0c5", + "0x455e0f3f874619fce323640983b9cc6ab7e0939fa3554c1d6e6f1dc2850536a4", + "0x0cb731db488d51696f60b38c15e0fbbf2ee9ffcc070601192a9e02f6adbb19d9", + "0xe190ec154493546f06322e499ac6bd923438403c676674dd40e0a02e1e45dce5", + "0x1c7c738bf9d0bbf2210090da40774221dad6d159cedf46e51af981282b92856d", + "0xe253577ef8179f8ea765286b6ac234748508c2a79aacb1cafa6993301bf2eb97", + "0x173d63641a7e8f3a7f53fa111b1bec0a2582adb18b3a5c78c81226d311ff079b", + "0xe8d8838dbde5c9150f82e297fe3d76a0c57f12e6f9dfe32453c7e94fcd7a9eb9", + "0x9a61bb64b265aee98d017b3859ad07e76266993c20d1a88ca1bbac3d5d1fcd46", + "0x6bd8bd7da7c642f94b8e0f3186ff027e09df12e34d8d2af607f59e06b87a48b5", + "0x45f6a8d776c000e9cad3f0f120b4986ab94ef4c6bd6d56aa80abe556552c7b7e", + "0x3d907fbbde00deb31cec9c06658b215c943b54127a2c48a462ff0e0c903dcd65", + "0xf5a91976a772292a27b5b54d5fc3ff8a983516eb91c052bb8b3379b1ad0176ed", + "0xd81e25a6ce87305c0d8787dba67dcb66f27b120dc46a91705d080676c81987c5", + "0x773bd2e85389ee5ddd8ec5dcf3795b99e338fa7bb6585dedf74e4483835884ea", + "0xe13a5ffea3cfebf9a0c4624e7cb6881a107a64a271b23bc4835001600f3bc2c2", + "0x535984a643ffb027cfc10476ea2954da4051e48f6157e08530785c2037687267", + "0xff25bad36168818211de295f993032077845e7548739290870055d412a9122b2", + "0x4421ac0d5d5744b71964409561bfd967357511feba5fde27f52302a28fc6b95d", + "0x8f459e102509740b99c3c8f7c3381c283b17d9b1f666a684f07c613954303214", + "0x149d211899e93714c2d702667f5d4d846faa8ddd2797ef997eadfb8d04e70830", + "0x939857b774469e5fa629f4974b0af64c852f0ab4b9f2f694c47c2185c0884c59", + "0x455506f075651b27e59360e405bb3ceba1b284eaaae9f8b7ededab0144241397", + "0x7794f14899d895626054a6775da2d759972ca19a2c7b7fcdafefd3ac81044dbb", + "0xcb1bf88f145349f03b12ad36ba1e2654cd0490262d2686a0390f871447c2dcb5", + "0xb06dabbf192e0f83a2ef9a5c64dd13ad925c0ba3bccd49ba4b9a8cdaa03e0f90", + "0x03a872389b9add1a56eb0ea9737e5f6324849c3f9a618052671b48b11a9ee51a", + "0xd3d865376c8affae8ab310e5cb18c45caec2fd3c8ae2881f06b8229b5855a69d", + "0x4ac61a400ea11b5470f7d2fb3429ea1dc3d4873154c3f7b357246deca3ec4f68", + "0x8f14c907c7bf50bcea75ae65c7d62e66772e5ff15faf430faa5898abbcf92363", + "0x6d151ff1347f6204806c5349fa3dd19bd0221a00773a3c5423328cfcdd64bbe3", + "0x4d4f747eea3eae7dc0423da074e257f523ea688874bcd3afc28e0cdfeab0c7a8", + "0x00a6c2c059ebb164a9377e084206ac5832cc5297cdc0200ca86762e31be4f134", + "0x8de486c426447c83cd81c2f4200e2026abd8833f2803da7568a9ddf11117aaf5", + "0x6ea90881d941139490f2d278593586a7a229ac789532627569c25af254539415", + "0xcb5f64f11bd0a0907b26e13c1f0e22f755907334307c26d2427a387ce70d482c", + "0x359f8609c02c2ef873c5524687a35f1608c71eba9d34c0afb2765a6519e4de3e", + "0xb65483aa05099003d212ec7bb1eac1dd62165d67da35ae1df758778591a778f7", + "0x6bb00536c7d49f225bb7eb094fea16b263549b585c019293dd6018817a82c100", + "0x2566da7d2213e0157c91b6e6644a17b978b93feb2b522621473c3f7bed541d76", + "0xb720814c561850a476c1070fa9fdce8a7fc4269bd7c56bb14479f4b1636800b2", + "0x84932dc6d0879e3b01bd311362403854fd9e4b1690fd59c323ba3b04cf19a969", + "0xe4250b66c3b5b017603e06477a5c252e9596595fd3d787c298ad2ac45e24019b", + "0x3941e8083df52e04e2a3fea6a6c310e558e6747146e676dd0e5942655e9ee3ab", + "0x2240c0287ef3b987e01d00db447982d5143fb272b9846b1af226664197c78292", + "0x0bebcaca64b07b7536c2f6fbfeb9d5141a2d8e309dd942852409bd21d17cbab2", + "0x4e32243bedcdb9f2a5c7f8d423d2a8aa2b487f23b1e3d7d8c220aa4a55cc0a9a", + "0x279a114f51480e923292c58807523e7da08fa9aeea5214f09d27dcb229ca3b1c", + "0xe7a4d1977c4aa7b776bf74e46f66c2e15589c67684bda719e3a6526b90bbc158", + "0x6cc1f2a3880c2aa41d11c901ba91926758e252ce1f148c51a769a81ae34303f8", + "0xbf9e7f17c786e48f5ac09b605da6619ae50788bea08cd42500835802e67b8729", + "0x8a6b70454cda7934a39cd374405f3f5f7c39977273912abb841c4eab738b1237", + "0xb3f52f9b2f7e6a37074d1e20751da4b4fac4fbe0a91386966670631f2a545f7e", + "0x7ec81bbdbbb56a40ad2d00ee6f34110ad97d9935c7dad83d7968ed5512ebdc7f", + "0x40a7df3b63d2f7c4fb05e37b1836dde00becd7166c82fe0df45bbaafa8d7d338", + "0x62ea7fdccc874b5f39f0290e4c0ba8b7b0b5eb12e2a83734a1e236cc87e6453d", + "0xd990e2aeffd23a789d298673c88498322ddc13729bdc6324b62ad6e0a6b2d939", + "0x13f5571d094bb53856ce1e6573a61dece7f32e0f593da75439d2cc59690828d7", + "0xf003ae90a37ca406f346082ff47df498cbdd720e2ed487277f31bdfe495fbd5c", + "0x1ac6ef95f6e007da86fcb50652491da28924854b45cb4d4cd8d574a5ae6da337", + "0x55f27c6633a639527034f2889551243ba50847721b11380bbbcae5ed9eba7c1c", + "0xe9f06076dfe328442393dae25b8fd8667b52c20b860b619947aeb758b5161288", + "0x93665a5b3ff727fabed298a5117a07214f44c80369c12b788e98e50adc9af06f", + "0x214b1e759711d63acd3aece629f1eb5d207b306c868cac471b40d4a264c425c0", + "0x88d2ef86af833ac6146fc687e93972b5f59089a0aadf14b7a731d60c1dc0aac9", + "0xf30a23080c5cb8a76bc1772a811fc7d770ac0595c7b52b606dc660965cab5218", + "0xa136d539a24861b4bdfc4f156347b436c2ea0b69d3e28f75863ab6e90d485e6c", + "0xfdcbcaf63e67009e531ff489c46b7a1433107ef7a54274190d4011d3190d34f2", + "0x70b735311bbd5733eedb484d4d11f88d3de47f3d14afa4fa182525801991f560", + "0x0b7cfa03f5c925257041b19f0ded62df6c29999d258b164592b54badbc0a1ee6", + "0x134e8d9ece216b70d69e369c28f38eca6a0c61b1d1242dcef7b576aa4ac57d3c", + "0x5dd966a462f85da54f3d1dc30b5a629977fbb7c279f754887bda19d89177ac31", + "0x4f2700f4b972c826c3d6bae610c1bf21f716ec99a4b1e1a37bd778a37ed1d81d", + "0x26e86dbdd46fa58dc7c0f23e3402def08d7f09b6600a012eb7503e868ef82239", + "0x1f009da6c2b59286201b3c12a9e52d821e520bb067ddee3c6d57b29b60394c16", + "0x36953b01dfbbb273002d819beb306d703688d6ade4430f8b258bf925c9bfcb1e", + "0x7a94d795bc76fba6899dba6b677d526dce7f01518f4cc8cd18bdbfc31a778b9d", + "0xaa8ad1cc6c206dd616eecfc976cbf2caf1e522facc73ca9d9494d89887d1e302", + "0xc8e5fb05d201e951af9be69c7afbff266cdb11110ad69be6748da319757596f3", + "0xfb394b3865fc15ebaab85682ebe8caf1e7a6ae340c127d9c6675447a8f87807a", + "0xc978e59542a478bba83a9890692f0ae88a0bb7a3df30b7cda0dea4ed7d34eae2", + "0x00832dc5f938a74505d93d3d5c4ab78a50752d45d27eec9ed1ab7a462abd8990", + "0x31cf0cf7fe510d6bc4108ede74063e1bdc3113a21f0212e5c6ab36eaa1faed37", + "0xdc87984673693bdb250a1896305262b2346ba3af4fa0676eca2eeba52eb040ed", + "0xfc945bf877357905dda2af20404e426a82db019437bf1b59f60b31bcdedfbe55", + "0x7f4cc1a12ef5979e6953d3e40f40a4dc932c6c174f183b30f701e6fa4f1b34cd", + "0xcbb31cec8319fa2fea3e7781f981e98aa0769ec751b2e97888b2bd2b5e5d8e58", + "0xee48275f37a5306d7c5d52a1ff2200d3150248f6c596994ee0e78fdde42f3811", + "0x5378d6d66892ceaf1089db912d546f565a2993de54e82eb548545eba7250b31d", + "0xbff7a5b25847fb689db38926fbba529b66374886952d641319cd8fb85731c58a", + "0xa2b4ff87dfb05b3cb88ac945706ef5e82765d11536a428c2ad27fecd19bd8947", + "0x71be8e30b0f2cf7df16495cdfd86e0012c5bb8f728374d86e772d4efa2beda20", + "0xdfe133c543e32cffec7bdf9febab7bb0eaf3d213094a102e4a1c21842fe7afdc", + "0x7874bfed2ae039536a7f92b4aa6f7b7f231152b04895ab0c6efbd3505cde5ae6", + "0x86f49a60e84b885fae20af792a164c679be96a5975ec83fc896cfe19a27a3181", + "0xd1f4af1826401ec1636d039bd528e496ac7ea3f181205eab5751778622129dd6", + "0xefa40fc9cc91899653d4218b8473bc49c3b64d84869cc996987729df1eaa4219", + "0xbdb65de637fa89949f69e6b39635e76fe3da079179612c8101c92df404388e6d", + "0xe13e4e0e93cd3e9e881688079ab5caeb603d53f3f8c3f159a3216dac654003a2", + "0x5b3bdd265d4c516c62a9685cd2e6343c3b542762f45d381aa10adbe4cc2a8ca1", + "0x3fbe2934edb9b4c74feac47a10c34f43c73ce9c04fd4aff4da9e9d53ae1a3b27", + "0xe7f738ca0d301c92913c1c5bd670f2958bc3bccd349206f798ff38615704da02", + "0xd6023f0729c366624fc9581e69c8217352113d2b15e8824d36b05fef5640d228", + "0x2a426c808ee5b9f8c405875e564962a318666acd06bc6942dfc7a4960dfc0a78", + "0x1346589ec5380ee0dcb41cf0af9e8b39a7395a13b94b97150bcc11a71e2b4c1f", + "0x2fcafbf9b787434cf0f745acd8d880cc553a432810acf1bbd66b433ed11c2c18", + "0x33290d023a695c514e8dc94f04719532e377eb81790e288a0b017312b41a0ff5", + "0xf8befaf58584c66572ce8251e42d002cfc5355a9efc8d5f9bc9088a1f7b9edfd", + "0x1f9f9f7a63d43d358f2499ce9b04ce9f0a4b35546011215fb83a22e68df87844", + "0x9a9c4815d5827ec055e39ec001252a5035e6317a05b508bbc3e6907b53109706", + "0x3b277af862fc12912cb482182175edd1ad4dd7f15b2614e0970c3ea09d1a8de3", + "0xf8967669bc80f68ea5c966bb42117851d80c18e81dfd184efc59491971a9165d", + "0x00814afcf1ecf5f401b450afcb6d73ce3fbf6338e8bc4dcec4c32d5b2ecc6454", + "0x63ce5390474616f8b3b70820e67460c6fda75f41f2a2c66ab467b0c311733f3b", + "0x176619adb025ee77960fcdabada971e20bd2789d8e3cf045c03d4ec2e97b2f79", + "0xcd01a05497515a98f69a8a48b534ea1e741bdee0dadb2a32a97c631212e2e5a5", + "0x0b808e84cb0904210581a81e312236efed3bca37e624f6a6c66bfed4134ebf9b", + "0x42ed1705030f23cbf6525095491f6f82914727b116c85bc24ebd9d3579508554", + "0xeb6c3c39f07929e4d0a2ae0a8f9390f2f286f715095f31f82b471cc3f02ab770", + "0x6d0b0405e16a27285e6645ff8a29e05a6f8d4a4ada9ce08b729caa89af96ea48", + "0xc02fd4ca15b5c7a0eb193e1a679fef5ce2cc9e5732209c8739b31cde876b524a", + "0xed2560d142a34db7c5b20125882fb61b8edc1cf22e12c81f860c47a1239706f2", + "0x9f9d607cb7d98b099460c97bd9bf4abe27524de0aa0494068d52cf380aa36293", + "0xca530177bdd648a62d09f77f03f134e3e3bc0d64bcb93a335ee2ca73d73ac2d3", + "0xf2bd87e7e61b6efaf4339ca27cc8d73df9d3d71799bd50ce1465ce9386350023", + "0xfef3aab8dceff11c8cf8fa282e482c9a4a7c53c471a7e329e9720bf917f744ce", + "0x56002bf72faf92897f58fe2679e3571ca490d6c036f724a80b74aac20970641c", + "0xb207591550eb9be35178a224e6ca46ec82b2a6a019bf961f13e7df2026009b9d", + "0x2b52868f8b17ef786cecd0a9a4fcfcee6d9551d4c9104a5ab8d8eeadb707d2dc", + "0x7c4acd1df61f61cc5e2f4ee9decc296d98f3f3fc89ae66bd07d41c84ff56a288", + "0x30adce03c6c2b1532e07e33a24c7f73d08feccb5497e33910226388032a80812", + "0xc79ae82c871ef39b9558c1339ea365dac86469a1165fa0d36b7775c49d3ac7f1", + "0xe4e52fb5b9f83cce4205f09f720c5a4556d0511e20c5c3e0c63f9a954a010adf", + "0xf53f57bfaa2aad6de5fd3f236bc8a4361e2af82fcc198bc058e5d7a5bccd0a02", + "0x5098a75b4dd1d02507416a6e3b8cf0b0ea6cb15bd68420ebc3b7915eb7839f1f", + "0xf9c0e2cbce9fe64623de15c2f85d7c3928ab0b37275660a242770219e3a96d93", + "0xc5b62e1433192d067cbd106d3c0ca218383ddbf14c00c9a13d56449f2bbb3a08", + "0x80d8820d2078d42c1a38e8d6f9f81d58cee3846aaa899261ee1ed85ff4d42533", + "0xd886a4c921ab8b11a3e855a7893664f09629814ad6df0f3bd36cfc37a42a0bbf", + "0xc1375c4f051bd893b5e8a3d768f398b396efec9c4b0e4c2b0d352bb69c9fb394", + "0x20ae02c26b613b05737e26202e18100c3eeeb84c078bb3d4912be5cd99787a2e", + "0xcfa25f8226fe9e61e35fc2ed63a52f2b2a4c3dee8e192831f7a0cda7b77022e7", + "0xf1506b0bb1dda73bbc12eaf379580952e65880b0aef361231d8686619da83b9b", + "0x0033b129deb9e2591fd3375084de6f00fc3f72d37911eb2d623820de7c03ed9c", + "0x245b2677360776f20b01ae7278b41980a63eb45cf6f7566f5804dbcb7e3ea66c", + "0x73081e452c9aa38f130ad22fa653519888b429d3e1aae6701fa5407ed088872a", + "0xcf8224c7675985b9d8411694d881a452b850c3d41331602c6d2ddc552500d9b6", + "0x8278bf3e350fa2532dba0e7082f64bb1fc08f3d2279525545a62ea488835c756", + "0x134a0e1ae78dd0da790d1dc3b1710caaeb3a6015c82fda8efdbd1e5ceb154767", + "0xa5a77ceb096cbc27b2eae356c942fde9db8d72161e9e559ad6376143d7f4d609", + "0xb34c9ca03d44c2512795c8572527f6285c3a6e954cf17668c51e8f22de24efb0", + "0x7cbf51c29022b4cf1e6203cf1322481a36f510c35bc1d78d972101e9593fcdc9", + "0xc3162682c647172119a2cebd3ce42fab8f20be84b20b3c4a85b4b2196eefb80b", + "0x4c8e78cad3ed836eb63af411b683a3d21d7a854004d026ee928870e7d8c94239", + "0x92a9ccef236e16e8d527f9b4e25861a670763b2845b9e7f2831d83f7411c1bb1", + "0xf82d9c2c67a3a6050a1003dfbcdbc6861ca699d41b2bdd69099196122365e218", + "0xafbe03db4a223222536b6ae7e65a66303bb2c89f978a49db05dcee8ce3eb9bfd", + "0x97db73ba455ac03902bfa5335d1efd1c3a08fdc5fe1bda5c402413a6c6bf6439", + "0xe87c9f587da80c6ebf6416e7851fdbd1a5e44b7bf68b7b5b93cae18f5b71001d", + "0x04977189709d5e8243e2cff4d6e9bebae049fcd78854c414276049c1dd8899a2", + "0x0dfb0bc4588450ba7f992ac46b5e68b740bc2373a944c70a440a25222451f868", + "0x2092f8d8303fdfd369f4a24ef504a9d48acdfdfab2e37115d5778a0adfac0082", + "0x19d81b509c576de5a558f9c8fb929d84fea577428fed5af023a7f7bb502e3643", + "0x935fb703a9a030bdad9db265cb3292c2c9a038f1837e049d1d5045b09047a4f0", + "0xbec9f75266da91b02a2d1b5f5b5449f7e6ba28ae402979af40a83c9b39cb483a", + "0x320c3fb2a136d04495087d27b0a5dc6ce90bf7a1bf15ae65d48146a9661f2aa9", + "0x364c285cabc386ccf0140bd24bbc98e37a88b6f88bdb7d34ff1d4a75f29573fc", + "0xe595476b0844351af48748f788d15b53eed8507c6c45faddc189d602d8de1bcc", + "0x803f52547a1ca56abdca4b4ccc28bff586fb3372d9bf677b3ca3400184d009ba", + "0xb90914aebed7eee12fb7d25e80c2f37f68dbf2a698faf2ac92faf0cffafdaf8f", + "0x129abb430b863be8ccb3e33a3194552ede9eca4bebdf34d6b53748213a734ff8", + "0x9a6dcb68b7b37e705ce7457fb1ca954cb5549eebcc4397ed29083df51362d779", + "0x12bd07e3b6ae0add86cc1467208532430c0c8d7d0e832e9c05134b07e46d1768", + "0x69d228b5d0f85a16596aff9f2b51ffc21497d4331a0c9b6fc5b3b1834654df09", + "0x3cd093afcf1b1588b12da25cdbeab67cf99fbbe9df026f78d3abffbd378eff6b", + "0xecb8e829dae8134f700eec6d11e0157bd761fcf0779bb603cd024d0ee8496717", + "0x0fb6faa0e9b556d1ca1e91f56117d417c29b7368445c18cdd0bc5060ee6c86f4", + "0xf126590823b117b19491aae44cdcfb18a3e130f00371398a254c3c3845ff0592", + "0x5f2432d15b4f94234961ce9e6165e9761accc88999c523315001340b96227519", + "0x60d53f01e91fde0da7f3a05a795a491466a211d6cc91e9e49e42d5bc4bd78606", + "0xf575fd0cb12b72eec6b639210f04b5bbad1a7c806fbc6c2c15ea62f97c2185c3", + "0x3ffe76eb15010487bfd65c38db0425d5df9a14df60dfcf0849bcd540aa1300db", + "0x6dfcea9390145d196db57f5b427d9c5e89f336a5bc81bdc3a4e246370feee2f0", + "0xe3c2e58ff9ff293dd8e2084b333bfefb506382848b1884ad03546f457f0559c2", + "0x807d9d862e2b760628b579b66b206ebcaf247f3d041fd9863fb375dd715b74f6", + "0x11e8e648599d5bbabd99f3693c090d95a7ebb66b82e5ce33ae47f7b570154423", + "0x4b59ea6016b208f2e5bef0606a7e81f05cd679eb7635db9e27e8458980281dbe", + "0x5afe3f29cd36132c4db3fe0d989dac1eb5cd6c7c4fffd22a03691a695df95489", + "0x5fc081154e1402309a9ada4d39f90a083bda62aaf4e643a58b2a1bbff8c030c2", + "0xc8f835e4abfa2f4d3cd3e1f92ed878a78253b828ddb8e89300e3de63cfc01775", + "0xa94346dc4441d16b65996b0b44d1ee7b737f75e6857d22a7a03f5cc9b7156c97", + "0x7a9d66965c11cedc5af224b42d407423d56dc291f9a6903f013e4c2801e28f94", + "0xe1592fdfc80452f70c8372bf59824166ccfed92ca6bbfb0a7f196bfbc11a3d7d", + "0x5800287a2051dc265161d6197ab2f0221dfc918f1b37b685f8d23c66af24412e", + "0x875c7d11313b43a3b2df625bf5f0f816b2743ba44165fda1a34c0d3c9d53c54c", + "0x9d87149d991d4a54cdd689e156c37eccb9c2e3022cef97d91bc04667905f8a7b", + "0xd39a5b4c38441cb4a7dff3a22d8ab37691f21dc3af438f4a1591058c214d1284", + "0x6de58f2f7f2b3f756b6c3683596b11c1b68235f032ff31561f92c6d2e0d31575", + "0x0d4b0e13f898561309b8b4ed0dae1f0698d4433aeb81a4e66a045b69c4356c94", + "0x82e58ed2c7e584e42f9f284a1171461338cdc40661e5457ce86621fb9939e3a5", + "0x0f6af8168a7e2e6f35539a5477d9dc4901af3eca6e3257d7f0ecc0b573bf2c73", + "0xe3eb2cce31d8e65106536c608a154388fa00cc5a481ce635c55eb130f021f218", + "0xf6829b8da3ddc1f71b3b14ca1b06c13e9e454a364899c4ce726d3b9a9b55b1ca", + "0x248b513050ea763394365a1ff6a295b6648fd1af0a5703daa7dac423cf5967ad", + "0xcbbd5076dc646780f3e582343c66090e477e32b56210adb84992e340b74a798d", + "0x6e844d7759a5aff03abb0efcc0cd74285a20521afd06c0ff98b82a2bf17a7224", + "0x116a574a2fd13487716a1e3bf78cbbc998dd5b3604caa567662055de8691a459", + "0xfe36c7f148af1c056b913b4f52c8dc4fa0e6cba42b1e7f5204c1d300f9ae1ab4", + "0x114da06ec0e3d1b14027802c9c3bb80922213786464dd5b6a1ee2a12523761aa", + "0x9c451c4cce3cf45b173cf2abf03ffcd11f5d471db49c5abc66603795c6c55494", + "0xd0dca6ee9e3cf1d9277aa5b9a97acf7a45d63d0f76ea8cd84366e2c787489061", + "0x4f93256e5e4af9d0771ee8cf5cf84b80d2f7298a76c77ddaeccd835d4717864f", + "0x174ab2c74403d9538aa14bb0549c33756095c78e647b18eaa818790876996f8a", + "0x0d62cbeb56e7c3db3c6845badab75d504695b40ae7e8b1e6ad13097f5cf65154", + "0x70c96dd19bca237759ee01a00ae6ec75873e2c1384908c86310e766e8352c299", + "0x2ba3360edd065360b551e07aac2fe6a1b646fae955c29d39c1e981b8b8541a72", + "0x64400f31f7e3deb29c6f22aee609a502db3e9c6c9eebd5a93087b6a1636d44ba", + "0x255c5d43117d4d6e3ee88610b3fa54a679ab1eb358153147188a5fbde1556db1", + "0x290e2c3125591d2f128758525955799fe08eb933dc0e22519810e63a10f0ea69" + ], + "cur_hash": "0x3c869591ac4295afc75154eaaf7a8b59a41af3cdcbad9d8c48fb7ef9853f9ec6" + }, + "withdrawals": [ + [ + "0xf197c6f2ac14d25ee2789a73e4847732c7f16bc9", + "0x119204e" + ], + [ + "0xf197c6f2ac14d25ee2789a73e4847732c7f16bc9", + "0x1193fa3" + ], + [ + "0xf197c6f2ac14d25ee2789a73e4847732c7f16bc9", + "0x118a669" + ], + [ + "0xf197c6f2ac14d25ee2789a73e4847732c7f16bc9", + "0x118d295" + ], + [ + "0xf197c6f2ac14d25ee2789a73e4847732c7f16bc9", + "0x1195db7" + ], + [ + "0xf197c6f2ac14d25ee2789a73e4847732c7f16bc9", + "0x3c46d9e" + ], + [ + "0xf197c6f2ac14d25ee2789a73e4847732c7f16bc9", + "0x118b8bb" + ], + [ + "0xf197c6f2ac14d25ee2789a73e4847732c7f16bc9", + "0x11994d6" + ], + [ + "0xf197c6f2ac14d25ee2789a73e4847732c7f16bc9", + "0x11822b1" + ], + [ + "0xf197c6f2ac14d25ee2789a73e4847732c7f16bc9", + "0x118b828" + ], + [ + "0xf197c6f2ac14d25ee2789a73e4847732c7f16bc9", + "0x118b7e1" + ], + [ + "0xf197c6f2ac14d25ee2789a73e4847732c7f16bc9", + "0x117492d" + ], + [ + "0xf197c6f2ac14d25ee2789a73e4847732c7f16bc9", + "0x3c1adb5" + ], + [ + "0xf197c6f2ac14d25ee2789a73e4847732c7f16bc9", + "0x11713d0" + ], + [ + "0xf197c6f2ac14d25ee2789a73e4847732c7f16bc9", + "0x1197bef" + ], + [ + "0xf197c6f2ac14d25ee2789a73e4847732c7f16bc9", + "0x1181b16" + ] + ] + }, + "checkpoint_state_trie_root": "0x319da7faf76836d1ca1c48e0540a97c0d7f2515b7fd7be7dfb1aef9ed5dd588a" + } +} \ No newline at end of file diff --git a/trace_decoder/tests/test_b19807080.rs b/trace_decoder/tests/test_parsing_and_proving.rs similarity index 82% rename from trace_decoder/tests/test_b19807080.rs rename to trace_decoder/tests/test_parsing_and_proving.rs index 62a4588c5..d194135a7 100644 --- a/trace_decoder/tests/test_b19807080.rs +++ b/trace_decoder/tests/test_parsing_and_proving.rs @@ -5,8 +5,6 @@ //! This test only `simulates` the zkEVM CPU, i.e. does not generate STARK //! traces nor generates proofs, as its purpose is to be runnable easily in the //! CI even in `debug` mode. -//! -//! The tested block is this one: . use std::time::Duration; @@ -33,11 +31,10 @@ pub struct ProverInput { type F = GoldilocksField; -#[test] -fn test_block_19807080() { +fn test_block(path: &str) { init_logger(); - let bytes = std::fs::read("tests/b19807080_trace.json").unwrap(); + let bytes = std::fs::read(path).unwrap(); let prover_input: ProverInput = serde_json::from_slice(&bytes).unwrap(); let tx_inputs = prover_input @@ -61,6 +58,18 @@ fn test_block_19807080() { } } -pub fn init_logger() { +/// Tests a small block with withdrawals: . +#[test] +fn test_block_19807080() { + test_block("tests/b19807080_trace.json") +} + +/// Tests an empty block with withdrawals: . +#[test] +fn test_block_19840104() { + test_block("tests/b19840104_trace.json") +} + +fn init_logger() { let _ = try_init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info")); } From abf919dd077be5337810df8e41e72475f9a43954 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Wed, 22 May 2024 07:39:24 -0400 Subject: [PATCH 23/40] doc: update README (#242) --- evm_arithmetization/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/evm_arithmetization/README.md b/evm_arithmetization/README.md index c3593e77d..1565bffa0 100644 --- a/evm_arithmetization/README.md +++ b/evm_arithmetization/README.md @@ -1,6 +1,6 @@ # Provable Stateless ZK-EVM -Included here is an implementation of a stateless, recursive ZK-EVM client implemented using Plonky2. It currently supports the full Merkle-Patricia Trie and has all Shanghai opcodes implemented. +Included here is an implementation of a stateless, recursive ZK-EVM client implemented using Plonky2. It currently supports the full Merkle-Patricia Trie and has all Cancun opcodes implemented. ## Performance @@ -10,7 +10,8 @@ Furthermore the implementation itself is highly optimized to provide fast provin ## Ethereum Compatibility -The aim of this module is to initially provide full ethereum compatibility. Today, all [EVM tests](https://github.com/0xPolygonZero/evm-tests) for the Shanghai hardfork are implemented. Work is progressing on supporting the upcoming [Cancun](https://github.com/0xPolygonZero/plonky2/labels/cancun) EVM changes. Furthermore, this prover uses the full ethereum state tree and hashing modes. +The aim of this module is to initially provide full Ethereum compatibility. +It is currently fully [Cancun](https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/cancun.md) HF compatible. ## Audits From aa36464108cfa37c410dc3ab53e5bb390445bb80 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Thu, 23 May 2024 07:17:57 -0400 Subject: [PATCH 24/40] Cleanup and bring back deadcode lint (#232) --- evm_arithmetization/src/arithmetic/addcy.rs | 1 - .../src/cpu/columns/general.rs | 1 - .../src/cpu/kernel/constants/txn_fields.rs | 2 +- .../src/cpu/kernel/interpreter.rs | 2 + evm_arithmetization/src/cpu/kernel/mod.rs | 1 + evm_arithmetization/src/cpu/kernel/parser.rs | 2 - .../src/cpu/kernel/tests/account_code.rs | 20 ++-- .../src/cpu/kernel/tests/add11.rs | 100 ++++++++++------- .../src/cpu/kernel/tests/balance.rs | 2 +- .../src/cpu/kernel/tests/blobhash.rs | 16 ++- .../src/cpu/kernel/tests/bls381.rs | 5 +- .../src/cpu/kernel/tests/core/access_lists.rs | 68 +++++++++--- .../kernel/tests/core/jumpdest_analysis.rs | 30 ++++-- .../src/cpu/kernel/tests/mpt/insert.rs | 2 +- .../transaction_parsing/parse_type_0_txn.rs | 2 +- .../src/cpu/kernel/tests/transient_storage.rs | 102 ++++++++++++------ evm_arithmetization/src/curve_pairings.rs | 35 ++---- .../src/generation/prover_input.rs | 10 +- evm_arithmetization/src/generation/state.rs | 8 +- .../src/generation/trie_extractor.rs | 102 +----------------- evm_arithmetization/src/lib.rs | 1 - evm_arithmetization/src/memory/segments.rs | 1 - evm_arithmetization/src/testing_utils.rs | 13 --- evm_arithmetization/src/util.rs | 11 -- evm_arithmetization/src/verifier.rs | 1 + 25 files changed, 246 insertions(+), 292 deletions(-) diff --git a/evm_arithmetization/src/arithmetic/addcy.rs b/evm_arithmetization/src/arithmetic/addcy.rs index c7a130b20..6de47845f 100644 --- a/evm_arithmetization/src/arithmetic/addcy.rs +++ b/evm_arithmetization/src/arithmetic/addcy.rs @@ -170,7 +170,6 @@ pub(crate) fn eval_packed_generic( eval_packed_generic_addcy(yield_constr, is_gt, in0, aux, in1, out, false); } -#[allow(clippy::needless_collect)] pub(crate) fn eval_ext_circuit_addcy, const D: usize>( builder: &mut CircuitBuilder, yield_constr: &mut RecursiveConstraintConsumer, diff --git a/evm_arithmetization/src/cpu/columns/general.rs b/evm_arithmetization/src/cpu/columns/general.rs index 8b2c2bc41..9e2713b9e 100644 --- a/evm_arithmetization/src/cpu/columns/general.rs +++ b/evm_arithmetization/src/cpu/columns/general.rs @@ -78,7 +78,6 @@ impl CpuGeneralColumnsView { } impl PartialEq for CpuGeneralColumnsView { - #[allow(clippy::unconditional_recursion)] // false positive fn eq(&self, other: &Self) -> bool { let self_arr: &[T; NUM_SHARED_COLUMNS] = self.borrow(); let other_arr: &[T; NUM_SHARED_COLUMNS] = other.borrow(); diff --git a/evm_arithmetization/src/cpu/kernel/constants/txn_fields.rs b/evm_arithmetization/src/cpu/kernel/constants/txn_fields.rs index e61c6ee51..9d38a8cf1 100644 --- a/evm_arithmetization/src/cpu/kernel/constants/txn_fields.rs +++ b/evm_arithmetization/src/cpu/kernel/constants/txn_fields.rs @@ -5,7 +5,6 @@ use crate::memory::segments::Segment; /// /// Each value is directly scaled by the corresponding `Segment::TxnFields` /// value for faster memory access in the kernel. -#[allow(dead_code)] #[allow(clippy::enum_clike_unportable_variant)] #[repr(usize)] #[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Debug)] @@ -41,6 +40,7 @@ impl NormalizedTxnField { pub(crate) const COUNT: usize = 17; /// Unscales this virtual offset by their respective `Segment` value. + #[cfg(test)] pub(crate) const fn unscale(&self) -> usize { *self as usize - Segment::TxnFields as usize } diff --git a/evm_arithmetization/src/cpu/kernel/interpreter.rs b/evm_arithmetization/src/cpu/kernel/interpreter.rs index 1197d2f83..6fdc0f231 100644 --- a/evm_arithmetization/src/cpu/kernel/interpreter.rs +++ b/evm_arithmetization/src/cpu/kernel/interpreter.rs @@ -51,6 +51,7 @@ pub(crate) struct Interpreter { /// halt_context pub(crate) halt_context: Option, /// Counts the number of appearances of each opcode. For debugging purposes. + #[allow(unused)] pub(crate) opcode_count: [usize; 0x100], jumpdest_table: HashMap>, /// `true` if the we are currently carrying out a jumpdest analysis. @@ -616,6 +617,7 @@ impl Transition for Interpreter { } } +#[cfg(debug_assertions)] fn get_mnemonic(opcode: u8) -> &'static str { match opcode { 0x00 => "STOP", diff --git a/evm_arithmetization/src/cpu/kernel/mod.rs b/evm_arithmetization/src/cpu/kernel/mod.rs index d39b015cf..8c9d1bf88 100644 --- a/evm_arithmetization/src/cpu/kernel/mod.rs +++ b/evm_arithmetization/src/cpu/kernel/mod.rs @@ -13,6 +13,7 @@ mod utils; pub(crate) mod interpreter; pub use constants::cancun_constants; +pub use constants::global_exit_root; #[cfg(test)] mod tests; diff --git a/evm_arithmetization/src/cpu/kernel/parser.rs b/evm_arithmetization/src/cpu/kernel/parser.rs index e21c6169e..7864acfe0 100644 --- a/evm_arithmetization/src/cpu/kernel/parser.rs +++ b/evm_arithmetization/src/cpu/kernel/parser.rs @@ -1,5 +1,3 @@ -#![allow(clippy::empty_docs)] - use std::str::FromStr; use ethereum_types::U256; diff --git a/evm_arithmetization/src/cpu/kernel/tests/account_code.rs b/evm_arithmetization/src/cpu/kernel/tests/account_code.rs index 5ad2d8485..125760ee5 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/account_code.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/account_code.rs @@ -144,7 +144,7 @@ fn prepare_interpreter( ); let hash = H256::from_uint(&interpreter.stack()[1]); - state_trie.insert(k, rlp::encode(account).to_vec()); + state_trie.insert(k, rlp::encode(account).to_vec())?; let expected_state_trie_hash = state_trie.hash(); assert_eq!(hash, expected_state_trie_hash); @@ -201,7 +201,9 @@ fn test_extcodecopy() -> Result<()> { // Pre-initialize the accessed addresses list. let init_accessed_addresses = KERNEL.global_labels["init_access_lists"]; interpreter.generation_state.registers.program_counter = init_accessed_addresses; - interpreter.push(0xdeadbeefu32.into()); + interpreter + .push(0xdeadbeefu32.into()) + .expect("The stack should not overflow"); interpreter.run()?; let extcodecopy = KERNEL.global_labels["sys_extcodecopy"]; @@ -321,7 +323,7 @@ fn sstore() -> Result<()> { let mut state_trie_before = HashedPartialTrie::from(Node::Empty); - state_trie_before.insert(addr_nibbles, rlp::encode(&account_before).to_vec()); + state_trie_before.insert(addr_nibbles, rlp::encode(&account_before).to_vec())?; let trie_inputs = TrieInputs { state_trie: state_trie_before.clone(), @@ -336,7 +338,9 @@ fn sstore() -> Result<()> { // Pre-initialize the accessed addresses list. let init_accessed_addresses = KERNEL.global_labels["init_access_lists"]; interpreter.generation_state.registers.program_counter = init_accessed_addresses; - interpreter.push(0xdeadbeefu32.into()); + interpreter + .push(0xdeadbeefu32.into()) + .expect("The stack should not overflow"); interpreter.run()?; // Prepare the interpreter by inserting the account in the state trie. @@ -384,7 +388,7 @@ fn sstore() -> Result<()> { let hash = H256::from_uint(&interpreter.stack()[1]); let mut expected_state_trie_after = HashedPartialTrie::from(Node::Empty); - expected_state_trie_after.insert(addr_nibbles, rlp::encode(&account_after).to_vec()); + expected_state_trie_after.insert(addr_nibbles, rlp::encode(&account_after).to_vec())?; let expected_state_trie_hash = expected_state_trie_after.hash(); assert_eq!(hash, expected_state_trie_hash); @@ -417,7 +421,7 @@ fn sload() -> Result<()> { let mut state_trie_before = HashedPartialTrie::from(Node::Empty); - state_trie_before.insert(addr_nibbles, rlp::encode(&account_before).to_vec()); + state_trie_before.insert(addr_nibbles, rlp::encode(&account_before).to_vec())?; let trie_inputs = TrieInputs { state_trie: state_trie_before.clone(), @@ -432,7 +436,9 @@ fn sload() -> Result<()> { // Pre-initialize the accessed addresses list. let init_accessed_addresses = KERNEL.global_labels["init_access_lists"]; interpreter.generation_state.registers.program_counter = init_accessed_addresses; - interpreter.push(0xdeadbeefu32.into()); + interpreter + .push(0xdeadbeefu32.into()) + .expect("The stack should not overflow"); interpreter.run()?; // Prepare the interpreter by inserting the account in the state trie. diff --git a/evm_arithmetization/src/cpu/kernel/tests/add11.rs b/evm_arithmetization/src/cpu/kernel/tests/add11.rs index 8d85c5a1e..ae5ac3871 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/add11.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/add11.rs @@ -122,26 +122,36 @@ fn test_add11_yml() { &mut beacon_roots_account_storage, block_metadata.block_timestamp, block_metadata.parent_beacon_block_root, - ); + ) + .unwrap(); let beacon_roots_account = beacon_roots_contract_from_storage(&beacon_roots_account_storage); let mut expected_state_trie_after = HashedPartialTrie::from(Node::Empty); - expected_state_trie_after.insert( - beneficiary_nibbles, - rlp::encode(&beneficiary_account_after).to_vec(), - ); expected_state_trie_after - .insert(sender_nibbles, rlp::encode(&sender_account_after).to_vec()); - expected_state_trie_after.insert(to_nibbles, rlp::encode(&to_account_after).to_vec()); - expected_state_trie_after.insert( - beacon_roots_account_nibbles(), - rlp::encode(&beacon_roots_account).to_vec(), - ); - expected_state_trie_after.insert( - ger_account_nibbles(), - rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), - ); + .insert( + beneficiary_nibbles, + rlp::encode(&beneficiary_account_after).to_vec(), + ) + .unwrap(); + expected_state_trie_after + .insert(sender_nibbles, rlp::encode(&sender_account_after).to_vec()) + .unwrap(); + expected_state_trie_after + .insert(to_nibbles, rlp::encode(&to_account_after).to_vec()) + .unwrap(); + expected_state_trie_after + .insert( + beacon_roots_account_nibbles(), + rlp::encode(&beacon_roots_account).to_vec(), + ) + .unwrap(); + expected_state_trie_after + .insert( + ger_account_nibbles(), + rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), + ) + .unwrap(); expected_state_trie_after }; let receipt_0 = LegacyReceiptRlp { @@ -151,10 +161,12 @@ fn test_add11_yml() { logs: vec![], }; let mut receipts_trie = HashedPartialTrie::from(Node::Empty); - receipts_trie.insert( - Nibbles::from_str("0x80").unwrap(), - rlp::encode(&receipt_0).to_vec(), - ); + receipts_trie + .insert( + Nibbles::from_str("0x80").unwrap(), + rlp::encode(&receipt_0).to_vec(), + ) + .unwrap(); let transactions_trie: HashedPartialTrie = Node::Leaf { nibbles: Nibbles::from_str("0x80").unwrap(), value: txn.to_vec(), @@ -290,26 +302,36 @@ fn test_add11_yml_with_exception() { &mut beacon_roots_account_storage, block_metadata.block_timestamp, block_metadata.parent_beacon_block_root, - ); + ) + .unwrap(); let beacon_roots_account = beacon_roots_contract_from_storage(&beacon_roots_account_storage); let mut expected_state_trie_after = HashedPartialTrie::from(Node::Empty); - expected_state_trie_after.insert( - beneficiary_nibbles, - rlp::encode(&beneficiary_account_after).to_vec(), - ); expected_state_trie_after - .insert(sender_nibbles, rlp::encode(&sender_account_after).to_vec()); - expected_state_trie_after.insert(to_nibbles, rlp::encode(&to_account_after).to_vec()); - expected_state_trie_after.insert( - beacon_roots_account_nibbles(), - rlp::encode(&beacon_roots_account).to_vec(), - ); - expected_state_trie_after.insert( - ger_account_nibbles(), - rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), - ); + .insert( + beneficiary_nibbles, + rlp::encode(&beneficiary_account_after).to_vec(), + ) + .unwrap(); + expected_state_trie_after + .insert(sender_nibbles, rlp::encode(&sender_account_after).to_vec()) + .unwrap(); + expected_state_trie_after + .insert(to_nibbles, rlp::encode(&to_account_after).to_vec()) + .unwrap(); + expected_state_trie_after + .insert( + beacon_roots_account_nibbles(), + rlp::encode(&beacon_roots_account).to_vec(), + ) + .unwrap(); + expected_state_trie_after + .insert( + ger_account_nibbles(), + rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), + ) + .unwrap(); expected_state_trie_after }; @@ -320,10 +342,12 @@ fn test_add11_yml_with_exception() { logs: vec![], }; let mut receipts_trie = HashedPartialTrie::from(Node::Empty); - receipts_trie.insert( - Nibbles::from_str("0x80").unwrap(), - rlp::encode(&receipt_0).to_vec(), - ); + receipts_trie + .insert( + Nibbles::from_str("0x80").unwrap(), + rlp::encode(&receipt_0).to_vec(), + ) + .unwrap(); let transactions_trie: HashedPartialTrie = Node::Leaf { nibbles: Nibbles::from_str("0x80").unwrap(), value: txn.to_vec(), diff --git a/evm_arithmetization/src/cpu/kernel/tests/balance.rs b/evm_arithmetization/src/cpu/kernel/tests/balance.rs index 034df53a8..2b8f8c241 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/balance.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/balance.rs @@ -96,7 +96,7 @@ fn prepare_interpreter( ); let hash = H256::from_uint(&interpreter.stack()[1]); - state_trie.insert(k, rlp::encode(account).to_vec()); + state_trie.insert(k, rlp::encode(account).to_vec())?; let expected_state_trie_hash = state_trie.hash(); assert_eq!(hash, expected_state_trie_hash); diff --git a/evm_arithmetization/src/cpu/kernel/tests/blobhash.rs b/evm_arithmetization/src/cpu/kernel/tests/blobhash.rs index 1ff200873..429bd729f 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/blobhash.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/blobhash.rs @@ -31,8 +31,12 @@ fn test_valid_blobhash() -> Result<()> { interpreter.set_context_metadata_field(1, GasLimit, U256::from(1000000000000u64)); - interpreter.push(index.into()); // target hash index - interpreter.push(retdest); // kexit_info + interpreter + .push(index.into()) + .expect("The stack should not overflow"); // target hash index + interpreter + .push(retdest) + .expect("The stack should not overflow"); // kexit_info interpreter.run()?; @@ -68,8 +72,12 @@ fn test_invalid_blobhash() -> Result<()> { interpreter.set_context_metadata_field(1, GasLimit, U256::from(1000000000000u64)); - interpreter.push(index.into()); // target hash index - interpreter.push(retdest); // kexit_info + interpreter + .push(index.into()) + .expect("The stack should not overflow"); // target hash index + interpreter + .push(retdest) + .expect("The stack should not overflow"); // kexit_info interpreter.run()?; diff --git a/evm_arithmetization/src/cpu/kernel/tests/bls381.rs b/evm_arithmetization/src/cpu/kernel/tests/bls381.rs index 56d6c1f7a..9fba3e7b4 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/bls381.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/bls381.rs @@ -1,7 +1,6 @@ use anyhow::Result; use ethereum_types::U256; use hex_literal::hex; -use keccak_hash::keccak; use plonky2::field::goldilocks_field::GoldilocksField as F; use rand::Rng; @@ -9,12 +8,10 @@ use super::{run_interpreter_with_memory, InterpreterMemoryInitialization}; use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::cancun_constants::POINT_EVALUATION_PRECOMPILE_RETURN_VALUE; use crate::cpu::kernel::constants::cancun_constants::KZG_VERSIONED_HASH; -use crate::cpu::kernel::constants::context_metadata::ContextMetadata; use crate::cpu::kernel::interpreter::Interpreter; use crate::extension_tower::{Fp2, Stack, BLS381}; -use crate::memory::segments::Segment::{self, KernelGeneral}; +use crate::memory::segments::Segment::KernelGeneral; use crate::util::sha2; -use crate::witness::errors::ProgramError; #[test] fn test_bls_fp2_mul() -> Result<()> { diff --git a/evm_arithmetization/src/cpu/kernel/tests/core/access_lists.rs b/evm_arithmetization/src/cpu/kernel/tests/core/access_lists.rs index 4b3aae8cc..d1f94c5cb 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/core/access_lists.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/core/access_lists.rs @@ -102,8 +102,12 @@ fn test_insert_address() -> Result<()> { assert!(address != H160::zero(), "Cosmic luck or bad RNG?"); - interpreter.push(retaddr); - interpreter.push(U256::from(address.0.as_slice())); + interpreter + .push(retaddr) + .expect("The stack should not overflow"); + interpreter + .push(U256::from(address.0.as_slice())) + .expect("The stack should not overflow"); interpreter.generation_state.registers.program_counter = insert_accessed_addresses; interpreter.run()?; @@ -146,8 +150,12 @@ fn test_insert_accessed_addresses() -> Result<()> { let offset = Segment::AccessedAddresses as usize; for i in 0..n { let addr = U256::from(addresses[i].0.as_slice()); - interpreter.push(0xdeadbeefu32.into()); - interpreter.push(addr); + interpreter + .push(0xdeadbeefu32.into()) + .expect("The stack should not overflow"); + interpreter + .push(addr) + .expect("The stack should not overflow"); interpreter.generation_state.registers.program_counter = insert_accessed_addresses; interpreter.run()?; assert_eq!(interpreter.pop().unwrap(), U256::one()); @@ -156,8 +164,12 @@ fn test_insert_accessed_addresses() -> Result<()> { for i in 0..n { // Test for address already in list. let addr_in_list = addresses[i]; - interpreter.push(retaddr); - interpreter.push(U256::from(addr_in_list.0.as_slice())); + interpreter + .push(retaddr) + .expect("The stack should not overflow"); + interpreter + .push(U256::from(addr_in_list.0.as_slice())) + .expect("The stack should not overflow"); interpreter.generation_state.registers.program_counter = insert_accessed_addresses; interpreter.run()?; assert_eq!(interpreter.pop().unwrap(), U256::zero()); @@ -170,8 +182,12 @@ fn test_insert_accessed_addresses() -> Result<()> { } // Test for address not in list. - interpreter.push(retaddr); - interpreter.push(U256::from(addr_not_in_list.0.as_slice())); + interpreter + .push(retaddr) + .expect("The stack should not overflow"); + interpreter + .push(U256::from(addr_not_in_list.0.as_slice())) + .expect("The stack should not overflow"); interpreter.generation_state.registers.program_counter = insert_accessed_addresses; interpreter.run()?; @@ -222,9 +238,15 @@ fn test_insert_accessed_storage_keys() -> Result<()> { for i in 0..n { let addr = U256::from(storage_keys[i].0 .0.as_slice()); let key = storage_keys[i].1; - interpreter.push(retaddr); - interpreter.push(key); - interpreter.push(addr); + interpreter + .push(retaddr) + .expect("The stack should not overflow"); + interpreter + .push(key) + .expect("The stack should not overflow"); + interpreter + .push(addr) + .expect("The stack should not overflow"); interpreter.generation_state.registers.program_counter = insert_accessed_storage_keys; interpreter.run()?; assert_eq!(interpreter.pop().unwrap(), U256::one()); @@ -234,9 +256,15 @@ fn test_insert_accessed_storage_keys() -> Result<()> { for i in 0..10 { // Test for storage key already in list. let (addr, key) = storage_keys[i]; - interpreter.push(retaddr); - interpreter.push(key); - interpreter.push(U256::from(addr.0.as_slice())); + interpreter + .push(retaddr) + .expect("The stack should not overflow"); + interpreter + .push(key) + .expect("The stack should not overflow"); + interpreter + .push(U256::from(addr.0.as_slice())) + .expect("The stack should not overflow"); interpreter.generation_state.registers.program_counter = insert_accessed_storage_keys; interpreter.run()?; assert_eq!(interpreter.pop().unwrap(), U256::zero()); @@ -250,9 +278,15 @@ fn test_insert_accessed_storage_keys() -> Result<()> { } // Test for storage key not in list. - interpreter.push(retaddr); - interpreter.push(storage_key_not_in_list.1); - interpreter.push(U256::from(storage_key_not_in_list.0 .0.as_slice())); + interpreter + .push(retaddr) + .expect("The stack should not overflow"); + interpreter + .push(storage_key_not_in_list.1) + .expect("The stack should not overflow"); + interpreter + .push(U256::from(storage_key_not_in_list.0 .0.as_slice())) + .expect("The stack should not overflow"); interpreter.generation_state.registers.program_counter = insert_accessed_storage_keys; interpreter.run()?; diff --git a/evm_arithmetization/src/cpu/kernel/tests/core/jumpdest_analysis.rs b/evm_arithmetization/src/cpu/kernel/tests/core/jumpdest_analysis.rs index dd9a295e2..61a580de7 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/core/jumpdest_analysis.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/core/jumpdest_analysis.rs @@ -72,9 +72,15 @@ fn test_jumpdest_analysis() -> Result<()> { // Run jumpdest analysis with context = 3 interpreter.generation_state.registers.context = CONTEXT; - interpreter.push(0xDEADBEEFu32.into()); - interpreter.push(code_len.into()); - interpreter.push(U256::from(CONTEXT) << CONTEXT_SCALING_FACTOR); + interpreter + .push(0xDEADBEEFu32.into()) + .expect("The stack should not overflow"); + interpreter + .push(code_len.into()) + .expect("The stack should not overflow"); + interpreter + .push(U256::from(CONTEXT) << CONTEXT_SCALING_FACTOR) + .expect("The stack should not overflow"); // We need to manually pop the jumpdest_table and push its value on the top of // the stack @@ -86,7 +92,9 @@ fn test_jumpdest_analysis() -> Result<()> { .get_mut(&CONTEXT) .unwrap() .pop(); - interpreter.push(41.into()); + interpreter + .push(41.into()) + .expect("The stack should not overflow"); interpreter.run()?; assert_eq!(interpreter.stack(), vec![]); @@ -195,14 +203,14 @@ fn test_verify_non_jumpdest() -> Result<()> { code[i] -= 1; // We check that all non jumpdests are indeed non jumpdests - for (j, &opcode) in code - .iter() - .enumerate() - .filter(|&(j, _)| j != 1 && j != 5 && j != 7) - { + for j in (0..code.len()).filter(|&i| i != 1 && i != 5 && i != 7) { interpreter.generation_state.registers.program_counter = verify_non_jumpdest; - interpreter.push(0xDEADBEEFu32.into()); - interpreter.push(j.into()); + interpreter + .push(0xDEADBEEFu32.into()) + .expect("The stack should not overflow"); + interpreter + .push(j.into()) + .expect("The stack should not overflow"); interpreter.run()?; assert!(interpreter.stack().is_empty()); assert_eq!(interpreter.get_jumpdest_bit(j), U256::zero()); diff --git a/evm_arithmetization/src/cpu/kernel/tests/mpt/insert.rs b/evm_arithmetization/src/cpu/kernel/tests/mpt/insert.rs index 771921173..d25138631 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/mpt/insert.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/mpt/insert.rs @@ -234,7 +234,7 @@ fn test_state_trie( ); let hash = H256::from_uint(&interpreter.stack()[1]); - state_trie.insert(k, rlp::encode(&account).to_vec()); + state_trie.insert(k, rlp::encode(&account).to_vec())?; let expected_state_trie_hash = state_trie.hash(); assert_eq!(hash, expected_state_trie_hash); diff --git a/evm_arithmetization/src/cpu/kernel/tests/transaction_parsing/parse_type_0_txn.rs b/evm_arithmetization/src/cpu/kernel/tests/transaction_parsing/parse_type_0_txn.rs index d9a952eed..08a74ee4c 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/transaction_parsing/parse_type_0_txn.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/transaction_parsing/parse_type_0_txn.rs @@ -15,7 +15,7 @@ fn process_type_0_txn() -> Result<()> { let process_normalized_txn = KERNEL.global_labels["process_normalized_txn"]; let retaddr = 0xDEADBEEFu32.into(); - const INITIAL_TXN_RLP_ADDR: usize = (Segment::RlpRaw as usize + 1); + const INITIAL_TXN_RLP_ADDR: usize = Segment::RlpRaw as usize + 1; let mut interpreter: Interpreter = Interpreter::new( process_type_0_txn, vec![retaddr, INITIAL_TXN_RLP_ADDR.into()], diff --git a/evm_arithmetization/src/cpu/kernel/tests/transient_storage.rs b/evm_arithmetization/src/cpu/kernel/tests/transient_storage.rs index d1332c586..f1ec59618 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/transient_storage.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/transient_storage.rs @@ -1,22 +1,16 @@ -use std::array; - use anyhow::Result; -use ethereum_types::{Address, U256}; +use ethereum_types::U256; use once_cell::sync::Lazy; -use pest::error::Error; use plonky2::field::goldilocks_field::GoldilocksField as F; -use rand::{thread_rng, Rng}; use crate::cpu::kernel::aggregator::{ combined_kernel_from_files, KERNEL_FILES, NUMBER_KERNEL_FILES, }; use crate::cpu::kernel::assembler::Kernel; use crate::cpu::kernel::constants::context_metadata::ContextMetadata; -use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; -use crate::cpu::kernel::interpreter::{self, Interpreter}; +use crate::cpu::kernel::interpreter::Interpreter; use crate::generation::state::GenerationState; use crate::memory::segments::Segment; -use crate::witness::errors::ProgramError; use crate::witness::memory::MemoryAddress; use crate::GenerationInputs; @@ -119,8 +113,12 @@ fn test_tstore_tload() -> Result<()> { + (U256::from(interpreter.generation_state.registers.gas_used) << 192); interpreter.generation_state.registers.program_counter = sys_tload; interpreter.generation_state.registers.is_kernel = true; - interpreter.push(2.into()); - interpreter.push(kexit_info); + interpreter + .push(2.into()) + .expect("The stack should not overflow"); + interpreter + .push(kexit_info) + .expect("The stack should not overflow"); interpreter.run()?; @@ -134,8 +132,12 @@ fn test_tstore_tload() -> Result<()> { interpreter.generation_state.registers.program_counter = sys_tload; interpreter.generation_state.registers.is_kernel = true; let slot: U256 = 4.into(); - interpreter.push(slot); - interpreter.push(kexit_info); + interpreter + .push(slot) + .expect("The stack should not overflow"); + interpreter + .push(kexit_info) + .expect("The stack should not overflow"); interpreter.run()?; @@ -175,7 +177,7 @@ fn test_many_tstore_many_tload() -> Result<()> { let sys_tstore = crate::cpu::kernel::aggregator::KERNEL.global_labels["sys_tstore"]; - for i in (0..10) { + for i in 0..10 { interpreter.generation_state.registers.program_counter = sys_tstore; interpreter.generation_state.registers.is_kernel = true; let kexit_info = U256::from(0xdeadbeefu32) @@ -183,9 +185,15 @@ fn test_many_tstore_many_tload() -> Result<()> { + (U256::from(interpreter.generation_state.registers.gas_used) << 192); let val: U256 = i.into(); let slot: U256 = i.into(); - interpreter.push(val); - interpreter.push(slot); - interpreter.push(kexit_info); + interpreter + .push(val) + .expect("The stack should not overflow"); + interpreter + .push(slot) + .expect("The stack should not overflow"); + interpreter + .push(kexit_info) + .expect("The stack should not overflow"); interpreter.run()?; assert_eq!( @@ -196,15 +204,19 @@ fn test_many_tstore_many_tload() -> Result<()> { let sys_tload = crate::cpu::kernel::aggregator::KERNEL.global_labels["sys_tload"]; - for i in (0..10) { + for i in 0..10 { interpreter.generation_state.registers.program_counter = sys_tload; interpreter.generation_state.registers.is_kernel = true; let kexit_info = U256::from(0xdeadbeefu32) + (U256::from(u64::from(true)) << 32) + (U256::from(interpreter.generation_state.registers.gas_used) << 192); let slot: U256 = i.into(); - interpreter.push(slot); - interpreter.push(kexit_info); + interpreter + .push(slot) + .expect("The stack should not overflow"); + interpreter + .push(kexit_info) + .expect("The stack should not overflow"); interpreter.run()?; assert_eq!( @@ -257,7 +269,7 @@ fn test_revert() -> Result<()> { interpreter.generation_state.memory.set(addr_addr, 3.into()); // Store different values at slot 1 - for i in (0..10) { + for i in 0..10 { interpreter.generation_state.registers.program_counter = sys_tstore; interpreter.generation_state.registers.is_kernel = true; let kexit_info = U256::from(0xdeadbeefu32) @@ -265,9 +277,15 @@ fn test_revert() -> Result<()> { + (U256::from(interpreter.generation_state.registers.gas_used) << 192); let val: U256 = i.into(); let slot: U256 = 1.into(); - interpreter.push(val); - interpreter.push(slot); - interpreter.push(kexit_info); + interpreter + .push(val) + .expect("The stack should not overflow"); + interpreter + .push(slot) + .expect("The stack should not overflow"); + interpreter + .push(kexit_info) + .expect("The stack should not overflow"); interpreter.run()?; assert_eq!( @@ -282,7 +300,9 @@ fn test_revert() -> Result<()> { let checkpoint = KERNEL.global_labels["checkpoint"]; interpreter.generation_state.registers.program_counter = checkpoint; interpreter.generation_state.registers.is_kernel = true; - interpreter.push(0xdeadbeefu32.into()); + interpreter + .push(0xdeadbeefu32.into()) + .expect("The stack should not overflow"); interpreter.run()?; assert!(interpreter.stack().is_empty()); @@ -290,7 +310,7 @@ fn test_revert() -> Result<()> { interpreter.generation_state.registers.gas_used = gas_before_checkpoint; // Now we change `val` 10 more times - for i in (10..20) { + for i in 10..20 { interpreter.generation_state.registers.program_counter = sys_tstore; interpreter.generation_state.registers.is_kernel = true; let kexit_info = U256::from(0xdeadbeefu32) @@ -298,9 +318,15 @@ fn test_revert() -> Result<()> { + (U256::from(interpreter.generation_state.registers.gas_used) << 192); let val: U256 = i.into(); let slot: U256 = 1.into(); - interpreter.push(val); - interpreter.push(slot); - interpreter.push(kexit_info); + interpreter + .push(val) + .expect("The stack should not overflow"); + interpreter + .push(slot) + .expect("The stack should not overflow"); + interpreter + .push(kexit_info) + .expect("The stack should not overflow"); interpreter.run()?; assert_eq!( @@ -315,9 +341,15 @@ fn test_revert() -> Result<()> { let kexit_info = U256::from(0xdeadbeefu32) + (U256::from(u64::from(true)) << 32) + (U256::from(interpreter.generation_state.registers.gas_used) << 192); - interpreter.push(3.into()); // val - interpreter.push(2.into()); // slot - interpreter.push(kexit_info); + interpreter + .push(3.into()) + .expect("The stack should not overflow"); // val + interpreter + .push(2.into()) + .expect("The stack should not overflow"); // slot + interpreter + .push(kexit_info) + .expect("The stack should not overflow"); assert!(interpreter.run().is_err()); // Now we should load the value before the revert @@ -326,8 +358,12 @@ fn test_revert() -> Result<()> { interpreter.generation_state.registers.gas_used = 0; let kexit_info = U256::from(0xdeadbeefu32) + (U256::from(u64::from(true)) << 32); interpreter.generation_state.registers.is_kernel = true; - interpreter.push(1.into()); - interpreter.push(kexit_info); + interpreter + .push(1.into()) + .expect("The stack should not overflow"); + interpreter + .push(kexit_info) + .expect("The stack should not overflow"); interpreter.run()?; diff --git a/evm_arithmetization/src/curve_pairings.rs b/evm_arithmetization/src/curve_pairings.rs index 2d9f8248d..f52d024d8 100644 --- a/evm_arithmetization/src/curve_pairings.rs +++ b/evm_arithmetization/src/curve_pairings.rs @@ -5,7 +5,7 @@ use rand::distributions::Standard; use rand::prelude::Distribution; use rand::Rng; -use crate::extension_tower::{Adj, FieldExt, Fp12, Fp2, Fp6, Stack, BLS381, BN254}; +use crate::extension_tower::{Adj, FieldExt, Fp12, Fp2, Fp6, Stack, BLS381}; #[derive(Debug, Copy, Clone, PartialEq)] pub(crate) struct CurveAff @@ -42,6 +42,7 @@ impl Stack for CurveAff { } } +#[cfg(test)] impl CurveAff where T: FieldExt, @@ -151,16 +152,6 @@ where pub z: T, } -impl CurveProj { - pub(crate) const fn unit() -> Self { - CurveProj { - x: T::ZERO, - y: T::ZERO, - z: T::ZERO, - } - } -} - impl Stack for CurveProj { const SIZE: usize = 3 * T::SIZE; @@ -182,6 +173,7 @@ impl Stack for CurveProj { /// The tangent and chord functions output sparse Fp12 elements. /// This map embeds the nonzero coefficients into an Fp12. +#[cfg(test)] pub(crate) const fn sparse_embed(g000: F, g01: Fp2, g11: Fp2) -> Fp12 where F: FieldExt, @@ -205,21 +197,8 @@ where Fp12 { z0: g0, z1: g1 } } -pub(crate) fn check_curve_eq_aff(p: CurveAff, b_coeff: T) -> bool -where - T: FieldExt, -{ - p.y * p.y == p.x * p.x * p.x + b_coeff -} - -pub(crate) fn check_curve_eq_proj(p: CurveProj, b_coeff: T) -> bool -where - T: FieldExt, -{ - p.y * p.y == p.x * p.x * p.x + b_coeff -} - /// Generates a sparse, random Fp12 element. +#[cfg(test)] pub(crate) fn gen_fp12_sparse(rng: &mut R) -> Fp12 where F: FieldExt, @@ -229,8 +208,10 @@ where sparse_embed::(rng.gen::(), rng.gen::>(), rng.gen::>()) } +#[cfg(test)] pub mod bn254 { use super::*; + use crate::extension_tower::BN254; /// The BN curve consists of pairs /// (x, y): (BN254, BN254) | y^2 = x^3 + 3 @@ -769,7 +750,6 @@ pub mod bls381 { z: Fp2::::UNIT, }; let mut acc: Fp12 = Fp12::::UNIT; - let mut line: Fp12; let mut found_one = false; for i in (0..64).rev().map(|b| (((X_GENERATOR >> 1) >> b) & 1) == 1) { @@ -1005,9 +985,8 @@ pub mod bls381 { #[cfg(test)] mod tests { - use rand::thread_rng; - use super::*; + use crate::extension_tower::BN254; #[test] fn test_bls_pairing() { diff --git a/evm_arithmetization/src/generation/prover_input.rs b/evm_arithmetization/src/generation/prover_input.rs index 2e05f8a2f..1ec692c0d 100644 --- a/evm_arithmetization/src/generation/prover_input.rs +++ b/evm_arithmetization/src/generation/prover_input.rs @@ -1,4 +1,3 @@ -use core::cmp::min; use core::mem::transmute; use core::ops::Neg; use std::collections::{BTreeSet, HashMap}; @@ -7,7 +6,6 @@ use std::str::FromStr; use anyhow::{bail, Error, Result}; use ethereum_types::{BigEndianHash, H256, U256, U512}; use itertools::Itertools; -use keccak_hash::keccak; use num_bigint::BigUint; use plonky2::field::types::Field; use serde::{Deserialize, Serialize}; @@ -29,7 +27,7 @@ use crate::generation::prover_input::FieldOp::{Inverse, Sqrt}; use crate::generation::state::GenerationState; use crate::memory::segments::Segment; use crate::memory::segments::Segment::BnPairing; -use crate::util::{biguint_to_mem_vec, h2u, mem_vec_to_biguint, sha2, u256_to_u8, u256_to_usize}; +use crate::util::{biguint_to_mem_vec, mem_vec_to_biguint, sha2, u256_to_u8, u256_to_usize}; use crate::witness::errors::ProverInputError::*; use crate::witness::errors::{ProgramError, ProverInputError}; use crate::witness::memory::MemoryAddress; @@ -304,8 +302,6 @@ impl GenerationState { )); }; - let jd_len = jumpdest_table.len(); - if let Some(ctx_jumpdest_table) = jumpdest_table.get_mut(&context) && let Some(next_jumpdest_address) = ctx_jumpdest_table.pop() { @@ -325,8 +321,6 @@ impl GenerationState { )); }; - let jd_len = jumpdest_table.len(); - if let Some(ctx_jumpdest_table) = jumpdest_table.get_mut(&context) && let Some(next_jumpdest_proof) = ctx_jumpdest_table.pop() { @@ -526,7 +520,7 @@ impl GenerationState { let mut z_bytes = [0u8; 32]; z.to_big_endian(&mut z_bytes); let mut acc = CurveAff::>::unit(); - for (i, &byte) in z_bytes.iter().enumerate() { + for byte in z_bytes.into_iter() { acc = acc * 256_i32; acc = acc + (CurveAff::>::GENERATOR * byte as i32); } diff --git a/evm_arithmetization/src/generation/state.rs b/evm_arithmetization/src/generation/state.rs index f62fe9902..bc1a02203 100644 --- a/evm_arithmetization/src/generation/state.rs +++ b/evm_arithmetization/src/generation/state.rs @@ -163,7 +163,7 @@ pub(crate) trait State { } } else { #[cfg(not(test))] - self.log_info(format!("CPU halted after {} cycles", self.get_clock())); + log::info!("CPU halted after {} cycles", self.get_clock()); return Ok(()); } } @@ -262,12 +262,6 @@ pub(crate) trait State { log::debug!("{}", msg); } - /// Logs `msg` in `info` mode. - #[inline] - fn log_info(&self, msg: String) { - log::info!("{}", msg); - } - /// Logs `msg` at `level`. #[inline] fn log(&self, level: Level, msg: String) { diff --git a/evm_arithmetization/src/generation/trie_extractor.rs b/evm_arithmetization/src/generation/trie_extractor.rs index 0d825643f..48fc28f53 100644 --- a/evm_arithmetization/src/generation/trie_extractor.rs +++ b/evm_arithmetization/src/generation/trie_extractor.rs @@ -1,9 +1,7 @@ //! Code for extracting trie data after witness generation. This is intended //! only for debugging. -use std::collections::HashMap; - -use ethereum_types::{BigEndianHash, H256, U256, U512}; +use ethereum_types::{BigEndianHash, H256, U256}; use mpt_trie::nibbles::{Nibbles, NibblesIntern}; use mpt_trie::partial_trie::{HashedPartialTrie, Node, PartialTrie, WrappedNode}; @@ -14,108 +12,10 @@ use crate::util::{u256_to_bool, u256_to_h160, u256_to_u8, u256_to_usize}; use crate::witness::errors::ProgramError; use crate::witness::memory::{MemoryAddress, MemoryState}; -/// Account data as it's stored in the state trie, with a pointer to the storage -/// trie. -#[derive(Debug)] -pub(crate) struct AccountTrieRecord { - pub(crate) nonce: u64, - pub(crate) balance: U256, - pub(crate) storage_ptr: usize, - pub(crate) code_hash: H256, -} - -pub(crate) fn read_state_trie_value( - slice: &[Option], -) -> Result { - Ok(AccountTrieRecord { - nonce: slice[0].unwrap_or_default().low_u64(), - balance: slice[1].unwrap_or_default(), - storage_ptr: u256_to_usize(slice[2].unwrap_or_default())?, - code_hash: H256::from_uint(&slice[3].unwrap_or_default()), - }) -} - pub(crate) fn read_storage_trie_value(slice: &[Option]) -> U256 { slice[0].unwrap_or_default() } -pub(crate) fn read_trie( - memory: &MemoryState, - ptr: usize, - read_value: fn(&[Option]) -> Result, -) -> Result, ProgramError> { - let mut res = HashMap::new(); - let empty_nibbles = Nibbles { - count: 0, - packed: NibblesIntern::zero(), - }; - read_trie_helper::(memory, ptr, read_value, empty_nibbles, &mut res)?; - Ok(res) -} - -pub(crate) fn read_trie_helper( - memory: &MemoryState, - ptr: usize, - read_value: fn(&[Option]) -> Result, - prefix: Nibbles, - res: &mut HashMap, -) -> Result<(), ProgramError> { - let load = |offset| memory.contexts[0].segments[Segment::TrieData as usize].content[offset]; - let load_slice_from = |init_offset| { - &memory.contexts[0].segments[Segment::TrieData.unscale()].content[init_offset..] - }; - - let trie_type = PartialTrieType::all()[u256_to_usize(load(ptr).unwrap_or_default())?]; - match trie_type { - PartialTrieType::Empty => Ok(()), - PartialTrieType::Hash => Ok(()), - PartialTrieType::Branch => { - let ptr_payload = ptr + 1; - for i in 0u8..16 { - let child_ptr = u256_to_usize(load(ptr_payload + i as usize).unwrap_or_default())?; - read_trie_helper::(memory, child_ptr, read_value, prefix.merge_nibble(i), res)?; - } - let value_ptr = u256_to_usize(load(ptr_payload + 16).unwrap_or_default())?; - if value_ptr != 0 { - res.insert(prefix, read_value(load_slice_from(value_ptr))?); - }; - - Ok(()) - } - PartialTrieType::Extension => { - let count = u256_to_usize(load(ptr + 1).unwrap_or_default())?; - let packed = load(ptr + 2).unwrap_or_default(); - let nibbles = Nibbles { - count, - packed: packed.into(), - }; - let child_ptr = u256_to_usize(load(ptr + 3).unwrap_or_default())?; - read_trie_helper::( - memory, - child_ptr, - read_value, - prefix.merge_nibbles(&nibbles), - res, - ) - } - PartialTrieType::Leaf => { - let count = u256_to_usize(load(ptr + 1).unwrap_or_default())?; - let packed = load(ptr + 2).unwrap_or_default(); - let nibbles = Nibbles { - count, - packed: packed.into(), - }; - let value_ptr = u256_to_usize(load(ptr + 3).unwrap_or_default())?; - res.insert( - prefix.merge_nibbles(&nibbles), - read_value(load_slice_from(value_ptr))?, - ); - - Ok(()) - } - } -} - pub(crate) fn read_receipt_trie_value( slice: &[Option], ) -> Result<(Option, LegacyReceiptRlp), ProgramError> { diff --git a/evm_arithmetization/src/lib.rs b/evm_arithmetization/src/lib.rs index 3809cc023..36814576c 100644 --- a/evm_arithmetization/src/lib.rs +++ b/evm_arithmetization/src/lib.rs @@ -181,7 +181,6 @@ #![allow(clippy::needless_range_loop)] #![allow(clippy::too_many_arguments)] #![allow(clippy::field_reassign_with_default)] -#![allow(unused)] #![feature(let_chains)] // Individual STARK processing units diff --git a/evm_arithmetization/src/memory/segments.rs b/evm_arithmetization/src/memory/segments.rs index 67aa93a6d..896d5571f 100644 --- a/evm_arithmetization/src/memory/segments.rs +++ b/evm_arithmetization/src/memory/segments.rs @@ -3,7 +3,6 @@ pub(crate) const SEGMENT_SCALING_FACTOR: usize = 32; /// This contains all the existing memory segments. The values in the enum are /// shifted by 32 bits to allow for convenient address components (context / /// segment / virtual) bundling in the kernel. -#[allow(dead_code)] #[allow(clippy::enum_clike_unportable_variant)] #[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Debug)] pub(crate) enum Segment { diff --git a/evm_arithmetization/src/testing_utils.rs b/evm_arithmetization/src/testing_utils.rs index a72c1359f..bb1f07b64 100644 --- a/evm_arithmetization/src/testing_utils.rs +++ b/evm_arithmetization/src/testing_utils.rs @@ -59,19 +59,6 @@ pub fn create_account_storage(storage_pairs: &[(U256, U256)]) -> anyhow::Result< Ok(trie) } -/// Creates the storage trie of the beacon roots contract account at the -/// provided timestamp. Not passing any parent root will consider the parent -/// root at genesis, i.e. the empty hash. -fn beacon_roots_contract_storage( - timestamp: U256, - parent_root: H256, -) -> anyhow::Result { - let timestamp_idx = timestamp % HISTORY_BUFFER_LENGTH.1; - let root_idx = timestamp_idx + HISTORY_BUFFER_LENGTH.1; - - create_account_storage(&[(timestamp_idx, timestamp), (root_idx, h2u(parent_root))]) -} - /// Updates the beacon roots account storage with the provided timestamp and /// block parent root. pub fn update_beacon_roots_account_storage( diff --git a/evm_arithmetization/src/util.rs b/evm_arithmetization/src/util.rs index 63adf7103..38503a091 100644 --- a/evm_arithmetization/src/util.rs +++ b/evm_arithmetization/src/util.rs @@ -127,17 +127,6 @@ pub(crate) fn h256_limbs(h256: H256) -> [F; 8] { .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() -} - pub(crate) const fn indices_arr() -> [usize; N] { let mut indices_arr = [0; N]; let mut i = 0; diff --git a/evm_arithmetization/src/verifier.rs b/evm_arithmetization/src/verifier.rs index 4de9b1b17..90a287f1e 100644 --- a/evm_arithmetization/src/verifier.rs +++ b/evm_arithmetization/src/verifier.rs @@ -294,6 +294,7 @@ where running_sum + challenge.combine(row.iter()).inverse() } +#[cfg(debug_assertions)] pub(crate) mod debug_utils { use super::*; From aea633c6fe78daa71d66865aa4af05d4714773da Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Mon, 27 May 2024 05:31:52 -0400 Subject: [PATCH 25/40] fix(cancun): dummy payloads and public input retrieval (#249) --- evm_arithmetization/src/proof.rs | 8 ++++---- trace_decoder/src/decoding.rs | 25 ++++++++++++++++++------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/evm_arithmetization/src/proof.rs b/evm_arithmetization/src/proof.rs index 56ff90dc1..076551dd8 100644 --- a/evm_arithmetization/src/proof.rs +++ b/evm_arithmetization/src/proof.rs @@ -211,12 +211,12 @@ impl BlockMetadata { (pis[18].to_canonical_u64() + (pis[19].to_canonical_u64() << 32)).into(); let block_gas_used = pis[20].to_canonical_u64().into(); let block_blob_gas_used = - (pis[23].to_canonical_u64() + (pis[24].to_canonical_u64() << 32)).into(); + (pis[21].to_canonical_u64() + (pis[22].to_canonical_u64() << 32)).into(); let block_excess_blob_gas = - (pis[25].to_canonical_u64() + (pis[26].to_canonical_u64() << 32)).into(); - let parent_beacon_block_root = get_h256(&pis[27..35]); + (pis[23].to_canonical_u64() + (pis[24].to_canonical_u64() << 32)).into(); + let parent_beacon_block_root = get_h256(&pis[25..33]); let block_bloom = - core::array::from_fn(|i| h2u(get_h256(&pis[35 + 8 * i..35 + 8 * (i + 1)]))); + core::array::from_fn(|i| h2u(get_h256(&pis[33 + 8 * i..33 + 8 * (i + 1)]))); Self { block_beneficiary, diff --git a/trace_decoder/src/decoding.rs b/trace_decoder/src/decoding.rs index fda6a9c76..486c28ca2 100644 --- a/trace_decoder/src/decoding.rs +++ b/trace_decoder/src/decoding.rs @@ -1,7 +1,7 @@ use std::{ collections::HashMap, fmt::{self, Display, Formatter}, - iter::{self, once}, + iter::once, }; use ethereum_types::{Address, BigEndianHash, H256, U256, U512}; @@ -256,13 +256,13 @@ impl ProcessedBlockTrace { let mut txn_gen_inputs = self .txn_info .into_iter() - .enumerate() - .map(|(idx, txn_info)| { + .map(|txn_info| { + let is_initial_payload = txn_idx == 0; + let current_idx = txn_idx; if !txn_info.meta.is_dummy() { txn_idx += 1; } - let is_initial_payload = idx == 0; Self::process_txn_info( current_idx, @@ -645,10 +645,21 @@ impl ProcessedBlockTrace { // state accesses to the withdrawal addresses. let withdrawal_addrs = withdrawals_with_hashed_addrs_iter().map(|(_, h_addr, _)| h_addr); + + let additional_paths = if last_inputs.txn_number_before == 0.into() { + // We need to include the beacon roots contract as this payload is at the + // start of the block execution. + vec![Nibbles::from_h256_be(H256( + BEACON_ROOTS_CONTRACT_ADDRESS_HASHED, + ))] + } else { + vec![] + }; + last_inputs.tries.state_trie = create_minimal_state_partial_trie( &final_trie_state.state, withdrawal_addrs, - iter::empty(), + additional_paths.into_iter(), )?; } @@ -716,7 +727,7 @@ impl ProcessedBlockTrace { ); // For each non-dummy txn, we increment `txn_number_after` by 1, and // update `gas_used_after` accordingly. - extra_data.txn_number_after += U256::one(); + extra_data.txn_number_after += U256::from(!txn_info.meta.is_dummy() as u8); extra_data.gas_used_after += txn_info.meta.gas_used.into(); // Because we need to run delta application before creating the minimal @@ -772,7 +783,7 @@ impl ProcessedBlockTrace { // After processing a transaction, we update the remaining accumulators // for the next transaction. - extra_data.txn_number_before += U256::one(); + extra_data.txn_number_before = extra_data.txn_number_after; extra_data.gas_used_before = extra_data.gas_used_after; Ok(gen_inputs) From d3e8357d4cbf830c19bad1147cae6646f104cb52 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Mon, 27 May 2024 21:45:01 -0400 Subject: [PATCH 26/40] fix: encode calldata for EIP-4780 as U256 (#253) --- trace_decoder/src/decoding.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/trace_decoder/src/decoding.rs b/trace_decoder/src/decoding.rs index 486c28ca2..e5ac34a3c 100644 --- a/trace_decoder/src/decoding.rs +++ b/trace_decoder/src/decoding.rs @@ -311,7 +311,10 @@ impl ProcessedBlockTrace { let timestamp = rlp::encode(&block_data.block_timestamp).to_vec(); let root_idx = timestamp_idx + HISTORY_BUFFER_LENGTH_MOD; - let calldata = rlp::encode(&block_data.parent_beacon_block_root).to_vec(); + let calldata = rlp::encode(&U256::from_big_endian( + &block_data.parent_beacon_block_root.0, + )) + .to_vec(); let storage_trie = trie_state .storage From 82d49012af1b5296c8ec8afc9f9a8fcde51c4a69 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Tue, 28 May 2024 07:11:05 -0400 Subject: [PATCH 27/40] fix: handle KZG precompile errors properly (#251) --- .../kernel/asm/core/precompiles/kzg_peval.asm | 3 + .../src/cpu/kernel/tests/bls381.rs | 14 ++-- .../src/generation/prover_input.rs | 73 +++++++------------ 3 files changed, 37 insertions(+), 53 deletions(-) diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/kzg_peval.asm b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/kzg_peval.asm index 796f5b390..5a8a372e4 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/kzg_peval.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/precompiles/kzg_peval.asm @@ -53,6 +53,9 @@ global precompile_kzg_peval: global verify_kzg_proof: // stack: versioned_hash, z, y, comm_hi, comm_lo, proof_hi, proof_lo, base_addr, kexit_info PROVER_INPUT(kzg_point_eval) + DUP1 ISZERO + // stack: is_invalid, res_hi, versioned_hash, z, y, comm_hi, comm_lo, proof_hi, proof_lo, base_addr, kexit_info + %jumpi(fault_exception) PROVER_INPUT(kzg_point_eval_2) // stack: res_lo, res_hi, versioned_hash, z, y, comm_hi, comm_lo, proof_hi, proof_lo, base_addr, kexit_info %stack (res_lo, res_hi, versioned_hash, z, y, comm_hi, comm_lo, proof_hi, proof_lo, base_addr, kexit_info) -> diff --git a/evm_arithmetization/src/cpu/kernel/tests/bls381.rs b/evm_arithmetization/src/cpu/kernel/tests/bls381.rs index 9fba3e7b4..0910ec75c 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/bls381.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/bls381.rs @@ -145,7 +145,10 @@ fn test_kzg_peval_precompile() -> Result<()> { let verify_kzg_proof = KERNEL.global_labels["verify_kzg_proof"]; let mut interpreter: Interpreter = Interpreter::new(verify_kzg_proof, stack); - interpreter.halt_offsets = vec![KERNEL.global_labels["store_kzg_verification"]]; + interpreter.halt_offsets = vec![ + KERNEL.global_labels["store_kzg_verification"], + KERNEL.global_labels["fault_exception"], + ]; if *is_correct { interpreter.run().unwrap(); @@ -161,10 +164,11 @@ fn test_kzg_peval_precompile() -> Result<()> { U256::from_big_endian(&POINT_EVALUATION_PRECOMPILE_RETURN_VALUE[1]) ); } else { - assert!(interpreter.run().is_err()); - - let err_msg = interpreter.run().unwrap_err(); - assert!(err_msg.to_string().contains("KzgEvalFailure")); + interpreter.run().unwrap(); + assert_eq!( + interpreter.generation_state.registers.program_counter, + KERNEL.global_labels["fault_exception"] + ); } } diff --git a/evm_arithmetization/src/generation/prover_input.rs b/evm_arithmetization/src/generation/prover_input.rs index 1ec692c0d..fd3c40117 100644 --- a/evm_arithmetization/src/generation/prover_input.rs +++ b/evm_arithmetization/src/generation/prover_input.rs @@ -412,26 +412,15 @@ impl GenerationState { let proof_lo = stack_peek(self, 6)?; // Validate scalars - if z > BLS_SCALAR { - return Err(ProgramError::ProverInputError( - ProverInputError::KzgEvalFailure("z is not canonical.".to_string()), - )); - } - if y > BLS_SCALAR { - return Err(ProgramError::ProverInputError( - ProverInputError::KzgEvalFailure("y is not canonical.".to_string()), - )); + if z > BLS_SCALAR || y > BLS_SCALAR { + return Ok(U256::zero()); } let mut comm_bytes = [0u8; 48]; comm_lo.to_big_endian(&mut comm_bytes[16..48]); // only actually 16 bytes if comm_bytes[16..32] != [0; 16] { // Commitments must fit in 48 bytes. - return Err(ProgramError::ProverInputError( - ProverInputError::KzgEvalFailure( - "Commitment does not fit in 48 bytes.".to_string(), - ), - )); + return Ok(U256::zero()); } comm_hi.to_big_endian(&mut comm_bytes[0..32]); @@ -439,9 +428,7 @@ impl GenerationState { proof_lo.to_big_endian(&mut proof_bytes[16..48]); // only actually 16 bytes if proof_bytes[16..32] != [0; 16] { // Proofs must fit in 48 bytes. - return Err(ProgramError::ProverInputError( - ProverInputError::KzgEvalFailure("Proof does not fit in 48 bytes.".to_string()), - )); + return Ok(U256::zero()); } proof_hi.to_big_endian(&mut proof_bytes[0..32]); @@ -457,38 +444,40 @@ impl GenerationState { expected_versioned_hash |= U256::from(KZG_VERSIONED_HASH) << 248; // append 1 if versioned_hash != expected_versioned_hash { - return Err(ProgramError::ProverInputError( - ProverInputError::KzgEvalFailure( - "Versioned hash does not match expected value.".to_string(), - ), - )); + return Ok(U256::zero()); } self.verify_kzg_proof(&comm_bytes, z, y, &proof_bytes) } /// Returns the second part of the KZG precompile output. - /// The POINT_EVALUATION_PRECOMPILE returns a 64-byte value. Because EVM - /// words only fit in 32 bytes, we read the previously pushed value and - /// then accordingly push the following word. + /// + /// The POINT_EVALUATION_PRECOMPILE returns a 64-byte value. + /// Because EVM words only fit in 32 bytes, we need to push + /// the following word separately. fn run_kzg_point_eval_2(&mut self) -> Result { let prev_value = stack_peek(self, 0)?; - if prev_value == U256::from_big_endian(&POINT_EVALUATION_PRECOMPILE_RETURN_VALUE[1]) { - Ok(U256::from_big_endian( - &POINT_EVALUATION_PRECOMPILE_RETURN_VALUE[0], - )) - } else { - Err(ProgramError::ProverInputError( + // `run_kzg_point_eval_1` should return 0 upon failure, which should be caught + // in the Kernel. Ending up here should hence not happen. + if prev_value != U256::from_big_endian(&POINT_EVALUATION_PRECOMPILE_RETURN_VALUE[1]) { + return Err(ProgramError::ProverInputError( ProverInputError::KzgEvalFailure( - "run_kzg_point_eval_1 should have output the expected return value or errored" + "run_kzg_point_eval_1 should have output the expected return value at this point" .to_string(), ), - )) + )); } + + Ok(U256::from_big_endian( + &POINT_EVALUATION_PRECOMPILE_RETURN_VALUE[0], + )) } /// Verifies a KZG proof, i.e. that the commitment opens to y at z. + /// + /// Returns `0` upon failure of one of the checks, or `BLS_MODULUS` upon + /// success. fn verify_kzg_proof( &self, comm_bytes: &[u8; 48], @@ -499,21 +488,13 @@ impl GenerationState { let comm = if let Ok(c) = bls381::g1_from_bytes(comm_bytes) { c } else { - return Err(ProgramError::ProverInputError( - ProverInputError::KzgEvalFailure( - "Commitment did not deserialize into a valid G1 point.".to_string(), - ), - )); + return Ok(U256::zero()); }; let proof = if let Ok(p) = bls381::g1_from_bytes(proof_bytes) { p } else { - return Err(ProgramError::ProverInputError( - ProverInputError::KzgEvalFailure( - "Proof did not deserialize into a valid G1 point.".to_string(), - ), - )); + return Ok(U256::zero()); }; // TODO: use some WNAF method if performance becomes critical @@ -561,11 +542,7 @@ impl GenerationState { * bls381::ate_optim(proof, x_minus_z) != Fp12::::UNIT { - Err(ProgramError::ProverInputError( - ProverInputError::KzgEvalFailure( - "Final pairing check did not succeed.".to_string(), - ), - )) + Ok(U256::zero()) } else { Ok(U256::from_big_endian( &POINT_EVALUATION_PRECOMPILE_RETURN_VALUE[1], From e48f8e9ee89789ff4e69dfd585d05b120999801c Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Wed, 29 May 2024 12:23:31 -0400 Subject: [PATCH 28/40] fix(cancun): `mcopy` check offsets and overwrites (#252) * fix: check invalid offsets * Update per Will's comment * Minor * fix(cancun): revamp `MCOPY` with overlapping (#254) --- .../src/cpu/kernel/asm/memory/memcpy.asm | 53 +++++++- .../src/cpu/kernel/asm/memory/syscalls.asm | 80 +++++++----- .../src/cpu/kernel/tests/mcopy.rs | 114 ++++++++++++++++++ .../src/cpu/kernel/tests/mod.rs | 1 + 4 files changed, 217 insertions(+), 31 deletions(-) create mode 100644 evm_arithmetization/src/cpu/kernel/tests/mcopy.rs diff --git a/evm_arithmetization/src/cpu/kernel/asm/memory/memcpy.asm b/evm_arithmetization/src/cpu/kernel/asm/memory/memcpy.asm index a7819bf6e..09f686965 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/memory/memcpy.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/memory/memcpy.asm @@ -45,7 +45,7 @@ global memcpy_bytes: %lt_const(0x21) // stack: count <= 32, DST, SRC, count, retdest %jumpi(memcpy_bytes_finish) - + // We will pack 32 bytes into a U256 from the source, and then unpack it at the destination. // Copy the next chunk of bytes. // stack: DST, SRC, count, retdest @@ -104,3 +104,54 @@ memcpy_finish: %jump(memcpy_bytes) %%after: %endmacro + +// Similar logic to memcpy_bytes, but proceeding the sequence in the backwards direction. +// Note that this is slightly heavier than the regular `memcpy_bytes`. +global memcpy_bytes_backwards: + // stack: DST, SRC, count, retdest + + // Handle small case + DUP3 + // stack: count, DST, SRC, count, retdest + %lt_const(0x21) + // stack: count <= 32, DST, SRC, count, retdest + %jumpi(memcpy_bytes_finish) + + // We will pack 32 bytes into a U256 from the source, and then unpack it at the destination. + // Copy the next chunk of bytes. + // stack: DST, SRC, count, retdest + PUSH 0x20 + DUP3 + // stack: SRC, 32, DST, SRC, count, retdest + MLOAD_32BYTES + // stack: value, DST, SRC, count, retdest + SWAP1 + // stack: DST, value, SRC, count, retdest + MSTORE_32BYTES_32 + // stack: DST'', SRC, count, retdest + + // Decrement count by 32. + SWAP2 + %sub_const(0x20) + SWAP2 + + // Decrement DST'' by 32 (from `MSTORE_32BYTES_32` increment) + min(32, count') for the next chunk. + // Decrement SRC by min(32, count'). + // stack: DST'', SRC, count', retdest + DUP3 PUSH 0x20 %min + // stack: min(32, count'), DST'', SRC, count', retdest + DUP1 %add_const(0x20) + // stack: 32 + min(32, count'), min(32, count'), DST'', SRC, count', retdest + SWAP3 SUB + // stack: SRC' = SRC-min(32, count'), DST'', 32 + min(32, count'), count', retdest + SWAP2 SWAP1 SUB + // stack: DST' = DST''-(32+min(32, count')), SRC', count', retdest + + // Continue the loop. + %jump(memcpy_bytes_backwards) + +%macro memcpy_bytes_backwards + %stack (dst, src, count) -> (dst, src, count, %%after) + %jump(memcpy_bytes_backwards) +%%after: +%endmacro diff --git a/evm_arithmetization/src/cpu/kernel/asm/memory/syscalls.asm b/evm_arithmetization/src/cpu/kernel/asm/memory/syscalls.asm index 97607d191..de04c111e 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/memory/syscalls.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/memory/syscalls.asm @@ -231,6 +231,11 @@ global sys_mcopy: DUP1 %ensure_reasonable_offset %update_mem_bytes + %stack (kexit_info, dest_offset, offset, size) -> (offset, size, kexit_info, dest_offset, offset, size) + %add_or_fault + DUP1 %ensure_reasonable_offset + %update_mem_bytes + // stack: kexit_info, dest_offset, offset, size DUP3 DUP3 EQ // stack: dest_offset = offset, kexit_info, dest_offset, offset, size @@ -239,46 +244,61 @@ global sys_mcopy: // stack: kexit_info, dest_offset, offset, size GET_CONTEXT PUSH @SEGMENT_MAIN_MEMORY - DUP6 DUP6 ADD - // stack: offset + size, segment, context, kexit_info, dest_offset, offset, size - DUP5 LT - // stack: dest_offset < offset + size, segment, context, kexit_info, dest_offset, offset, size - DUP6 DUP6 GT - // stack: dest_offset > offset, dest_offset < offset + size, segment, context, kexit_info, dest_offset, offset, size + + DUP5 DUP5 LT + // stack: dest_offset < offset, kexit_info, dest_offset, offset, size + %jumpi(wcopy_within_bounds) + + // stack: segment, context, kexit_info, dest_offset, offset, size + DUP6 PUSH 32 %min + // stack: shift=min(size, 32), segment, context, kexit_info, dest_offset, offset, size + DUP6 DUP8 ADD + // stack: offset + size, shift, segment, context, kexit_info, dest_offset, offset, size + DUP6 LT + // stack: dest_offset < offset + size, shift, segment, context, kexit_info, dest_offset, offset, size + DUP2 + // stack: shift, dest_offset < offset + size, shift, segment, context, kexit_info, dest_offset, offset, size + DUP9 GT + // stack: size > shift, dest_offset < offset + size, shift, segment, context, kexit_info, dest_offset, offset, size MUL // AND - // stack: (dest_offset > offset) && (dest_offset < offset + size), segment, context, kexit_info, dest_offset, offset, size + // stack: (size > shift) && (dest_offset < offset + size), shift, segment, context, kexit_info, dest_offset, offset, size - // If both conditions are satisfied, that means we will get an overlap, in which case we need to process the copy - // in two chunks to prevent overwriting memory data before reading it. + // If the conditions `size > shift` and `dest_offset < offset + size` are satisfied, that means + // we will get an overlap that will overwrite some SRC data. In that case, we will proceed to the + // memcpy in the backwards direction to never overwrite the SRC section before it has been read. %jumpi(mcopy_with_overlap) - // stack: segment, context, kexit_info, dest_offset, offset, size + // Otherwise, we either have `SRC` < `DST`, or a small enough `size` that a single loop of + // `memcpy_bytes` suffices and does not risk to overwrite `SRC` data before being read. + // stack: shift, segment, context, kexit_info, dest_offset, offset, size + POP %jump(wcopy_within_bounds) mcopy_with_overlap: - // We do have an overlap between the SRC and DST ranges. We will first copy the overlapping segment - // (i.e. end of the copy portion), then copy the remaining (i.e. beginning) portion. - - // stack: segment, context, kexit_info, dest_offset, offset, size - DUP5 DUP5 SUB - // stack: remaining_size = dest_offset - offset, segment, context, kexit_info, dest_offset, offset, size - DUP1 DUP8 - SUB // overlapping_size = size - remaining_size - // stack: overlapping_size, remaining_size, segment, context, kexit_info, dest_offset, offset, size - - // Shift the initial offsets to copy the overlapping segment first. - DUP2 DUP8 ADD - // stack: offset_first_copy, overlapping_size, remaining_size, segment, context, kexit_info, dest_offset, offset, size - DUP3 DUP8 ADD - // stack: dest_offset_first_copy, offset_first_copy, overlapping_size, remaining_size, segment, context, kexit_info, dest_offset, offset, size - - %stack (dest_offset_first_copy, offset_first_copy, overlapping_size, remaining_size, segment, context, kexit_info, dest_offset, offset, size) -> - (context, segment, offset_first_copy, segment, dest_offset_first_copy, context, overlapping_size, wcopy_within_bounds, segment, context, kexit_info, dest_offset, offset, remaining_size) + // We do have an overlap between the SRC and DST ranges. + // We will proceed to `memcpy` in the backwards direction to prevent overwriting unread SRC data. + // For this, we need to update `offset` and `dest_offset` to their final position, corresponding + // to `x + size - min(32, size)`. + + // stack: shift=min(size, 32), segment, context, kexit_info, dest_offset, offset, size + DUP1 + // stack: shift, shift, segment, context, kexit_info, dest_offset, offset, size + DUP8 DUP8 ADD + // stack: offset+size, shift, shift, segment, context, kexit_info, dest_offset, offset, size + SUB + // stack: offset'=offset+size-shift, shift, segment, context, kexit_info, dest_offset, offset, size + SWAP5 DUP8 ADD + // stack: dest_offset+size, shift, segment, context, kexit_info, offset', offset, size + SUB + // stack: dest_offset'=dest_offset+size-shift, segment, context, kexit_info, offset', offset, size + + %stack (next_dst_offset, segment, context, kexit_info, new_offset, offset, size) -> + (context, segment, new_offset, segment, next_dst_offset, context, size, wcopy_after, kexit_info) %build_address // SRC SWAP3 %build_address // DST - // stack: DST, SRC, overlapping_size, wcopy_within_bounds, segment, context, kexit_info, dest_offset, offset, remaining_size - %jump(memcpy_bytes) + // stack: DST, SRC, size, wcopy_after, kexit_info + %jump(memcpy_bytes_backwards) mcopy_empty: // kexit_info, dest_offset, offset, size diff --git a/evm_arithmetization/src/cpu/kernel/tests/mcopy.rs b/evm_arithmetization/src/cpu/kernel/tests/mcopy.rs new file mode 100644 index 000000000..07ca1bda8 --- /dev/null +++ b/evm_arithmetization/src/cpu/kernel/tests/mcopy.rs @@ -0,0 +1,114 @@ +use anyhow::Result; +use ethereum_types::U256; +use hex_literal::hex; +use itertools::Itertools; +use plonky2::field::goldilocks_field::GoldilocksField as F; + +use crate::cpu::kernel::constants::context_metadata::ContextMetadata; +use crate::cpu::kernel::interpreter::Interpreter; +use crate::memory::segments::Segment; +use crate::testing_utils::init_logger; + +fn test_mcopy( + dest_offset: usize, + offset: usize, + size: usize, + pre_memory: &[u8], + post_memory: &[u8], +) -> Result<()> { + init_logger(); + + let sys_mcopy = crate::cpu::kernel::aggregator::KERNEL.global_labels["sys_mcopy"]; + let kexit_info = U256::from(0xdeadbeefu32) + (U256::from(u64::from(true)) << 32); + let initial_stack = vec![size.into(), offset.into(), dest_offset.into(), kexit_info]; + + let mut interpreter: Interpreter = Interpreter::new(sys_mcopy, initial_stack); + interpreter.set_context_metadata_field( + 0, + ContextMetadata::GasLimit, + U256::from(1000000000000u64), + ); + + let pre_memory: Vec = pre_memory.iter().map(|&b| b.into()).collect_vec(); + let post_memory: Vec = post_memory.iter().map(|&b| b.into()).collect_vec(); + + interpreter.set_memory_segment(Segment::MainMemory, pre_memory); + interpreter.run()?; + + let main_memory_data = interpreter.get_memory_segment(Segment::MainMemory); + assert_eq!(&main_memory_data, &post_memory); + + Ok(()) +} + +#[test] +fn test_mcopy_0_32_32() { + let dest_offset = 0; + let offset = 32; + let size = 32; + let pre_memory = hex!("0000000000000000000000000000000000000000000000000000000000000000000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"); + let post_memory = hex!("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"); + + assert!(test_mcopy(dest_offset, offset, size, &pre_memory, &post_memory).is_ok()) +} + +#[test] +fn test_mcopy_0_0_32() { + let dest_offset = 0; + let offset = 0; + let size = 32; + let pre_memory = hex!("0101010101010101010101010101010101010101010101010101010101010101"); + let post_memory = hex!("0101010101010101010101010101010101010101010101010101010101010101"); + + assert!(test_mcopy(dest_offset, offset, size, &pre_memory, &post_memory).is_ok()) +} + +#[test] +fn test_mcopy_0_1_8() { + let dest_offset = 0; + let offset = 1; + let size = 8; + let pre_memory = hex!("0001020304050607080000000000000000000000000000000000000000000000"); + let post_memory = hex!("0102030405060708080000000000000000000000000000000000000000000000"); + + assert!(test_mcopy(dest_offset, offset, size, &pre_memory, &post_memory).is_ok()) +} + +#[test] +fn test_mcopy_1_0_8() { + let dest_offset = 1; + let offset = 0; + let size = 8; + let pre_memory = hex!("0001020304050607080000000000000000000000000000000000000000000000"); + let post_memory = hex!("0000010203040506070000000000000000000000000000000000000000000000"); + + assert!(test_mcopy(dest_offset, offset, size, &pre_memory, &post_memory).is_ok()) +} + +#[test] +fn test_mcopy_1_0_33() { + init_logger(); + let dest_offset = 1; + let offset = 0; + let size = 33; + let pre_memory = + hex!("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021222324252627"); + let post_memory = + hex!("00000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20222324252627"); + + assert!(test_mcopy(dest_offset, offset, size, &pre_memory, &post_memory).is_ok()) +} + +#[test] +fn test_mcopy_1_2_33() { + init_logger(); + let dest_offset = 1; + let offset = 2; + let size = 33; + let pre_memory = + hex!("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728"); + let post_memory = + hex!("0002030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212222232425262728"); + + assert!(test_mcopy(dest_offset, offset, size, &pre_memory, &post_memory).is_ok()) +} diff --git a/evm_arithmetization/src/cpu/kernel/tests/mod.rs b/evm_arithmetization/src/cpu/kernel/tests/mod.rs index 8ce0e7604..c44eb8454 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/mod.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/mod.rs @@ -13,6 +13,7 @@ mod exp; mod hash; mod kernel_consistency; mod log; +mod mcopy; mod mpt; mod packing; mod receipt; From 275c2dd0ac07393e99ae3942e54c3f4e5e0205c9 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Mon, 3 Jun 2024 12:13:18 -0400 Subject: [PATCH 29/40] fix(cancun): correct search loop in transient storage (#257) --- .../asm/journal/transient_storage_change.asm | 8 +- .../src/cpu/kernel/asm/main.asm | 3 + .../kernel/asm/memory/transient_storage.asm | 31 ++++-- .../src/cpu/kernel/tests/transient_storage.rs | 102 +++++++----------- 4 files changed, 65 insertions(+), 79 deletions(-) diff --git a/evm_arithmetization/src/cpu/kernel/asm/journal/transient_storage_change.asm b/evm_arithmetization/src/cpu/kernel/asm/journal/transient_storage_change.asm index dcd43e456..ce74df0a2 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/journal/transient_storage_change.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/journal/transient_storage_change.asm @@ -9,13 +9,15 @@ global revert_transient_storage_change: POP %journal_load_3 // We will always write 0 for deletions as it makes no difference. - // stack: address, slot, prev_value, retdest + // stack: addr, slot, prev_value, retdest %search_transient_storage + // stack: found, pos, addr, value, slot, prev_value, retdest // The value must have been stored %assert_nonzero - // stack: pos, addr, value, key, prev_value, retdest + // stack: pos, addr, value, slot, prev_value, retdest %add_const(2) DUP5 + // stack: prev_value, pos+2, addr, value, slot, prev_value, retdest MSTORE_GENERAL %pop4 - JUMP \ No newline at end of file + JUMP diff --git a/evm_arithmetization/src/cpu/kernel/asm/main.asm b/evm_arithmetization/src/cpu/kernel/asm/main.asm index 234643da2..5d6d96799 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/main.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/main.asm @@ -15,6 +15,9 @@ global main: // Initialize accessed addresses and storage keys lists %init_access_lists + // Initialize transient storage length + %init_transient_storage_len + // Initialize the RLP DATA pointer to its initial position, // skipping over the preinitialized empty node. PUSH @INITIAL_TXN_RLP_ADDR diff --git a/evm_arithmetization/src/cpu/kernel/asm/memory/transient_storage.asm b/evm_arithmetization/src/cpu/kernel/asm/memory/transient_storage.asm index 6c7b3929c..b2530240c 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/memory/transient_storage.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/memory/transient_storage.asm @@ -8,6 +8,13 @@ /// If the key isn't found in the array, it is inserted at the end. /// TODO: Look into using a more efficient data structure. +/// The initial length, 0, must be scaled by its segment for +/// comparison with the accumulator when iterating through the list. +%macro init_transient_storage_len + PUSH @SEGMENT_TRANSIENT_STORAGE + %mstore_global_metadata(@GLOBAL_METADATA_TRANSIENT_STORAGE_LEN) +%endmacro + %macro search_transient_storage %stack (addr, key) -> (addr, key, %%after) %jump(search_transient_storage) @@ -15,14 +22,15 @@ // stack: (is_present, pos, addr, key, val) %endmacro -/// Looks for an address, key pair into the transient storage. Returns 1 and the position in @SEGMENT_TRANSIENT_STORAGE -/// if present or 0 and @GLOBAL_METADATA_TRANSIENT_STORAGE_LEN if not. +/// Looks for an address, key pair into the transient storage. +/// Returns 1 and the position in @SEGMENT_TRANSIENT_STORAGE if present, +/// or 0 and @GLOBAL_METADATA_TRANSIENT_STORAGE_LEN if not. global search_transient_storage: // stack: addr, key, retdest %mload_global_metadata(@GLOBAL_METADATA_TRANSIENT_STORAGE_LEN) // stack: len, addr, key, retdest - PUSH @SEGMENT_TRANSIENT_STORAGE ADD PUSH @SEGMENT_TRANSIENT_STORAGE + // stack: i = 0, len, addr, key, retdest search_transient_storage_loop: // `i` and `len` are both scaled by SEGMENT_TRANSIENT_STORAGE %stack (i, len, addr, key, retdest) -> (i, len, i, len, addr, key, retdest) @@ -56,6 +64,7 @@ search_transient_storage_not_found: JUMP search_transient_storage_found: + // stack: i, len, addr, key, retdest DUP1 %add_const(2) MLOAD_GENERAL %stack (val, i, len, addr, key, retdest) -> (retdest, 1, i, addr, val, key) // Return 1 to indicate that the address was already present. @@ -119,6 +128,7 @@ global sys_tstore: MSTORE_GENERAL %increment DUP1 DUP6 + // stack: value, pos'', pos'', addr, original_value, slot, value, kexit_info MSTORE_GENERAL // stack: pos'', addr, original_value, slot, value, kexit_info // If pos'' > @GLOBAL_METADATA_TRANSIENT_STORAGE_LEN we need to also store the new @GLOBAL_METADATA_TRANSIENT_STORAGE_LEN @@ -133,10 +143,10 @@ sys_tstore_charge_gas: %stack (addr, original_value, slot, value, kexit_info) -> (value, original_value, addr, slot, original_value, kexit_info) - EQ %jumpi(sstore_noop) + EQ %jumpi(tstore_noop) +add_to_journal: // stack: addr, slot, original_value, kexit_info -global debug_journal: %journal_add_transient_storage_change // stack: kexit_info @@ -144,15 +154,14 @@ global debug_journal: new_transient_storage_len: // Store the new (unscaled) length. - // stack: addr, original_value, slot, value, kexit_info - PUSH @SEGMENT_TRANSIENT_STORAGE - PUSH 1 - SUB // 1 - seg - ADD // new_len = (addr - seg) + 1 + // stack: pos, addr, original_value, slot, value, kexit_info + %increment + // stack: pos + 1, addr, original_value, slot, value, kexit_info %mstore_global_metadata(@GLOBAL_METADATA_TRANSIENT_STORAGE_LEN) + // stack: addr, original_value, slot, value, kexit_info %jump(sys_tstore_charge_gas) -sstore_noop: +tstore_noop: // stack: current_value, slot, value, kexit_info %pop3 EXIT_KERNEL diff --git a/evm_arithmetization/src/cpu/kernel/tests/transient_storage.rs b/evm_arithmetization/src/cpu/kernel/tests/transient_storage.rs index f1ec59618..774ac51d7 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/transient_storage.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/transient_storage.rs @@ -2,46 +2,64 @@ use anyhow::Result; use ethereum_types::U256; use once_cell::sync::Lazy; use plonky2::field::goldilocks_field::GoldilocksField as F; +use plonky2::field::types::Field; use crate::cpu::kernel::aggregator::{ combined_kernel_from_files, KERNEL_FILES, NUMBER_KERNEL_FILES, }; use crate::cpu::kernel::assembler::Kernel; use crate::cpu::kernel::constants::context_metadata::ContextMetadata; +use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; use crate::cpu::kernel::interpreter::Interpreter; use crate::generation::state::GenerationState; use crate::memory::segments::Segment; use crate::witness::memory::MemoryAddress; use crate::GenerationInputs; -#[test] -fn test_tstore() -> Result<()> { - let sys_tstore = crate::cpu::kernel::aggregator::KERNEL.global_labels["sys_tstore"]; - - let kexit_info = U256::from(0xdeadbeefu32) + (U256::from(u64::from(true)) << 32); - - let initial_stack = vec![ - 1.into(), // val - 2.into(), // slot - kexit_info, - ]; - - let mut interpreter: Interpreter = Interpreter::new(sys_tstore, initial_stack); +fn initialize_interpreter(interpreter: &mut Interpreter, gas_limit: U256) { let gas_limit_address = MemoryAddress { context: 0, segment: Segment::ContextMetadata.unscale(), virt: ContextMetadata::GasLimit.unscale(), }; + interpreter + .generation_state + .memory + .set(gas_limit_address, gas_limit); + let addr_addr = MemoryAddress { context: 0, segment: Segment::ContextMetadata.unscale(), virt: ContextMetadata::Address.unscale(), }; + interpreter.generation_state.memory.set(addr_addr, 3.into()); + + let initial_len = MemoryAddress { + context: 0, + segment: Segment::GlobalMetadata.unscale(), + virt: GlobalMetadata::TransientStorageLen.unscale(), + }; + // Store 0 but scaled by its segment interpreter .generation_state .memory - .set(gas_limit_address, 100.into()); - interpreter.generation_state.memory.set(addr_addr, 3.into()); + .set(initial_len, (Segment::TransientStorage as usize).into()); +} + +#[test] +fn test_tstore() -> Result<()> { + let sys_tstore = crate::cpu::kernel::aggregator::KERNEL.global_labels["sys_tstore"]; + + let kexit_info = U256::from(0xdeadbeefu32) + (U256::from(u64::from(true)) << 32); + + let initial_stack = vec![ + 1.into(), // val + 2.into(), // slot + kexit_info, + ]; + + let mut interpreter: Interpreter = Interpreter::new(sys_tstore, initial_stack); + initialize_interpreter(&mut interpreter, 100.into()); interpreter.run()?; @@ -87,21 +105,7 @@ fn test_tstore_tload() -> Result<()> { ]; let mut interpreter: Interpreter = Interpreter::new(sys_tstore, initial_stack); - let gas_limit_address = MemoryAddress { - context: 0, - segment: Segment::ContextMetadata.unscale(), - virt: ContextMetadata::GasLimit.unscale(), - }; - let addr_addr = MemoryAddress { - context: 0, - segment: Segment::ContextMetadata.unscale(), - virt: ContextMetadata::Address.unscale(), - }; - interpreter - .generation_state - .memory - .set(gas_limit_address, 200.into()); - interpreter.generation_state.memory.set(addr_addr, 3.into()); + initialize_interpreter(&mut interpreter, 200.into()); interpreter.run()?; @@ -150,6 +154,7 @@ fn test_tstore_tload() -> Result<()> { #[test] fn test_many_tstore_many_tload() -> Result<()> { let kexit_info = U256::from(0xdeadbeefu32) + (U256::from(u64::from(true)) << 32); + let sys_tstore = crate::cpu::kernel::aggregator::KERNEL.global_labels["sys_tstore"]; let initial_stack = vec![ 1.into(), // val @@ -158,24 +163,7 @@ fn test_many_tstore_many_tload() -> Result<()> { ]; let mut interpreter: Interpreter = Interpreter::new(0, initial_stack); - let gas_limit_address = MemoryAddress { - context: 0, - segment: Segment::ContextMetadata.unscale(), - virt: ContextMetadata::GasLimit.unscale(), - }; - let addr_addr = MemoryAddress { - context: 0, - segment: Segment::ContextMetadata.unscale(), - virt: ContextMetadata::Address.unscale(), - }; - - interpreter - .generation_state - .memory - .set(gas_limit_address, (10 * 200).into()); - interpreter.generation_state.memory.set(addr_addr, 3.into()); - - let sys_tstore = crate::cpu::kernel::aggregator::KERNEL.global_labels["sys_tstore"]; + initialize_interpreter(&mut interpreter, (10 * 200).into()); for i in 0..10 { interpreter.generation_state.registers.program_counter = sys_tstore; @@ -250,23 +238,7 @@ fn test_revert() -> Result<()> { let mut interpreter = Interpreter::::new(sys_tstore, vec![]); interpreter.generation_state = GenerationState::::new(GenerationInputs::default(), &KERNEL.code).unwrap(); - - let gas_limit_address = MemoryAddress { - context: 0, - segment: Segment::ContextMetadata.unscale(), - virt: ContextMetadata::GasLimit.unscale(), - }; - let addr_addr = MemoryAddress { - context: 0, - segment: Segment::ContextMetadata.unscale(), - virt: ContextMetadata::Address.unscale(), - }; - - interpreter - .generation_state - .memory - .set(gas_limit_address, (20 * 100).into()); - interpreter.generation_state.memory.set(addr_addr, 3.into()); + initialize_interpreter(&mut interpreter, (20 * 100).into()); // Store different values at slot 1 for i in 0..10 { From ef6b99df82967c2311568a058e4f4e761633924a Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Mon, 10 Jun 2024 08:45:08 -0400 Subject: [PATCH 30/40] perf: Charge gas before tload search (#272) --- .../src/cpu/kernel/asm/memory/transient_storage.asm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/evm_arithmetization/src/cpu/kernel/asm/memory/transient_storage.asm b/evm_arithmetization/src/cpu/kernel/asm/memory/transient_storage.asm index b2530240c..8aa32c3e2 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/memory/transient_storage.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/memory/transient_storage.asm @@ -95,13 +95,14 @@ tload_found: // Pre stack: kexit_info, slot // Post stack: value global sys_tload: + // stack: kexit_info, slot + %charge_gas_const(@GAS_WARMACCESS) // stack: kexit_info, slot SWAP1 // stack: slot, kexit_info %tload_current SWAP1 - %charge_gas_const(@GAS_WARMACCESS) // stack: kexit_info, value EXIT_KERNEL From 2ffda4f8a0d9e642a47d217a4426b08ec833d286 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Thu, 13 Jun 2024 18:20:12 -0400 Subject: [PATCH 31/40] fix: add check on decoded versioned hashes (#278) --- .../asm/transactions/common_decoding.asm | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/evm_arithmetization/src/cpu/kernel/asm/transactions/common_decoding.asm b/evm_arithmetization/src/cpu/kernel/asm/transactions/common_decoding.asm index b81823a6f..cd58763a3 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/transactions/common_decoding.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/transactions/common_decoding.asm @@ -146,11 +146,12 @@ DUP1 %mstore_global_metadata(@GLOBAL_METADATA_BLOB_VERSIONED_HASHES_RLP_START) %decode_rlp_list_len %stack (rlp_addr, len) -> (len, len, rlp_addr, %%after) - %jumpi(decode_and_store_blob_versioned_hashes) + + // EIP-4844: Blob transactions should have at least 1 versioned hash + %assert_nonzero(invalid_txn_2) + // stack: len, rlp_addr, %%after - POP SWAP1 POP - // stack: rlp_addr - %mload_global_metadata(@GLOBAL_METADATA_BLOB_VERSIONED_HASHES_RLP_START) DUP2 SUB %mstore_global_metadata(@GLOBAL_METADATA_BLOB_VERSIONED_HASHES_RLP_LEN) + %jump(decode_and_store_blob_versioned_hashes) %%after: %endmacro @@ -173,6 +174,16 @@ decode_and_store_blob_versioned_hashes_loop: DUP2 DUP2 EQ %jumpi(decode_and_store_blob_versioned_hashes_finish) // stack: rlp_addr, end_rlp_addr, store_addr %decode_rlp_scalar // blob_versioned_hashes[i] + // stack: rlp_addr, hash, end_rlp_addr, store_addr + + // EIP-4844: Versioned hashes should have `VERSIONED_HASH_VERSION_KZG` as MSB + DUP2 + %shr_const(248) + // stack: MSB, hash, end_rlp_addr, store_addr + %eq_const(1) + // stack: hash_is_valid?, rlp_addr, hash, end_rlp_addr, store_addr + %assert_nonzero(invalid_txn_3) + // stack: rlp_addr, hash, end_rlp_addr, store_addr SWAP3 DUP1 SWAP2 // stack: hash, store_addr, store_addr, end_rlp_addr, rlp_addr From b3ef414e08931a4681609aa3704bb1ca12c8c63c Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Mon, 1 Jul 2024 11:51:35 -0400 Subject: [PATCH 32/40] fix: Fix CI job --- .github/workflows/ci.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 437692ca4..03b54341b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -176,10 +176,6 @@ jobs: CARGO_INCREMENTAL: 1 RUST_BACKTRACE: 1 - simple_proof_regular: - name: Execute bash script to generate and verify a proof for a small block. - runs-on: zero-ci - # TODO: Update artifact files # simple_proof_regular: # name: Execute bash script to generate and verify a proof for a small block. From 51256ac2c073624cec4a83be153ad058ce41feaf Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Mon, 1 Jul 2024 11:57:34 -0400 Subject: [PATCH 33/40] fix: pacify clippy --- evm_arithmetization/src/cpu/columns/mod.rs | 3 --- evm_arithmetization/src/cpu/columns/ops.rs | 8 -------- evm_arithmetization/src/keccak_sponge/columns.rs | 1 - evm_arithmetization/src/util.rs | 2 -- 4 files changed, 14 deletions(-) diff --git a/evm_arithmetization/src/cpu/columns/mod.rs b/evm_arithmetization/src/cpu/columns/mod.rs index 661c4d18f..5c005cde9 100644 --- a/evm_arithmetization/src/cpu/columns/mod.rs +++ b/evm_arithmetization/src/cpu/columns/mod.rs @@ -1,9 +1,6 @@ -use core::borrow::{Borrow, BorrowMut}; use core::fmt::Debug; use core::mem::{size_of, transmute}; -use core::ops::{Index, IndexMut}; -use plonky2::field::types::Field; use zk_evm_proc_macro::Columns; use crate::cpu::columns::general::CpuGeneralColumnsView; diff --git a/evm_arithmetization/src/cpu/columns/ops.rs b/evm_arithmetization/src/cpu/columns/ops.rs index 9963215f6..c3d1281a6 100644 --- a/evm_arithmetization/src/cpu/columns/ops.rs +++ b/evm_arithmetization/src/cpu/columns/ops.rs @@ -1,7 +1,3 @@ -use core::borrow::{Borrow, BorrowMut}; -use core::mem::{size_of, transmute}; -use core::ops::{Deref, DerefMut}; - use zk_evm_proc_macro::{Columns, DerefColumns}; /// Structure representing the flags for the various opcodes. @@ -46,7 +42,3 @@ pub(crate) struct OpsColumnsView { /// Flag for exceptions. pub exception: T, } - -/// Number of columns in Cpu Stark. -/// `u8` is guaranteed to have a `size_of` of 1. -pub(crate) const NUM_OPS_COLUMNS: usize = size_of::>(); diff --git a/evm_arithmetization/src/keccak_sponge/columns.rs b/evm_arithmetization/src/keccak_sponge/columns.rs index ce35c0867..36679bc0a 100644 --- a/evm_arithmetization/src/keccak_sponge/columns.rs +++ b/evm_arithmetization/src/keccak_sponge/columns.rs @@ -1,4 +1,3 @@ -use core::borrow::{Borrow, BorrowMut}; use core::mem::{size_of, transmute}; use core::ops::Range; diff --git a/evm_arithmetization/src/util.rs b/evm_arithmetization/src/util.rs index 874ded1b1..f8463258b 100644 --- a/evm_arithmetization/src/util.rs +++ b/evm_arithmetization/src/util.rs @@ -1,5 +1,3 @@ -use core::mem::{size_of, transmute_copy, ManuallyDrop}; - use ethereum_types::{H160, H256, U256}; use itertools::Itertools; use num::BigUint; From 59cdfad590509e0d16bc7511b62d775e97bb00a5 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Wed, 3 Jul 2024 23:07:02 +0900 Subject: [PATCH 34/40] fix: Add beacon roots touched slots into `state_access` with native tracer (#353) * Add beacon roots to state with native tracer * Try fix * Cleanup --- zero_bin/rpc/src/native/state.rs | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/zero_bin/rpc/src/native/state.rs b/zero_bin/rpc/src/native/state.rs index d69b48cb9..d5e6946f7 100644 --- a/zero_bin/rpc/src/native/state.rs +++ b/zero_bin/rpc/src/native/state.rs @@ -1,12 +1,13 @@ use std::collections::{HashMap, HashSet}; use alloy::{ - primitives::{keccak256, Address, StorageKey, B256}, + primitives::{keccak256, Address, StorageKey, B256, U256}, providers::Provider, rpc::types::eth::{Block, BlockTransactionsKind, EIP1186AccountProofResponse}, transports::Transport, }; use anyhow::Context as _; +use evm_arithmetization::testing_utils::{BEACON_ROOTS_CONTRACT_STATE_KEY, HISTORY_BUFFER_LENGTH}; use futures::future::{try_join, try_join_all}; use mpt_trie::{builder::PartialTrieBuilder, partial_trie::HashedPartialTrie}; use trace_decoder::trace_protocol::{ @@ -59,7 +60,8 @@ where } /// Iterate over the tx_infos and process the state access for each address. -/// Also includes the state access for withdrawals and the block author. +/// Also includes the state access for the beacon roots contract, withdrawals +/// and the block author. /// /// Returns a map from address to the set of storage keys accessed by that /// address. @@ -69,6 +71,8 @@ pub fn process_states_access( ) -> anyhow::Result>> { let mut state_access = HashMap::>::new(); + insert_beacon_roots_update(&mut state_access, block)?; + if let Some(w) = block.withdrawals.as_ref() { w.iter().for_each(|w| { state_access.insert(w.address, Default::default()); @@ -93,6 +97,24 @@ pub fn process_states_access( Ok(state_access) } +/// Cancun HF specific, see . +fn insert_beacon_roots_update( + state_access: &mut HashMap>, + block: &Block, +) -> anyhow::Result<()> { + let timestamp = block.header.timestamp; + + const MODULUS: u64 = HISTORY_BUFFER_LENGTH.1; + + let keys = HashSet::from_iter([ + U256::from(timestamp % MODULUS).into(), // timestamp_idx + U256::from((timestamp % MODULUS) + MODULUS).into(), // root_idx + ]); + state_access.insert(BEACON_ROOTS_CONTRACT_STATE_KEY.1.into(), keys); + + Ok(()) +} + /// Generates the state witness for the given block. async fn generate_state_witness( prev_state_root: B256, From adf00d8264021c306c99cf42554786af40562d2f Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Tue, 9 Jul 2024 19:15:32 +0900 Subject: [PATCH 35/40] feat(cancun): update test blocks (#365) --- .github/workflows/ci.yml | 77 +- .../tools/artifacts/witness_b19240705.json | 1390 ---------- .../tools/artifacts/witness_b19807080.json | 1025 +++++++ zero_bin/tools/artifacts/witness_b2_b7.json | 2414 ----------------- zero_bin/tools/artifacts/witness_b3_b6.json | 1295 +++++++++ zero_bin/tools/prove_stdio.sh | 26 +- 6 files changed, 2376 insertions(+), 3851 deletions(-) delete mode 100644 zero_bin/tools/artifacts/witness_b19240705.json create mode 100644 zero_bin/tools/artifacts/witness_b19807080.json delete mode 100644 zero_bin/tools/artifacts/witness_b2_b7.json create mode 100644 zero_bin/tools/artifacts/witness_b3_b6.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 03b54341b..a786cf3e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -176,45 +176,44 @@ jobs: CARGO_INCREMENTAL: 1 RUST_BACKTRACE: 1 - # TODO: Update artifact files - # simple_proof_regular: - # name: Execute bash script to generate and verify a proof for a small block. - # runs-on: zero-ci - - # steps: - # - name: Checkout code - # uses: actions/checkout@v3 - - # - name: Run the script - # run: | - # pushd zero_bin/tools - # ./prove_stdio.sh artifacts/witness_b19240705.json - - # simple_proof_witness_only: - # name: Execute bash script to generate the proof witness for a small block. - # runs-on: zero-ci - - # steps: - # - name: Checkout code - # uses: actions/checkout@v3 - - # - name: Run the script - # run: | - # pushd zero_bin/tools - # ./prove_stdio.sh artifacts/witness_b19240705.json test_only - - # multi_blocks_proof_regular: - # name: Execute bash script to generate and verify a proof for multiple blocks using parallel proving. - # runs-on: zero-ci - - # steps: - # - name: Checkout code - # uses: actions/checkout@v3 - - # - name: Run the script - # run: | - # pushd zero_bin/tools - # ./prove_stdio.sh artifacts/witness_b2_b7.json + simple_proof_regular: + name: Execute bash script to generate and verify a proof for a small block. + runs-on: zero-ci + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Run the script + run: | + pushd zero_bin/tools + ./prove_stdio.sh artifacts/witness_b19807080.json + + simple_proof_witness_only: + name: Execute bash script to generate the proof witness for a small block. + runs-on: zero-ci + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Run the script + run: | + pushd zero_bin/tools + ./prove_stdio.sh artifacts/witness_b19807080.json test_only + + multi_blocks_proof_regular: + name: Execute bash script to generate and verify a proof for multiple blocks using parallel proving. + runs-on: zero-ci + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Run the script + run: | + pushd zero_bin/tools + ./prove_stdio.sh artifacts/witness_b3_b6.json lints: name: Rustdoc, Formatting and Clippy diff --git a/zero_bin/tools/artifacts/witness_b19240705.json b/zero_bin/tools/artifacts/witness_b19240705.json deleted file mode 100644 index 1422c84ee..000000000 --- a/zero_bin/tools/artifacts/witness_b19240705.json +++ /dev/null @@ -1,1390 +0,0 @@ -[ - { - "block_trace": { - "trie_pre_images": { - "combined": { - "compact": "0x01031664dbd4a27781c8d129bab370c7498db3f71ad38853cf1c9b54ed0aefdfbc87034d9119280e061867c595d7df73f20434265a3062cae79c005715414be8e0846703324e2ab34b184864cd4c923cff7ac3d9744e0405a88580f858f8999924cb0ff503a6b60e17c4c4601ba1af49e95cf74e90c136153a495b419eb9e05756ff3da0de03310252de1f39746fdf13fa8c97d847a102ba32b3d152730d9423048eccda50790368d497df1f682ffdd3ed4f4a82e250271ac7ae096bead2a17a6207929c610482032479a848475f822582422a97702ae252f39b10b48d669bcc4bc8abdd8b205043036606ddc5a70f0fcfe956030a35ae91a6b003259bfa774a6a2b018a1b8a270c5a03e9f9e21290bfcc332e2c940e6ac9a99dc5e940085314ce91ae5ba4fcbcdca80403681320aa56c59dbc6d76dd24f979fd8e6431d4a555579b03fbd8f4debb14d4940329a98aafd5b252a5cb6edf169e6da01f3131b73a61241b0e5a9974023f20bce303064373a92fbaa74c494b9bb3c09ed235d90ee175fbb2fce2f07cb0d7e6517e3d03e9dcebfbf76992b81a6b596f81f8280c7007539757190f61b4bf7691846952c1038c3e0ea786a08d9d4ff010c17dd80831350bdd04109bd2e5641b1102d0d201d703ce018171a52ff434a6fd849ca812ad5fb12ea24ab5d00e3e92fe4517243c13e1035cfc9421e276d124a62c46c72f2feb99d7f616fa25acd16cf597b7dc531a66ca0312e5a34c61ed89765cb41e1af31f58a2387b45d3d40976446390a53f4c2eb652030a63f2ca64bf32f0a365a1728cda44264fbeefe86d9be4eb6059406b8dfb7c1003d9b3278e5c61faaf09b6914d8626dfe663a88b8b4486219f0e784205c9962a5003078468a40a017c769b325c488872cb5be8a5716d891d0e9d05676b95034a4d7c03f58b271b1ab544c0329216099bc2ea100431a4fb64920eedebe971caa0a74804038931a7b3f1f35ac1e5d35d3c5fc3771bb9f91d77a26cb1d31f8b2f118c75d69003dacbd8664c534de3efc89b9f4320ab03ef2546fc335d79396b02922d12845476033de8ca51e8f5722f83aee0eaca24e08137c8fb72a174e3de4d64e3b828601541032b8de6bd8adf2f22f570b158e9e5a68342d21614eb496e3abfb1d39d4fa0c1a303af3f7c4a5d228f3bf81538c09d60e1ca847c8ff863da885a4d320566d898009d0331e6f9f849baa8f4d97907719237c7e1c465807efb84710d9ea1bfd1360a093903068210e24a700477fa40e242b1287c7405c7332aefcaf77e92fb87b63a104700030bfaac2d323eb6898db67737c27769c6cc5898346fff8f67f60009412c5acc6a03ae56aaf99e2b4171979478eb1f9a0caafe2e98292c6c1526b63910397217cd20031196de1dcd4a9e9ac5e1bf6ae9db3bda7ac39f41a31f90ea9aa985a5e0e23bab0338a3648fe43d0759c2545fbac374ab550f6155703a37f1216014f02c11614ccd05581e0333617c4920d035c85869829581a0a3a3dd9dadf122daad9c47fc2edf400c074771c0d792eca0cb05581e03154df0016effd23a529fe9dba19a32aa7838f956bbc8c9334f0666a5d0040405581e032b3ff3ba56b5c3d896d23ff067c9e26cad3f115e9283dce09e19a270c0040305581e03f158e6cd12159a3cf47403a789dc6c9e7e8c9a09b6da1e03f8e0979e400c0346afca8172ffa0038211f446ac23f136adb55c95b8eba937b16b12d9d32ee30eaca4059191e0207503da31270adeab5b50d0101caa8dd4e2326fae6a43562501680271f83f8fb34787037a3670f78c75958f13f8579474b348612a80af61fa9b61b2c81c415523f170590605581e035e4e75fffbb329e4ba7f1b98156fd87b98d7f9b60be56ac357673ff22007011bffffffffffffffff05581e033cc8f8c15d49a17ea4ffc954f9927f2ea565c8ca347927a41b994e7110040104595ca7608060405260043610610087575f3560e01c806375713a081161005757806375713a08146101215780638024c73a146101345780638129fc1c14610148578063e634edd31461015c578063fa461e331461016f575f80fd5b80630162e2d0146100925780631a12d67c146100a7578063547d00a2146100d45780636e20b1071461010e575f80fd5b3661008e57005b5f80fd5b6100a56100a03660046150df565b61018e565b005b6100ba6100b5366004615263565b61027b565b604080519283526020830191909152015b60405180910390f35b3480156100df575f80fd5b506100f36100ee366004615306565b6103db565b604080519384526020840192909252908201526060016100cb565b6100ba61011c366004615388565b610ac5565b6100a561012f36600461547b565b610bfb565b34801561013f575f80fd5b506100a56113a3565b348015610153575f80fd5b506100a561149c565b6100f361016a36600461552b565b6115c4565b34801561017a575f80fd5b506100a56101893660046155a8565b611c20565b81158061019a57508142145b6101bf5760405162461bcd60e51b81526004016101b690615620565b60405180910390fd5b5f898960028181106101d3576101d361564f565b90506020020135111561026157888860028181106101f3576101f361564f565b905060200201353410156102195760405162461bcd60e51b81526004016101b690615663565b416108fc8a8a60028181106102305761023061564f565b9050602002013590811502906040515f60405180830381858888f1935050505015801561025f573d5f803e3d5ffd5b505b61027089898989898887611f17565b505050505050505050565b5f8083158061028957508342145b6102a55760405162461bcd60e51b81526004016101b690615620565b8b156102f7578b3410156102cb5760405162461bcd60e51b81526004016101b690615663565b60405141908d156108fc02908e905f818181858888f193505050501580156102f5573d5f803e3d5ffd5b505b5f6040518061012001604052808b815260200189151581526020018e81526020018f60028151811061032b5761032b61564f565b60200260200101516001600160a01b031681526020015f6001600160a01b031681526020018881526020018a81526020018781526020018581525090506103728e826124f8565b9094509250508b158061038557508b8310155b6103a15760405162461bcd60e51b81526004016101b69061569a565b8a15806103ae57508a8311155b6103ca5760405162461bcd60e51b81526004016101b6906156c9565b509b509b9950505050505050505050565b5f805f80845f815181106103f1576103f161564f565b60209081029190910101516040516370a0823160e01b81523360048201526001600160a01b03909116906370a0823190602401602060405180830381865afa15801561043f573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061046391906156f6565b90505f8a6064148061047357508a155b61049e575f8a1161049857610493606461048d848e613429565b9061345a565b6104a0565b896104a0565b815b90505f81116104e05760405162461bcd60e51b815260206004820152600c60248201526b5a45524f5f42414c414e434560a01b60448201526064016101b6565b866001815181106104f3576104f361564f565b60200260200101516001600160a01b03167368b3465833fb72a70ecdf485e0e4c7bd8665fc456001600160a01b0316036106c4575f6040518061012001604052808d5f14801561054257505f8d115b61054c575f61054e565b8c5b81526020015f151581526020015f8152602001896002815181106105745761057461564f565b60200260200101516001600160a01b03168152602001895f8151811061059c5761059c61564f565b60200260200101516001600160a01b031681526020015f81526020018381526020015f81526020018b81525090505f60026001600160401b038111156105e4576105e461517b565b60405190808252806020026020018201604052801561060d578160200160208202803683370190505b509050875f815181106106225761062261564f565b6020026020010151815f8151811061063c5761063c61564f565b60200260200101906001600160a01b031690816001600160a01b0316815250508760018151811061066f5761066f61564f565b60200260200101518160018151811061068a5761068a61564f565b60200260200101906001600160a01b031690816001600160a01b0316815250506106b481836124f8565b9198509096509450610ab6915050565b8a1580156106d157505f8a115b15610745575f6106fd8b888a6001815181106106ef576106ef61564f565b60200260200101518c61346c565b905082815f815181106107125761071261564f565b602002602001015111156107265782610741565b805f815181106107385761073861564f565b60200260200101515b9150505b855f815181106107575761075761564f565b60200260200101516001600160a01b03166370a08231886002815181106107805761078061564f565b60200260200101516040518263ffffffff1660e01b81526004016107b391906001600160a01b0391909116815260200190565b602060405180830381865afa1580156107ce573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107f291906156f6565b9450865f815181106108065761080661564f565b60200260200101516001600160a01b031663199f7260875f8151811061082e5761082e61564f565b6020026020010151338a60028151811061084a5761084a61564f565b6020026020010151856040518563ffffffff1660e01b8152600401610872949392919061570d565b5f604051808303815f87803b158015610889575f80fd5b505af115801561089b573d5f803e3d5ffd5b5050505084865f815181106108b2576108b261564f565b60200260200101516001600160a01b03166370a08231896002815181106108db576108db61564f565b60200260200101516040518263ffffffff1660e01b815260040161090e91906001600160a01b0391909116815260200190565b602060405180830381865afa158015610929573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061094d91906156f6565b610957919061574b565b6040516370a0823160e01b81523060048201529095505f905f80516020615c52833981519152906370a0823190602401602060405180830381865afa1580156109a2573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109c691906156f6565b90506109ed87308a6001815181106109e0576109e061564f565b60200260200101516135e0565b506040516370a0823160e01b81523060048201525f9082905f80516020615c52833981519152906370a0823190602401602060405180830381865afa158015610a38573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a5c91906156f6565b610a66919061574b565b90505f8111610aac5760405162461bcd60e51b81526020600482015260126024820152711393c81513d2d15394c8149150d15255915160721b60448201526064016101b6565b9194509092508390505b50505b96509650969350505050565b5f80831580610ad357508342145b610aef5760405162461bcd60e51b81526004016101b690615620565b8a15610b41578a341015610b155760405162461bcd60e51b81526004016101b690615663565b60405141908c156108fc02908d905f818181858888f19350505050158015610b3f573d5f803e3d5ffd5b505b5f6040518061012001604052808b815260200189151581526020018d81526020018e600281518110610b7557610b7561564f565b60200260200101516001600160a01b031681526020015f6001600160a01b031681526020018881526020018a8152602001878152602001858152509050610bbc8d826124f8565b9094509250508a1580610bcf57508a8310155b610beb5760405162461bcd60e51b81526004016101b69061569a565b509a509a98505050505050505050565b5f82600481518110610c0f57610c0f61564f565b6020026020010151118015610c3e575081600481518110610c3257610c3261564f565b60200260200101514214155b15610c97576040516001600160a01b038a1681527ff2e7574263ab25e854ccac210f6060de1cb000d665c40e21b3bcb80f2b4511bc9060200160405180910390a160405162461bcd60e51b81526004016101b690615620565b5f5f80516020615c528339815191526001600160a01b038a1614610cbc576003610cbf565b60025b60ff166001600160401b03811115610cd957610cd961517b565b604051908082528060200260200182016040528015610d02578160200160208202803683370190505b50905089815f81518110610d1857610d1861564f565b60200260200101906001600160a01b031690816001600160a01b0316815250508881600181518110610d4c57610d4c61564f565b60200260200101906001600160a01b031690816001600160a01b0316815250508051600303610db7575f80516020615c5283398151915281600281518110610d9657610d9661564f565b60200260200101906001600160a01b031690816001600160a01b0316815250505b6040516370a0823160e01b81523360048201525f906001600160a01b038c16906370a0823190602401602060405180830381865afa158015610dfb573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610e1f91906156f6565b90506001600160a01b0389167368b3465833fb72a70ecdf485e0e4c7bd8665fc45036110c3575f6040518061012001604052805f87600581518110610e6657610e6661564f565b602002602001015111610e79575f610e95565b86600581518110610e8c57610e8c61564f565b60200260200101515b81526020015f151581526020015f81526020018a6001600160a01b03168152602001876001600160a01b0316815260200186600181518110610ed957610ed961564f565b602002602001015181526020015f8a11610f4a57865f81518110610eff57610eff61564f565b6020026020010151606414610f44576064875f81518110610f2257610f2261564f565b602002602001015185610f35919061575e565b610f3f9190615789565b610f4c565b83610f4c565b895b815260200186600281518110610f6457610f6461564f565b602090810291909101810151825201859052604080516002808252606082019092529192505f91908160200160208202803683370190505090508c815f81518110610fb157610fb161564f565b60200260200101906001600160a01b031690816001600160a01b0316815250508b81600181518110610fe557610fe561564f565b60200260200101906001600160a01b031690816001600160a01b0316815250505f61101082846124f8565b509150507f522881958b3c4a6fc0840ad3b7fb947b881edc28c004245a62541647422ade978160405161104591815260200190565b60405180910390a15f8911801561105b57508881105b156110785760405162461bcd60e51b81526004016101b69061569a565b86516007036110bb57866006815181106110945761109461564f565b60200260200101518111156110bb5760405162461bcd60e51b81526004016101b6906156c9565b505050611396565b5f808811611177575f871161112f57845f815181106110e4576110e461564f565b6020026020010151606414611129576064855f815181106111075761110761564f565b60200260200101518361111a919061575e565b6111249190615789565b611179565b81611179565b61112461115888858d8960038151811061114b5761114b61564f565b602002602001015161346c565b5f815181106111695761116961564f565b602002602001015183613837565b875b9050856001600160a01b031663199f7260845f8151811061119c5761119c61564f565b6020026020010151338c856040518563ffffffff1660e01b81526004016111c6949392919061570d565b5f604051808303815f87803b1580156111dd575f80fd5b505af11580156111ef573d5f803e3d5ffd5b505050506111fe83308c6135e0565b506040516370a0823160e01b81523060048201525f905f80516020615c52833981519152906370a0823190602401602060405180830381865afa158015611247573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061126b91906156f6565b90505f8811801561127b57508781105b156112985760405162461bcd60e51b81526004016101b69061569a565b85516007036112db57856006815181106112b4576112b461564f565b60200260200101518111156112db5760405162461bcd60e51b81526004016101b6906156c9565b801561133b57604051632e1a7d4d60e01b8152600481018290525f80516020615c5283398151915290632e1a7d4d906024015f604051808303815f87803b158015611324575f80fd5b505af1158015611336573d5f803e3d5ffd5b505050505b6113608186886002815181106113535761135361564f565b602002602001015161384c565b6040518181527f522881958b3c4a6fc0840ad3b7fb947b881edc28c004245a62541647422ade979060200160405180910390a150505b5050505050505050505050565b5f546001600160a01b031633146113e25760405162461bcd60e51b8152602060048201526003602482015262486d6d60e81b60448201526064016101b6565b5f6001541161142c5760405162461bcd60e51b8152602060048201526016602482015275139bc81199595cc810dd5c9c995b9d1b1e4813ddd95960521b60448201526064016101b6565b60015447101561147e5760405162461bcd60e51b815260206004820181905260248201527f4e6f7420456e6f7567682042616c616e636520546f20436f766572204665657360448201526064016101b6565b5f54600154611496916001600160a01b0316906138fd565b5f600155565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff1615906001600160401b03165f811580156114e05750825b90505f826001600160401b031660011480156114fb5750303b155b905081158015611509575080155b156115275760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561155157845460ff60401b1916600160401b1785555b5f80546001600160a01b0319167337aab97476ba8dc785476611006fd5dda4eed66b17905583156115bd57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2906020015b60405180910390a15b5050505050565b5f80806001600160a01b0386167368b3465833fb72a70ecdf485e0e4c7bd8665fc4503611730576040805160028082526060820183525f9260208301908036833701905050905089815f8151811061161e5761161e61564f565b60200260200101906001600160a01b031690816001600160a01b03168152505088816001815181106116525761165261564f565b60200260200101906001600160a01b031690816001600160a01b0316815250505f604051806101200160405280875f815181106116915761169161564f565b602002602001015181526020016001151581526020015f81526020018a6001600160a01b031681526020015f6001600160a01b03168152602001876001815181106116de576116de61564f565b60200260200101518152602001348152602001876002815181106117045761170461564f565b6020026020010151815260200188815250905061172182826124f8565b90965094509250610ab9915050565b5f61174c611740876103e86157a8565b61048d346103e8613429565b90505f5f80516020615c528339815191526001600160a01b038b1614611773576003611776565b60025b60ff166001600160401b038111156117905761179061517b565b6040519080825280602002602001820160405280156117b9578160200160208202803683370190505b5090505f80516020615c52833981519152815f815181106117dc576117dc61564f565b6001600160a01b0392831660209182029290920101528b9082908c165f80516020615c5283398151915214611812576002611815565b60015b60ff16815181106118285761182861564f565b6001600160a01b0392831660209182029290920101528a165f80516020615c52833981519152146118885789816001815181106118675761186761564f565b60200260200101906001600160a01b031690816001600160a01b0316815250505b5f80875f8151811061189c5761189c61564f565b6020026020010151111561193a575f6118cf885f815181106118c0576118c061564f565b6020026020010151848c6139c7565b905083815f815181106118e4576118e461564f565b602002602001015111156118f85783611913565b805f8151811061190a5761190a61564f565b60200260200101515b945083815f815181106119285761192861564f565b6020026020010151111591505061193e565b8293505b61194984838b613b23565b60018351611957919061574b565b815181106119675761196761564f565b602002602001015194505f80516020615c528339815191526001600160a01b031663d0e30db0856040518263ffffffff1660e01b81526004015f604051808303818588803b1580156119b7575f80fd5b505af11580156119c9573d5f803e3d5ffd5b50505050505f80516020615c528339815191526001600160a01b031663a9059cbb611a288b855f81518110611a0057611a0061564f565b602002602001015186600181518110611a1b57611a1b61564f565b6020026020010151613c59565b6040516001600160e01b031960e084901b1681526001600160a01b039091166004820152602481018790526044016020604051808303815f875af1158015611a72573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a9691906157bb565b611aa257611aa26157d6565b6040516370a0823160e01b81523360048201525f906001600160a01b038e16906370a0823190602401602060405180830381865afa158015611ae6573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611b0a91906156f6565b90508115611b4b575f611b37895f81518110611b2857611b2861564f565b6020026020010151858d6139c7565b9050611b458185338e614100565b50611b58565b611b5683338c6135e0565b505b6040516370a0823160e01b815233600482015281906001600160a01b038f16906370a0823190602401602060405180830381865afa158015611b9c573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611bc091906156f6565b611bca919061574b565b96505f8711611c105760405162461bcd60e51b81526020600482015260126024820152711393c81513d2d15394c8149150d15255915160721b60448201526064016101b6565b5050505096509650969350505050565b5f841380611c2d57505f83135b611c35575f80fd5b5f80808080611c46868801886157ea565b9550955095505094509450611cbb8585336001600160a01b031663ddca3f436040518163ffffffff1660e01b8152600401602060405180830381865afa158015611c92573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611cb6919061585f565b614290565b6001600160a01b0316336001600160a01b031614611d0b5760405162461bcd60e51b815260206004820152600d60248201526c46616b652063616c6c6261636b60981b60448201526064016101b6565b5f805f8b13611d2f57866001600160a01b0316866001600160a01b0316108a611d46565b856001600160a01b0316876001600160a01b0316108b5b91509150828111611396578115611e33575f5f80516020615c528339815191526001600160a01b03891614611d7b5787611d8a565b5f80516020615c528339815191525b90506001600160a01b038616301480611db857505f80516020615c528339815191526001600160a01b038916145b15611dce57611dc981873385614354565b611e2d565b60405162ccfb9360e51b81526001600160a01b0386169063199f726090611dff9084908a903390889060040161570d565b5f604051808303815f87803b158015611e16575f80fd5b505af1158015611e28573d5f803e3d5ffd5b505050505b50611396565b5f5f80516020615c528339815191526001600160a01b03881614611e575786611e66565b5f80516020615c528339815191525b90506001600160a01b038616301480611e9457505f80516020615c528339815191526001600160a01b038816145b15611eaa57611ea581873385614354565b611f09565b60405162ccfb9360e51b81526001600160a01b0386169063199f726090611edb9084908a903390889060040161570d565b5f604051808303815f87803b158015611ef2575f80fd5b505af1158015611f04573d5f803e3d5ffd5b505050505b505050505050505050505050565b5f611f5f611f27836103e86157a8565b61048d6103e8611f598c8c6002818110611f4357611f4361564f565b905060200201353461448390919063ffffffff16565b90613429565b90505f8686611f6f60018261574b565b818110611f7e57611f7e61564f565b9050602002016020810190611f939190615881565b6040516370a0823160e01b81523360048201529091505f906001600160a01b038316906370a0823190602401602060405180830381865afa158015611fda573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611ffe91906156f6565b90505f808b8b5f8181106120145761201461564f565b905060200201351161204357604080516001808252818301909252906020808301908036833701905050612099565b6120998b8b5f8181106120585761205861564f565b905060200201358a8a808060200260200160405190810160405280939291908181526020018383602002808284375f920191909152508c92506139c7915050565b90505f808c8c5f8181106120af576120af61564f565b905060200201351180156120dc575084825f815181106120d1576120d161564f565b602002602001015111155b6120e65784612101565b815f815181106120f8576120f861564f565b60200260200101515b90505f80516020615c528339815191526001600160a01b031663d0e30db0826040518263ffffffff1660e01b81526004015f604051808303818588803b158015612149575f80fd5b505af115801561215b573d5f803e3d5ffd5b50505050505f80516020615c528339815191526001600160a01b031663a9059cbb6121d48a8d8d5f8181106121925761219261564f565b90506020020160208101906121a79190615881565b8e8e60018181106121ba576121ba61564f565b90506020020160208101906121cf9190615881565b613c59565b6040516001600160e01b031960e084901b1681526001600160a01b039091166004820152602481018490526044016020604051808303815f875af115801561221e573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061224291906157bb565b61224e5761224e6157d6565b5f8c8c5f8181106122615761226161564f565b9050602002013511801561228e575084825f815181106122835761228361564f565b602002602001015111155b156122d8576122d3828b8b808060200260200160405190810160405280939291908181526020018383602002808284375f920191909152503392508d91506141009050565b612336565b6123178a8a808060200260200160405190810160405280939291908181526020018383602002808284375f920191909152503392508c91506135e09050565b825f815181106123295761232961564f565b6020026020010181815250505b6040516370a0823160e01b81523360048201526123a99084906001600160a01b038716906370a08231906024015b602060405180830381865afa15801561237f573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906123a391906156f6565b90614483565b92505f8c8c60018181106123bf576123bf61564f565b905060200201351115612405578b8b60018181106123df576123df61564f565b905060200201358310156124055760405162461bcd60e51b81526004016101b69061569a565b60048b10612446578b8b60038181106124205761242061564f565b905060200201358311156124465760405162461bcd60e51b81526004016101b6906156c9565b61245181878961384c565b7f9f849d23f4955d98202378ea318f2b0c7533695d3c9fb2a3931f0f919fa8c42081845f8f8f5f8181106124875761248761564f565b90506020020135116124b257845f815181106124a5576124a561564f565b60200260200101516124cc565b8e8e5f8181106124c4576124c461564f565b905060200201355b6040805193845260208401929092529082015260600160405180910390a1505050505050505050505050565b5f805f806040518061010001604052808760018151811061251b5761251b61564f565b60200260200101516001600160a01b03168152602001875f815181106125435761254361564f565b60200260200101516001600160a01b03168152602001336001600160a01b03168152602001306001600160a01b0316815260200186608001516001600160a01b031681526020018660c0015181526020015f81526020016001151581525090505f47905085602001511561271757866001815181106125c4576125c461564f565b60200260200101516001600160a01b03165f80516020615c528339815191526001600160a01b03160361262e576126248661010001516103e861260791906157a8565b61048d6103e8611f598a604001513461448390919063ffffffff16565b60a083015261294c565b6126925f80516020615c52833981519152886001815181106126525761265261564f565b602002602001015161268d8961010001516103e861267091906157a8565b61048d6103e8611f598d604001513461448390919063ffffffff16565b61449c565b866001815181106126a5576126a561564f565b60209081029190910101516040516370a0823160e01b81523060048201526001600160a01b03909116906370a0823190602401602060405180830381865afa1580156126f3573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061262491906156f6565b865f815181106127295761272961564f565b60209081029190910101516001600160a01b031682528651879060019081106127545761275461564f565b6020908102919091018101516001600160a01b0316908301523060408301523360608301525f60e0830181905287518891906127925761279261564f565b60209081029190910101516040516370a0823160e01b81523360048201526001600160a01b03909116906370a0823190602401602060405180830381865afa1580156127e0573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061280491906156f6565b60c08301528551158015906128525750866001815181106128275761282761564f565b60200260200101516001600160a01b03165f80516020615c528339815191526001600160a01b031614155b1561294c576040805160028082526060820183525f926020830190803683370190505090508760018151811061288a5761288a61564f565b6020026020010151815f815181106128a4576128a461564f565b60200260200101906001600160a01b031690816001600160a01b0316815250505f80516020615c52833981519152816001815181106128e5576128e561564f565b60200260200101906001600160a01b031690816001600160a01b0316815250505f612928885f015183737a250d5630b4cf539739df2c5dacb4c659f2488d6139c7565b9050805f8151811061293c5761293c61564f565b6020908102919091010151885250505b85515f9015612adf575f87606001516001600160a01b0316633850c7bd6040518163ffffffff1660e01b815260040160e060405180830381865afa158015612996573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906129ba91906158b2565b50505050509150505f6129cc82614577565b90506001600160801b036001600160a01b03821611612a60575f6129f96001600160a01b0383168061575e565b905085602001516001600160a01b0316865f01516001600160a01b031610612a3c57612a37600160c01b8760a001516001600160801b031683614892565b612a58565b612a58818760a001516001600160801b0316600160c01b614892565b935050612adc565b5f612a796001600160a01b03831680600160401b614892565b905085602001516001600160a01b0316865f01516001600160a01b031610612abc57612ab7600160801b8760a001516001600160801b031683614892565b612ad8565b612ad8818760a001516001600160801b0316600160801b614892565b9350505b50505b86515f9015801590612af2575087518210155b15612d5357602084015160408086015190516370a0823160e01b81526001600160a01b0391821660048201529116906370a0823190602401602060405180830381865afa158015612b45573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612b6991906156f6565b90505f8089606001516001600160a01b031663128acb08876040015188602001516001600160a01b0316895f01516001600160a01b031610612bad8e5f015161493c565b612bb69061594a565b8a602001516001600160a01b03168b5f01516001600160a01b031610612bfa57612bf5600173fffd8963efd1fc6a506488495d951d5263988d26615964565b612c0a565b612c0a6401000276a3600161598b565b8b604051602001612c1b91906159ab565b6040516020818303038152906040526040518663ffffffff1660e01b8152600401612c4a959493929190615a64565b60408051808303815f875af1158015612c65573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612c899190615a9e565b9150915085602001516001600160a01b0316865f01516001600160a01b031610612cbc5780612cb78361594a565b612cc6565b81612cc68261594a565b60208801516040808a015190516370a0823160e01b81526001600160a01b039182166004820152929a50612d08935086929116906370a0823190602401612364565b92505f8311612d495760405162461bcd60e51b815260206004820152600d60248201526c1b9bc81d1bdad95b9cc81bdd5d609a1b60448201526064016101b6565b8297505050612f7b565b602084015160408086015190516370a0823160e01b81526001600160a01b0391821660048201529116906370a0823190602401602060405180830381865afa158015612da1573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612dc591906156f6565b606089015160408601516020870151875160a08901519495505f9485946001600160a01b039081169463128acb089490939082169116109081612e2657612e21600173fffd8963efd1fc6a506488495d951d5263988d26615964565b612e36565b612e366401000276a3600161598b565b8b604051602001612e4791906159ab565b6040516020818303038152906040526040518663ffffffff1660e01b8152600401612e76959493929190615a64565b60408051808303815f875af1158015612e91573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612eb59190615a9e565b9150915085602001516001600160a01b0316865f01516001600160a01b031610612ee85780612ee38361594a565b612ef2565b81612ef28261594a565b60208801516040808a015190516370a0823160e01b81526001600160a01b039182166004820152929a50612f34935086929116906370a0823190602401612364565b92505f8311612f755760405162461bcd60e51b815260206004820152600d60248201526c1b9bc81d1bdad95b9cc81bdd5d609a1b60448201526064016101b6565b82975050505b87602001516132015783602001516001600160a01b03165f80516020615c528339815191526001600160a01b0316146130405760208401516040516370a0823160e01b81523060048201525f916001600160a01b0316906370a0823190602401602060405180830381865afa158015612ff6573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061301a91906156f6565b9050801561303e5761303e85602001515f80516020615c528339815191528361449c565b505b6040516370a0823160e01b81523060048201525f905f80516020615c52833981519152906370a0823190602401602060405180830381865afa158015613088573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906130ac91906156f6565b9050801561310e57604051632e1a7d4d60e01b8152600481018290525f80516020615c5283398151915290632e1a7d4d906024015f604051808303815f87803b1580156130f7575f80fd5b505af1158015613109573d5f803e3d5ffd5b505050505b613122818a61010001518b60e0015161384c565b8096506131b78a5f8151811061313a5761313a61564f565b60209081029190910101516040516370a0823160e01b81523360048201526001600160a01b03909116906370a0823190602401602060405180830381865afa158015613188573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906131ac91906156f6565b60c087015190614483565b60408051828152602081018a90529081018890529098507f9f849d23f4955d98202378ea318f2b0c7533695d3c9fb2a3931f0f919fa8c4209060600160405180910390a1506133de565b83516001600160a01b03165f80516020615c52833981519152146132ac5783516040516370a0823160e01b81523060048201525f916001600160a01b0316906370a0823190602401602060405180830381865afa158015613264573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061328891906156f6565b905080156132aa5784516132aa905f80516020615c528339815191528361449c565b505b6040516370a0823160e01b81523060048201525f905f80516020615c52833981519152906370a0823190602401602060405180830381865afa1580156132f4573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061331891906156f6565b9050801561337a57604051632e1a7d4d60e01b8152600481018290525f80516020615c5283398151915290632e1a7d4d906024015f604051808303815f87803b158015613363575f80fd5b505af1158015613375573d5f803e3d5ffd5b505050505b613384478561574b565b60408051828152602081018a90529081018890529098507f9f849d23f4955d98202378ea318f2b0c7533695d3c9fb2a3931f0f919fa8c4209060600160405180910390a16133dc888a61010001518b60e0015161384c565b505b5f871161341e5760405162461bcd60e51b815260206004820152600e60248201526d1b9bc81b5bdb995e481cdc195b9d60921b60448201526064016101b6565b505050509250925092565b5f82158061344c5750818361343e828261575e565b925061344a9083615789565b145b613454575f80fd5b92915050565b5f6134658284615789565b9392505050565b60606002845110156134905760405162461bcd60e51b81526004016101b690615ac0565b83516001600160401b038111156134a9576134a961517b565b6040519080825280602002602001820160405280156134d2578160200160208202803683370190505b5090508481600183516134e5919061574b565b815181106134f5576134f561564f565b6020026020010181815250505f60018551613510919061574b565b90505b80156135d7575f806135628761352a60018661574b565b8151811061353a5761353a61564f565b60200260200101518885815181106135545761355461564f565b6020026020010151886149a9565b9150915061359a84848151811061357b5761357b61564f565b602002602001015183838987600114613594575f614a6e565b89614a6e565b846135a660018661574b565b815181106135b6576135b661564f565b602002602001018181525050505080806135cf90615ae6565b915050613513565b50949350505050565b5f805b600185516135f1919061574b565b81101561382f575f8086838151811061360c5761360c61564f565b60200260200101518784600161362291906157a8565b815181106136325761363261564f565b6020026020010151915091505f6136498383614baf565b5090505f613658878585613c59565b90505f805f836001600160a01b0316630902f1ac6040518163ffffffff1660e01b8152600401606060405180830381865afa158015613699573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906136bd9190615b11565b506001600160701b031691506001600160701b031691505f80866001600160a01b0316896001600160a01b0316146136f65782846136f9565b83835b6040516370a0823160e01b81526001600160a01b038981166004830152929450909250613734918491908c16906370a0823190602401612364565b94506137428583838f614bdf565b9a50505050505f80846001600160a01b0316876001600160a01b03161461376a57885f61376d565b5f895b915091505f60028d51613780919061574b565b891061378c578b6137ad565b6137ad8b888f61379d8d60026157a8565b81518110611a1b57611a1b61564f565b604080515f8152602081019182905263022c0d9f60e01b9091529091506001600160a01b0386169063022c0d9f906137ee9086908690869060248101615b5d565b5f604051808303815f87803b158015613805575f80fd5b505af1158015613817573d5f803e3d5ffd5b50506001909a0199506135e398505050505050505050565b509392505050565b5f8183106138455781613465565b5090919050565b5f61385d6103e861048d8686613429565b90505f61387f61387883600154614cfe90919063ffffffff16565b4790614483565b905080156138aa57600a8110156138a05761389a8282614cfe565b506138aa565b6138aa33826138fd565b6001546138b79083614cfe565b600155604080518381523360208201529081018490527f72015ace03712f361249380657b3d40777dd8f8a686664cab48afd9dbbe4499f906060016115b4565b50505050565b604080515f808252602082019092526001600160a01b0384169083906040516139269190615b93565b5f6040518083038185875af1925050503d805f8114613960576040519150601f19603f3d011682016040523d82523d5f602084013e613965565b606091505b50509050806139c25760405162461bcd60e51b815260206004820152602360248201527f5472616e7366657248656c7065723a204554485f5452414e534645525f46414960448201526213115160ea1b60648201526084016101b6565b505050565b60606002835110156139eb5760405162461bcd60e51b81526004016101b690615ac0565b82516001600160401b03811115613a0457613a0461517b565b604051908082528060200260200182016040528015613a2d578160200160208202803683370190505b509050838160018351613a40919061574b565b81518110613a5057613a5061564f565b6020026020010181815250505f60018451613a6b919061574b565b90505b801561382f575f80613abd86613a8560018661574b565b81518110613a9557613a9561564f565b6020026020010151878581518110613aaf57613aaf61564f565b6020026020010151876149a9565b91509150613ae6848481518110613ad657613ad661564f565b6020026020010151838388614d17565b84613af260018661574b565b81518110613b0257613b0261564f565b60200260200101818152505050508080613b1b90615ae6565b915050613a6e565b6060600283511015613b475760405162461bcd60e51b81526004016101b690615ac0565b82516001600160401b03811115613b6057613b6061517b565b604051908082528060200260200182016040528015613b89578160200160208202803683370190505b50905083815f81518110613b9f57613b9f61564f565b6020026020010181815250505f5b60018451613bbb919061574b565b81101561382f575f80613bff868481518110613bd957613bd961564f565b602002602001015187856001613bef91906157a8565b81518110613aaf57613aaf61564f565b91509150613c28848481518110613c1857613c1861564f565b6020026020010151838388614bdf565b84613c348560016157a8565b81518110613c4457613c4461564f565b60209081029190910101525050600101613bad565b5f805f613c668585614baf565b90925090506001600160a01b038616737a250d5630b4cf539739df2c5dacb4c659f2488d03613d4957735c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8282604051602001613cb7929190615bae565b60405160208183030381529060405280519060200120604051602001613d2a9291906001600160f81b0319815260609290921b6001600160601b031916600183015260158201527f96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f603582015260550190565b604051602081830303815290604052805190602001205f1c92506140f7565b6001600160a01b0386167368b3465833fb72a70ecdf485e0e4c7bd8665fc4503613dbc57731f98431c8ad98523631ae4a59f267346ea31f9848282604051602001613d95929190615bae565b60405160208183030381529060405280519060200120604051602001613d2a929190615bd0565b6001600160a01b03861673d9e1ce17f2641f24ae83637ab66a2cca9c378b9f03613e7b5773c0aee478e3658e2610c5f7a4a2e1777ce9e4f2ac8282604051602001613e08929190615bae565b60405160208183030381529060405280519060200120604051602001613d2a9291906001600160f81b0319815260609290921b6001600160601b031916600183015260158201527fe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303603582015260550190565b6001600160a01b03861673eff92a263d31888d860bd50809a8d171709b7b1c03613f3a57731097053fd2ea711dad45caccc45eff7548fcb3628282604051602001613ec7929190615bae565b60405160208183030381529060405280519060200120604051602001613d2a9291906001600160f81b0319815260609290921b6001600160601b031916600183015260158201527f57224589c67f3f30a6b0d7a1b54cf3153ab84563bc609ef41dfb34f8b2974d2d603582015260550190565b6001600160a01b0386167303f7724180aa6b939894b5ca4314783b0b36b32903613ff95773115934131916c8b277dd010ee02de363c09d037c8282604051602001613f86929190615bae565b60405160208183030381529060405280519060200120604051602001613d2a9291906001600160f81b0319815260609290921b6001600160601b031916600183015260158201527f65d1a3b1e46c6e4f1be1ad5f99ef14dc488ae0549dc97db9b30afe2241ce1c7a603582015260550190565b6001600160a01b038616730c17e776cd218252adfca8d4e761d3fe757e9778036140b8577335113a300ca0d7621374890abfeac30e88f214b18282604051602001614045929190615bae565b60405160208183030381529060405280519060200120604051602001613d2a9291906001600160f81b0319815260609290921b6001600160601b031916600183015260158201527f49d9acd3e20042617be7e378294c731749bc579b09dfd560cd3357445ce0b9e9603582015260550190565b60405162461bcd60e51b8152602060048201526014602482015273149bdd5d195c881b9bdd081cdd5c1c1bdc9d195960621b60448201526064016101b6565b50509392505050565b5f5b60018451614110919061574b565b8110156115bd575f8085838151811061412b5761412b61564f565b60200260200101518684600161414191906157a8565b815181106141515761415161564f565b6020026020010151915091505f6141688383614baf565b5090505f886141788660016157a8565b815181106141885761418861564f565b602002602001015190505f80836001600160a01b0316866001600160a01b0316146141b457825f6141b7565b5f835b915091505f60028b516141ca919061574b565b88106141d657896141e7565b6141e789878d61379d8c60026157a8565b90506141f4898888613c59565b6001600160a01b031663022c0d9f8484845f6040519080825280601f01601f191660200182016040528015614230576020820181803683370190505b506040518563ffffffff1660e01b81526004016142509493929190615b5d565b5f604051808303815f87803b158015614267575f80fd5b505af1158015614279573d5f803e3d5ffd5b505060019099019850614102975050505050505050565b5f826001600160a01b0316846001600160a01b031611156142af579192915b826001600160a01b0316846001600160a01b0316106142cc575f80fd5b604080516001600160a01b03808716602083015285169181019190915262ffffff83166060820152731f98431c8ad98523631ae4a59f267346ea31f9849060800160405160208183030381529060405280519060200120604051602001614334929190615bd0565b60408051601f198184030181529190528051602090910120949350505050565b804710614457576001600160a01b0384165f80516020615c5283398151915203614457575f80516020615c528339815191526001600160a01b031663d0e30db0826040518263ffffffff1660e01b81526004015f604051808303818588803b1580156143be575f80fd5b505af11580156143d0573d5f803e3d5ffd5b505060405163a9059cbb60e01b81526001600160a01b0386166004820152602481018590525f80516020615c52833981519152935063a9059cbb925060440190506020604051808303815f875af115801561442d573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061445191906157bb565b506138f7565b306001600160a01b0384160361447757614472848383614e47565b6138f7565b6138f784848484614f56565b5f8261448f838261574b565b9150811115613454575f80fd5b6040805160028082526060820183525f9260208301908036833701905050905083815f815181106144cf576144cf61564f565b60200260200101906001600160a01b031690816001600160a01b03168152505082816001815181106145035761450361564f565b60200260200101906001600160a01b031690816001600160a01b0316815250506145588430614552737a250d5630b4cf539739df2c5dacb4c659f2488d855f81518110611a0057611a0061564f565b85614354565b6115bd8130737a250d5630b4cf539739df2c5dacb4c659f2488d6135e0565b5f805f8360020b1261458c578260020b614593565b8260020b5f035b9050620d89e88111156145b9576040516333a3bdff60e21b815260040160405180910390fd5b5f816001165f036145ce57600160801b6145e0565b6ffffcb933bd6fad37aa2d162d1a5940015b70ffffffffffffffffffffffffffffffffff1690506002821615614614576ffff97272373d413259a46990580e213a0260801c5b6004821615614633576ffff2e50f5f656932ef12357cf3c7fdcc0260801c5b6008821615614652576fffe5caca7e10e4e61c3624eaa0941cd00260801c5b6010821615614671576fffcb9843d60f6159c9db58835c9266440260801c5b6020821615614690576fff973b41fa98c081472e6896dfb254c00260801c5b60408216156146af576fff2ea16466c96a3843ec78b326b528610260801c5b60808216156146ce576ffe5dee046a99a2a811c461f1969c30530260801c5b6101008216156146ee576ffcbe86c7900a88aedcffc83b479aa3a40260801c5b61020082161561470e576ff987a7253ac413176f2b074cf7815e540260801c5b61040082161561472e576ff3392b0822b70005940c7a398e4b70f30260801c5b61080082161561474e576fe7159475a2c29b7443b29c7fa6e889d90260801c5b61100082161561476e576fd097f3bdfd2022b8845ad8f792aa58250260801c5b61200082161561478e576fa9f746462d870fdf8a65dc1f90e061e50260801c5b6140008216156147ae576f70d869a156d2a1b890bb3df62baf32f70260801c5b6180008216156147ce576f31be135f97d08fd981231505542fcfa60260801c5b620100008216156147ef576f09aa508b5b7a84e1c677de54f3e99bc90260801c5b6202000082161561480f576e5d6af8dedb81196699c329225ee6040260801c5b6204000082161561482e576d2216e584f5fa1ea926041bedfe980260801c5b6208000082161561484b576b048a170391f7dc42444e8fa20260801c5b5f8460020b131561486a57805f198161486657614866615775565b0490505b64010000000081061561487e576001614880565b5f5b60ff16602082901c0192505050919050565b5f80805f19858709858702925082811083820303915050805f036148c6575f84116148bb575f80fd5b508290049050613465565b8084116148d1575f80fd5b5f848688095f868103871696879004966002600389028118808a02820302808a02820302808a02820302808a02820302808a02820302808a02909103029181900381900460010186841190950394909402919094039290920491909117919091029150509392505050565b5f6001600160ff1b038211156149a55760405162461bcd60e51b815260206004820152602860248201527f53616665436173743a2076616c756520646f65736e27742066697420696e2061604482015267371034b73a191a9b60c11b60648201526084016101b6565b5090565b5f805f6149b68686614baf565b5090505f806149c6868989613c59565b6001600160a01b0316630902f1ac6040518163ffffffff1660e01b8152600401606060405180830381865afa158015614a01573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190614a259190615b11565b506001600160701b031691506001600160701b03169150826001600160a01b0316886001600160a01b031614614a5c578082614a5f565b81815b90999098509650505050505050565b5f808611614abe5760405162461bcd60e51b815260206004820152601a60248201527f494e53554646494349454e545f4f55545055545f414d4f554e5400000000000060448201526064016101b6565b5f85118015614acc57505f84115b614ae85760405162461bcd60e51b81526004016101b690615c21565b5f614af9612710611f59888a613429565b90507310ed43c718714eb63d5aa57b78b54704e256024e6001600160a01b0385161480614b42575073eff92a263d31888d860bd50809a8d171709b7b1c6001600160a01b038516145b15614b80575f614b61614b57856126f761574b565b611f59888b614483565b9050614b786001614b728385615789565b90614cfe565b925050614ba5565b5f614b90614b57856126f261574b565b9050614ba16001614b728385615789565b9250505b5095945050505050565b5f80826001600160a01b0316846001600160a01b031610614bd1578284614bd4565b83835b909590945092505050565b5f808511614c2f5760405162461bcd60e51b815260206004820152601960248201527f494e53554646494349454e545f494e5055545f414d4f554e540000000000000060448201526064016101b6565b5f84118015614c3d57505f83115b614c595760405162461bcd60e51b81526004016101b690615c21565b5f7310ed43c718714eb63d5aa57b78b54704e256024e6001600160a01b0384161480614ca1575073eff92a263d31888d860bd50809a8d171709b7b1c6001600160a01b038416145b15614cb957614cb2866126f7613429565b9050614cc8565b614cc5866126f2613429565b90505b5f614cd38286613429565b90505f614ce683614b7289612710613429565b9050614cf28183615789565b98975050505050505050565b5f82614d0a83826157a8565b9150811015613454575f80fd5b5f808511614d675760405162461bcd60e51b815260206004820152601a60248201527f494e53554646494349454e545f4f55545055545f414d4f554e5400000000000060448201526064016101b6565b5f84118015614d7557505f83115b614d915760405162461bcd60e51b81526004016101b690615c21565b5f614da2612710611f598789613429565b90507310ed43c718714eb63d5aa57b78b54704e256024e6001600160a01b0384161480614deb575073eff92a263d31888d860bd50809a8d171709b7b1c6001600160a01b038416145b15614e1a575f614e016126f7611f59878a614483565b9050614e126001614b728385615789565b9250506135d7565b5f614e2b6126f2611f59878a614483565b9050614e3c6001614b728385615789565b979650505050505050565b604080516001600160a01b038481166024830152604480830185905283518084039091018152606490920183526020820180516001600160e01b031663a9059cbb60e01b17905291515f92839290871691614ea29190615b93565b5f604051808303815f865af19150503d805f8114614edb576040519150601f19603f3d011682016040523d82523d5f602084013e614ee0565b606091505b5091509150818015614f0a575080511580614f0a575080806020019051810190614f0a91906157bb565b6115bd5760405162461bcd60e51b815260206004820152601f60248201527f5472616e7366657248656c7065723a205452414e534645525f4641494c45440060448201526064016101b6565b604080516001600160a01b0385811660248301528481166044830152606480830185905283518084039091018152608490920183526020820180516001600160e01b03166323b872dd60e01b17905291515f92839290881691614fb99190615b93565b5f604051808303815f865af19150503d805f8114614ff2576040519150601f19603f3d011682016040523d82523d5f602084013e614ff7565b606091505b509150915081801561502157508051158061502157508080602001905181019061502191906157bb565b6150795760405162461bcd60e51b8152602060048201526024808201527f5472616e7366657248656c7065723a205452414e534645525f46524f4d5f46416044820152631253115160e21b60648201526084016101b6565b505050505050565b5f8083601f840112615091575f80fd5b5081356001600160401b038111156150a7575f80fd5b6020830191508360208260051b85010111156150c1575f80fd5b9250929050565b6001600160a01b03811681146150dc575f80fd5b50565b5f805f805f805f805f60e08a8c0312156150f7575f80fd5b89356001600160401b038082111561510d575f80fd5b6151198d838e01615081565b909b50995060208c0135915080821115615131575f80fd5b5061513e8c828d01615081565b90985096505060408a0135615152816150c8565b989b979a5095989497966060860135965060808601359560a0810135955060c001359350915050565b634e487b7160e01b5f52604160045260245ffd5b604051601f8201601f191681016001600160401b03811182821017156151b7576151b761517b565b604052919050565b5f6001600160401b038211156151d7576151d761517b565b5060051b60200190565b5f82601f8301126151f0575f80fd5b81356020615205615200836151bf565b61518f565b8083825260208201915060208460051b870101935086841115615226575f80fd5b602086015b8481101561524b57803561523e816150c8565b835291830191830161522b565b509695505050505050565b80151581146150dc575f80fd5b5f805f805f805f805f805f6101608c8e03121561527e575f80fd5b8b356001600160401b03811115615293575f80fd5b61529f8e828f016151e1565b9b505060208c0135995060408c0135985060608c0135975060808c0135965060a08c0135955060c08c01356152d381615256565b8095505060e08c013593506101008c013592506101208c013591506101408c013590509295989b509295989b9093969950565b5f805f805f8060c0878903121561531b575f80fd5b8635955060208701359450604087013593506060870135925060808701356001600160401b038082111561534d575f80fd5b6153598a838b016151e1565b935060a089013591508082111561536e575f80fd5b5061537b89828a016151e1565b9150509295509295509295565b5f805f805f805f805f806101408b8d0312156153a2575f80fd5b8a356001600160401b038111156153b7575f80fd5b6153c38d828e016151e1565b9a505060208b0135985060408b0135975060608b0135965060808b0135955060a08b01356153f081615256565b999c989b5096999598949794965050505060c08301359260e08101359261010082013592506101209091013590565b5f82601f83011261542e575f80fd5b8135602061543e615200836151bf565b8083825260208201915060208460051b87010193508684111561545f575f80fd5b602086015b8481101561524b5780358352918301918301615464565b5f805f805f805f805f6101208a8c031215615494575f80fd5b893561549f816150c8565b985060208a01356154af816150c8565b975060408a01356154bf816150c8565b965060608a01356154cf816150c8565b955060808a0135945060a08a0135935060c08a01356154ed816150c8565b925060e08a01356001600160401b03811115615507575f80fd5b6155138c828d0161541f565b9250506101008a013590509295985092959850929598565b5f805f805f8060c08789031215615540575f80fd5b863561554b816150c8565b9550602087013561555b816150c8565b9450604087013561556b816150c8565b9350606087013561557b816150c8565b92506080870135915060a08701356001600160401b0381111561559c575f80fd5b61537b89828a0161541f565b5f805f80606085870312156155bb575f80fd5b843593506020850135925060408501356001600160401b03808211156155df575f80fd5b818701915087601f8301126155f2575f80fd5b813581811115615600575f80fd5b886020828501011115615611575f80fd5b95989497505060200194505050565b60208082526015908201527410da185a5b8814994b5bdc99c819195d1958dd1959605a1b604082015260600190565b634e487b7160e01b5f52603260045260245ffd5b6020808252601c908201527f4e6f7420656e6f7567682045544820666f72206d696e65722074697000000000604082015260600190565b602080825260159082015274139bdd08195b9bdd59da081d1bdad95b9cc81bdd5d605a1b604082015260600190565b602080825260139082015272151bdbc81b585b9e481d1bdad95b9cc81bdd5d606a1b604082015260600190565b5f60208284031215615706575f80fd5b5051919050565b6001600160a01b039485168152928416602084015292166040820152606081019190915260800190565b634e487b7160e01b5f52601160045260245ffd5b8181038181111561345457613454615737565b808202811582820484141761345457613454615737565b634e487b7160e01b5f52601260045260245ffd5b5f826157a357634e487b7160e01b5f52601260045260245ffd5b500490565b8082018082111561345457613454615737565b5f602082840312156157cb575f80fd5b815161346581615256565b634e487b7160e01b5f52600160045260245ffd5b5f805f805f8060c087890312156157ff575f80fd5b863561580a816150c8565b9550602087013561581a816150c8565b9450604087013561582a816150c8565b9350606087013561583a816150c8565b9250608087013561584a816150c8565b8092505060a087013590509295509295509295565b5f6020828403121561586f575f80fd5b815162ffffff81168114613465575f80fd5b5f60208284031215615891575f80fd5b8135613465816150c8565b805161ffff811681146158ad575f80fd5b919050565b5f805f805f805f60e0888a0312156158c8575f80fd5b87516158d3816150c8565b8097505060208801518060020b81146158ea575f80fd5b95506158f86040890161589c565b94506159066060890161589c565b93506159146080890161589c565b925060a088015160ff81168114615929575f80fd5b60c089015190925061593a81615256565b8091505092959891949750929550565b5f600160ff1b820161595e5761595e615737565b505f0390565b6001600160a01b0382811682821603908082111561598457615984615737565b5092915050565b6001600160a01b0381811683821601908082111561598457615984615737565b5f6101008201905060018060a01b038084511683528060208501511660208401528060408501511660408401528060608501511660608401528060808501511660808401525060a083015160a083015260c083015160c083015260e0830151151560e083015292915050565b5f5b83811015615a31578181015183820152602001615a19565b50505f910152565b5f8151808452615a50816020860160208601615a17565b601f01601f19169290920160200192915050565b6001600160a01b0386811682528515156020830152604082018590528316606082015260a0608082018190525f90614e3c90830184615a39565b5f8060408385031215615aaf575f80fd5b505080516020909101519092909150565b6020808252600c908201526b0929cac82989288bea082a8960a31b604082015260600190565b5f81615af457615af4615737565b505f190190565b80516001600160701b03811681146158ad575f80fd5b5f805f60608486031215615b23575f80fd5b615b2c84615afb565b9250615b3a60208501615afb565b9150604084015163ffffffff81168114615b52575f80fd5b809150509250925092565b84815283602082015260018060a01b0383166040820152608060608201525f615b896080830184615a39565b9695505050505050565b5f8251615ba4818460208701615a17565b9190910192915050565b6001600160601b0319606093841b811682529190921b16601482015260280190565b6001600160f81b0319815260609290921b6001600160601b031916600183015260158201527fe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54603582015260550190565b602080825260169082015275494e53554646494349454e545f4c495155494449545960501b60408201526060019056fe000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a2646970667358221220743c8892851ac9b13e466532e3ac230413800df85301b7404b7ea95484eb2c4a64736f6c634300081700330605581e03ae88d60ce5722ba4d45bad2212f1c19dbc045e025144e80b926be7b3400701195ca705581e035f517349c69668843e7b5ebd815ad1205dadeede0831b55f80a5552820040103f693203bcd1962bbac5a3c4d08f7db978e02899ebd2f3a41f5be1702dd265e3f0219fd4d036cf48af9ce5bbf5d3c4d8679a22d6dc4ce50f2460e589be9b4eef3c3b296ac09034c387cc134b025bc72580db4a32e6f93eb6cff1f632cc031116863918672ea6d03aec6040e90b5e1939362f47cfe1b75fc6afc77ed285efbf31d37f66c8c73c8390366dd5a8943fa2e34afdd38a7e29f9b5a260c6aa866bef41848cc9e846278fd0803ec8dcfeb7af17e68816e778c606f011814c881a7ca0420db53979a560445b66003e8658b485991901a3388ac76de1a80ab3c3fe1cef9b781995ceb1e554138717103f274ba96d638a0f12fb0ece331da8b8bfbebde904e8c235ac7a6587a1e8ce1d60219ffff03f1da45c57c01d4f8bc8bd6c9bc6393e84372cfe0f58d59c4d89d45eb6384462203346b8a0a99c4ba441015c58299e81089a9b802b1255390b29c412ca1a12bb86503c55cc21f1e0b7ed64a4d3242270c11fd47d3f4a1cfde25e9687aed1f3d3fb7ad03eb45196994cb6827f64c63e12ae4c35fa30847adc8dac3dfd0d17444cb7e27460219ffff033ea37b4bc18ff0c8c1d4c6eba7ac89a5659ece6afa749617199d93dac0e57f8b031167476f34c04d2d2a1d063e633a9b976d734c6ce3b9aa78f54d772292cb7880036668221048b112a7543000f8295428ea4c6019668dbafa097054fbd35cf538ce03942446c6cf3bb9d492e6fe0d48eb500f03e6347f5da3842de17263cf1786166003d56e88c6f6a7ca937626c16d6bb9df0f72b871c9872a1f86cf5cc744565bfc3b030d933a399cb276af17886a28cbbae9d28561cbc70caf5e03adef1cf434ba86ea030b64ffeb2d73c8658b2f32d23c27c41434b84546c37f6c333fe83c489f052c2d0219ffff032a920fcec7ad7eaf3da21f74afd3eca01ba7ab3c5a978e777300a274a3bd1d2203dc21ea4894a17cb8f0a9e5c0c204c99080059cc2682e8a14c444dd7a53b5023903637df4de77435671cf15bd74dd44977f729aa956a2adef9d6f6f4156d5ba3de1037a1372027f775e5e20feb3b5c6ac3fc3a2d5bd7d4d3301a6797cdb6be9a6492203eadee423ae6a30aa34bf0d837a5ce95fda44aa80c8b092bbe9f78ce0b7241705034470d70e1a9f2a34078454b6ed0a60322dff81f416debb9d2bce3670c3af37800342befbc08e9fd98ae09d083baa646724cb427d58a8d03f5583fd1b8e0b7d254d03b113c8b01d890dc8a4069c34ad779b103a00cb8de02f240db781e00929d71b97033cb0316ffc5cdd9a69b57a3507b6b2420cb74d71b5ad7467a2d8d843d6c5062103a1b4328a38cfac9268faf280fc98bd0c0fa8574f07a1a3afa490d682916a974c03f3fcc742e1ba3a075b94ee89e3326a8ce7b25ee755b744797e27bc0cfa5ef17b03fd0284aaf80f26a314d965f927714102c04737bdd23701a3906de4bfe4de4fd203bb7673f584d9e797bd06344fafec5905507ee8baeb7ad66ce4ae49f37a3e0dd10372d59d1bb5ba502a8310d2a626d6b813ca380433f5419b3d2e734e39a565709d0354ac6fbf7b3633a66737869de6ecaec8f628d1831094be91c6f9951c2f1bf6c10219ffff03735679cda2399ba770a95ba2eec939f077a2a138ef29a62ec1f7f85be02c8b3d03e71d683987c8bab462f584bf46080780650fa506702756b021604a486311cc2d03665dba8379d0e8bbaa1e07b2397e57bbc17314dfbfb3782101be57aaae200ba803018d7267d3a0a8c6070f79f8c42468dc69f6c4899ac7c809bd356a3456c767f60309c52341c2ea613092fd07fe860b6ed3835e54d7c983ea3e8629f4730f2ab01f03393a490158ae5ca743df92d5668aa63a7576c7a8184d8fe26593df8295266c5f03e7c4611ca114168df06cf1807e024d84187bddc5143c5253a1a35acb628d9a4703ecc762b2f0b4ba9b9f2c7f8b0ca3a90ec70a8ec6d80f97202c3e6bd9f1564bb3031b5eca64c3a92e360845c85fe17279d0b81f22da61902bc43d24814ce40f63480399819b79aa9f27c6fd6ae6e2ccfb5872420ba00bad60ee8ff4618422c65011b70219ffff035c72a5b8df6d140b2375c72cac1c4e7881e210d87fe2c87730eb741a17a77d7303905928e185f123232c8c7261b02971dab40ed8e82da5c7a6b15391d3f069403c030bd78f252075f21349d67e3327473579bde5d2acc634ab78a91e79b646746e080399a32d53de3b2f35d769d5b1c4dc76301ed21ce83465e20110fff41ae5b9a8f6032e21e312e91ad7e9d49fe1ba481bfdf70c1fbb49fa03e9d8ce4a82e3c4b5c7b30341a81b27db19ddb1213831bb5cd6b590cf695f321c550f41d455f1b34c199c7a031d66064ee221ce4b3203bbdc0aabd875e2c4fdb789b5f5a61ab0750e3b959194034990e84322e8a1f997bc3b4808f72fee14a893b0c829322f28eecbc0b8ad638c03cf4568c6936b516d41400e0e11cc45d9b0f81a5493080b766305f9aa5959c01103454e866cd0ab911708b1c91e64ad2b97db195f1fe54bf745590b54218f89638e03f1d5ff12623b88e84b2add4e2db8bc622ee888fcc7b185f2581afd8029fe78d5030ce8f42b55e2ea45b52bb0bd7e05d11fb2e1ea1519a05eacf20854cba023f533038f9737d46f65365b46069f3fc22802377e9b61a4872d49e6e00e184d85d16fb0034b19b564ee45e9185490014a4ee16b43dc439d9015426e17fd50e92bf5e866640310ddce46903b3b0b8189d497261fea503471caf88643f4e1f726d880efbbbfaa03965da42acbedae7c965766b94a0193058f482c35ef57423bd68615af8d4b57f10375bd09ae24f56000053686e9f3a867bc53a495d6be7d3ff56ee234cf92192a0303e46f4db1633764b2a6077603a107cb73009d73a677ce7fde14f22a31b9c646fb03b8f7efd3098f280d4c17614060db426eb788d6ea49924918d4225394c83d8cd1034d0daae0cd47b570caf372cc2cfd7fbf5e71b9df1564991394bb9edb7114864503e9cb8c0e0d19329b5e232da4daa112c63da5b4f74859b35797fabc776fa5a3450305cdd717d655902fc921df296e863a7f4000962d18e79bf5bbb5009e1385bab00335ab2dcea26a7a1dea24c18d7dff4b4239977fc6fcd7cbe0e9dd047323a745ca03203db6f69a8ee4c323ed8056da115dc9da6558b3300be2512a9f58d234e50c7403eeeb77529480b032b76ab3271179724ffc4520917fa8950ec9f974a60e3f7ff803a25192e64f727de2f50934985afbee7ac7effba582ab669b12157d3e494fcc45037bedb346c6de914b136ac09c6877c0820b689567f23dcfd7f2ce2a8a4520ff8603908d912c1b509820efe4607bef4463c0347b5d4cda2a4cdbafa3a88c959c624703688354a1e85a8a186ae6e881f2c1c83c7f99470b4ea8ce301074d89660cafd35037efd37571911580076cea911cddbf4d16a6b475a3e37ea084e717ffc04fe6add032031ac15c31e6b72f2561e974bdaace2b18f5ac67db7bf328f567767627d8cce0356ee30f29377dd0bf2c83d72bf759d3a63246fa90e03f65c864bd13e5cf25ee403f55981fb71e58799afe2fd5fb907dc0d0b24052ad552b770f3310b7ddc12f27703a0854707ecc0abdb75020ed2dce429e1786054a44f832bc59a13ab802f7cc925035fdb5b525336196e741576eeec7f4d7aa5bf1ce2d3d248768327ac5e107feaf903cc0ef8701b1ee3e5f8fb5156a8a6a6997f1314ade00b95aad0e57eb8225d98cf038648170679b8fca45120e6350eff29915ca1711a7116ec3fb2869efb3c63ee43034575afeab279534e8792828438ed135a56c875c09d0b3fea7f99840e8bb1a81905581e03861256bc430a7d76c124258551957f339ba0edbd3f4d5b692b5f7cce400c02467a6a2b5f02e805581e03f3875dcbc5783835dc6682d990c44e394b1a1d0d2f3a56a74d8f66496008480de23ca122bec40005581e032625892a32fd2ada575bde80f2dca9e28dfaf0b6feac08e7eb4d8af560040205581e03d569b8e0dfdd1b88f1e60d86ee1f7a8e6f838188564bc8a3b88c1514b0040405581e03786fe573a7611f61fc6214e2be41183b47551bd0c11383d07accd3f1a0084801b10bcae6d91600021919b10390572b0396a1f40ae9189362c57f73dce6d7368ed7d5468bf5f0b0af06bd787303fb5dc0ee7dcf373c536d7113bdb20a5312a30eb9f754d823e856f3f83fa125e503e22712db2102e3ca381e7f1fd521303d75c5f8bdf7d8e99ca9b6240e6f92bdf10219ffff0312ef95182a010f63c037bc5ef6c1edada9ce415b134a6e1adf350854e08cf4a3035e778f7669a42ae82a352f9af4f3b41c101d9eb6a27f339c0f1a9b7acc9852b2039337a9c3bc9ae2e76e91384c31736fdc26a8557c92afebd1938815d4b51978d003291bc35c5bb11dcf7f97da1c2ed0625620f750c27d0aac5ef22fec7634e8b54f0334ef646a608eec2d1d5057624beeb9aa262505160668fe557a5a4b6df8b9096203b14e559e722bac627754c0107c5b18cb8c5d8ef214421c8e1762c220c605329303ae9da8b9fb939d833a55f89cf9a6e64748de6bebb56c01fd1c3a1c26c13fa0b6038c3ba669eea4e60b096bb6062599855d3314d12a8e3b06533eefb5328e78ad130219ffff03226bc07a7cc8aea352cce49b4fd39d020684a1271c88be3aa3b03bab878fd051030586962dfc97fbe066a567d0b1388c20fcd6337fcb275db5db78050405b6d20f0328ba3d28d19bd8c0f0f0016d42133f16a6075c756b9f62712fa7e79a9d1695b103b8f8c6fa9ffaac2de69ddf9f6fe4944eca4eb330b9151a1bc3f69f15e8c0e5d10357fc85c0d5a5a5dacec8e7f7162c24cfe2baabf5c3d07f530e3cea6cd1ac1dd4039a9c45ef912d6d4c03246e84e1c19af508e2613ce61678303ad70e26f5408bf70219ffff0318d43e2a6b470b6a7435f4eae4d7819fa1f66e867c8a27a5442ee98c23e52a34038bda7b4f790cb7ff1861b19407f79b4b52e83ccd012cd9dec73a2f2097296d12037ece001af6626b48e63a0f5d1edb4f64d7ea556694c1151c25b9d3ceea8a04280369782b3fa9c58bfb9c7347c48069ab8063561113c776cabd179e41e2350f84ba039baf46cf56af0ef9d31f77aa778a93c2a131fb0e5b885a634f3c0cef7e2fd4ee036b97b4060da3b1e5c8b785165f2cb0e5bfd0430fbf0548ef3dae6b7aeba297bf032f71b5ce39aed3b63f4f0f089e5e21d227201b8b3a76bd06d34d8407219b7f6503ccec62ee6f67f930afcc0e9996e173589568eb54355b2a3e6ad18a36d5ce7755032276e5c6be2b8fd4327e0b405978eeeb081407fe81c59d3366a4818dab32e78503bcf7e9ca72eea0ba7eea71e7fa426a6e15dbfc4b845ca9f5311147c42c3dd81a03cde19ed13118ec034326156a535603fe3f69b6b50c54357aaf78cd9afffc932e031d03a9ece069167f8b41b18f2c94b4476c0b4529e7287990c98731645acc81680219ffff035c3f99bfb5712981c5a688b2da16749515f3f6ded477e364339b1e147de8f430035769eff213dbda1566c2943184fb815471d65b72c791eede6ad5b703ee634592031ff500b18ef3c07715f78824498a3c0737dc44bb995946f82b41a629159f1b6f0319bdabb5dea19cff6fde5587cd28bcd72ee5e4170ba24070f6a6002fdf731fa503f16b14175705ddb439a255c2c2803be1b25fdb87ae95216bcb7a610e9a9063360324394503bc7bab04c3ae1141256777ecfc42465d006ce4c09bf8ae3a0505fdec03fa0c79c692ca546e2bca4050d15dfb6fed2451e19ace6c8a45c256307152122403e9a87d7f00d36247214bac5ac23668bad58d0e703689a626311795ea662a8f0903d4d1eb3aa092873dfb631e9c717f85690d1cfe18f4f95b815fa1a034745b82fb0331f798882485ca8f70026b07efdfdfbc8a321ac2fef536a2e3d7a13fb2874cf603b222d031faf18b2125cfb48cd5b35bb5b07396f645d123f4e6d29ef8c3b364e80324bbfed77e3bf4d94330e8e7afd5f2f8415bef1985dcc285df500f1fdc7ca2ff03f4073a7a3f0aad3360bfa09da2901733b15821c3e6dc429c201067c58ff96994032c8ad7cb79bf584d41811b65276d3fffda3c3bbf8821db50ce3712de762c2dee03d46133d041ee62fa33bd9341c24169a7d41871b638e08d5999ebc590cc4b08ce03727540e192d1abdee75c4d04dcfe05141d896d4df59d1d3434b4e62d232930e903fc3f397b6c9586e93d73efe0120c7a6f97f23be88d6e8dd9c56e27bca9993dd20373f5e2c15353050277b3d28c14c3eaff37b6c52c7a598724ba1cd5029695f9d703a08979fc8d59e672f084440f9e2752b06bb7594dc4e52e2c043aa90f8ab19666039145679165c3a2ec4db63d803f3ae1f0195c02062cac214cca96e9e450af3aab05581e037a53550dc399a156d54347ef50def8f425931c331dd2b8e53442208bd0040205581e03480a7c8dda2f8d183a8e975e60aba0e1d6bb1ca841027528c06d0f21300c0146b5e620f78d4005581e0346dc4933acd0fe8c0d3469bdbbd7522c52de9cc1c803e0a5f70e312690040305581e0342a72fd9151705b181ebc17546587d9113d29b35e7b87d6e75d5bfd2200c044702801a77a2f94405581e03d51b7fdff2fbaff38b64ad1a59df11a34c66dc0a2c648297d12c9e56100c02470119782e133fb005581e037c48cf7dabeaebd595de1ae697978a8fd4ab361157ccd3094d19998f40040105581e03e00c26999ded424d15fa5b92552e0638e325d168d234595c98c3c721000c014703328b944c400003ce33220d5c7f0d09d75ceff76c05863c5e7d6e801c70dfe7d5d45d4c44e806540306b487d15c028b6df56c3ebb9b7086965eba3a240857a647faece2ff13269f2b05581e03e31911ffeab5320719c6d1778f576f6042a38712df31403962d169452007011bffffffffffffffff04590855608060405234801561001057600080fd5b506004361061007d5760003560e01c80638da5cb5b1161005b5780638da5cb5b146100ce578063aad0ae5b146100df578063c4d66de8146100f2578063f2fde38b1461010557600080fd5b80630a5ea4661461008257806312ec8dcc14610097578063715018a6146100c6575b600080fd5b6100956100903660046106f7565b610118565b005b6065546100aa906001600160a01b031681565b6040516001600160a01b03909116815260200160405180910390f35b610095610198565b6033546001600160a01b03166100aa565b6100956100ed3660046106d5565b6101ce565b6100956101003660046106d5565b610244565b6100956101133660046106d5565b610321565b6065546001600160a01b031633146101775760405162461bcd60e51b815260206004820152601f60248201527f546f6b656e417070726f76653a2041636365737320726573747269637465640060448201526064015b60405180910390fd5b8015610192576101926001600160a01b0385168484846103bc565b50505050565b6033546001600160a01b031633146101c25760405162461bcd60e51b815260040161016e9061079f565b6101cc6000610416565b565b6033546001600160a01b031633146101f85760405162461bcd60e51b815260040161016e9061079f565b606580546001600160a01b0319166001600160a01b03831690811790915560405181907ff213750e75b7d8975215501c91778a99d0e1d7e30453ea61619aba02b5e0d17d90600090a350565b600054610100900460ff1661025f5760005460ff1615610263565b303b155b6102c65760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840161016e565b600054610100900460ff161580156102e8576000805461ffff19166101011790555b6102f0610468565b606580546001600160a01b0319166001600160a01b038416179055801561031d576000805461ff00191690555b5050565b6033546001600160a01b0316331461034b5760405162461bcd60e51b815260040161016e9061079f565b6001600160a01b0381166103b05760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b606482015260840161016e565b6103b981610416565b50565b604080516001600160a01b0385811660248301528416604482015260648082018490528251808303909101815260849091019091526020810180516001600160e01b03166323b872dd60e01b17905261019290859061049f565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b600054610100900460ff1661048f5760405162461bcd60e51b815260040161016e906107d4565b610497610626565b6101cc61064d565b6104b1826001600160a01b031661067d565b6104fd5760405162461bcd60e51b815260206004820152601f60248201527f5361666545524332303a2063616c6c20746f206e6f6e2d636f6e747261637400604482015260640161016e565b600080836001600160a01b0316836040516105189190610764565b6000604051808303816000865af19150503d8060008114610555576040519150601f19603f3d011682016040523d82523d6000602084013e61055a565b606091505b5091509150816105ac5760405162461bcd60e51b815260206004820181905260248201527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564604482015260640161016e565b80511561019257808060200190518101906105c79190610742565b6101925760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b606482015260840161016e565b600054610100900460ff166101cc5760405162461bcd60e51b815260040161016e906107d4565b600054610100900460ff166106745760405162461bcd60e51b815260040161016e906107d4565b6101cc33610416565b6000813f7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a4708181148015906106b157508115155b949350505050565b80356001600160a01b03811681146106d057600080fd5b919050565b6000602082840312156106e757600080fd5b6106f0826106b9565b9392505050565b6000806000806080858703121561070d57600080fd5b610716856106b9565b9350610724602086016106b9565b9250610732604086016106b9565b9396929550929360600135925050565b60006020828403121561075457600080fd5b815180151581146106f057600080fd5b6000825160005b81811015610785576020818601810151858301520161076b565b81811115610794576000828501525b509190910192915050565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b60608201526080019056fea26469706673582212204145507bfa15500c0eee86c57c125e8500315ccc134912cdaa0914a0badb3f8d64736f6c634300080600330058210390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5630410100582002a75bdeeae8604d839476ae9efd8b0e15aa447e21bfd7f41283bb54e22c9a8254e1c7db7575babf0d3369835678ec9b7f15c0886b00582002f97419363ffd7000167f130ef7168fbea05faf9251824ca5043f113cc6a7c75470cbb871e8f30fc8ce23609e9e0ea87b6b222f58021980040219010405581e0364d11e8e3a1420889b55b40236f26171452fc5c2039b36c2ff7eebd1e0070119085505581e03c041a125d5bebf793e4fe76765079b1a6f58561779843267210dab21b00c044656385853200005581e0311f5442609a3d1c9d8b6fcd4116925086103aaa59609edf9372c61ebb00c014622f186d1abe60338d318867d6777429ae9d27eeb574a09ece60292650356363e87b497663cf7db05581e03a72e70a896c5e7e3fd73fc037f9e385a2ff1fcd346c335e8e608f124400c186d471f71d628f1f38803eea6e015c5abaf705debdb853983b714b38e22667b85ff6e05a2db41df0504dc01410f0219effd03a6843d794191119540d18dd93aa3f26f19ee6736289b1728bdb6fc94c6d01aef03f05b8be49c774ea5112ae2b06f09779a49dc595dcd4f90ee6c608aaee100ff3c03acec0c85618661dfc8a7474ffd8eed9c8015ffebd298a260dcd5646f746ac8f30375c93313558d212b012ba9de45416de8a868081a9b692dbe734dac81924addd40328fdc911f176342e62c4f6f5c116a9e9409782b874c181e4deb1363e3ef504f2035f71f76b8ff24905310063097922eb65d32443d0a72bafb3644e7c09a75d73d80375251b39dcf86fbedeb4f16acfa9a96fab03f908a387e2965a90759e8ffa218103b5d8a37ef68dac3b8812669b09392960fc92c87540cd6d61616a7e5e419e495b03dcfdd8ddac2f3c79a632c51a37e9ecf139707623464f7c4cfacb2751ed93e7e60368406e0b3ffe2c3b5a929555b32adfe58f3b85bafc33604e6997cb906673bb7803037103a8515f11e5164f6121e4b98403db352e195fe7d848c812690013d4c18c035c52f86143284b4ffb948c26902cfa5ac33408c4d653b07c7c9356fb4f91c2a903e7148db0ce78e416bd6927721e3beaa5e4c707f3eed4ab9b85d0718bf2c753a30219ffff03a71e294d2d02571517deb4634b261cae905d73885dbac3bb92b437783c03fad5034617a132fd46b0746a642477af55f04d508bf633624ff3a24f92026c42ce5ef703ea8a32a0892cc9162a190dc9557003262a033d6cecd61792656fddb656611905032d815036f3b9c8a7899fc31cabb7d2a30ab09240bb7716a89923847d4692b2e8038170d98859c3e5afaf9382d4ad20f0f0667e474944a816c579830bacd8ed136303fbb43df4eb9fd44f41e741253bb9f1cc03fc4337741529cbef6a90092571c01d035bcf85d3ff03edf81b1367adbc9cb9180d04c08712d7882900e508738fc3089b03e4dbf27791ed2eb9580d54e0c9966a86a290abaeb665c4e3ac7408a07c75f0b003fe736eaf793ce468af4bbb253c8c6309905baf6a0a385b7b1ffba78e72c632dd031eb5bba110765533307bc7844d2549f6aad55d2bdc4d01d704c819627c57bcc40321d1d474b26b89b2aef4d29af2209a8aedefd68060da4fa7134c42cbdcb5c8060385d0fb46317e8decc4c8a9b9396a2cc67b8d8d84951c8ca09916c2651be388080219ffff035900025946d281922a2a3144a22dad27408351e8d3cc81ef80e261b7ad787b1103fe05601ed45fca9fae57f5fde490f733dcc403cce9dfe6e11a6572dfdff55a4d0219ffff035693c2c1841cadcce6bbdf834b1eb0ab5b353a3a0e97227fb049ec9615da3abd0397a56b4955d6201ce452807a7090f7816d5b336774bddc884c2aa979709869fc033bb228620700ed48d7c6722b359bbccaf43843e4eec132e892c589906ba0e34d03208fa1a8cdb23079c52bc53a60e18fcfe50dba6434ae3b881dde353b4b6484630321e9ccdc9c7e8008bddda6018876a33a7fc099bb8a61ac0a6aea5b086047f48703e7d2807dff149f510f87e46de01df997829c73935301328346b0a99f5f04b49903d92232a1be23509197c762fc3ddc4e116af2b00d2b58fffac62b9a29722b2bbc0394b688a92a36ea0541f132f6b3a148f96b78c4e560857195771e40b6ebb98b96032b17c04a457e4b51f16b166eeb7d523c2d9914565fac05922f4e40996df8e8fd0304c46b43f6e10ecdc76a36a768bd1a5dbb2e839fc9b81d3b1905f4014b96225f0380e1582aa2c0269014764b12f00cfd4fb0de432c629108b4f39d16f06b63d68b037ab32135d12fbe72d13468e4a09ade0ca57073cf23eeb094492415d0603f5810039042a74959f462854170ee15ff86df9bd3cdea7b7e278007abc2ebe86b6e7a940219ffff0302ee6748e49feefcd81d7f36cbb0262dd5e253cb09e54b55f89f1bedc2081198037ced7ad4bb05522b0a843ff26d459a8b767e3eabf53c02f6bcd7f09329e2ca4f0381f33cd259fa3721eb9ee3ba328e2662fac73c608223755eb466211ce5cad314037e6b32d3fea0c44ad09041dc3bd7b8844556a358978e1990b35f6b4114665c12035adec0570b1e27de63def72581c89d2b637a660b41c9c4ecc6420a2215a1b3e4035da952844db89701769bacf14fc0318956bc954f6d3e56ea03c33deeb217be610320d471a4c51bda9f860a58db58e5fa40ee851c49de8b07b4a11c52853d9a7a5403b8a80c95d0bc8750735c278aadc0f99ce7abcb8aceeda2047eee7a8b7f89dceb03da6e9638370df8eea75bb2dda24b0da4f2ce74c11fd2af8d16c9e901c7ba96cc03ddefb159b278ad3fb41cd2980832af3cfee6b2e4989e00f69559d0ec14009bf503d342b74cdab6c920201ed43a009696450f0bd78270bc12483fd5dce8492fddc003d9dea40b85826d838afd593b8522829f92b39b2e200d46727dff2556ba97d7c203fa71c50f29c4c86af3617e3e49d01fe9d992839c22dd0e1f67bf5aaabfd0c97903a3fc42bf47045d0114f7cab5b152874e61d00db9daf556eb3e9cecae7c804890033ac9291c220ef9563b6fe0aa556902adb116236b3b499e4b9e1b2d4756476f40034a88fb904aa63c19ff6b92f1a1b7ae8a2cb3232ad3eb85266da1ef00027749e1030873e3808ad184715bbe3b68ac856d9c3b37a187e6d98e584251be1c71e6053f03a40c2194b288fd8373f20cbf63a30b712dfa6c6eb4be8f4d55170d1fde0092bc03b4daca7d995845477da69ae5bfe38f9c8efe98dd81aad0679ad02e4cc100e9ca0398eb3be1d07e74e56a38dd852a517f8627d4c3d8eaea366cb6ebea356659f1d6033b3b9e721e3706a59dffabeabe18561a7abfd200c90944f716f9d3f32e8735bd03758d97923b36ee90d1d726a0ca2941e7794fed165d1c15ec60b63f22586583b403c3313e2c86abebf00faef8000594fc94292e9d953054406e912ba0de562a1045034f6f06069510da768d3d1b765a255f73d7d721cdbb950cccd386da66411d00d803fde78858d765faac65e171d4d997f5a607c9fc7d2f57c1f356b6f9dee89b2819031b54fa51c8b98ace8b52dbb878e6f331913f0207320b4c17602c91a1ea241ebf03692b7aa1880b2fd3cb08a69fb170b955865c0ebe1fa8922bf18303bb7c0739de032683f4365950ab6078ca7dccfecdbd2cc7cf7d902353e26b26c4a9d2bcb727e4038a9deca6a56557b0f8a9a37e363b143791ce28d8ef86427540c607005058906b03163aa4972ecd867d23dfee8b0dc0a7f84dc5b802f239bc597388dc36fe1761e603a1337173b66588069b505117f2f6b03f2687decd611d23089cf266747a3d86a10326ba9631082b28e97a3d2c2d9ce796e2a7e99f0f981bc93223c9b48ba390cd13036eef6590e68a52f414e37daa1cda86067bd4a08f33ffdad65a56eb9d762a1cad032f429836817e5f030d88502f411733ff43cb87b34dc5aae7457447a17f5c870703f5ded695fd33a93686cd4af6e215da0c29a0c3b6a26fd4c42d04d4bd6bfa2ec303916e9b45c46253767d84dc2c538b2a4dbd513ac672874fc2a354fd77c6924a9c03b0b2bfaa982bfa0b74152f2488e9b9d32ff418e8890904dadb4210b9fb766cd603e2c01afb40a1bb4ccf9a79c4915eecbe587ae719c8fbe1f17e23ee0f7995f598037907fd5613aee26f32bc20daa1a7444a3d45422ec495e6ee41997e01dd56f8b403988210ffded5ffb34d5945c489523735955ff1f133794ce9de831ff2b55ee58103e63ca8ea36587045751cd52ff23f480cfd8bfb1df3c60e19e22b9391b1ee30350329f924759d52253b62c21e4b7dde3d0728014b21b292451c4918fad1f6bf46f303c4c062397f4ed5718992327cdb72794471e6da53e0c9fa6f7a0d40ab10845c92035e860bb2f34de14c371c11e22070be4b57ed2ace7a01a2bc4f6d5af79cf4496203f2732092af7bdeb2eda70c80e5784eb5f0ebc392a4a801b3caa70a971991f8c203a5f91fac66be92d9e4a883ed27f772d3213e414c49a338e143825c95ee7f5fda05581e0377e234d8a1d0c939f87874cfb86dfe782e023c206bfe4fbad9a3217240040105581e03471d189c4b053895c86042ac4f3df3293fb829e40af0d3a77e59e5e7d00c02443b9aca00031659de68e6a279da28599e113109376d848bf72a54b56d945750294b8c97e773030657e508cc6d404d8cc89513d451fab06b3288013a5753906560d9cbed22571f05581e0309f88ee63cf75e84a39c1be2be1a2cae55d179b8bdfd190a874eec787007011bffffffffffffffff05581e039d8fb44577e2daf690f073b13fda3e9530b8d7de93799939ee840128b00c0146045b423f9c0004592c1d608060405234801561001057600080fd5b50600436106101b95760003560e01c80636a627842116100f9578063ba9a7a5611610097578063d21220a711610071578063d21220a7146105da578063d505accf146105e2578063dd62ed3e14610640578063fff6cae91461067b576101b9565b8063ba9a7a5614610597578063bc25cf771461059f578063c45a0155146105d2576101b9565b80637ecebe00116100d35780637ecebe00146104d757806389afcb441461050a57806395d89b4114610556578063a9059cbb1461055e576101b9565b80636a6278421461046957806370a082311461049c5780637464fc3d146104cf576101b9565b806323b872dd116101665780633644e515116101405780633644e51514610416578063485cc9551461041e5780635909c0d5146104595780635a3d549314610461576101b9565b806323b872dd146103ad57806330adf81f146103f0578063313ce567146103f8576101b9565b8063095ea7b311610197578063095ea7b3146103155780630dfe16811461036257806318160ddd14610393576101b9565b8063022c0d9f146101be57806306fdde03146102595780630902f1ac146102d6575b600080fd5b610257600480360360808110156101d457600080fd5b81359160208101359173ffffffffffffffffffffffffffffffffffffffff604083013516919081019060808101606082013564010000000081111561021857600080fd5b82018360208201111561022a57600080fd5b8035906020019184600183028401116401000000008311171561024c57600080fd5b509092509050610683565b005b610261610d57565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561029b578181015183820152602001610283565b50505050905090810190601f1680156102c85780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6102de610d90565b604080516dffffffffffffffffffffffffffff948516815292909316602083015263ffffffff168183015290519081900360600190f35b61034e6004803603604081101561032b57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610de5565b604080519115158252519081900360200190f35b61036a610dfc565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b61039b610e18565b60408051918252519081900360200190f35b61034e600480360360608110156103c357600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060400135610e1e565b61039b610efd565b610400610f21565b6040805160ff9092168252519081900360200190f35b61039b610f26565b6102576004803603604081101561043457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020013516610f2c565b61039b611005565b61039b61100b565b61039b6004803603602081101561047f57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16611011565b61039b600480360360208110156104b257600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113cb565b61039b6113dd565b61039b600480360360208110156104ed57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113e3565b61053d6004803603602081101561052057600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113f5565b6040805192835260208301919091528051918290030190f35b610261611892565b61034e6004803603604081101561057457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81351690602001356118cb565b61039b6118d8565b610257600480360360208110156105b557600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166118de565b61036a611ad4565b61036a611af0565b610257600480360360e08110156105f857600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060408101359060608101359060ff6080820135169060a08101359060c00135611b0c565b61039b6004803603604081101561065657600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020013516611dd8565b610257611df5565b600c546001146106f457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c55841515806107075750600084115b61075c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180612b2f6025913960400191505060405180910390fd5b600080610767610d90565b5091509150816dffffffffffffffffffffffffffff168710801561079a5750806dffffffffffffffffffffffffffff1686105b6107ef576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526021815260200180612b786021913960400191505060405180910390fd5b600654600754600091829173ffffffffffffffffffffffffffffffffffffffff91821691908116908916821480159061085457508073ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff1614155b6108bf57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f556e697377617056323a20494e56414c49445f544f0000000000000000000000604482015290519081900360640190fd5b8a156108d0576108d0828a8d611fdb565b89156108e1576108e1818a8c611fdb565b86156109c3578873ffffffffffffffffffffffffffffffffffffffff166310d1e85c338d8d8c8c6040518663ffffffff1660e01b8152600401808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b1580156109aa57600080fd5b505af11580156109be573d6000803e3d6000fd5b505050505b604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff8416916370a08231916024808301926020929190829003018186803b158015610a2f57600080fd5b505afa158015610a43573d6000803e3d6000fd5b505050506040513d6020811015610a5957600080fd5b5051604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905191955073ffffffffffffffffffffffffffffffffffffffff8316916370a0823191602480820192602092909190829003018186803b158015610acb57600080fd5b505afa158015610adf573d6000803e3d6000fd5b505050506040513d6020811015610af557600080fd5b5051925060009150506dffffffffffffffffffffffffffff85168a90038311610b1f576000610b35565b89856dffffffffffffffffffffffffffff160383035b9050600089856dffffffffffffffffffffffffffff16038311610b59576000610b6f565b89856dffffffffffffffffffffffffffff160383035b90506000821180610b805750600081115b610bd5576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526024815260200180612b546024913960400191505060405180910390fd5b6000610c09610beb84600363ffffffff6121e816565b610bfd876103e863ffffffff6121e816565b9063ffffffff61226e16565b90506000610c21610beb84600363ffffffff6121e816565b9050610c59620f4240610c4d6dffffffffffffffffffffffffffff8b8116908b1663ffffffff6121e816565b9063ffffffff6121e816565b610c69838363ffffffff6121e816565b1015610cd657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f556e697377617056323a204b0000000000000000000000000000000000000000604482015290519081900360640190fd5b5050610ce4848488886122e0565b60408051838152602081018390528082018d9052606081018c9052905173ffffffffffffffffffffffffffffffffffffffff8b169133917fd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d8229181900360800190a350506001600c55505050505050505050565b6040518060400160405280600a81526020017f556e69737761702056320000000000000000000000000000000000000000000081525081565b6008546dffffffffffffffffffffffffffff808216926e0100000000000000000000000000008304909116917c0100000000000000000000000000000000000000000000000000000000900463ffffffff1690565b6000610df233848461259c565b5060015b92915050565b60065473ffffffffffffffffffffffffffffffffffffffff1681565b60005481565b73ffffffffffffffffffffffffffffffffffffffff831660009081526002602090815260408083203384529091528120547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14610ee85773ffffffffffffffffffffffffffffffffffffffff84166000908152600260209081526040808320338452909152902054610eb6908363ffffffff61226e16565b73ffffffffffffffffffffffffffffffffffffffff851660009081526002602090815260408083203384529091529020555b610ef384848461260b565b5060019392505050565b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b601281565b60035481565b60055473ffffffffffffffffffffffffffffffffffffffff163314610fb257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f556e697377617056323a20464f5242494444454e000000000000000000000000604482015290519081900360640190fd5b6006805473ffffffffffffffffffffffffffffffffffffffff9384167fffffffffffffffffffffffff00000000000000000000000000000000000000009182161790915560078054929093169116179055565b60095481565b600a5481565b6000600c5460011461108457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c81905580611094610d90565b50600654604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905193955091935060009273ffffffffffffffffffffffffffffffffffffffff909116916370a08231916024808301926020929190829003018186803b15801561110e57600080fd5b505afa158015611122573d6000803e3d6000fd5b505050506040513d602081101561113857600080fd5b5051600754604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905192935060009273ffffffffffffffffffffffffffffffffffffffff909216916370a0823191602480820192602092909190829003018186803b1580156111b157600080fd5b505afa1580156111c5573d6000803e3d6000fd5b505050506040513d60208110156111db57600080fd5b505190506000611201836dffffffffffffffffffffffffffff871663ffffffff61226e16565b90506000611225836dffffffffffffffffffffffffffff871663ffffffff61226e16565b9050600061123387876126ec565b600054909150806112705761125c6103e8610bfd611257878763ffffffff6121e816565b612878565b985061126b60006103e86128ca565b6112cd565b6112ca6dffffffffffffffffffffffffffff8916611294868463ffffffff6121e816565b8161129b57fe5b046dffffffffffffffffffffffffffff89166112bd868563ffffffff6121e816565b816112c457fe5b0461297a565b98505b60008911611326576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180612bc16028913960400191505060405180910390fd5b6113308a8a6128ca565b61133c86868a8a6122e0565b811561137e5760085461137a906dffffffffffffffffffffffffffff808216916e01000000000000000000000000000090041663ffffffff6121e816565b600b555b6040805185815260208101859052815133927f4c209b5fc8ad50758f13e2e1088ba56a560dff690a1c6fef26394f4c03821c4f928290030190a250506001600c5550949695505050505050565b60016020526000908152604090205481565b600b5481565b60046020526000908152604090205481565b600080600c5460011461146957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c81905580611479610d90565b50600654600754604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905194965092945073ffffffffffffffffffffffffffffffffffffffff9182169391169160009184916370a08231916024808301926020929190829003018186803b1580156114fb57600080fd5b505afa15801561150f573d6000803e3d6000fd5b505050506040513d602081101561152557600080fd5b5051604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905191925060009173ffffffffffffffffffffffffffffffffffffffff8516916370a08231916024808301926020929190829003018186803b15801561159957600080fd5b505afa1580156115ad573d6000803e3d6000fd5b505050506040513d60208110156115c357600080fd5b5051306000908152600160205260408120549192506115e288886126ec565b600054909150806115f9848763ffffffff6121e816565b8161160057fe5b049a5080611614848663ffffffff6121e816565b8161161b57fe5b04995060008b11801561162e575060008a115b611683576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180612b996028913960400191505060405180910390fd5b61168d3084612992565b611698878d8d611fdb565b6116a3868d8c611fdb565b604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff8916916370a08231916024808301926020929190829003018186803b15801561170f57600080fd5b505afa158015611723573d6000803e3d6000fd5b505050506040513d602081101561173957600080fd5b5051604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905191965073ffffffffffffffffffffffffffffffffffffffff8816916370a0823191602480820192602092909190829003018186803b1580156117ab57600080fd5b505afa1580156117bf573d6000803e3d6000fd5b505050506040513d60208110156117d557600080fd5b505193506117e585858b8b6122e0565b811561182757600854611823906dffffffffffffffffffffffffffff808216916e01000000000000000000000000000090041663ffffffff6121e816565b600b555b604080518c8152602081018c9052815173ffffffffffffffffffffffffffffffffffffffff8f169233927fdccd412f0b1252819cb1fd330b93224ca42612892bb3f4f789976e6d81936496929081900390910190a35050505050505050506001600c81905550915091565b6040518060400160405280600681526020017f554e492d5632000000000000000000000000000000000000000000000000000081525081565b6000610df233848461260b565b6103e881565b600c5460011461194f57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c55600654600754600854604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff9485169490931692611a2b9285928792611a26926dffffffffffffffffffffffffffff169185916370a0823191602480820192602092909190829003018186803b1580156119ee57600080fd5b505afa158015611a02573d6000803e3d6000fd5b505050506040513d6020811015611a1857600080fd5b50519063ffffffff61226e16565b611fdb565b600854604080517f70a082310000000000000000000000000000000000000000000000000000000081523060048201529051611aca9284928792611a26926e01000000000000000000000000000090046dffffffffffffffffffffffffffff169173ffffffffffffffffffffffffffffffffffffffff8616916370a0823191602480820192602092909190829003018186803b1580156119ee57600080fd5b50506001600c5550565b60055473ffffffffffffffffffffffffffffffffffffffff1681565b60075473ffffffffffffffffffffffffffffffffffffffff1681565b42841015611b7b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f556e697377617056323a20455850495245440000000000000000000000000000604482015290519081900360640190fd5b60035473ffffffffffffffffffffffffffffffffffffffff80891660008181526004602090815260408083208054600180820190925582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98186015280840196909652958d166060860152608085018c905260a085019590955260c08085018b90528151808603909101815260e0850182528051908301207f19010000000000000000000000000000000000000000000000000000000000006101008601526101028501969096526101228085019690965280518085039096018652610142840180825286519683019690962095839052610162840180825286905260ff89166101828501526101a284018890526101c28401879052519193926101e2808201937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081019281900390910190855afa158015611cdc573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff811615801590611d5757508873ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b611dc257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f556e697377617056323a20494e56414c49445f5349474e415455524500000000604482015290519081900360640190fd5b611dcd89898961259c565b505050505050505050565b600260209081526000928352604080842090915290825290205481565b600c54600114611e6657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c55600654604080517f70a082310000000000000000000000000000000000000000000000000000000081523060048201529051611fd49273ffffffffffffffffffffffffffffffffffffffff16916370a08231916024808301926020929190829003018186803b158015611edd57600080fd5b505afa158015611ef1573d6000803e3d6000fd5b505050506040513d6020811015611f0757600080fd5b5051600754604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff909216916370a0823191602480820192602092909190829003018186803b158015611f7a57600080fd5b505afa158015611f8e573d6000803e3d6000fd5b505050506040513d6020811015611fa457600080fd5b50516008546dffffffffffffffffffffffffffff808216916e0100000000000000000000000000009004166122e0565b6001600c55565b604080518082018252601981527f7472616e7366657228616464726573732c75696e743235362900000000000000602091820152815173ffffffffffffffffffffffffffffffffffffffff85811660248301526044808301869052845180840390910181526064909201845291810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb000000000000000000000000000000000000000000000000000000001781529251815160009460609489169392918291908083835b602083106120e157805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016120a4565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114612143576040519150601f19603f3d011682016040523d82523d6000602084013e612148565b606091505b5091509150818015612176575080511580612176575080806020019051602081101561217357600080fd5b50515b6121e157604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f556e697377617056323a205452414e534645525f4641494c4544000000000000604482015290519081900360640190fd5b5050505050565b60008115806122035750508082028282828161220057fe5b04145b610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f64732d6d6174682d6d756c2d6f766572666c6f77000000000000000000000000604482015290519081900360640190fd5b80820382811115610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f64732d6d6174682d7375622d756e646572666c6f770000000000000000000000604482015290519081900360640190fd5b6dffffffffffffffffffffffffffff841180159061230c57506dffffffffffffffffffffffffffff8311155b61237757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f556e697377617056323a204f564552464c4f5700000000000000000000000000604482015290519081900360640190fd5b60085463ffffffff428116917c0100000000000000000000000000000000000000000000000000000000900481168203908116158015906123c757506dffffffffffffffffffffffffffff841615155b80156123e257506dffffffffffffffffffffffffffff831615155b15612492578063ffffffff16612425856123fb86612a57565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff169063ffffffff612a7b16565b600980547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff929092169290920201905563ffffffff8116612465846123fb87612a57565b600a80547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff92909216929092020190555b600880547fffffffffffffffffffffffffffffffffffff0000000000000000000000000000166dffffffffffffffffffffffffffff888116919091177fffffffff0000000000000000000000000000ffffffffffffffffffffffffffff166e0100000000000000000000000000008883168102919091177bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167c010000000000000000000000000000000000000000000000000000000063ffffffff871602179283905560408051848416815291909304909116602082015281517f1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1929181900390910190a1505050505050565b73ffffffffffffffffffffffffffffffffffffffff808416600081815260026020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b73ffffffffffffffffffffffffffffffffffffffff8316600090815260016020526040902054612641908263ffffffff61226e16565b73ffffffffffffffffffffffffffffffffffffffff8085166000908152600160205260408082209390935590841681522054612683908263ffffffff612abc16565b73ffffffffffffffffffffffffffffffffffffffff80841660008181526001602090815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600080600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663017e7e586040518163ffffffff1660e01b815260040160206040518083038186803b15801561275757600080fd5b505afa15801561276b573d6000803e3d6000fd5b505050506040513d602081101561278157600080fd5b5051600b5473ffffffffffffffffffffffffffffffffffffffff821615801594509192509061286457801561285f5760006127d86112576dffffffffffffffffffffffffffff88811690881663ffffffff6121e816565b905060006127e583612878565b90508082111561285c576000612813612804848463ffffffff61226e16565b6000549063ffffffff6121e816565b905060006128388361282c86600563ffffffff6121e816565b9063ffffffff612abc16565b9050600081838161284557fe5b04905080156128585761285887826128ca565b5050505b50505b612870565b8015612870576000600b555b505092915050565b600060038211156128bb575080600160028204015b818110156128b5578091506002818285816128a457fe5b0401816128ad57fe5b04905061288d565b506128c5565b81156128c5575060015b919050565b6000546128dd908263ffffffff612abc16565b600090815573ffffffffffffffffffffffffffffffffffffffff8316815260016020526040902054612915908263ffffffff612abc16565b73ffffffffffffffffffffffffffffffffffffffff831660008181526001602090815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b6000818310612989578161298b565b825b9392505050565b73ffffffffffffffffffffffffffffffffffffffff82166000908152600160205260409020546129c8908263ffffffff61226e16565b73ffffffffffffffffffffffffffffffffffffffff831660009081526001602052604081209190915554612a02908263ffffffff61226e16565b600090815560408051838152905173ffffffffffffffffffffffffffffffffffffffff8516917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef919081900360200190a35050565b6dffffffffffffffffffffffffffff166e0100000000000000000000000000000290565b60006dffffffffffffffffffffffffffff82167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff841681612ab457fe5b049392505050565b80820182811015610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f64732d6d6174682d6164642d6f766572666c6f77000000000000000000000000604482015290519081900360640190fdfe556e697377617056323a20494e53554646494349454e545f4f55545055545f414d4f554e54556e697377617056323a20494e53554646494349454e545f494e5055545f414d4f554e54556e697377617056323a20494e53554646494349454e545f4c4951554944495459556e697377617056323a20494e53554646494349454e545f4c49515549444954595f4255524e4544556e697377617056323a20494e53554646494349454e545f4c49515549444954595f4d494e544544a265627a7a723158207dca18479e58487606bf70c79e44d8dee62353c9ee6d01f9a9d70885b8765f2264736f6c63430005100032032e2bc0c0ff22609eac8f10e1c8736f3e780dcb85055451e7ac674e2667ce4b570058210390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563048f9c610ebbc61159e00582103e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af051017712623e1bcef3779fa029d2e2f4c9d4005820026cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c68854ee1c139ea09924a95b98b56a68efc1104f049f2100582002b661198733f53bb9b621ec808effd2e8a3d86db6962103738e13951e49aab048027f6bb06ff6bb0e0058200242acdcf31e478e05416ff0498747b1eafa6dded723cd84aa3fbaf351918e1b5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0219814000582002575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b582075a68b0fa4968ae75daab7bfd26f9ab84ab59f2c370a2bb03f935f76f2084074005820025a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a84ff26820becbb6cffeb183005372ad1400582002941fd76a619f3a67fa78ba1487c712734af6eab41dc9677b31f2061df8da265820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0219404400582103f6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c704101005821031f25289b5c9db29d46c3566463f71796d2e07c9a7a96a888214082f19288cd0048f746a53b4c6a56a800582002f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee3582065cf607b00000000000215a2f25a7370a09800000000000078f317b5af08a25d0058200252222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f54c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20218480219f44505581e03d4635628fef027e57129bd3a095819ae66dbb21fb4a76125a74aae57d00701192c1d03ba69b6e13b06e84ce4faf381891057c2373b95161158cd388cde1468371b679003a730bf4ae8b8d79a8c09ab8d29b845fdd4c90f497f2b15089a8c5320d8cd7d9c03dc9634d9c39ab372aaf8be320e08b89f81ffa41da053dacdb5d6d9c25941f79805581e034591b5db0714bdb9feb551698706b0d8cff472f22c58bf09e6f6905d7007011bffffffffffffffff05581e03b1a88893bf611ec668150981b0b37067adaf203b9858aeed822c2d8c300c04473a8f8ca6524e2e05581e03e1c68af45e0c85feaf3a7e9fe92e28f05c39b3685484103358404cb2b00c0245e8d4a510010219a5f30312ec879be9c677e9cd8c58b81338a3c1f442a39534fa5a20480361821c674ebb03a9b19de15c2de2252a22b9b5d887c9c436742eb269eadc247209b24b4172bacf033506e714a4a01db4c0842dfa4660ff9461fa8dcd0016b355c697f5cff35e542a03325e7a922b7b10fd078dde633987d39d8f11d36c73c25772f0d9292ab0e9843203cf5e1050d248908b6c3f95dd3a9856e716780c8bbc1cedff515677287dcbaf480348e1ea176bc5ad367b7983a2ab37538560113be6bd6b6c4e4a5b64ee104f503f030f06198042726a04fefa7d9a78c2fb1cd58250947f8a622d64a00508e309035303c0a46a8f8e407587943847e371372bb9cd11dadd451a80c84fd172c729f931770386e834850f3cbf84fb35f9c43c8a4af6311c86416b751fb68441e720a454e6cd03e350976bf817e314e74655a286a781918c2e8fb14b93b1cae155199497c6086f0396f1f5d6b310c90daf49e363058565126fb58470cfbbdddd46281740a72fed6e03fef3723d9d6f88255fc973e1c05cf370e97c6b4ca4d817e246da14e2ba1f5165039926cd4851688a6aefcf632eb5c95972342d8043bfee4ed5ddb714ad545c7d8c0219ffff039ce82626dfccfdc04a23de94000cb7e7b5ff29196454a6d3f29d2f34ae3e1ee10300f8bc8529a658177b91483a8c20dd822882ffa84c2173b97f3c54c2e5aa5ab60219ffff03aa5b66c190b04ab77071f0af2a64d663196df1d4b0d26048c21be3431efbc4730353a2fb7a6edface47ef1d1e969afdbbcd75b98f9507b0573087ade8cfbcdbe6e0219ffff03097157d03fca75074a3dc93deb9dafc6342d7b715bfabf71fca899f08b2dd23a0326b342158886796927600e31784c379f8e1e7d1d67327d82bcad9078223b9b73038d77db4a697a0bedc7f67619e6733d2bcc034da5ebc15510c6a114a72b1d04f60219ffff03d0de2e180fff3a55c29c3f515a6018e7c3eb4c32a03ac92442e0d3f687d9b65103ecd7a4ac75cdf85fa4431962fd0f1983a089dc08696a3f2287706e19ace0f6c803f10900d6fdfb05ce1547c147e643ff12ebcea5741a819fb8145b517fcc18b23c0219ffff03878100c6c9978d175f775bc3ae266eb60efc6cfa774c2baa124921d393df04c203ca4eb829dcfaecec905c4f3aa891b389b10d22ed8cd6a945705ee096c7ba8b32035aef37e568230984375b8c038ae04b46877562d2cc4db86c07969dc12a79a68a034b965bdaab97431fb164f668dabd56f4023e5e8589626cca7e625cc85562bbd9038ce1019ba6386eeb4d667511780d27b81eae1c9e4fffe6a59ffd13a71b4d87ef03ac7bd0407d067c58ccb9e10b7c68e497286c6f5710e97b5f4fcbe2d6ae30c740030c91f514a6dc623b7cbb4d3a4fd91ad2827ba569fd6c109b05d8c48a7107155f031f8c7783ff88d7e710280676359dde9afc5b12e4b3ed612c160c36c477fc4f0103e4ce0629ccfa996c8f77b4b8bea1e0e14eeab7c0aeb5224c59531eeec9361c4503db0e5c1161a9ab04e3c80f8e69410bd456db47648ce65ba2524a153129e4ac2e033cb52f93ec611a8da0a05bea4c61653be3b5fee33b255f7c4a4a33d7a598f1fb03e574c20d5407882ea0826a1d6f23218fdcf684c872de8412ae633052ae679b7603c71c4f70e1fcb790d7ea148787479c232ce53786da5931662e3305295b7eda1303fc5d61ab5aea99e55b274ab21b6a194c669d11e6746d4fce409c60294b4bfb92031e1c8733fdd568cc4e51d07fffe8a54175ab5fd9e83375f11da2893ca7e1681b03f4fdc6d79030a30c431df4e85969ee99e6fd99b51342541e5a9801cba5281a870384d0a936894c155431dbf0bcaef8d4c58c66e0b68b2163a0defe33f2afc3d67603a8d4459a41a545aa37f1df4cd1e53dbdfccfe1b92739db8f0b507ba816ea71ab030b081eae3fcb2995062804dfe05064190e89879fe6a8358b7e0716883222b1e20373da16b5a11fd31f687468e7e437c0ce88ed2218ec87097fd68d12aecedbe82103458ae837264127bae70701a77efbd93777c4f2f964eb0ff8749febf21b08f30a03180409dbce0f471a844cde7655d95f99ce57465f2c9b3a3519f6ef0fdf1296ac032411ceacd5de7fcb10320f232f57522a65bdcf9a0367f17cd2a6f2134ab128b6037c229ffc216827e2e26285abdbb2aafec7f91c595696c56c788e8c9d6b378fb30314d17ef0919b0aa2c2c93f207d5eb384998bde11623ef29cb6c192db580c226e03e165e3f2145ad1b10898dbcd711b093702d032e174a5bfcff28e34c0a9915e7203d8a2a04978703613b39788713a45ce2347fe08f90b592b2fd0f77c782f3fd9cd03d142273a6a1c19cc86b6aedf3e313aca349ea9d25b1398b2f2f3cd1ace87667a033c7edaf4c8a204444d65b7f9acb35770d92fe09237a5018e699090ad0a187d4303d0cdbf72330850e1ea2eab63c2c486efa350a5af71292b89b52516ac046a335a03e60096b2ed0595435e292119086017532da0b5ceef785f0b34adce9b8cb383960309e7c17f44c6c75499f1bf67a2b9c9448ac5adae6221167115e0d9ce0e49f23503ec3f036e612279d24a3d407ca0ca1b8137a8d3d2098a5e66a8f214125b3a7f3503c6371abded603253ee4228bcea5add2a8b7400008cd2c7b5aa2ff3f4022f900f03cb803d44a46f933e524d11c4bc2f26aa300b0d2560dc348d3806cf00554f0569039583cb3595c1a6fbaa09a3ccd519076b7720661a4066f398865650ea5ae1896b031b229c9aa259eab43f0e90082bc8425f5e6c04c20e70999a2c9f48d7b72487cf03c67a043590d9eab3067a1c34a179e424063785319153efdc4afb6c94ed99c8c403a8e4a1de078a7ea7818384927df551760df45d456d2a5b9efc8d797378c7246903f0555de3a780a48be182cb3956f3972eaf5106a284c0a7d87c1f77ff3967285a03783780f3097d9d3b8d67969c62f43b69a1c1f7f8b5663c07a34a3780f8c5e29d03103aa12154c07d1154135f1fc68be927ab68f4d8d75b08258469719f378d35c803a40d312748d6b29a5f4d7dd517d1428e5eadb39832845c7993fe5e30384352eb037a4fda7eecbf10b451093aa7e2ad1ba546c4a311b9afa34602a683bfa6af4602037effdea2cbf5b602df64645f5b90f76cc0fa0bcf9b6b20d5148ec979986b066103974a3df45a37ad885cb8d634f3ed912a8487ff4289ce9f7bf69b9d008ee9a18903f27ee8ab8332ff77ad5487e6a9ad201754bc5b99429cd6829d48d57cd689a9d90355d40058e37fccd6ba820f57b8ba73329b414bef035b4b2fa58dcef21445079a036271b1db5ad4c0ad3f180eb86687dd406625ba472119ddaef0b57a160d0aea6903c47460ddfd8303529a1e20b02b0283a695f2c54f1969d4ae9375a871d8bc880503f1b574431f3838d9cdff6e701afd5a058652dab5ae5523288a83d5fad7696139035d1e21f700a49be24991d5b20b17b5bd6c9f9a8827fdcdd22ee081f8bbd1bb9005581e033438101ef5396c10534be9546fc834bd35e6ccfcf8cd3552b4b854d33007011bffffffffffffffff03fe8bcc95af69bde603a5a768dbaa33251965e49b6f41b8029c8662a8db4969f5038d4c389f756f95f370cf7c6fa0651dd5b2cd27e42d12dd9222b0ee4e91797ad205581e03c1da930cccc481a412b5efdbf4997d85c44a1a7b63e2992224a2f4a5300c014705fd13b7002800035fbb31888e45cf7930d8c8e1029831f8ce15991af5b655662300055997f61b3405581e0359b9faca07078cae61867ac6e3dc14b902198ae1980e99c0001ab016600c0346ca879bffa87803fb917bf9a473489d27e723f0e8f448c4e545e27331b6ff02f21f902d020da35805581e03996a3aa27c767b8c3ac816236a0b34c8f2e66d191c3212eb329856db100c0247021a2f33a4a2d005581e0302165eaa9d6e6b7eaf421cf598b0638802abd4d1277516e120f88e71f0040105581d023a6babfab699ca3d07c66886fa4e9e51a2a6e21297dca705c934a9570c1819483f9090fb7868e6ee05581d026cd14160e241b00429968b1668fc8b8e3d9d191b136b0637ef5b1a450c014687852573cc10021948000219fe5803a05659574551ea0cf290a2c70af2481e86893cd0ebcbc5fc2387c80e817927b203c542072960261f3b38cd2dc79531c70c6fc4c0aed4c87f4c6cbd07e79d1c490003c63d5ff108063e3a89891c3f4ea60dd31ca4697cd58adff1a2370b952c47da67034c73f237b3dbc56ce47ece6783b811a0ddfd8a6c3aec26c796cac29d612ce9560394be43c2adad543ba84fdc7f7bd7afdf03a91b794f416fdb5c4c0c2b7b41f67a0219ffff030e0881ad66a02f3fe2c9ebcb27f845215b8b4dc93ea5501b459478f8701e992b0219ffff03a8d34fc25359ac47eb194ea6e30768823481ee1ba924b9848c4ee23556817a3203d85fd32fca06ed5f490940bbfe9d9806208f2362eaed9b75eb27e407532df021030f8f2c11a52847a7be932fd334ae7db84dd2ff21646b915c811f18e68b4182470335891ad75f98d3652a9dbfbf992443c43e26602aa777cefe83fcc70e19e77d170394690fbdfa2ad5e4e129dec7c3237b4f7734abed6dcea962084ea426aceba58f03926b0e126a044fe044ce1d0831c226ad12d47181570def599c1d2d29c2befc95037c5c4cb86021328742bc66980c7b2b71ec34ac9a817cb96e136f713593152a0d03501338a2a200b60e6d0f87dba02d16abd7b1686ccb3136e9b0240a4c0cf5b6c7032db8e251de21bbdaa52da2ab2ac3b2a14886689e3ed30b4cb6a844b51a3c20f303dca97dc0f65415e9879799ba77b6783282c65332dd3a13f425795281003245070219ffff0367f451a4ad47c8220f1729d0a9791ec1ce5bea8f6b7906645e1355bab18735ff0322b54ab719b9e3e71900115871b1f48077880126a617bf38c3b8150b978021960219ffff0302bd4698994add16277367c4bccbb70f1248e5d28e378c04deeaf5688274b1b503f1fc7bfdbc17f4086b74aef53d09b2a82506feebd991a8a0ade752e0003fe5d903171ed47d15ad1089bcf6d352466f44e99bdd2af2ad2ba4cf80363323d9f176fb0355bc01d7ffce21e32df87fb848757e0878eca09f76e1fda3dc7415e0b1ce07c20378e5fdcfbe4d95ddbddf83d1b2110372aff3e388b8c9e4e52a65ddbd34c01b870372002b1bc559bd8b33137ebc94cbaa3ce5f134d124872331b8d1910e83130ff303c21fca0227893af7ee96d8ca1450a18a5d8e5c3c11a8c62801da5739bbd1569a03062b54192802608f44da7d8dc88572d95e312046b506774b73421262e610fe75034599d0b1b1cfc5b7c2211b22d00929b48d1c447e43786f5430727294e4a1a1c7036f3dbf2aefde19bcd4efd4ad6b3863ea328336faad90dc710f7e6e2dc6975c0e03c7ef8aefc48789dc86397aa6315851d86b100b8911c962005c069c4874a2fe0003115289873e516be36694c7c922de641db9395df113345a505433f386dd1b9873030a5aa79d60cdac8e72264a13b956bf58e8f34ca6fb0e8ec6f3db057dea8cd65c03fb1031261c5d5d1992c80f0037f4d191c92398c2fa126389e356d9f38c13d7cf0362323e7dc261fd76fe5455219780f1c9c9f982774cb196f8f765c0af4cc7e6c303fa269f41ed80985a1950fbf26ebad060dedf816a6d5f78d3f7ec07e9827df75303b29f231fe527f9f435efcb02d86914c05750f96b4463eb8483376a680e6eb991030df7a18ffeae72999e556197def50a6baa33a2c3b657eb5cac7b023200e93c7c0310b4b1f0815162aa248e0c9c588f31f00f45ec5418647eb8748b55b81aa0f66f031ac4758ca7b0d836a009ff26dd3c075e679a3162a4a02426e21f115ff549d582034e3fe4262eb2506ce70778c550a215651f9b234b1fff3c13e36ed89f22f7aead03e483ecca68b2b9532792ca69ad8eaa73552e6a75f9b3b235606c60cdcce8d5b30376b84eea0f0aaa1676c6a0ae8b1a6a042041739824a19da7d473a1b2a56bc97703ff604c3b5e3154617bf0364f42c18d10ca313bc62821b24c4aebec56b26fe15a03562c3f1d73ff4f5a6b3f2ae66df841e74add41bc94f4cb5f25302d6f9f43a43f0353978061defd7c4ebb3ff153cddb5b9b3a5b09fb73ba5f0897c1d6c937043ec203630745dc758c3fb321763b4441fe788474762bbc52061f2ce4cb09c5e651ab9f032fab18a72edb2a1718d770f78c3c2851330c5f67aea71542432b0cca2d558d9d03ebf5407db2103bc2170644c2ea67c5fa21cf167f3e4a182dee62a9b6a0e525ed03ca02e8f285c5e442bf6bc2ff36bb401891cc4f772574e6a217c1357894d2b150030558d991cec03efdee59d2d25815b0f71de4a7e4bdb8a4d0c27c1d715c6f1143031d4085a52c1aac855aae027cd0c1d4130979f306d5ba05518a3a5b7c39f9087703ef5431197bff07b8a476584c5c61a40e3cb7ff726d9f1d405f298c270fb6683803a761059ea5af9a1b64c6ff398a71a1e0572de2037f6bfe1969e7166492411d96038138530f6fb246a158dd6b68bc756817e02696f8548fe93ee008aa59e37930e0038425c190044f3304a1aa14185c149858784b9b582178e80721157109b6f0d20e03d1fb26ef4ef14fd1cd15f2aaeb42cc49048fe7d1ffc1023078902b53cb37aa79033ff5200910bb08510effdd04ea6d4f989ce27254fe38eb3cab754fda2a22d21a03a08ba3a324df41304cfe3374fabf34b70a5e477f0013a58cb491bacc0d87d0770335bcbc8fba81b3fb9c6229660536a8614decb7a45b6c3d47cb085cc1ed403fc903da811c580b78eac3ae5ba3c1265c3aa65d21deef1b44fb752d73e67cee94214303cb4d7eb01e97ce22d09191b41d86a60d816b821de3b510486879b8c40778366e0355f374186530d90e7c9719bc3cf70fb0cc5c712c24228a1f5e4458382170eb74036a29255426190bb25fa8db64db6956b68a9be29b0ffb49cd008a89899731aa2703e2b9de7909547916f9f268098b9d3df29f01da900cd7d3c9bcec5f2170fcc21c035d134e2f370c028df4a34fa28eaff9242ad0e804976612aea7cef898722939950399c99f1a6d65a9097e0f8ca61683878ed26099e347359f5cdc600bd194dbe9080334f8e8acfd3df21fb6b4920d3fa69a9856f37a4f9cad8dbcc9ecd7f7a73c7d8e05581d039affa12f89f8662c1507f375c69d80101aaa5b480d7e964080f7d73007011bffffffffffffffff05581d03b51aeb3ee9e7ca0d7d47ac795675339bcd29c50c6ac19c125bc4df60084803ceabeb6ff266000219010801410b05581e03dc1677c4522eca559a65132eb4b85199e621072d97377578460c696cd0040105581e034f25072e0b9fdfbfdea8aa79146512dd27421f764497a8be2957f43a900847421499f8d2800003ae1af30a21312b72edcb045f5268155e027b81270fe04dc2921f94bb804705b70321754bf7895b8714445d33ee8983b68a6ba8fdd6d4e100c0d35d03c09ec29a0d05581e030c22d311bdc1e70fc4b42fff2c58373b6a8e00640950858edc1f22f97007011bffffffffffffffff0219ca42030cfc9fc5667290eed7b5ac7adb68e286859e4ee77557e8560b8abd4db3a9cb560219ffff0219ffff03c518c068d62fd3d91cf967ec08251172165d9ee5751baaa60082ff7bc36d690403b75e4fd4abf4bbd921afa539bf71ce13d3f4c11caec4e5594a306f361eef81dd03995fa5fcf1ab4232768b3266bd9be5549b571fd7928573a7d879bd44c17ce67a0326c967f97e9999cc08ea55be423847fb7ca65a2e66da929ff920751061c7d4e90219ffff03a5066587c590ed21e9f7dc7e44976e9b10472674f6bd37ce627b464ae0a75dd403ee92b8f62185d11b314fddc8c9894dae73a8d1d566ff46b325380cc33c12388303e2cc8541cccaa04c09040f581808a870038ad5931cb969370ad3242f5aad387d0311bdb9fad4c09d7067260c14230de1c6ab3becc2bc6c15b160dea322739a1b37038db49d765e38fc43a357651cf7deb2ad2fee35790c849d0d87d20f69ef0f257903d8ac6b6b7c0d0c49edb0d8aa589b59ca52ac5d6450f14e39b08eb4f29b0dc9c5038293282d89c2663c4ecb581b01adc19b0bf2f146f1ca904a890624a34a6b61a3032294f3f6186f4a5562d601140d3ce5dbc0dc251dbccae758ddb41f50816859ff03f994b3e2f65b080d61b21aab639f99ce65352b1960fca5831efd6ce5e29aefaf03ff38083b3bf9431afdc20c083099823247ff62849a3d8eeaabe78c5d18bd70d003014c09ba7243985098597f841a6ca69159150413e7f8263849c95a8a3cbce86f03c285b0de973206517e2f32b72f6e7665a08306427b96326b6baa3478555362280219ffff03cf4b7ad72e2f7a3b01b4c7c417abfe0840bd69003bef46c3d3eed21de2531ac803c038947683b938fd5a917a384a8ac3ae2873193b1cd3a0b0446acd14dcee91cc037b4f0a4555cee9cb31f97e25e76513ad4f1100eb8ef5464b9871a22d8c0f52f5038f05094655eb5336b7c1ab05e6826bc23f04d4f5ee914d2a3247d6e22be72cd203e5525ecf7a788dfcf1cf011caff57b8668e553256bc9b30abbdf77b97b28765803cbd8906e97ee531eabd8d49fee9c3ae78f5b2f28c20696557ca09654c5f1ade303fcc753912a9db2a5a0b8b3ceecc1f621e1a942c42baf17c3b4882e26a6b37ca303e163ae3ca9fe4cf2d99eedb537f5dbb9b68e9dc7fbab48c83a682d5adb518f9d031d493888c974607dbf2456f935206ef317d3fa83e175eb3c719a262b16ad5d2a031bcdfa82750fcbac4bdbaa853a2bf29593383aaff1c96b19c98453a58aa67e2003abc592271f11f7b1a4ae6c1bad45486c7115063735532507b0d255361cec2f2a03df515b9d6e1e4fe32f602169a59bd354264ad8128455f576cb86775ce31dfe4303d7934fc800f5abb969e43f4702ba707c596df15f3c4bcec2a295ca6fb0a243cc03488d761aa0cbe0a0472e042a4d29b60c162e197a6f154e09f81aad9f7101c35f0383a3092a8366a04b3b83f747c8918e0edd2435d2a0f826036f6d9d99324aaf8603d1da83539dae0099f72cd69e9667e4cb28b5b33ad2d96258c088ee114748c8c503f1ce13bef235983d4066d4f705ea80b2874c73836a2a5640546334f9e45cb96603722022519cf3f20e1b07211e22fb6f65d34ccb0429f638c58d08bf6e1ecb79ea03761f0c601bf8cb02b95116fce2f8c21e7ebd67939b4520972f91ee160b310c94034125e8dd1d536ac4c80e6e9840ac8b29d30465c2dd34ee2d704cfd82c22b51280389d4da338edf84d390cf34c69e8c36ed764423c1a15c50213f1dc1e5dc369a1d0378e94952ce1bc0afde1eea71ca51c365c3d9f1806d3edfcc535103cc32dd245703da781397a8a19b3cb849eb2429e9b3f2d9c7fd8833c19a8be1178ae937302efd037dacccfd5d2dbc236bee7cb777e959ce654c610908d338de3dd4ebaf20704ba303d51f2fca8487e57207f3a4c260bda7252a1d6a2f6f3bbe434c82ed5461867f0e03f83f96d228dc2640f82b03b8016166598a770cb24fea736b49c85ab1885e2c6403fd2d5808e1fceced0e4fcb12844cacae66ebdfe52b2977fc7d298ead33d8cb1303ace34b6bd027f526944c7ec351d854b362f59b789f9b0fea04ce8c5728a539dc030c30dd688be7da097a7931192372452de60035776d01f2ea5444c96e99ee34e103c2c344b5398d297d91632f0f15db6d8396aa44ee5f68fc14c298b091a9fb02a103c52a80712f639e5d71618d76158e26835abec7f439e4b283980fa5f1640bf3980375f97ce3e0fc8ee3c26bafbf906f35ad3f8c5aaf0338ba1b20c5c0352566116c03ae55ce69e6c98fbce62692032113356e0efe54be677f4bee7976bec37d17a7f5033b405d677c8017060b68df3c28b6c15da9e94505c3df263f6ad471fab20e4fcf0378f5167a494b7d754bed3a275e39803baed72470ded6ec1caa0582e17578acff03578c2127074f32fc131f17dc09716bd59d1a9da28e0525440b85e91a77fa5c36034aadfa8ae380e751a027d2dc84f0bfc9a1cd4f4a73b0464a95e491ac905ba1c1032f2577ad00321839febab8416cf64b0e0fdea6d5ada8c4fe105a477625fdbd3203d0745f81e49855fc6a9bd2d3e3897bcdcfb7f23fd5eac8132698624f9f8f939003e472b8b3a4cc6a574a9e899a72381da4eeede12a238fae43cc064996cd15d65c030816e1229f4b52302711d8a527bfca050711ab10235bec03520d3077d6a9908e030291527e64a07367aab20faf5c96279c7900cd4a4e32a3d4fe0c0c418ccb6d8003555d6acdcb125c7be00f942339138a2741ddd1985dfe1da2e2b006d1445b325401410c05581e03d76845474f6b8fc056f04fbf29ab738b82af3d0fda0374cb1c7b3ae980040103a730bf4ae8b8d79a8c09ab8d29b845fdd4c90f497f2b15089a8c5320d8cd7d9c030ca6da658307619963e16e7ee61a42cc711e48fce75d8b9190011addf16a3c9805581d021f15ef2ca3fce0c62b42e9d74d2a181d9cc435631edf0db21949ee9a07011bffffffffffffffff0459368b6080604052600436106102b25760003560e01c80638ae458da11610175578063b2494df3116100dc578063d7f1b27c11610095578063ea0ee5591161006f578063ea0ee55914610842578063eaf993e114610862578063f77c479114610882578063f83d08ba14610897576102b9565b8063d7f1b27c146107e2578063dd62ed3e14610802578063df5e9b2914610822576102b9565b8063b2494df314610738578063c2de0e9d1461074d578063c5d574fe1461076d578063c75640171461078d578063d0ebdbe7146107ad578063d7b96d4e146107cd576102b9565b8063a457c2d71161012e578063a457c2d71461068e578063a4e2d634146106ae578063a69df4b5146106c3578063a7bdad03146106d8578063a9059cbb146106f8578063acf3f07714610718576102b9565b80638ae458da146105d75780638f6f0332146105f757806395d89b411461061757806399d50d5d1461062c5780639dc29fac1461064e578063a06324611461066e576102b9565b806340c10f191161021957806366cb8d2f116101d257806366cb8d2f146105155780636f86c8971461053557806370a08231146105555780637d96659314610575578063802758601461059557806381b2248a146105b7576102b9565b806340c10f191461045e578063481c6a751461047e5780634e353270146104a05780635230c396146104c057806353bae5f7146104d557806363a90fc1146104f5576102b9565b806323b872dd1161026b57806323b872dd1461038f57806326898fe1146103af5780632ba57d17146103cf578063313ce567146103ef57806335bc4e5214610411578063395093511461043e576102b9565b806306fdde03146102be578063095ea7b3146102e95780630ffe0f1e1461031657806318160ddd1461032d5780631ed86f191461034f57806322ebeba41461036f576102b9565b366102b957005b600080fd5b3480156102ca57600080fd5b506102d36108ac565b6040516102e09190612db5565b60405180910390f35b3480156102f557600080fd5b50610309610304366004612b31565b610943565b6040516102e09190612d66565b34801561032257600080fd5b5061032b610961565b005b34801561033957600080fd5b50610342610a4b565b6040516102e09190612ddc565b34801561035b57600080fd5b5061032b61036a366004612a02565b610a51565b34801561037b57600080fd5b5061034261038a366004612a1d565b610b89565b34801561039b57600080fd5b506103096103aa366004612af1565b610ba4565b3480156103bb57600080fd5b5061032b6103ca366004612a51565b610c31565b3480156103db57600080fd5b5061032b6103ea366004612b31565b610ccc565b3480156103fb57600080fd5b50610404610d43565b6040516102e09190613548565b34801561041d57600080fd5b5061043161042c366004612a02565b610d4c565b6040516102e09190612dc8565b34801561044a57600080fd5b50610309610459366004612b31565b610d61565b34801561046a57600080fd5b5061032b610479366004612b31565b610db5565b34801561048a57600080fd5b50610493610dd3565b6040516102e09190612c47565b3480156104ac57600080fd5b5061032b6104bb366004612a02565b610de2565b3480156104cc57600080fd5b50610342610e90565b3480156104e157600080fd5b506103096104f0366004612a02565b610e96565b34801561050157600080fd5b5061032b610510366004612ab4565b610ec9565b34801561052157600080fd5b50610342610530366004612a02565b610f48565b34801561054157600080fd5b5061032b610550366004612a02565b610f56565b34801561056157600080fd5b50610342610570366004612a02565b610fae565b34801561058157600080fd5b50610309610590366004612a1d565b610fc9565b3480156105a157600080fd5b506105aa610fe4565b6040516102e09190612cc2565b3480156105c357600080fd5b506104936105d2366004612bbd565b6111a1565b3480156105e357600080fd5b506102d36105f2366004612a1d565b6111c8565b34801561060357600080fd5b506102d3610612366004612b5b565b6111d4565b34801561062357600080fd5b506102d3611281565b34801561063857600080fd5b506106416112e2565b6040516102e09190612c75565b34801561065a57600080fd5b5061032b610669366004612b31565b611343565b34801561067a57600080fd5b5061032b610689366004612a02565b61135d565b34801561069a57600080fd5b506103096106a9366004612b31565b611483565b3480156106ba57600080fd5b506103096114f1565b3480156106cf57600080fd5b5061032b6114fa565b3480156106e457600080fd5b506106416106f3366004612a02565b61156a565b34801561070457600080fd5b50610309610713366004612b31565b611575565b34801561072457600080fd5b5061032b610733366004612a1d565b611589565b34801561074457600080fd5b50610641611646565b34801561075957600080fd5b5061032b610768366004612bbd565b6116a6565b34801561077957600080fd5b50610493610788366004612bbd565b6116ff565b34801561079957600080fd5b5061032b6107a8366004612a02565b61170c565b3480156107b957600080fd5b5061032b6107c8366004612a02565b6117c6565b3480156107d957600080fd5b50610493611850565b3480156107ee57600080fd5b506103096107fd366004612a02565b61185f565b34801561080e57600080fd5b5061034261081d366004612a1d565b611868565b34801561082e57600080fd5b5061030961083d366004612a02565b611893565b34801561084e57600080fd5b5061032b61085d366004612a1d565b611902565b34801561086e57600080fd5b5061034261087d366004612a02565b6119a6565b34801561088e57600080fd5b50610493611a0d565b3480156108a357600080fd5b5061032b611a21565b60038054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156109385780601f1061090d57610100808354040283529160200191610938565b820191906000526020600020905b81548152906001019060200180831161091b57829003601f168201915b505050505090505b90565b6000610957610950611a79565b8484611a7d565b5060015b92915050565b600a5460ff161561098d5760405162461bcd60e51b815260040161098490613427565b60405180910390fd5b60013360009081526009602052604090205460ff1660028111156109ad57fe5b146109ca5760405162461bcd60e51b8152600401610984906134b7565b33600081815260096020526040808220805460ff19166002179055600880546001810182559083527ff3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee30180546001600160a01b03191684179055517f27b541a16df0902e262f34789782092ab25125513b8ed73608e802951771b9289190a2565b60025490565b610a59611b31565b6001600160a01b03811660009081526009602052604081205460ff166002811115610a8057fe5b14610a9d5760405162461bcd60e51b815260040161098490612eb1565b6005546040516342f6e38960e01b81526101009091046001600160a01b0316906342f6e38990610ad1908490600401612c47565b60206040518083038186803b158015610ae957600080fd5b505afa158015610afd573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b219190612b9d565b610b3d5760405162461bcd60e51b81526004016109849061316e565b6001600160a01b038116600081815260096020526040808220805460ff19166001179055517fead6a006345da1073a106d5f32372d2d2204f46cb0b4bca8f5ebafcbbed12b8a9190a250565b6000610b9d610b988484611b5d565b611b8c565b9392505050565b6000610bb1848484611ba3565b610c2784610bbd611a79565b610c2285604051806060016040528060288152602001613609602891396001600160a01b038a16600090815260016020526040812090610bfb611a79565b6001600160a01b03168152602081019190915260400160002054919063ffffffff611cc416565b611a7d565b5060019392505050565b610c39611cf0565b610c41611dcd565b6001600160a01b038085166000908152600c6020908152604080832093871683526002909301905220610c7890600101838361288c565b50826001600160a01b0316846001600160a01b03167fb373eedb8e9038a2dcba31f06b2735887c1a904125e5c9f0021ba41ed972318e8484604051610cbe929190612d71565b60405180910390a350505050565b610cd4611cf0565b610cdc611dcd565b6000610ce782611e02565b6001600160a01b0384166000818152600c60205260409081902083905551919250907f8133e2bf34edab764b55c59d1d41f9df637e7c22828bb6b0a9d55b429d008a9790610d36908590612ddc565b60405180910390a2505050565b60055460ff1690565b60096020526000908152604090205460ff1681565b6000610957610d6e611a79565b84610c228560016000610d7f611a79565b6001600160a01b03908116825260208083019390935260409182016000908120918c16815292529020549063ffffffff611e4716565b610dbd611cf0565b610dc5611dcd565b610dcf8282611e6c565b5050565b6006546001600160a01b031681565b610dea611cf0565b610df2611dcd565b610dfb81611893565b15610e185760405162461bcd60e51b81526004016109849061313f565b600b805460018101825560009182527f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db90180546001600160a01b0319166001600160a01b03841690811790915560405190917f76da6cf8b40dd2b2c223a5956831e0ff4e75522538a86782108a0bbe1577e29891a250565b600d5481565b600060015b6001600160a01b03831660009081526009602052604090205460ff166002811115610ec257fe5b1492915050565b610ed1611cf0565b610ed9611dcd565b6000610ee482611e02565b6001600160a01b038086166000818152600c602090815260408083209489168084526002909501909152908190208490555192935090917f81a422e27f503e1b92cdb616a6e653aac10a8e0c3fa6832a58dc616c080fd7bd90610cbe908690612ddc565b600061095b610b9883611f38565b610f5e611cf0565b610f66611dcd565b610f77600b8263ffffffff611f5316565b6040516001600160a01b038216907fc605d0bf97f9b921340106a2e59f9428d5eb9039b16866159e0cdd2bf8e963df90600090a250565b6001600160a01b031660009081526020819052604090205490565b6000610b9d82610fd885612080565b9063ffffffff6120f916565b606080610fef61210f565b67ffffffffffffffff8111801561100557600080fd5b5060405190808252806020026020018201604052801561103f57816020015b61102c612906565b8152602001906001900390816110245790505b5090506000805b600b54811015611199576000600b828154811061105f57fe5b60009182526020822001546001600160a01b0316915061107e82611f38565b13156110ed576040805160a0810182526001600160a01b0383168152600060208201529081016110ad83610f48565b8152602001600060ff168152602001604051806020016040528060008152508152508484815181106110db57fe5b60209081029190910101526001909201915b60606110f882612080565b905060005b815181101561118e57600082828151811061111457fe5b602002602001015190506040518060a00160405280856001600160a01b03168152602001826001600160a01b031681526020016111518684610b89565b8152600160208201526040016111678684612194565b81525087878151811061117657fe5b602090810291909101015250600194850194016110fd565b505050600101611046565b509091505090565b600881815481106111ae57fe5b6000918252602090912001546001600160a01b0316905081565b6060610b9d8383612194565b60606111de611cf0565b6111e6611dcd565b61123183838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050506001600160a01b03881691905086612252565b905083856001600160a01b03167f2a936dbabeaea30adc1ddad138b1958497988474bef8b09a29411f0105ab8ab285858560405161127193929190612d85565b60405180910390a3949350505050565b60048054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156109385780601f1061090d57610100808354040283529160200191610938565b6060600b80548060200260200160405190810160405280929190818152602001828054801561093857602002820191906000526020600020905b81546001600160a01b0316815260019091019060200180831161131c575050505050905090565b61134b611cf0565b611353611dcd565b610dcf8282612280565b611365611b31565b600a5460ff16156113885760405162461bcd60e51b815260040161098490613427565b60026001600160a01b03821660009081526009602052604090205460ff1660028111156113b157fe5b146113ce5760405162461bcd60e51b81526004016109849061301f565b806001600160a01b031663847ef08d6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561140957600080fd5b505af115801561141d573d6000803e3d6000fd5b505050506001600160a01b0381166000908152600960205260409020805460ff1916905561144c600882611f53565b6040516001600160a01b038216907f0a1ee69f55c33d8467c69ca59ce2007a737a88603d75392972520bf67cb513b890600090a250565b6000610957611490611a79565b84610c228560405180606001604052806025815260200161363160259139600160006114ba611a79565b6001600160a01b03908116825260208083019390935260409182016000908120918d1681529252902054919063ffffffff611cc416565b600a5460ff1681565b611502611cf0565b600a5460ff166115245760405162461bcd60e51b8152600401610984906131ec565b6007546001600160a01b0316331461154e5760405162461bcd60e51b81526004016109849061327c565b600780546001600160a01b0319169055600a805460ff19169055565b606061095b82612080565b6000610957611582611a79565b8484611ba3565b611591611cf0565b611599611dcd565b6001600160a01b0382166000908152600c602052604090206115c4906001018263ffffffff611f5316565b6001600160a01b038083166000908152600c6020908152604080832093851683526002909301905290812081815590611600600183018261294a565b5050806001600160a01b0316826001600160a01b03167f5d0275f68d583838a7e2be68e3ef8ac2c1f9d4eeaa4b223360f4cd093a160d7560405160405180910390a35050565b60606008805480602002602001604051908101604052809291908181526020018280548015610938576020028201919060005260206000209081546001600160a01b0316815260019091019060200180831161131c575050505050905090565b6116ae611cf0565b6116b6611dcd565b6116bf81612362565b600d8190556040517fc4e78b3245dc105eefced18655b978e194ff858545a1080f2888dc3b6ae8df0a906116f4908390612ddc565b60405180910390a150565b600b81815481106111ae57fe5b611714611b31565b600a5460ff16156117375760405162461bcd60e51b815260040161098490613427565b60016001600160a01b03821660009081526009602052604090205460ff16600281111561176057fe5b1461177d5760405162461bcd60e51b8152600401610984906134b7565b6001600160a01b038116600081815260096020526040808220805460ff19169055517fd6b18042563148f38d728c1c4d339ffd515b632d0ad2e1833b822412f3f4d8779190a250565b6117ce611b31565b600a5460ff16156117f15760405162461bcd60e51b815260040161098490613427565b600680546001600160a01b038381166001600160a01b03198316179092556040519116907f43fcfef38622d6a5b118be09c27a6ed8cbdbfca21f0ea9245412ce8031c0423c906118449084908490612c5b565b60405180910390a15050565b6007546001600160a01b031681565b60006002610e9b565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b600061095b82600b8054806020026020016040519081016040528092919081815260200182805480156118ef57602002820191906000526020600020905b81546001600160a01b031681526001909101906020018083116118d1575b50505050506120f990919063ffffffff16565b61190a611cf0565b611912611dcd565b61191c8282610fc9565b156119395760405162461bcd60e51b815260040161098490612e54565b6001600160a01b038281166000818152600c60209081526040808320600190810180549182018155845291832090910180546001600160a01b0319169486169485179055517ff71324fa78e1894b364a1fc8d0535c4208f28c7b380f752afe00c8ce386023fb9190a35050565b6000806119b283610f48565b905060606119bf84612080565b905060005b8151811015611a04576119fa6119ed868484815181106119e057fe5b6020026020010151610b89565b849063ffffffff61239d16565b92506001016119c4565b50909392505050565b60055461010090046001600160a01b031681565b611a29611cf0565b600a5460ff1615611a4c5760405162461bcd60e51b815260040161098490612de5565b600780546001600160a01b03191633179055600a805460ff19166001179055565b670de0b6b3a764000090565b3390565b6001600160a01b038316611aa35760405162461bcd60e51b815260040161098490613320565b6001600160a01b038216611ac95760405162461bcd60e51b815260040161098490612ee8565b6001600160a01b0380841660008181526001602090815260408083209487168084529490915290819020849055517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92590611b24908590612ddc565b60405180910390a3505050565b6006546001600160a01b03163314611b5b5760405162461bcd60e51b815260040161098490613453565b565b6001600160a01b039182166000908152600c602090815260408083209390941682526002909201909152205490565b600061095b600d54836123e390919063ffffffff16565b6001600160a01b038316611bc95760405162461bcd60e51b8152600401610984906132a4565b6001600160a01b038216611bef5760405162461bcd60e51b815260040161098490612e11565b611bfa838383612406565b611c3d816040518060600160405280602681526020016135ba602691396001600160a01b038616600090815260208190526040902054919063ffffffff611cc416565b6001600160a01b038085166000908152602081905260408082209390935590841681522054611c72908263ffffffff611e4716565b6001600160a01b0380841660008181526020819052604090819020939093559151908516907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90611b24908590612ddc565b60008184841115611ce85760405162461bcd60e51b81526004016109849190612db5565b505050900390565b60023360009081526009602052604090205460ff166002811115611d1057fe5b14611d2d5760405162461bcd60e51b8152600401610984906130c7565b6005546040516342f6e38960e01b81526101009091046001600160a01b0316906342f6e38990611d61903390600401612c47565b60206040518083038186803b158015611d7957600080fd5b505afa158015611d8d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611db19190612b9d565b611b5b5760405162461bcd60e51b8152600401610984906133e3565b600a5460ff1615611b5b576007546001600160a01b03163314611b5b5760405162461bcd60e51b81526004016109849061304d565b600080611e1a600d548461240b90919063ffffffff16565b9050600083138015611e2a575080155b1561095b5760405162461bcd60e51b815260040161098490612fe8565b600082820183811015610b9d5760405162461bcd60e51b815260040161098490612f2a565b6001600160a01b038216611e925760405162461bcd60e51b8152600401610984906134e7565b611e9e60008383612406565b600254611eb1908263ffffffff611e4716565b6002556001600160a01b038216600090815260208190526040902054611edd908263ffffffff611e4716565b6001600160a01b0383166000818152602081905260408082209390935591519091907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90611f2c908590612ddc565b60405180910390a35050565b6001600160a01b03166000908152600c602052604090205490565b600080611fb984805480602002602001604051908101604052809291908181526020018280548015611fae57602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311611f90575b50505050508461242e565b9150915080611fda5760405162461bcd60e51b815260040161098490612e82565b83546000190182811461204c57848181548110611ff357fe5b9060005260206000200160009054906101000a90046001600160a01b031685848154811061201d57fe5b9060005260206000200160006101000a8154816001600160a01b0302191690836001600160a01b031602179055505b8480548061205657fe5b600082815260209020810160001990810180546001600160a01b0319169055019055505b50505050565b6001600160a01b0381166000908152600c60209081526040918290206001018054835181840281018401909452808452606093928301828280156120ed57602002820191906000526020600020905b81546001600160a01b031681526001909101906020018083116120cf575b50505050509050919050565b600080612106848461242e565b95945050505050565b600080805b600b5481101561218e576000600b828154811061212d57fe5b60009182526020822001546001600160a01b0316915061214c82611f38565b1315612159576001909201915b606061216482612080565b80519091501561218457805161218190859063ffffffff611e4716565b93505b5050600101612114565b50905090565b6001600160a01b038281166000908152600c60209081526040808320938516835260029384018252918290206001908101805484519281161561010002600019011694909404601f8101839004830282018301909352828152606093909290918301828280156122455780601f1061221a57610100808354040283529160200191612245565b820191906000526020600020905b81548152906001019060200180831161222857829003601f168201915b5050505050905092915050565b60606122788484846040518060600160405280602981526020016135e060299139612494565b949350505050565b6001600160a01b0382166122a65760405162461bcd60e51b81526004016109849061323b565b6122b282600083612406565b6122f581604051806060016040528060228152602001613598602291396001600160a01b038516600090815260208190526040902054919063ffffffff611cc416565b6001600160a01b038316600090815260208190526040902055600254612321908263ffffffff61255516565b6002556040516000906001600160a01b038416907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90611f2c908590612ddc565b600061236c612597565b90506000612380828463ffffffff6123e316565b13610dcf5760405162461bcd60e51b8152600401610984906132e9565b60008282018183128015906123b25750838112155b806123c757506000831280156123c757508381125b610b9d5760405162461bcd60e51b815260040161098490612f61565b6000610b9d6123f8848463ffffffff61268116565b670de0b6b3a76400006126ec565b505050565b6000610b9d61242884670de0b6b3a764000063ffffffff61268116565b836126ec565b81516000908190815b8181101561248157846001600160a01b031686828151811061245557fe5b60200260200101516001600160a01b031614156124795792506001915061248d9050565b600101612437565b50600019600092509250505b9250929050565b6060824710156124b65760405162461bcd60e51b815260040161098490612fa2565b6124bf8561277a565b6124db5760405162461bcd60e51b815260040161098490613364565b60006060866001600160a01b031685876040516124f89190612c2b565b60006040518083038185875af1925050503d8060008114612535576040519150601f19603f3d011682016040523d82523d6000602084013e61253a565b606091505b509150915061254a828286612780565b979650505050505050565b6000610b9d83836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250611cc4565b6000600019815b600b54811015612671576000600b82815481106125b757fe5b60009182526020822001546001600160a01b031691506125de6125d983611f38565b6127b9565b90506000811180156125ef57508381105b156125f8578093505b606061260383612080565b905060005b815181101561266157600082828151811061261f57fe5b60200260200101519050600061263d6126388784611b5d565b6127df565b905060008111801561264e57508781105b15612657578097505b5050600101612608565b50506001909201915061259e9050565b5061267b81612803565b91505090565b6000826126905750600061095b565b826000191480156126a45750600160ff1b82145b156126c15760405162461bcd60e51b8152600401610984906131a5565b828202828482816126ce57fe5b0514610b9d5760405162461bcd60e51b8152600401610984906131a5565b60008161270b5760405162461bcd60e51b81526004016109849061351e565b600160ff1b8314158061272057508160001914155b61273c5760405162461bcd60e51b815260040161098490613214565b600061274e848463ffffffff61282816565b9050600083851812801561276a575082848161276657fe5b0715155b15610b9d57600019019392505050565b3b151590565b6060831561278f575081610b9d565b82511561279f5782518084602001fd5b8160405162461bcd60e51b81526004016109849190612db5565b6000808212156127db5760405162461bcd60e51b815260040161098490613092565b5090565b6000808212156127fa576127f5826000036127b9565b61095b565b61095b826127b9565b6000600160ff1b82106127db5760405162461bcd60e51b81526004016109849061339b565b6000816128475760405162461bcd60e51b815260040161098490613482565b8160001914801561285b5750600160ff1b83145b156128785760405162461bcd60e51b8152600401610984906130fe565b600082848161288357fe5b05949350505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106128cd5782800160ff198235161785556128fa565b828001600101855582156128fa579182015b828111156128fa5782358255916020019190600101906128df565b506127db929150612991565b6040518060a0016040528060006001600160a01b0316815260200160006001600160a01b0316815260200160008152602001600060ff168152602001606081525090565b50805460018160011615610100020316600290046000825580601f10612970575061298e565b601f01602090049060005260206000209081019061298e9190612991565b50565b61094091905b808211156127db5760008155600101612997565b80356001600160a01b038116811461095b57600080fd5b60008083601f8401126129d3578182fd5b50813567ffffffffffffffff8111156129ea578182fd5b60208301915083602082850101111561248d57600080fd5b600060208284031215612a13578081fd5b610b9d83836129ab565b60008060408385031215612a2f578081fd5b612a3984846129ab565b9150612a4884602085016129ab565b90509250929050565b60008060008060608587031215612a66578182fd5b8435612a7181613582565b93506020850135612a8181613582565b9250604085013567ffffffffffffffff811115612a9c578283fd5b612aa8878288016129c2565b95989497509550505050565b600080600060608486031215612ac8578283fd5b612ad285856129ab565b9250612ae185602086016129ab565b9150604084013590509250925092565b600080600060608486031215612b05578283fd5b8335612b1081613582565b92506020840135612b2081613582565b929592945050506040919091013590565b60008060408385031215612b43578182fd5b612b4d84846129ab565b946020939093013593505050565b60008060008060608587031215612b70578384fd5b8435612b7b81613582565b935060208501359250604085013567ffffffffffffffff811115612a9c578283fd5b600060208284031215612bae578081fd5b81518015158114610b9d578182fd5b600060208284031215612bce578081fd5b5035919050565b60008284528282602086013780602084860101526020601f19601f85011685010190509392505050565b60008151808452612c17816020860160208601613556565b601f01601f19169290920160200192915050565b60008251612c3d818460208701613556565b9190910192915050565b6001600160a01b0391909116815260200190565b6001600160a01b0392831681529116602082015260400190565b6020808252825182820181905260009190848201906040850190845b81811015612cb65783516001600160a01b031683529284019291840191600101612c91565b50909695505050505050565b60208082528251828201819052600091906040908185019080840286018301878501865b83811015612d5857888303603f19018552815180516001600160a01b039081168552888201511688850152868101518785015260608082015160ff169085015260808082015160a08287018190529190612d4283880182612bff565b988b019896505050928801925050600101612ce6565b509098975050505050505050565b901515815260200190565b600060208252612278602083018486612bd5565b600060408252612d99604083018587612bd5565b8281036020840152612dab8185612bff565b9695505050505050565b600060208252610b9d6020830184612bff565b6020810160038310612dd657fe5b91905290565b90815260200190565b602080825260129082015271135d5cdd081b9bdd081899481b1bd8dad95960721b604082015260600190565b60208082526023908201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260408201526265737360e81b606082015260800190565b602080825260149082015273135bd91d5b1948185b1c9958591e48185919195960621b604082015260600190565b60208082526015908201527420b2323932b9b9903737ba1034b71030b93930bc9760591b604082015260600190565b60208082526018908201527f4d6f64756c65206d757374206e6f742062652061646465640000000000000000604082015260600190565b60208082526022908201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604082015261737360f01b606082015260800190565b6020808252601b908201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604082015260600190565b60208082526021908201527f5369676e6564536166654d6174683a206164646974696f6e206f766572666c6f6040820152607760f81b606082015260800190565b60208082526026908201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6040820152651c8818d85b1b60d21b606082015260800190565b6020808252601f908201527f5669727475616c20756e697420636f6e76657273696f6e20696e76616c696400604082015260600190565b602080825260149082015273135bd91d5b19481b5d5cdd08189948185919195960621b604082015260600190565b60208082526025908201527f5768656e206c6f636b65642c206f6e6c7920746865206c6f636b65722063616e6040820152640818d85b1b60da1b606082015260800190565b6020808252818101527f53616665436173743a2076616c7565206d75737420626520706f736974697665604082015260600190565b60208082526018908201527f4f6e6c7920746865206d6f64756c652063616e2063616c6c0000000000000000604082015260600190565b60208082526021908201527f5369676e6564536166654d6174683a206469766973696f6e206f766572666c6f6040820152607760f81b606082015260800190565b602080825260159082015274135d5cdd081b9bdd0818994818dbdb5c1bdb995b9d605a1b604082015260600190565b6020808252601d908201527f4d75737420626520656e61626c6564206f6e20436f6e74726f6c6c6572000000604082015260600190565b60208082526027908201527f5369676e6564536166654d6174683a206d756c7469706c69636174696f6e206f604082015266766572666c6f7760c81b606082015260800190565b6020808252600e908201526d135d5cdd081899481b1bd8dad95960921b604082015260600190565b6020808252600d908201526c125b9d985b1a59081a5b9c1d5d609a1b604082015260600190565b60208082526021908201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736040820152607360f81b606082015260800190565b6020808252600e908201526d26bab9ba103132903637b1b5b2b960911b604082015260600190565b60208082526025908201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604082015264647265737360d81b606082015260800190565b60208082526018908201527f4e6577206d756c7469706c69657220746f6f20736d616c6c0000000000000000604082015260600190565b60208082526024908201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646040820152637265737360e01b606082015260800190565b6020808252601d908201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604082015260600190565b60208082526028908201527f53616665436173743a2076616c756520646f65736e27742066697420696e2061604082015267371034b73a191a9b60c11b606082015260800190565b60208082526024908201527f4d6f64756c65206d75737420626520656e61626c6564206f6e20636f6e74726f604082015263363632b960e11b606082015260800190565b60208082526012908201527113db9b1e481dda195b881d5b9b1bd8dad95960721b604082015260600190565b60208082526015908201527413db9b1e481b585b9859d95c8818d85b8818d85b1b605a1b604082015260600190565b6020808252818101527f5369676e6564536166654d6174683a206469766973696f6e206279207a65726f604082015260600190565b6020808252601690820152754d6f64756c65206d7573742062652070656e64696e6760501b604082015260600190565b6020808252601f908201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604082015260600190565b60208082526010908201526f043616e742064697669646520627920360841b604082015260600190565b60ff91909116815260200190565b60005b83811015613571578181015183820152602001613559565b8381111561207a5750506000910152565b6001600160a01b038116811461298e57600080fdfe45524332303a206275726e20616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e6365416464726573733a206c6f772d6c6576656c2063616c6c20776974682076616c7565206661696c656445524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa2646970667358221220cf4af89f7cfa78e2d4384cff55d4548a6062d655f5be8606d039069af7c903b464736f6c634300060a00330360ea7920ce62b933839c983cff4359cea9f4d40ba7645cca39cc1e5402a537a203aece605703a42cc3191903ca44e8d93c7f0cfe9c38a11b43d20d98fe19d079eb03817af525e983bab0f89ac3adc040c7ea5ba5406dc11cd9f0e6807ee0092f096e03b248b0aa218ce9437d5527aeea2acbcf30e1ce54e9d04934d6f5fc98b9b1589e03597fb92a13c5f9cee69de79819459616163aa87f6a7bab7063bbdc69318c7c04038ef6e594201de8a497fa51cbcd415ff8e6a9da00c7f141d3a86fef301ba8e3fd0345ebdbdf1e995f0b24c19d2306d067636e61ff25afbb6a66172546a401c94a9803d06d15fc48545f2e9495d2e67b15efa8e82e1da82d89f7197f137430810f71a403c7fa824c400e5c511ca9e354f208c685994b6bca2b71c683b03f16fd9aca489f0367f3a50dd1540fd0566e4b9971db42f2d7feef95f7258fec97021c25d213338a03dd6eab68699901082a8ea8d0b1b0019eb89f5808cb23b2ffdce917b5f178dbd00327b174f3d457b9574eaf956fb6894cf6c171c28f9fbaa1d89974abf48faac22703c002a2a7fa946bc4c8d85e660d58eeeffba1a0a2d037b69610368dcfd25febc70319b0306bfe9f803e5a76984fbd3b1dcd65c82acc4eb92f256ee22bd1b5f5de0a00581f02349d274bc7188a5b46e6473ac73cdd8e84278fa55a42020e3650f9fe227f5820fffffffffffffffffffffffffffffffffffffffffffffffe57ffdfa9f6bbfd5000581f02cd4e641d5dc097d27f28b9a0e23a3fe5458682d748afe201584dc073c0a04a014dc15ac70ec6dcdf7f021980100330ca4f85d273fa056f2cbf7cc6f6813650cb8dcba9f80af2a7a93426c61821ec03ef14b2051cb18ad4412cd8975c3e937db64da530a77bf912dbf8e64b07fdc90303e6e1a02945964757a0d562478a0e60a225887f43ae2528255bd178815091a61c00582003ba55c60a0fa4a9fd899dfd4a673d0f9719dac10148afcd5f228ba8ec2d00f04904b8e090c224f16bf703e78b24aca702ba454c9c71a1e6e489a5de4f094887155d3d420125d989a511fd0058200320a8568100753c638523b4721eecd2bc1d49f730adc499e0d7e229c39d5e8049068dc0f6ab76b78e5f038a6a96e2dabaeea96c1d6e50ed429ee22ce2ccc25dbc378f2d3985ee41d9340403532addfb77fb955fb1ca1654ce5f5accefcbaea4a1d5091a9a03a59ee6087d7703ebb91cbd0b3bee7763bf408c2a5e1d1a2d841aafb91e9f90e1312faf2e8d8c88032ed05a017af5c7a17d46022baaf89126037c7a79b212cf638f2c9f6c9e4727c403bf3c18f8c4a1b70103e40fdc3db3fafa127ae0fec7ea38c6d6fda73c92c3e25503503644cc1f69c199037d536d499e33130227e32e9a41282b198228752c9ffe200219ffff0348d264aa096932ffa17497680ec9a0c9fddf5716342ac396bf3ec401dee0adab035f9d2c753b1ece54eaa908171fd993c47277de099863a9f8bb3c729d9ea4681a03fef9e54243ca5f8290d4b6dbe2693bb3cbea9b9df6c24949093e9af33465e4fd03434620618ba07b50f13d8ac61f7718fc4fbf8818ec6881fa8df7ce5b37da6c0d0384e05cc761bf45572ef246a8247ae2d25b27d6e6d27c7e9be97ec9c10e58a89b03fec86154980cc510ad9cb54d257e626983170480f87e7c25c8a824810a55518b034419463465364aa8325f43091944a5715e3bc28a106b44344d4e55119bd62e50032692185f5f8daf24ad9c448a9099bacbd2f3165bf603930ad58c7c4f7b0de58f0219ffff03d34e72b777f28187e287baccd83f73ae9d59bbd26c48f55bec11610c8bc18e3d034097c3505614db42ae89105c5334fb1b065486d8fffcda15955883bd75ff309403e8c29bab046dcf256bb8cb9b6284d387af9dbe6852c78200ece60bd24d4e0b1803ef66232d0f25c47317093eea84c87a75199053f00c6b187fb21365c8f3aa56eb037c6fe2336d56eb64b66ea00824910b85afc78d2cfcaa584da6b28eedf2201f7603b1d51ff745f99c3756b975dc58c27f8d50909be76a9f7e2ac3e32fffff00d39e03b35364f5359252179be91915679b254b9eda1c2d77a1eae414cac7b9c2688345038525f23898b028d8d93ef19d0d78c2eaf32b9525ab25b8049b2722720322152e03f3d86a1e65fff6f637afd02b70b895fd785e0a5bdd3870e1c8b5acf2f7488c700396f90e2844902be3c8b104db58b12dbc6d79b43289c1c75405b33202fbad0d9103f22e70d0927b05d0794334cba00174b6f9030e9ded5d97d1af99255ad21668560336ff865d837cfa5031191bf3633a8b804528311d95af99b967a729729f19fd29038bfb3a29daadd8149f00bcc25c151feb990005f5ae9c41ba76d527255cd8762900582003fa2e1ef7ed4891f7eaf3d99f5ba2d0d270aabd29fcfb9f427469b271f0000041010350ad7a1e103c77c97c3786c8842d58642577cd3105ad215f2fd65ae59e58404a03da884bffa16ef65efc0a542b7346b972b7233f038fbab1e60c9f4d31be985c9e03cc6e71d42b4406ffed6dd22263f27d7721e3ab0a56a91a4926936d90f63962b8032390cae870eda904e39d7c47e01863e25526b66a200ecb6965a154d18194a97203000df4cc52516e3fe0da6292e3945c34a44dbd6a7aef46f11f2719e80fe4f52403e98a78aacfbdc9c25ca5a34b6a50557e4d21fa11f5d14a0e176cc3106830d1b5035f77b7bc095b0487d263589244245a90d5e2a7ed9ecb6852a3ae5acc3ea4c43900582003c7e3794394556e25f5ecf13c05e2fd775bda6b1ac4dd572cbf4311e9d1a4905820fffffffffffffffffffffffffffffffffffffffffffffec6c1075c727b5a516b005820032078711ce01294252b003085eaa0c49afbe6047cca8f1b43f722dab2816c405820fffffffffffffffffffffffffffffffffffffffffffffffe865e8a159b6b9de300581f02eb2c36525a13106cf4035bb6535fea7075d37229dc58c70eb0fbfb83691a5820ffffffffffffffffffffffffffffffffffffffffffffffff26540946cfdb900300581f02e3a84ee4fdc3406cd7a4a31a029ed9ebb7e007228c1a7c10837417923945410100581f028e8ba439adda901ef6c44a001d3dd47301d440e3c8617d025132c678aa9b5820ffffffffffffffffffffffffffffffffffffffffffffffff93a7cc40c3283e3700581f024a8d5c341123cd4ee949dddcb8326974b5d6608244275a63d245c6c267c44618cf323777f40219142803fdc756f7ff0d6e35848fd8e7743150d34e54c8ba511691c9bfda3192237a3fcc03ac2b17e0b2cf0117223684152f239f13b4dd6b3655b2be49db3c2ee9cda03591014200c000582003286855214df8487cfbde892a2d08ce7d0079c7f30c33edc06e2ef7ac3584004954867eca8054b4c8490375829d49c1e3abb17ce1db004b280341685a1602d952d087aea0113d74249a82033b06f0ea641d79d71ebf35c2c325c980044bd5df9aa4b16678aa3289565db0290219ffff034519ab64346f260f31d9de1f04248005442a8bf487763c6af7b3bb941ee57a9603f307c5fd7664ceb397c9ce3943f8e3766ed250b9cfa4dca7c58011e2810cd95c03562414525af04a71e21ad734b9ff47c81a515fbfb2c513917a23494949be50830314a922fbbbc24948c5f28901ed5610a3aa9c8d1b285c564fa4429c1e7bb4b06c036e0f2112a5d75e0963f403b1b561e586740505049e6bca6832028efe11a3a7d30324ee5216e994d7278246247d2140a3a38cb83c27d7908e62a7ca37df0433f2c103c9e2c77bf1158d77fbd2f25d994af57934ecec6850c715f9f41853f46002f2e30219ffff0374b1edb35e17096fc083ddd2a98ff7e996118203b52495c00d7d156c22ed95e8030c9e45be9d8d8f100c01d2ea7e833d4047e90701e08c9f684a1a0e25cecbc9fa03b478375a808daf3ee32b07bf58819bdf924f4df7fcc23ac9653b66edab65b84403566e369f5089642a229892c21f813d3d493a22e7cb37093be36608ae679063fa03612465375a6cdd9a587b08ab4c0c1f7e59d139cbdbd21183cf7434b2bf98e4110380e74b90e67785fe9650cd60c807c53dfe29d02c2aed8e98f8acbd8653ecad0603b8a97ee5c773b0f588bcf0a47e83aad620e34952220680bd6d9e7eff29b5e5fc03c2dc088d21c4069684fc081c0a41b4bd695c2def482ebe5975462f745e8a36e903e97446caf037d2c9e137c062ec8f8945fe04693b206e7325d091185494072d330058200356ea8e9f7a6717567746691d538d897879b771bc4647c73a5a69da6d7349205820fffffffffffffffffffffffffffffffffffffffffffffffee7a8b2fcbfe4ed61036c5628dbb4553305f4bd47efd16c2d85ec0a1e09a63426fbdd1f2ba438759b84033646458c1352d8f5c9890c44102f7bf14eab3457581c50bea8b6c7d354d5232303e67163cfc4f7e53889c4dfa56fae5011a9b668db7d8454084db63420120bb49403b35bf65a50a0c6433a39c4545bd447a175f048cccdf42dfa820071fdf6fef776030d1ce395e015320b7b65a221a135bd96911a5d9a620949ba6590734db74fc3a3033db1cdd3603dd119534c5ca54cc0b563fd84c718c1b4e39a611d4bc58814e9bf03f8dcbd8742d4d43a64ab217499f57b41af1207eea88e56145094a38be1717c8f03ea6e75d3a2c9b48ecc340655734f52a2ed2d0118eb509f166e9c9567f6f97baf03c28d9290573080823105496400298c97e4f1fabb8890779454a913a6cacdedd50304f75971cefaef4023706e4e3c5753c7b34dddc9668a54573808cf0b332af271033d0113019c09bb2f7ad4631dfb8bff0608aac950662b264362d5dbe343dc66bc00582003dd1436ec7d1cd778844d164a21673feae58d0d2ae85340c9b7f68d1bdcc6405820fffffffffffffffffffffffffffffffffffffffffffffffcbe22ed38dfec95440389e216c0828cea6cfda62f518f071ae5f64cdf88f6f9af6571f40db499b94ccb00581f0203cb857c1f88da526238466fcd27659c0601137a2dcb35570ff9288a640f4916bbaf49d9387d1c5400581f02f6d14e8ca06f41679c222b0c291899103ed465ccb47b0fd7d57fe255c3d75820fffffffffffffffffffffffffffffffffffffffffffffffeec3768c48e7bdfaf00581f020e7aa3f550a98e7441986decbba54de0aa16494c47aa0f7786441fbc6f9f5820fe0000000000000000000000000000000000000000000000000000000000000000581f028dbab647369f9731d1ca69aae8a3bdf7ecfe0c247c511dc126569abc6b54484563918244f4000000581f026f50c206ade3be1f6529893678df62d52a20139bc3a789c915374b9c5d2d4a13d746145432c04ad2ef00581f02a54a7ac95148ed8c3c25dbc293c8c1746dfff10acff06f73457717cfb6894814d1120d7b1600000219248d0219ffff03329fd1f4096da071fa63dcb910af71f117af4926fcbe504f4269c8e93b919415031ee90fed9afb36d522978b12dedb0d248da22e721b3f6eb3776ef4234179618f0340b074212ce7e4f6fae4709120f07d0f374bfdce880b43a4f3d7021a549c392e03099b0062d64f901d1b18b502decfb5c9d164fb2621b672fcc6097454a5d780ed03c218b51ccdcf91dc4f95d93a4f79888b3405137c10ea2b685c5cf5dcd334bc70034d707da5efac27c85ed701329d3995ae8d0562327991f75242955e4418fb8ad903779459992fb9bbc066a61ef0a184ad74418a686bb3bf9bf6aff6ba40b595ceba03266ee2f5e65a039fb4a63e3c86257bc112325fd07ebc44f6a9aca25fbd7c07a40312868640842f9d6dadb4f2e49286f56ad6ed6875fb34ff115110344e33d9d1fd0219ffff03c05fa755f2f77c8c5ddcda898ca4784ad7a2dd153acfa494c34c5ccbed7ea4f703e0efb3d5e7913914866b51f5b01656b0ce2b98d5dbe867965247bc9f90cc4ec90219ffff05581d022c8dab52f8f90c271f05bb67675f083fccc293e7a326c3c7afe62bcb0f014805802fe7d705400019368b0219104003562d59a51820d47f520c975e0b2bcffac644a509749a3161f481f57b6e826d210605581e03aba08c489b5ad474c07ebfbe33410ca24439571cfeb3b95724d068960007011bffffffffffffffff05581e03aa752171b2ca99214fbe9c912258dd07e87b2f30c6257a4da69f8770300403031f8b67be329f6419c9282095843235301b6b3475e42bc9e3262b646aba8072060605581e036b0d8253248b15b7a1b12d54f229ee511bcc40b69efc84bf2a16f98ef007011bffffffffffffffff05581e03b60175c43cee12b1004866aa086665d2bec547880437a2759549373f1004010219721a03898e78dad11e17be644381cea0ae7c1d3ad38bfdcc74cd49b46b1fc3b7bb07d10395b59808672fb1353e7f403586324cbca6be75dc3cd93abfe452d0a035f3d538032603d571b139ebd7f01e0066d7dac164a7b8a09c28c1f402f5518b0cc89c8e2d0364805af60650dda41b0b200bfbef51742bbc77da888430fa31b2c8eac68cb918034ceb69ddd1d6fde23d3ac5f6220b87db6d963401eb92df331cc7623fb826c49f03ef053ddb10e6df20e063f1532925f59c38420f435751cff49a6bc16dd32d272003ef5d5dae4ffabe5c846caa0ea177bc513bf30e99bc699dfbffa85ca594edb6790319abfe24aaad4d0fd73cb4f2a5612929c99bd833d65011de189b58c246a9435e03d863a0bcc9387464de132fbbfe12dfde51fee7b3dfc882b0787a6c43ab03276403e81d046085fc47a090b8673c5f77c9ac62232c516f32b895c7bcb203b9faf7b403041dbab8b88d792a0779752b2c1c39a4cbae55bb10fa7f3665781046f1d571fc03ad2468e65640bc4b71a67a8ac043c87860db7e8f200743a19d068d17924ecb430219ffff032f0ee4de7848e944f0b98ccb217fc157f94c5c98ec150f94fb486b6cce04acb503613125e1c61b163fee0d4103f0c8fdd759477be7b8388a30dae3051c79e9ba22032dd258d203531b3bf255270e63356a6c0342895fcbf15a3ff2626e766d96c4550219ffff0386206746b858df0669069442a67ec91f51920cbb483fd8b15bdec382b1a6be860344e6baf813a08fbec1e3dcaaae945379191d13ddeebbccab80560fde94f414650219ffff03d871fc79b151f46dfdbf8c5ab616a8a12274a83dece9f0b88a0563704a387c9703a017311497679948fc2437cde943a41222b0fe06513389f23c8ac362f38baf1a0219ffff030ac22c7b7cce2b8f9fb02f07630853936ed508daed05fe8737d7bbc6aa9e794c03795bf94ba2792add431fa06bfc3c8b7c99f80b32d4bcf808eef9262cb346cb600304a2ecf1a99c3b598c080d4be1bb342a8c9412c8dbe4aa2df63183bcf79d51800219ffff031650191497a8b409c8c7fe01e4591cf485a4b313fd33d3401bcf56a39f97696b03a468d17fd6092b4efdf1790bad8fd877b2bed09d91f570cab5220f1fcd39a2f50306a86098c43e0e3fbdb92cd47459b5dd34c59a089388811190a92a56c6ad97b30305e427e36bc77e9a276c4f8457751fc33b9e022252c1013cda99cd21bc274da7036786e1993791baf9226c4052a8fb37ad27c37ff2bb4f2307f951c328d19904c2039073ee1e985d60302901229de3bcf3bcf578e7e18b7edb00b33d2480191efc890314a13eddeab38bd7df7e4599950ca4255216e9a03d2c09d8c2f3770525af95280362faa5a52289db1f4cfba0b9cc3bdc98edf1203d8507083f4d2b3a58f1d134770339f1c67b059e353d696ac82e7a02bd0a094a3294b41d266d97af01a803f8c1910384082ca5af310f354f23a486be5cbc122c519b8fe4334028720f62154b985ffb033780ea8ec72c0e6c467b4b4262a2a932a05518757d071926fff5d87b29d53a1d0362d0d30c6723438533d8567be7513fe9aa667eb8abaeafc61232486aaa0925a003cbdb3dc65513c802e090a4fbb39e5b323209f6c25d2a27bce5fd481660d1dd0503e8ec37f620834a0c637fa712aa273637f57d53f99dfb6e8f6938f1da3dd381ef030feb1f1078ac4c1f3baa14913b4216ea6fef46388602ffac84cc780c41a35e4c03033cb3e76606fbf8ff953f7594690d3a0331eda7aaadf9e642042df2ecf278ef03f127650e78abd68d95788ae8d7b6d6805e30c5d89d2f3bef3f7b074e18000e9403a95c321fe995d210668461fd4a18e4f471d3d500b370c898bc8587c10e2f6388035131269b9c77b2071c4853d77ce8603ad337693a24a17bc7f0a33932883dd107032b4ddec0b73159580dd4d4706594ed481e9abfee57c4c237f9936f8a40d199f703a1bbe3db57d1bc1b7e873c35ee267c63054f860226377813215564e8ab1cc09103f3ce2fdcd9f13fa517de9dd704a66a02ded5f91f155ea099c40821f4abbf54760351b83ce2933440635dcfdb4808b0b5884e1b6de629b5e4150fb624b771d3ebf40359cd7a40e337cdde43d6b25a43706a33019db9730f6858234b2317727ff067100365d5135d92bff813c2e85bea832701c859f711557a021548a4197ad8cff1398f036d2eec0d268a3dae8cc81b4461ce5d867271d0909ec73fa6d6a8e83a36b53aea036f0581fd2e16dc5b66f066d70690c895eb9871b512a3adc84b24e74db9f7863e0371968765226d34981a4fde2fbfbf3e10669aaf8cf15ca49b9df93e54129739080366b29765d4793c92d469b520ae3809acef4f14b76eec1d9cdf2140a42dab428b05581e03b1ca458d01246eea6fbe769c335cb7ab3f938c5a8726fc54777b261330040205581e033d616945a7d42cfd2e5310f685d39212557c18ee821c96579e5020555004188d05581e0352507f09d93289f2000854ca21ec6d108c413ec69d69e55501c7272540040105581d0265a919c155a4eee6caa20efae6287f002fb003186271ffb1ad95f7c60c02470b7c2e85dcd80005581d02ca4549cdbff3ad31d60800a5e862e8ef6deb21f722a281579428568f0c1a000a961d489d98cc653885ce0905581d0252594122e76166b01cc01f13d41fb1c926568c2f2a14b8ef66fdc0de0c0c47026c8613727150021903800352fc2d0cdc5601d1f944529012aa66566856c5b49b1600c7b40dc84ebe04a38d03b94106c7f2b6483fd61c1a29dcf684bb89827596178cbe8bacf65fa13b7e02140219879803e1c15b457c4e685ba81d745e72451ecf4e083b3f37fada38b5a143336763da6003302374dcc17553bbdd76e46e72cf5d943c9a36aed332a94c20cfd060d15f8bce031d8282a04508b70abd8003c0ee0d0ccd0f740a9dbc743000bdb4c25f20afe76003c5f7ba145fb83f3ba2cc939c780506dbb1086883fe5c4079306b2109bb0c3d60034ffb0f0ec57ef20be8e62668abb6228c41e87695ef06dcb62bb82776f9c9b8e3039228bb9a0e66b2462d2f3df607e607ea6310ad4765c1a8ac4631af32391efed103e28bb3118baa2978d1ed9b188b54dd80406a8cd46ca427e1d6dc34dedf0c80080219ffff03771d8db16d382d38ef617e8bb782406b1fb13a50bc9febe21e166c5dd29a81c703b77c3400efac675ecdcb59e8178b6854dbb5e9c31b46bbe0e467a0aca54a4a2e030e384599b2ac0292cc22fae825a3d5abcc0f6faff8410d3754ead4993c17973903d2f1288a415c951a8d746bb159a68ad49a4066f4279be61983e2f0539f6d24f303a6f5c5fdfab3ff8722d226912d7c35eca8bae179c3cb6dbd2aa9c1ecd2ddcfdc03865149feefc897cf04771e800c7fb4665bd1cd4fe203686889b2604711f8eb2f0389c452df893aefd649e9d4196439ea08129b9905659943c0c60fc18dad94c2bc035a466b549122fc384c6e7191ae783ca4c9f0b4913cc73d3c9ffb3f689f1f0af303dae896e4927a7ad953cb2c4dbf9b573da71f7c4038ef71302fe280ca1b7354430363693490b2d2d231fda356997cbc728e794e4b8c8767d6e23d54cf17f8b861fa0219ffff03d1b42740a4ca6170c9ba5b6c80267ea519c2f398e0b3071e256212270c76e9d40309ae2f8f1724a042ae130555b06ac7873309397ef80fa07d50404fb86b4e49ac0219ffff03e19200f860799073d841dc262d18b6867a70147a377dc6f4c858882fe915d070035637bc54419e6beb7fe3e91086822c4ee1a1984ec724ba003c2fcd92b132a4920350946476b382df6bebd3ee8bb188b2a161a651f06a7a25b5f5c58128b688ecc103308fe2baa64a823ee555712f6d2f4a2e95f1502f1473659209749ae54f637f0e03b6320bcae493696b76e43172f30f3da2da647a53564bbd66871221cad0d0822b03e65ba1802ffff3227e6ed356c9ae478e1de66d20a8d4ce2d8b030a43e5e49b6d031589a93abaa8a24afc80890cf1698e85850fb2acb4b9b2edd8ae5cb189e584aa034f1d8f35b6750a99e6551b4b2afac052924b9f7302fd7d1f4cbb94369c54087303738002a4e001da2183af4778cfd5d80ed5b0dd4116a276824108e0c9d2566f8d03ce331695f7b71fdb63c75f437111eb811d416425044ef936bbb67c2e1513351703d6987fa412d7b5a2c42b1fb0ac04ce5f318b4e9b94af84a20983f97be7c100be034dc6318143d95df384490475d5f685dcdcacb611c977608633384f1cfa7ab1380329232b7985102061ed0a668cdd2cd36d3853f27c17ddd1fd933b7c23c3f87fce0219ffff0354fd639d867589582ddfab7c86c3ff531dfded260dd3d415f7d9f00485a7f7380307c230ba4e2cab7a05b25cc51eb3d33e5760d0da4dc0e8997cc385f1ab5cd0aa03e14cfbcd7b5f1934c5ac07ea5fb182981b1123461ce7819a16fe73e43d90785c03559cf2ca572db8e189a045baf7e8ac291acbb4791a25ea44c8ba728dbe14e042036ae5fedd41c7339d532280816468c4719b4b84cf5f17d2afc642919e96f0d6b103e955618dc0bb756d8c6652599972db422e4eafccb6327ec3ee959ad6c42d4a0b03a0e17dcda347c4ca3ca50b67306df84f097207ca3ac80636e37e8b06bae6afa3037b3c6eab1337187dc2bf4a37ed2371f787003bb33f630221ace92ad7bf6039b20323ca34b42d86bdb89683152acc69a4c9021c428359cf0a933e91d279d7c6819c03f21a3690f370c848513e4423f67d9551d86562dd18cb75d76eb6cc19d44bc67f032697161076dccdb5bb807daad1c4c2e65781002cf718fd32572e0d0ba353915b0326fe00b928c372b6f8c6d826149f09a4aaa97884c1b426e351f9542475de85b40337d5be143d3b11bc857c8b21f3a8241f04b2ff4d19b629899e2b08163c94892303a2bc49e02fdf2ea64de424a5825421cd93ca0f3c63814745ff6ceab7014caff90312e8764a1cea4bddcdde6f6eac6512cbedb9286c41339e28c1650be164de273f0325f9e620b76a6eb7eedcdc0a061ca60bcde101148ba0cf04241c27bd29f66708033ee075a411eae29a56eff8c7c77a6e6a34e41cc3760a30a44bf8ff34251bdbce038b6924e116b176ee9fe8978e10614a63458ba9f81a60582bb2b3c49c7f1cd20a03e5822aa6ba6ca5b14e505a2b58cdc8943197838faa1fdb526755f45870244182038ee495a8519790aed8b58d90f7b0cd0033ce5a6fbf862076afcb1edc23828731033254dea2db34e2cd37ddfb19b7e66ab81a329c1fe94f3ec2ce18354cebcf885703a99ed0ec8142969be41841e6d0409009562a492a52c8d134e791ad623b06456c037bc7bc83dfbf967f09157306507f44775e29d7a711a4d79b64d87d87b1c4f31e03c44e74d57101475667afe2997499e71b521e22cae34b4595bb7180a90fd8eeb4031adf17b5e0d1ed1f4f09408ac7ee25ac7b89ff3e85c4b9293f943207d2d87059034996fb772acc5279ff91ced11bf308fa12a9777c84fa6c09292b04b1d1a5872e03e853ff9f5dda0e9a9c42117c61530a1bdeffef2ef32eaeb071588072eec83402035dce5e3ef39dbc83cb2079f098100a7018c8d1b1b6067d983edce4d331326b530359fe458f8dbc67f5c68b43badb67d0174ac2b9f286d11906c61506617fc6652903d82b805143693d8d0bd0c068d123152e20ef3147789afb74c8a740f385a6eeaf03f130c410a608922d0b516569c279aa5e4f1675ff4ee9e9701068be76a7144df403bbac346e06b07d560b86b40af45fd01aaea3081e8b5f1ab3d3427f321eaf976e03cb98d9270f284630e965774620e0cd58169f8fbd1334bf09f516c1b1b15efdc003685c3bebf67758c1368aa1d3e5698814ee842eddfb1326c8a8862af4e695ff9f030d07b583b4f1a28444d0d5645eedd17e3c62e9a0993f5b5ce1d8edf3c5563b3d033dcf31cc2f00f81cdbe8086e7a74badff58ebc1f190b3467ef9db96697ac9977036cdf1af1bc927549c2def3e877c510d38687b1f672219d47137d64425c6b037503f68cf71d3952d3b1b82360797e16dbf479d39843d8c8fdabf8b5f65f891505c605581e03b2758878c2c36786d8487c37df8e9a27b11118c4e2fd76181cd4e648f00c03464315072a35c805581e03e7618f47a37803fd7f7e2b610c14ee757ee031b936d1f786bf523885e00c01470643706cbd300005581e03b2849ef417d0962c9e837433d17e174ab6f3a5ddbdd107a60f9af4e2a0040203bcf2e69616dfb6dd55d8fcc3a1195f986c99ab606a7f455c1a96fc05ac992d9903d6fe2da97a0df942a65306743ed8e8e53a6360e23194e2510dee03a983d0a1c603f251e2396575a420bbc3c92454aaaa80408ace11a4e1f25a907d3d954a2e97c301410305581e03345302bcaedd0839e22b5d09d3e7577662b0c24e06a11d1837acd01230040103cb14cd5329e35ccbaedb8234f208b713968485497b3f95490e1c59d86c0203ab05581e03d4d377e6932bb19975bd84c39303eb5efdeeb681f70cbc6cb4916ec480040a05581e034f5e278d32b0c694fce17c7080ab33451d85db74cad7ea4e0a29eaed600c18d54902413032c3c53958f805581e03f98f04a75a099653f017e5f0007aef28962073bdcd9dbb2fc1c3d8fb800403021979e703f0a2d2929cb2b23688087875667533715a4d68329289e44136a5badcca5a613b03909f3d1032da5d9e28a25d8c41fa301b0ba133271cccf57be936f24d6b513a6803949cdd1c87ced27b3a3683a2b241b88acd0faec0f3bfee27146615cc9b18d25303660fdc33311b31df8311594411d70dd79d9579c8ed3be050722ad828809f76d9030147dd41c1751910dd8a44d2100dada36ef20080a496d6243efebeb7dbea675d03a8e1dcaa48019d979abb4a1abe5e41b07eb2a83d24248d0f1f2d4799d54adee003ddf013d8db0002df9ac4e0cb79428bf41829678d9e1e9e43a3c32efa250301650387dc1d609547d808578144a5004409543c7067c89f08a9a4d3c06ab05ea1a664037bca481166d6c2f11f582e64074da688c8f8b48551529035a94002bb65de3c7903579deb1009854973ff53977a90f39390b0394de60cba6346816ac572123e58f1034b8ad5983e8cbea18cccf263fc495aec17d42472337d98b674cc85b28fb0de74036ab5538930d803a1751b375e8d2bb3e91a762683181332fdaca1bab792769d08036febc88624a4a3fda3ea4cd8fc3ea6e544c72aafc2a43ccde4ad89828edb0cac0364b7abd45a833820d491fba95703f1c91f4c310d83373c63cc159e8b99a6672f031d1ea4d795373868a1228ad7fbd197aa9097b46ad5bc73a0fb2221416f54d33d0219ffff03dafac1a4364c19bee0f82282f7d8684be93f26b47b6dd421c48807103860a25603767382e59c8963c11bfa50a7cae10224a62fe242f3d75eb0e24bfa885c79ccfa0303d8445482fde0975642775921eeaab332b8db852f1a66ddd53f52537d8e467f0349cd4d5d42a3b842584f901aa98200482e915f02dd391df23c7185c088912a61037c89f6b25c1abf657c1f7037d97c802dc39f07f519369a0057a3bba306c1df230219ffff03598953842790b7a0c5222c5c0a57b830de41daac0ca960583b35ce2b1aaeab2703d532eeab714a9df7607cf66e5ed785e0e40a4f70ec7fd26d7a786a10959bbf2f03d6b1e8fc636643482cc6569d25c2e08ab08fd50802e076a3dcf394db2551b7ca03caca77e6e00d25ddb1ffd1442c946d4809e6d8f75c3997e723ec456e69e882810219ffff032129bb96b2f23eed5d77fd2a9e68d36dc320d9ac5505179ad5eb80684dfbb5260219ffff038365acc675f29cff433b9f43fb82a33ac7434c040ec57488d5b2379768790fbe036a21363dfce2d504d1343d8b2c92108da021f217347ca1febc66beaa60d24af9038cba605eb9df782b050a3adf9853285100007a5ab2e5aa13e2e26abd0aac74e803f7f24fc9cae29ca46d54d50602a703ce5fab714412f8edc14ee61bd1b3ac25bf030df7d5b4f664e00eb5a17a023bd09e7301b915c05f6296c0383011304a859d2103db9f0e02aed5fbb6e1bd855992df00e0da8b84f3419ec3908c78d9429d3a7b470320ac67890b1f95a634e3410ab5383197e44c61b476859d491ada6a3372ec836703f45468c69b1e5fc5ddc7ff6b2887592c262eee4b6cc6fc72365dcf8f6936011d0333e4f0629bb26e3a3c006a6639d09932935586a1492e2794f419497eab819076036893cb4d59104ec9d84505d1785d519b47edb28e8b4669aa6681bcd6d55cb4fc035857d3a8442906347983db9d6f2d898240d605a4f754198066cf22c0d6c425c60219ffff038cec94690e812e7fb50dff8c81d6211cb04743e5f8b76ec3a0ae059cac47e35903c5d01e47803849da226a02611ef29cb491a21cdc63096268eb51c1a0843c4ed003afe9d0d8fa6e4747ad158ced6b55f01473e75f41d1e7d47adcce27132d7b025c03bc85eae3105a35f82706f6319857a7ac5eebf8610cb2b353a6ed897ab70dcb28033f8fdc957e9a6be57bdafcdce8e717438e528911250703139eced50227b94732038e2700a442df5351298173355c3b6948692a0aa43d0044ec7c86a515bdf00a0b03aa0ec262c412f391b647ab1c28c33bd5793e892053147db4f7ebf0760c25429f034a678e08eef171a227e2c5b85449d0f4edc4a1bf5af18ba3d50b259466fb5edb03dc10b47057d2b48f4e4f2b1158dd54217c501ff16f9317c8d3e0b84a4f30ba010317c739f6fe76b7c7abf9bb10be50efdf902cafe342a502ab30874a0b2af7f14a03fb1afe85865a484dc8bf452c789e5011a1e59903d20972f05924a3e2b6deb85a03669dcc7f9b458db580f119eb416696503d3d31b28b02006cd2a8aee8793ccc740371164d32d2071d367ac6f34d4f75dce044da25150290ee13d769e5582e44371903ff32cdf921656f5171bb158da3402c8cfd140a82cb082e97a038e365fafcc8ee031110365a261d78d3acdd84899925a17832414b7d5ee2dcb16a7e08794143900603f9ee72e47c08060252c92cff490f96258af3c94f91c4b4d84542834fa05871b7033cbeda459afbb6320e196a79432f699b31617792176aaacf13c6d8a1986e954003886c9f36f5ada18e9f6ab067414197c8a3782aef8f8779823e9f2b4ca354ada303a3cef5ad072fd3df16c8693ad4370eaf9fd2e94ce4f912b93dea097d034c78b103cfaac0025f48082bac7d449ce07b8e16aebdfb8d4393e1c2bf0fe18afd53b7990382308d803b12041ee39f1c4bcc77d1fa42ad4cbe9904adf28c80d44a285afde40393ad0a1fdacab9ff29e597772250f84c8eb04a5cbfcf0f37025c412b4e9bfa9f03d1e0219bf0dba4a943bdb2c350e355e231e5e841b553823cc0ed7230c048c0ef03820a6aff14571bb86f6d84a375b7d521c08a08496287215a10cbdae279d2b9ca0334afbb53384b515db917dcca63f9d03d58c5ce2547ad24d4576a8d9d02f5a177036aef2b3501214e8dc59b5bd2805acfcb72df6d0da71f9e639d60f101b347c7710381de9086c125bd22dbb92dcd1fbc97f4d4312d2c26c0706956671676f440f41703aef984fd1b9ad3eda0cb4a142bee3736b43c9527a075a44bfbefc5d232f1f85803a61ca9068d61226a8dfffc2477247b37de32d21b128a2ab35fe460987b07c505034c3a2507f2e7db35027f405b2cd849c5eccd2a1e6bb50a6c84da7249e2db82c4032318b89b50b7f709fa75c443ddb31fc492cb3cf3477b7984c8473059e29c4d74039ba4abd791f8ae056a86ef9042a7899ee877d32b493d5f4749aa10fff32a7f1003ddf112e1fc6b153bcc5ca042ac2ba3155aa74860672801cdb276f86ee57ddeaa033a599055e818613288c41f1bd8fe2a4b4e2b05fd23a3a15bf93f16aebf8016ad038ef61839303015b85e3b16fb1fafa1e328fbbfe1fa320753725f982d957654d8038cfa512313f881fdb9e35572587bf69f18ced08776ce49252737295a305966e003f5b536057aa56871b74c38946aa8e54bc4e7dfa53378a2cd9cbeb74cd62720ff03512e7fd967289262104cdbce4b8bc0529bc3a681e9bd1522398b320f89818bb103f9751ff6409ef2fdea49767eed7d654cc98e85f42df3dc401a32d04271577a7303ee7d6a2b0d6d1bc06fdc15320f90300f0ba7642580502c913ba5cbdcb181b1820331010542bfa65854dcce29889cc98de3c639df67379a3b3b3cf98affe2e74f3103dcb79a64cea3597b43fda68eb4d9b9180c3c5ac3512735a1cedf8454bbae58b8034ae3d75f8427121cdf2477d59c6209b77097f8c87292c1ab449e76639360891003914a40818907e5a93f60b05f39e062761b32d515d00583d3aa46034b1c908fbd030b6aaabb5b08f7f67a0b51d394270b37d22bce2df53899d94a428f4f026e0cac03d500a65e021bcace8afe1b96681b393a03d1403a0091bf471807405f950dafcc034eb68f2c06a6b6c91785f0b6d138c33d758e5ad655c179c88caea797147eccef033c3bb23d53ff8242dac04fc1731d0039eeeced92739e248c5b61a60244c03e7603070ce07d739dde5b920b62d01f256ce5466d56eb21f474757116dddcab87f21b03487bc66bb3e9ceaa6ea1782bd007b206d00b76b2b87c65a4ae83a82575fabe0503d4110432f0e1b72c655394ea770fa57c098cd925bde49fee3fa55c3274c04f3603eb8ddc9bd99dc0ae4003a5f637703be6c885cb30dccd4afee9636ca47a90addc038ac54f5d263ffe0769d2abf5f4b61c121180148b19c72a91011f0d3f8d3d54390330d48c7f9098573d652f0b71ed5f14a03f057dca4576bdf5c516b12aa17947c60605581e0309af0f46d24615c7d9edef73d22cc4390ff5f7167da82a3908fcb2e10007011bffffffffffffffff05581e03aac863645a002d6cd78e5f8a9af3dd879a4df84f17e74984f627c294f00c024686bad161a36003bb6c280d77e6d9ca1259c06dc4f38c59a8b3aab4b5fe6c5d20dc76f3818f438405581e037490de37e2dbfaa1e5da021e21b5a47d6fc60d929a3c39c2058fdd3d500c190654485cac3a41e77d836305581e0393884a96280807b1f28aef6947f8ed40af4aae57dd1a924be89aa731e00c014728e59229efe600030f8135fcb5c0719bb19a119537daee05e6ff0236a3a29a05e54e9ec4fc3bc25f05581e03f62e6e00c763e422f7ea24bd5c0e12f0184127ec8b603839a97dd4a4200c024676638b4d70a005581e03e76af22859b199cc092ad3b4ec3eafd7272e6998b1ad598ee26827b5e00c0146082b01dc616003d5a867d268961668f5797a04462bf5b4471525438ca312d5017d72f177b06e5a05581e0303175de6e25ee85989d4d1322bc974085c6e21b54ca16ec6aadc23d6b0040105581e039fad25174958c685cb6a9dc4f9945a11e2f9f4909a3d0d72b52c51f06004010219e7f103cfb7f60c65280c250ed8b66b63b64da972fe24c6603e6861cef1cb14cd23981a0219ffff034409d069f19218810c8e4b88c61128ae0efb3a56059cf946fe38dba77df0c05b0219ffff03459dbb1d11ed80ccdce6cffb2f541fc3e080c9bafff551b98cbd47224ae2d1de03c642b98ce0ccb2a233b951d05583436549a8497802a9b7e450fd9dd030dd270a037379bb1baa8c19f85dcc404819b82be8fa1d10173e59d5dc35bd67deb32e468d0219ffff03ddfd921ece199fdf9ce07d967cf21f29d6811e08436712c9d0676ed741c05282036f50cb292366eef9d858b3826744b2a41ecd06decace609438f63e0605c7d69e0311f0a7bdb78427132f11be5ad99ed58d019449d18daecea686d6b44a7027815403e44b739b1b2908ec7bc7f9ffd26d8da62b806b8bdeb771bfac7691480e9574e30219ffff03b2fc20b5aeb90cbb2f3c51dae028fb77ab15c165180e7295d58795a30544ef3e03979d76527eb461e0dd8e60369ac29beaf5494f9e27b34b1b682735999732809403d0a37bf5814eff746b6a32ce73e12f8885e688acbff824f2494318adac90de8203def5824947d97bdd038734f86e55bd395009261a1deabae90de5f434c03258820311a4afc85f12ab94373f29e51038721da8d12d4452c776ac89e4942c69d0dcae032410e0b73c01dfbc6d692850566e18ed74c647ef55e2a3d65686319f34d9c77703b1c0023f718a63cb2e1c7c3f02106d0faba37a4c38bddb5c6ba5e5f261b3219c03e10f202981587d0adbb061d8554ac0ce7b8767e69867b33d27f09e3178a87cc303fefb3ec6f8eb7e49ec0dc26bd164508cf7ad526fac485c018a06d78b3c5c61da03f1b021d8786270ffbcb4b12a7b796b1e9a079eeb1ab92ca7572377d05353d6270384ffe12720f1aad8e8505ebb585c100a8efe2ca78cc69a61d2b920eb6e6de25003ab0d481d603da4d7d3d30c2e03ca6ecfa986fd91b1edb1a1e42c2890b0d3ed550385e4a91ef8687739a0496322e9ff9ecdcc5e68167380e963a55ec5997182e8a60219ffff03ae293660abe3cddbc2c4e6817310249595745b8fb1c6c1b4da79e90fd51b235d035ad9172f95726ad772496c05b0bcfbc282260fe9d00ad2c40febc637d8b308a3035c5b7583250a54d367052f3844cfe3221b38ef3d1504503bbd779a881f41919d03bef722a29810d8be278b94808ffd425612a0fd56f0aaf969aa3a27bfd533a7ed03936826abe95e5dfeaf6478e54741a9a01f584e3b1f8b05d176291622f4378d3f0363b39b562a171826db83775ae1f478895fc31f510da32df98092fe62463257e903464a191921272ba4a42fa6db53e551c5bbfe3168de5181509ed51d2eac9810f9035fc2d93a5f27bcf189a50fbeabc574d45d40121b19dd3efeff86696528112f4a0377aa2e8df60c94eae555ea56a1328ff71cacb178ae8824de4ab8534c19f9c26603d9c04efb4ad6661520df6289bf6a4cd744b069c1140e9726f1a00baf8b6150c803786dd9ed4539405d4be63a773f8729f79df8a7a7fb3f70b83e2007bafd4a99ef030834cb96749641492857e5f4d71d47b8136a484058e25b8eeaf8b6dba0e3dcd303a1527dd47896f6d7f847576ef9b4330e2c1cca5653381b4b8b9b0608d7a3a45a03cfd3fa4caa95159f045dd28f2a6bf7cb576e533bfcab8918a56ef1e7d337658c03b01f89d3d73e072e79efdf19f4faf5dccd712bc4cb84119cfc0caea00a0a600e03452ad127705a5b6337f8e610a5c267dc9e6a9f81f0974a689da9795ffe787b7c032085fb3cdac5c4ee5fff92861d0cf965d0e6d0c6a2ec83a8b25aab42a4aac4dc03a551029b1762732d70d18f63fb3316b54252d09161432bf22301e734f813889f032f9904487b58121f4ec788fcda7a55acd495a0868af2760a47ab77875706a504038b199df28300fd77865fc68f88e022e7edde0ba0493f9d60041b5ff9c65c4af403296d3fa78b37ae73c02266ad6f7f21003f3c03abd3c979fa676837348f49dc42038781f363df6dd0ef638edc888e8f3529bc8e63326a4ca25f635214f483bb84020305639796c89edea337f3fc0d18bc6ed0bd006eea7fca668cfba48814b5ef153f03f76c5106240ca3da8dcf0aefa301d62524ac023371f4466e6f28c982d2cee71e03d96bedc83d6489e77fe4fee276088c5d390469868fbeb47c399e3fe115d4c74d034a0285e050b910bbd82ab2f451ca9e712ae0a139402cae99797d772572d4e06e03cf03cd33e89e67a721cdccfda36681ec2b5e6c31cf9f5139c625ce197fd1aa5c031f1ea19f5abc5eff32f2eda8e2c5e55d9b49ff02589779e629a324de60f3bb3e03deebc1ad243622d47ea74c10e94994572aa36ea066fe52ad1402bc2309ee51220331eba3738c7900df164323e31d56fcf16dab78c326d5a72b747368ffe008cc3203abf53e9d639ef7b9d62cc052cd2fca230c5696de26abf4bb3167e20533596ffc05581e03b8971967e2f5e83fd50155e29b0d1e3222134b6de74f48246afa33cda00c014614938f7bc00005581e036488ceb871fe4c94751d347eb0480cbe68f771256414587d625860cee0040105581e030ac604447b7178b395be93b3784feeb389f9dc79c831a14db8ae8412f0040205581e0392ce3266e0617c4a23a635682047f6189cd79f9f2d28bcc949c0b1314004020322685ffd66f4d243d83fac445f4713466e6dce4c46c7744ba4a0afbde5ae7d2203cb2944c6ea13de67d0cad588f3fd4493c2412350076a5c13f39b00f682eb2de70459270460806040526004361061009a5760003560e01c80638da5cb5b11610069578063b1ebddda1161004e578063b1ebddda14611616578063c44de60c14611636578063f2fde38b14611656576100a1565b80638da5cb5b14611592578063ad5c4648146115e9576100a1565b806301e33667146115355780631de3df2c146115555780634782f7791461155d578063715018a61461157d576100a1565b366100a157005b60003560e01c6001811461013057600281146101cb578015610278576006811461031b576003811461043b57600481146104c7576005811461059f576007811461069257600881146108505763fa461e33811461096f576310d1e85c81146109e45761012e60067f55444645727200000000000000000000000000000000000000000000000000006114df565b005b60043560e01c61013e610a24565b8015610182578115824314176101795761017960107f426c6f636b4e756d6265724572726f72000000000000000000000000000000006114df565b61018233610a61565b505060083560901c60163560901c60243560f81c60253560601c6101bb848273c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2611448565b61012e8284600030856000610e5b565b60043560e01c6101d9610a24565b801561021d578115824314176102145761021460107f426c6f636b4e756d6265724572726f72000000000000000000000000000000006114df565b61021d33610a61565b60083560901c915060163560901c60243560601c60383560601c610242858284611448565b6102656000823073c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28688610e27565b505050801561012e5761012e6000611327565b60043560e01c610286610a24565b60243560f01c81156102d4578215834314176102c7576102c760107f426c6f636b4e756d6265724572726f72000000000000000000000000000000006114df565b6102d46000821133610a41565b60263560f81c925060278360140281016001850160140281016102fa8183888688610c6d565b505050811561012e5761030d6001611327565b801561012e5761012e610b50565b60043560e01c610329610a24565b801561036d578115824314176103645761036460107f426c6f636b4e756d6265724572726f72000000000000000000000000000000006114df565b61036d33610ada565b60463560f81c604735606090811c90602435811c90605b35901c603835609090811c90606f35901c9650346103aa576103aa81888585888a6110e6565b605b36036103b981605b61151b565b90503086156001811461040d576103d483888489898f610ef3565b848110156104075761040760097f5633414f4572726f7200000000000000000000000000000000000000000000006114df565b5061041b565b61041b838884898989610e27565b5050505050505050801561012e576104336001611327565b61012e610b50565b60043560e01c610449610a24565b801561048d578115824314176104845761048460107f426c6f636b4e756d6265724572726f72000000000000000000000000000000006114df565b61048d33610ada565b60163560601c9150602a3560f81c602b3560901c60393560901c6104b683828430896001610e5b565b505050801561012e5761012e610b50565b60043560e01c6104d5610a24565b8015610519578115824314176105105761051060107f426c6f636b4e756d6265724572726f72000000000000000000000000000000006114df565b61051933610ada565b60163560601c9150602a3560601c603e3560901c604c3560901c61055781833073c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2878a6001610dd8565b94508085101561058c5761058c60097f5633414f4572726f7200000000000000000000000000000000000000000000006114df565b505050801561012e576104336000611327565b60043560e01c6105ad610a24565b60243560f01c81156105fb578215834314176105ee576105ee60107f426c6f636b4e756d6265724572726f72000000000000000000000000000000006114df565b6105fb6000821133610a41565b60263560601c9250603a3560601c604e3560f81c604f3560901c605d3560901c606b3560901c6003861634610637576106378385878c85611116565b8061065b5761065b848a73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2611448565b610669858486898d86610e5b565b5061067d8460010382600030896000610e5b565b5050505050811561012e5761030d6001611327565b60043560e01c6106a0610a24565b60243560f01c81156106ea578215834314176106e1576106e160107f426c6f636b4e756d6265724572726f72000000000000000000000000000000006114df565b6106ea33610ada565b60263560601c9250608a3560901c60038216603a3560601c60623560601c60983560901c346107355761073581868473c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28c896110e6565b6003600287901c168381156107475750305b8561076b5761076b878b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2611448565b61078e8388838773c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28f8c610dd8565b50604e3560601c995060763560601c965060a63560901c95506003600489901c16975089905087156107bd5750305b6107cc8684838a888a88610dd8565b5050505050506107de60b43560901c90565b61080181833073c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2878b8a610dd8565b9550831561083c578086101561083c5761083c60097f5633414f4572726f7200000000000000000000000000000000000000000000006114df565b50505050801561012e576104336001611327565b60043560e01c61085e610a24565b60243560f01c81156108ac5782158343141761089f5761089f60107f426c6f636b4e756d6265724572726f72000000000000000000000000000000006114df565b6108ac6000821133610a41565b60263560601c925060653560901c60038216603a3560601c60623560f81c60733560901c346108e2576108e28186848b88611116565b8361090657610906858973c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2611448565b610914828287868c89610e5b565b5050604e3560601c955060633560f81c925060813560901c915061093e8383600089856000610e5b565b50505060643560f81c608f3560901c61095d8282600030896000610e5b565b5050811561012e5761030d6001611327565b61097832610ada565b60643580601481146109c35761098d82610bab565b600181146109b75760043560008112156109a657506024355b60843560601c61012e823383611448565b61012e83336084610bbf565b60043560008112156109a6575060243560843560601c61012e823383611448565b6109ed32610ada565b6084356109f981610bab565b60018114610a185760a43560601c60b83560901c61012e813384611448565b61012e823360a4610bbf565b600030331460018114610a3957600191505090565b600091505090565b8160018114610a5857610a5382610a61565b505050565b610a5382610ada565b73de09838eedb3030153524b5a14972e7c9ef6576581147383b4f74296738d8c2e15bd7c1f4a19df122354f582141773744bc1d963e8f54395dfe504e343fc3f2fe8fc8b821417610ad757610ad760087f4e6f7441646d696e0000000000000000000000000000000000000000000000006114df565b50565b7319719e2aa3a47cb0080d7ba10ac3f9651ccd2a7c81147349f18c6370d3235fcc7cf4fd8167b8ca9bed0af882141773e4b35743c2c15760d7551afc9c304ec0dc30ba50821417610ad757610ad760097f4e6f7441646d696e4300000000000000000000000000000000000000000000006114df565b60083560901c610b743073c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26112e0565b81811015610ba757610ba760077f42414572726f72000000000000000000000000000000000000000000000000006114df565b5050565b60006058821115610bba575060015b919050565b602281013560f81c6023820135606090811c908335811c906037850135901c6014850135609090811c90604b870135901c60378903610c018160378a0161151b565b87610c51576059821015610c3857610c1a838887611448565b506000610c2b81888c898989610e27565b5050505050505050505050565b610c46818830898989610e27565b610c2b848b88611448565b610c5f81888c898988610ef3565b505050505050505050505050565b60005b83811015610dd05760036002820283901c1660018201606f86821015610c9c57506002810284901c6003165b601484028801803560601c601482013560601c9150601486028801803560601c3085610ccc5750601482013560601c5b878015610d7757600e8a028f013560901c965089610d0357600e88028f013560901c935034610d0357610d0384888888878e6110e6565b6040519350601484528460601b602085015260348401604052610d2a84848489898c610ef3565b96508c8803610d7257600e88028f013560901c935083871015610d7257610d7260097f5633414f4572726f7200000000000000000000000000000000000000000000006114df565b610dbc565b600e88028f013560901c965089610dad578e3560901c935034610da257610da287858888878e6110e6565b610dad848487611448565b610dbc6000848489898c610e27565b505050505050505050600181019050610c70565b505050505050565b60008115610e0d57604051601481528460601b602082015260348101604052610e0581858989898d610ef3565b915050610e1c565b610e1c6000848888888d610e27565b979650505050505050565b610e3183836114b7565b50600082828514610e43575082905060005b610e50898883858c61102f565b505050505050505050565b808015610ecd5760016401000276a488610e8a57506000905073fffd8963efd1fc6a506488495d951d5263988d255b6040516014815273c02aaa39b223fe8d0a0e5c4f27ead9083c756cc260601b602082015260348101604052610ec38188848b878b610f63565b5050505050610eea565b60008688610edc575086905060005b610e5060008783858961102f565b50505050505050565b6000610eff84846114b7565b5060016401000276a4828614610f2a57506000905073fffd8963efd1fc6a506488495d951d5263988d255b610f388a898388868e610f63565b93509050818015610f4e57836000039450610f55565b8160000394505b505050509695505050505050565b6000806040517f128acb08000000000000000000000000000000000000000000000000000000008152876004820152846024820152856044820152866064820152885160a060848301528060a483015260a482016001820160205b81811015610fd6578c81015183820152602001610fbe565b601f84169150811580610fec57818e0151828501525b505050506000828260c401846000895af190503d806000843e81801561101c578351955060208401519450611020565b8184fd5b50505050965096945050505050565b6040517f022c0d9f00000000000000000000000000000000000000000000000000000000815282600482015283602482015284604482015285516080606483015280608483015260008111156110bd57608482016001820160205b818110156110a257898101518382015260200161108a565b601f841691508115806110b857818b0151828501525b505050505b6000828260a401846000875af1905080806110dc573d806000853e8084fd5b5050505050505050565b6110f084846114b7565b50600084821460018114611107576000915061110c565b600191505b506110dc88888387875b8015600181146111cc57367fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec013560601c611150846112a0565b8560018114611191578282111561118c5761118c60037f43453300000000000000000000000000000000000000000000000000000000006114df565b6111c4565b828210156111c4576111c460037f43453200000000000000000000000000000000000000000000000000000000006114df565b505050610dd0565b6111d784868561120a565b86811015610eea57610eea60037f43453100000000000000000000000000000000000000000000000000000000006114df565b60006112168483611238565b6103e585028181029150806103e8840201925050818104925050509392505050565b6000806040517f0902f1ac000000000000000000000000000000000000000000000000000000008152600081600483875afa503d806000833e50805160208201519150856001811461128f57829450819350611296565b8194508293505b5050509250929050565b60006040517f3850c7bd000000000000000000000000000000000000000000000000000000008152600081600483865afa503d806000833e505192915050565b60006040517f70a08231000000000000000000000000000000000000000000000000000000008152836004820152600081602483865afa503d806000833e50519392505050565b346000811160018114611391578215610a535760163560901c91508115610a5357611351826113cb565b60008060008085415af18061138b5761138b60197f7472616e7366657220636f696e62617365206572726f723a32000000000000006114df565b50505050565b60008060008085415af18061138b5761138b60197f7472616e7366657220636f696e62617365206572726f723a31000000000000006114df565b6040517f2e1a7d4d000000000000000000000000000000000000000000000000000000008152816004820152600081602483600073c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25af1905080610ba757610ba760137f776974686472617757657468206661696c6564000000000000000000000000006114df565b6040517fa9059cbb0000000000000000000000000000000000000000000000000000000081528260048201528360248201526000816044836000865af190508061138b5761138b601c7f736166655472616e736665723a7472616e73666572206661696c6564000000006114df565b600080838310600181146114d0578391508492506114d7565b8392508491505b509250929050565b62461bcd60e51b6000527c20000000000000000000000000000000000000000000000000000000006020528160e01b6040528060445260646000fd5b604051828152828260208301379182016020016040525090565b34801561154157600080fd5b5061012e611550366004612278565b611676565b61012e6116ed565b34801561156957600080fd5b5061012e6115783660046122b4565b611762565b34801561158957600080fd5b5061012e6117d3565b34801561159e57600080fd5b506000546115bf9073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b3480156115f557600080fd5b506001546115bf9073ffffffffffffffffffffffffffffffffffffffff1681565b6116296116243660046122de565b6118a9565b6040516115e091906123df565b34801561164257600080fd5b5061012e61165136600461246d565b611cc6565b34801561166257600080fd5b5061012e6116713660046124ad565b611e5d565b60005473ffffffffffffffffffffffffffffffffffffffff1633146116e25760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064015b60405180910390fd5b610a53838383611fda565b6000341161173d5760405162461bcd60e51b815260206004820152600a60248201527f76616c756520697320300000000000000000000000000000000000000000000060448201526064016116d9565b6001546117609073ffffffffffffffffffffffffffffffffffffffff16346120c5565b565b60005473ffffffffffffffffffffffffffffffffffffffff1633146117c95760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016116d9565b610ba782826120c5565b60005473ffffffffffffffffffffffffffffffffffffffff16331461183a5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016116d9565b6000805460405173ffffffffffffffffffffffffffffffffffffffff909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600080547fffffffffffffffffffffffff0000000000000000000000000000000000000000169055565b60608315806118b757504384145b6119035760405162461bcd60e51b815260206004820152601160248201527f426c6f636b4e756d6265724572726f724d00000000000000000000000000000060448201526064016116d9565b337383b4f74296738d8c2e15bd7c1f4a19df122354f581148061194f575073ffffffffffffffffffffffffffffffffffffffff81167349f18c6370d3235fcc7cf4fd8167b8ca9bed0af8145b80611983575073ffffffffffffffffffffffffffffffffffffffff811673744bc1d963e8f54395dfe504e343fc3f2fe8fc8b145b806119b7575073ffffffffffffffffffffffffffffffffffffffff811673e4b35743c2c15760d7551afc9c304ec0dc30ba50145b806119eb575073ffffffffffffffffffffffffffffffffffffffff811673de09838eedb3030153524b5a14972e7c9ef65765145b80611a1f575073ffffffffffffffffffffffffffffffffffffffff81167319719e2aa3a47cb0080d7ba10ac3f9651ccd2a7c145b611a6b5760405162461bcd60e51b815260206004820152600960248201527f4e6f7441646d696e4d000000000000000000000000000000000000000000000060448201526064016116d9565b8567ffffffffffffffff811115611a8457611a846124cf565b604051908082528060200260200182016040528015611ab757816020015b6060815260200190600190039081611aa25790505b5091503060005b87811015611bc8576000808373ffffffffffffffffffffffffffffffffffffffff168b8b85818110611af257611af26124fe565b9050602002810190611b04919061252d565b604051611b12929190612599565b6000604051808303816000865af19150503d8060008114611b4f576040519150601f19603f3d011682016040523d82523d6000602084013e611b54565b606091505b509150915081611ba057604481511015611b6d57600080fd5b60048101905080806020019051810190611b8791906125a9565b60405162461bcd60e51b81526004016116d99190612669565b80868481518110611bb357611bb36124fe565b60209081029190910101525050600101611abe565b50611bd2856121b5565b8315611cbb576040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2906370a0823190602401602060405180830381865afa158015611c43573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c67919061267c565b905084811015611cb95760405162461bcd60e51b815260206004820152600860248201527f42414572726f724d00000000000000000000000000000000000000000000000060448201526064016116d9565b505b505095945050505050565b60005473ffffffffffffffffffffffffffffffffffffffff163314611d2d5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016116d9565b80611dcf576001546040517fa9059cbb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8581166004830152602482018590529091169063a9059cbb906044016020604051808303816000875af1158015611dab573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061138b9190612695565b6001546040517f2e1a7d4d0000000000000000000000000000000000000000000000000000000081526004810184905273ffffffffffffffffffffffffffffffffffffffff90911690632e1a7d4d90602401600060405180830381600087803b158015611e3b57600080fd5b505af1158015611e4f573d6000803e3d6000fd5b50505050610a5383836120c5565b60005473ffffffffffffffffffffffffffffffffffffffff163314611ec45760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016116d9565b73ffffffffffffffffffffffffffffffffffffffff8116611f4d5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016116d9565b6000805460405173ffffffffffffffffffffffffffffffffffffffff808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b600060606040517fa9059cbb00000000000000000000000000000000000000000000000000000000815284600482015283602482015260008060448360008a5af192503d806000833e5051905081801561204c57508051158061204c57508080602001905181019061204c9190612695565b6120be5760405162461bcd60e51b815260206004820152602d60248201527f5472616e7366657248656c7065723a3a736166655472616e736665723a20747260448201527f616e73666572206661696c65640000000000000000000000000000000000000060648201526084016116d9565b5050505050565b6040805160008082526020820190925273ffffffffffffffffffffffffffffffffffffffff84169083906040516120fc91906126b2565b60006040518083038185875af1925050503d8060008114612139576040519150601f19603f3d011682016040523d82523d6000602084013e61213e565b606091505b5050905080610a535760405162461bcd60e51b815260206004820152603460248201527f5472616e7366657248656c7065723a3a736166655472616e736665724554483a60448201527f20455448207472616e73666572206661696c656400000000000000000000000060648201526084016116d9565b3480156121c657610ba741826120c5565b8115610ba7576040517f2e1a7d4d0000000000000000000000000000000000000000000000000000000081526004810183905273c02aaa39b223fe8d0a0e5c4f27ead9083c756cc290632e1a7d4d90602401600060405180830381600087803b15801561223257600080fd5b505af1158015612246573d6000803e3d6000fd5b50505050610ba741836120c5565b803573ffffffffffffffffffffffffffffffffffffffff81168114610bba57600080fd5b60008060006060848603121561228d57600080fd5b61229684612254565b92506122a460208501612254565b9150604084013590509250925092565b600080604083850312156122c757600080fd5b6122d083612254565b946020939093013593505050565b6000806000806000608086880312156122f657600080fd5b853567ffffffffffffffff8082111561230e57600080fd5b818801915088601f83011261232257600080fd5b81358181111561233157600080fd5b8960208260051b850101111561234657600080fd5b60209283019a909950918801359760408101359750606001359550909350505050565b60005b8381101561238457818101518382015260200161236c565b8381111561138b5750506000910152565b600081518084526123ad816020860160208601612369565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6000602080830181845280855180835260408601915060408160051b870101925083870160005b82811015612452577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0888603018452612440858351612395565b94509285019290850190600101612406565b5092979650505050505050565b8015158114610ad757600080fd5b60008060006060848603121561248257600080fd5b61248b84612254565b92506020840135915060408401356124a28161245f565b809150509250925092565b6000602082840312156124bf57600080fd5b6124c882612254565b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe184360301811261256257600080fd5b83018035915067ffffffffffffffff82111561257d57600080fd5b60200191503681900382131561259257600080fd5b9250929050565b8183823760009101908152919050565b6000602082840312156125bb57600080fd5b815167ffffffffffffffff808211156125d357600080fd5b818401915084601f8301126125e757600080fd5b8151818111156125f9576125f96124cf565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561263f5761263f6124cf565b8160405282815287602084870101111561265857600080fd5b610e1c836020830160208801612369565b6020815260006124c86020830184612395565b60006020828403121561268e57600080fd5b5051919050565b6000602082840312156126a757600080fd5b81516124c88161245f565b600082516126c4818460208701612369565b919091019291505056fea2646970667358221220932e621a318d90d57728b23b6b8ebe354e5199bd783a1acaf86074346a7ae8ba64736f6c634300080e00330058210390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56305410d943f5633601c7b8c3b372344e6efc81eef8590058210310e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6054c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20219080405581e03d1e36fd391765bbd4a519f884d78455ee5bfdf384736a00e181b3acb100f0147061bd47e8b4f4419270403166cba5acb1a5132ed604d27477f76feb649a56bfeddd965f1cff1f60eb003f905581e038de083c28af92607f637131c37dba4c7b72df805cb584abc910c6ae2400c18a14739b7bb9f2afac705581e036ee6a1d847851f4666bed5c0d3d27784495e23255b85c86710c68cbe500c024702e5972354226c02190dfe037fbcdd3bbd34d9b1721a72138883c1d2d4c619eba0d4899bcc9300ba2c08d90203ec81b54e953804ed8c7b82108f2990ee41f569a9a5cbac888ed9c34418d87677032e75389d74795252e76a0a0910440e1a62c88dab5b799acabf4d78c81ba197d2037fa45a9fe8977e379d4529cd907bec97ede6598301278add040b3c2ea0a407d603a11b78759f6317337167e25e628be5d7bfc9c37ad7dcf9c0ccc77158450380520219ffff03cc0040f8b4fbff51c0ad1b7710242eeff01654763ec33aeb52e2f8a1a3a4f9e503af26dddb3bd264d7db42076334a9d44d72e8130d1147f949eb1a5f4cfb44e52f030bf5636a845cb1e79b67d37e199d98495dd97114aac9533a1c247d2178c0b40d0336d7421ec59a1fc107f3a40c857ef57060af1b683dbbe7e1ae3c277187ba80ea0353d23c8d8191f6bfeb79319f3cbf068a04505486c3ed54e949d9d44ef679d45103331c67b9d15b402274e86815be78462c5be10d3621ab38a0d6ae2ddd7b8baf8e0219ffff031033cf636ce42c22de07cc39d3478b3368bbf3638526fad4927e074a01560239036a22116ed3ac780090ba206b862e044ff0cf2ee389cef93dbc9e4fcc415b90f7035b258a1e6267f6e1fa7e8b6fdf3b69f705527a0817b96205474e3baa43149f3003f09a95e75308449bb78aa85a27c7a4cd7c137fa63a7f79d15bcc09dce3d822e40376e68cb02ba670190c510c3b4392cf7d37e7bf15ae6a8909da54e757ccbe6c5a03907475f8f41ba06e55670725d6a42430f78f584e782f29feb47b5871bb89903703b300a0d103c0557e248b6c3326c75162e578f66d37d48db092184806baeb27dc032057ebd34af07d82d56b0f5ceb2c8e242848f2f14befab412c1b30aafbbefe710219ffff03babe6b515cf2a449471a5015e761c78fbfc4b467e36aeac9e95687b130ee5f51036b9db22a9a878c2087fc8f038e0b56a66a78f83929717819640adb2a1e113fc103f7f2c2f10a8eecf5786704ea87dc6d22ae4f36afb8155b60c1fad8dee4b4712403bad25ff330db4a52a5def2afeb01dcd7575e24c356b1373c1b3e9372a7a8cc4e033853e06919d519c5f38d17d4314470bffef11b68196778e48cae52863f25dea503dc63ac4e2437e738f391eef51d42a951c95e976b05b6d7074cd96474eccb09b203e34dc65c5890e69bbb32a9ef90c57d2ef46a142712ab577f53e3fc06b328f7ed03968c7804351e62726e4ff4693a1e99c81645be8ed49c74e31a96437c416e9bb80360b11412e8a9f294f6e01cf4556b0af7e6b67788af4215dc6e74f0b19bdf1e42034488b2271b1634cdda9c8b32bea9114f04cede3f99afa15436439d583186970e0219ffff0350066cb67c52b9d23d6f6e85c58c2e02d85e7dc9928e0b6a2fa468f2b2ffd4ab03b39e4e5884dac60a8278529349312286331f51e37d0b0747a829b80ad290139e036f862d27e55f213ad069e611d4652601c9e554a0d161140f65276ac2dbdfeaa803fb39494bb9d03c568859c4a168143118335a3c818c5c219a900ef328fd67c301035d5e64342445f410edcc877e67f5241963338cdddcdaedb7d55e5db546517989033b4793ca1456af2e262c56b84ba1f742c23e0b97a585e4752128ed1d27d7c7a903b67230383f3183bf9bcc0f462c724572ad43a9ecec6383ed9a9bdf6d002f61ce0384ae369c8ddc9bad7ccda88e7c9c4798bf62161f3f039b572bce167fb27ecc0f03a8135b3f62177e7a9ae95a7eb2ea4cfd5ba5878b453a4c731d93ff3b6e9daf6903314ff144a7e487cfb5bc452dfbb742fb9c1c7055019723524936aabd00a7b8b1038e4401767f85303e41a6e7f2e51ba885f429056454ec0d36b0da624f41ca8528031264525fa2a269995835d3e83791e40aa57d5fc97c2d08f44de3acf55164ae1003de8be2322451cfc03cae9f799eed95dd44cdb530d178306fa7199616add4253a03ddec68283ccd6e20527e07bf9d2ef5cafd17e805649707987bf5cb6c82c89bb90350968574dc68676695037a86c185ba272c93aa7ff84788c87f0661a2aec449230219ffff0329f5ea03f138654821a56c803f1c8781e95cd37ad57e295fc9a614574390d39403a83733e7a9e8e45397a4f248abf791ddca5a8596579467eba057e2b12d2a6d090363e407ce706f3c4b6dcbcc412ac9faeb063a0f0280d46acaec189b747361cf240316e5616221e0f41071280d2f81ef5c685d140eb7d94a4f172fff4808fa0586dd036adf750f60b31cc5992b96556a44bb210008c0d193aaf86a10b646c345c16b52036a6265623e513be11407ddde45601c989111a620b438420f978e51aa2299a45c031fc06c1559d481fd7751cab8a1a28aa043b09dd6b8a9003c07e99fa232b9d53803e39c343a022e5adb5ddb9de1fb5429380e84fbf2edc958a9fb9235d6a20f6edf035943e330cdc50f5c240ce93e408c8352e1c1860448f590f7ab110c96333a5fc3033cfd59903e9469371629266f4dfe8497fd9d64c0a0d69435ab5ef096717f500803a7bd073efeade7339903c1c84b380c596f6bb03a468435acbb58ad2d55503588033db92409d33e9c5ed24b70b60ccd28cb150cfd080be3dda3dd2ed6864f220d0f036bfa5ec82ad90f847ac7b1204d3722741e9b6fe1e5d627bb9f07245ce98ff12d0364889c57678439f414dad7f20b34e390507d7dfd771178f2b861afd07358973003f311510188e2ada3c74a8af101b9ead4bd3a0504f29daae9edefcfeb6d51a77f031cf4da75e563752699a28a070e83902720798921ef0243233a44cc952b631be803a2b288c3f489f99e9fbc68793de3dd5a602474bed5d41439d90ff45a6d00452c03ac9627b7f877637fcae78668a792401b53347c2c8502ef1a5b850226321e806e03cbe73dd06a125feaab189d1c3fac24a9c20e51f27a43ea82d297a159b179aacc03abb4c0d6016205cbcfcefae5fb5ea50cd2d4b283a3a03c7919bc26329552287a037c2264be4993bf805a7e035a9220f894640846b5409f1a7b6ddac86d093dd12f03ba9b0a4fd0a60166b71ca888cdb209e144a10ea491396661b6d3e01db952d45f03c800fde1d87a58058d05e9a69d513f3ec61751988718de94daec4468d471c5f703334bf048147bc3c8d5eda628fa4e2b7da233137a09e9bd2d0968ae13b021891a03dc8479561c4b58e13eefd0fefae7a2b5943468036a14ecb43f433704645383260303071dbb5906fdd60e3f3793cd0f1c4612019151db0a430c21ba9d5a8ea4282d035aa3d5150785de378a065383c6e99fcaa54d34a18dbf48785bcb3e474161fca1031a6fc96f268cdf8409a5afd3d76950e04b629bd1c14ecdf9e514ce9c4fed6ec1033ed2d7fe66a7bb3d912ecd8e8ef51b79e550d1759f93152a1a8b28f1c647286703a33d3c3ed50965a874d739d252afe0069b6d8353ddf1d4e525967322b8f5a79903fbe4a701c0efe983660ad7caa7cee90a3c1b45ee16a2d5f75e667796f93b6f81038b89d95fcde74215b4cee473bf62ba977832e2ea3c99505e31d637c30694695e05581e0387aafd947848989648f2c48c38ab94d95be2615644b89c5b14cb7f6ae00c044727329e584eb3570314c1811cbbd2b3af2e2399efbbc145d5566e88644f8ad6b48f29b0de6335d3c4031b460c826a854d61dca82f718e088b8b4c4082ffeb93752d7691bc62c51dc0280605581e03a70a818564a13adb6652153ada0fdd5bf1f4f94da63b492dcaf9e428b007011bffffffffffffffff03e25d4f154acb2394ee6c18d64fb5635959ba063d57f83091ec9cf34be16224d703608a02f403f30892b8967da9ac469a8c36335336a69bfcb93089ae791d2c976f05581e035331503212364343f6be717d883932db5da5e03c0c6ce7823efe84e8100f01447807c1dc1bffffffffffffffff031bb9923cd5c240f934d10aa469719216b5085bad3872169ccced536edc22277b03da9a8dc62f6fe7e06cf36272a7824915f0fa67a577b587182bb8eb00ead29d6103ec549f9c6738e97fd8fe16864a50696530be736dbe5b3f828cc38c4201b2fae804592c1d608060405234801561001057600080fd5b50600436106101b95760003560e01c80636a627842116100f9578063ba9a7a5611610097578063d21220a711610071578063d21220a7146105da578063d505accf146105e2578063dd62ed3e14610640578063fff6cae91461067b576101b9565b8063ba9a7a5614610597578063bc25cf771461059f578063c45a0155146105d2576101b9565b80637ecebe00116100d35780637ecebe00146104d757806389afcb441461050a57806395d89b4114610556578063a9059cbb1461055e576101b9565b80636a6278421461046957806370a082311461049c5780637464fc3d146104cf576101b9565b806323b872dd116101665780633644e515116101405780633644e51514610416578063485cc9551461041e5780635909c0d5146104595780635a3d549314610461576101b9565b806323b872dd146103ad57806330adf81f146103f0578063313ce567146103f8576101b9565b8063095ea7b311610197578063095ea7b3146103155780630dfe16811461036257806318160ddd14610393576101b9565b8063022c0d9f146101be57806306fdde03146102595780630902f1ac146102d6575b600080fd5b610257600480360360808110156101d457600080fd5b81359160208101359173ffffffffffffffffffffffffffffffffffffffff604083013516919081019060808101606082013564010000000081111561021857600080fd5b82018360208201111561022a57600080fd5b8035906020019184600183028401116401000000008311171561024c57600080fd5b509092509050610683565b005b610261610d57565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561029b578181015183820152602001610283565b50505050905090810190601f1680156102c85780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6102de610d90565b604080516dffffffffffffffffffffffffffff948516815292909316602083015263ffffffff168183015290519081900360600190f35b61034e6004803603604081101561032b57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610de5565b604080519115158252519081900360200190f35b61036a610dfc565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b61039b610e18565b60408051918252519081900360200190f35b61034e600480360360608110156103c357600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060400135610e1e565b61039b610efd565b610400610f21565b6040805160ff9092168252519081900360200190f35b61039b610f26565b6102576004803603604081101561043457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020013516610f2c565b61039b611005565b61039b61100b565b61039b6004803603602081101561047f57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16611011565b61039b600480360360208110156104b257600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113cb565b61039b6113dd565b61039b600480360360208110156104ed57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113e3565b61053d6004803603602081101561052057600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113f5565b6040805192835260208301919091528051918290030190f35b610261611892565b61034e6004803603604081101561057457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81351690602001356118cb565b61039b6118d8565b610257600480360360208110156105b557600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166118de565b61036a611ad4565b61036a611af0565b610257600480360360e08110156105f857600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060408101359060608101359060ff6080820135169060a08101359060c00135611b0c565b61039b6004803603604081101561065657600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020013516611dd8565b610257611df5565b600c546001146106f457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c55841515806107075750600084115b61075c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180612b2f6025913960400191505060405180910390fd5b600080610767610d90565b5091509150816dffffffffffffffffffffffffffff168710801561079a5750806dffffffffffffffffffffffffffff1686105b6107ef576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526021815260200180612b786021913960400191505060405180910390fd5b600654600754600091829173ffffffffffffffffffffffffffffffffffffffff91821691908116908916821480159061085457508073ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff1614155b6108bf57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f556e697377617056323a20494e56414c49445f544f0000000000000000000000604482015290519081900360640190fd5b8a156108d0576108d0828a8d611fdb565b89156108e1576108e1818a8c611fdb565b86156109c3578873ffffffffffffffffffffffffffffffffffffffff166310d1e85c338d8d8c8c6040518663ffffffff1660e01b8152600401808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b1580156109aa57600080fd5b505af11580156109be573d6000803e3d6000fd5b505050505b604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff8416916370a08231916024808301926020929190829003018186803b158015610a2f57600080fd5b505afa158015610a43573d6000803e3d6000fd5b505050506040513d6020811015610a5957600080fd5b5051604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905191955073ffffffffffffffffffffffffffffffffffffffff8316916370a0823191602480820192602092909190829003018186803b158015610acb57600080fd5b505afa158015610adf573d6000803e3d6000fd5b505050506040513d6020811015610af557600080fd5b5051925060009150506dffffffffffffffffffffffffffff85168a90038311610b1f576000610b35565b89856dffffffffffffffffffffffffffff160383035b9050600089856dffffffffffffffffffffffffffff16038311610b59576000610b6f565b89856dffffffffffffffffffffffffffff160383035b90506000821180610b805750600081115b610bd5576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526024815260200180612b546024913960400191505060405180910390fd5b6000610c09610beb84600363ffffffff6121e816565b610bfd876103e863ffffffff6121e816565b9063ffffffff61226e16565b90506000610c21610beb84600363ffffffff6121e816565b9050610c59620f4240610c4d6dffffffffffffffffffffffffffff8b8116908b1663ffffffff6121e816565b9063ffffffff6121e816565b610c69838363ffffffff6121e816565b1015610cd657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f556e697377617056323a204b0000000000000000000000000000000000000000604482015290519081900360640190fd5b5050610ce4848488886122e0565b60408051838152602081018390528082018d9052606081018c9052905173ffffffffffffffffffffffffffffffffffffffff8b169133917fd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d8229181900360800190a350506001600c55505050505050505050565b6040518060400160405280600a81526020017f556e69737761702056320000000000000000000000000000000000000000000081525081565b6008546dffffffffffffffffffffffffffff808216926e0100000000000000000000000000008304909116917c0100000000000000000000000000000000000000000000000000000000900463ffffffff1690565b6000610df233848461259c565b5060015b92915050565b60065473ffffffffffffffffffffffffffffffffffffffff1681565b60005481565b73ffffffffffffffffffffffffffffffffffffffff831660009081526002602090815260408083203384529091528120547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14610ee85773ffffffffffffffffffffffffffffffffffffffff84166000908152600260209081526040808320338452909152902054610eb6908363ffffffff61226e16565b73ffffffffffffffffffffffffffffffffffffffff851660009081526002602090815260408083203384529091529020555b610ef384848461260b565b5060019392505050565b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b601281565b60035481565b60055473ffffffffffffffffffffffffffffffffffffffff163314610fb257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f556e697377617056323a20464f5242494444454e000000000000000000000000604482015290519081900360640190fd5b6006805473ffffffffffffffffffffffffffffffffffffffff9384167fffffffffffffffffffffffff00000000000000000000000000000000000000009182161790915560078054929093169116179055565b60095481565b600a5481565b6000600c5460011461108457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c81905580611094610d90565b50600654604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905193955091935060009273ffffffffffffffffffffffffffffffffffffffff909116916370a08231916024808301926020929190829003018186803b15801561110e57600080fd5b505afa158015611122573d6000803e3d6000fd5b505050506040513d602081101561113857600080fd5b5051600754604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905192935060009273ffffffffffffffffffffffffffffffffffffffff909216916370a0823191602480820192602092909190829003018186803b1580156111b157600080fd5b505afa1580156111c5573d6000803e3d6000fd5b505050506040513d60208110156111db57600080fd5b505190506000611201836dffffffffffffffffffffffffffff871663ffffffff61226e16565b90506000611225836dffffffffffffffffffffffffffff871663ffffffff61226e16565b9050600061123387876126ec565b600054909150806112705761125c6103e8610bfd611257878763ffffffff6121e816565b612878565b985061126b60006103e86128ca565b6112cd565b6112ca6dffffffffffffffffffffffffffff8916611294868463ffffffff6121e816565b8161129b57fe5b046dffffffffffffffffffffffffffff89166112bd868563ffffffff6121e816565b816112c457fe5b0461297a565b98505b60008911611326576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180612bc16028913960400191505060405180910390fd5b6113308a8a6128ca565b61133c86868a8a6122e0565b811561137e5760085461137a906dffffffffffffffffffffffffffff808216916e01000000000000000000000000000090041663ffffffff6121e816565b600b555b6040805185815260208101859052815133927f4c209b5fc8ad50758f13e2e1088ba56a560dff690a1c6fef26394f4c03821c4f928290030190a250506001600c5550949695505050505050565b60016020526000908152604090205481565b600b5481565b60046020526000908152604090205481565b600080600c5460011461146957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c81905580611479610d90565b50600654600754604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905194965092945073ffffffffffffffffffffffffffffffffffffffff9182169391169160009184916370a08231916024808301926020929190829003018186803b1580156114fb57600080fd5b505afa15801561150f573d6000803e3d6000fd5b505050506040513d602081101561152557600080fd5b5051604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905191925060009173ffffffffffffffffffffffffffffffffffffffff8516916370a08231916024808301926020929190829003018186803b15801561159957600080fd5b505afa1580156115ad573d6000803e3d6000fd5b505050506040513d60208110156115c357600080fd5b5051306000908152600160205260408120549192506115e288886126ec565b600054909150806115f9848763ffffffff6121e816565b8161160057fe5b049a5080611614848663ffffffff6121e816565b8161161b57fe5b04995060008b11801561162e575060008a115b611683576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180612b996028913960400191505060405180910390fd5b61168d3084612992565b611698878d8d611fdb565b6116a3868d8c611fdb565b604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff8916916370a08231916024808301926020929190829003018186803b15801561170f57600080fd5b505afa158015611723573d6000803e3d6000fd5b505050506040513d602081101561173957600080fd5b5051604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905191965073ffffffffffffffffffffffffffffffffffffffff8816916370a0823191602480820192602092909190829003018186803b1580156117ab57600080fd5b505afa1580156117bf573d6000803e3d6000fd5b505050506040513d60208110156117d557600080fd5b505193506117e585858b8b6122e0565b811561182757600854611823906dffffffffffffffffffffffffffff808216916e01000000000000000000000000000090041663ffffffff6121e816565b600b555b604080518c8152602081018c9052815173ffffffffffffffffffffffffffffffffffffffff8f169233927fdccd412f0b1252819cb1fd330b93224ca42612892bb3f4f789976e6d81936496929081900390910190a35050505050505050506001600c81905550915091565b6040518060400160405280600681526020017f554e492d5632000000000000000000000000000000000000000000000000000081525081565b6000610df233848461260b565b6103e881565b600c5460011461194f57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c55600654600754600854604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff9485169490931692611a2b9285928792611a26926dffffffffffffffffffffffffffff169185916370a0823191602480820192602092909190829003018186803b1580156119ee57600080fd5b505afa158015611a02573d6000803e3d6000fd5b505050506040513d6020811015611a1857600080fd5b50519063ffffffff61226e16565b611fdb565b600854604080517f70a082310000000000000000000000000000000000000000000000000000000081523060048201529051611aca9284928792611a26926e01000000000000000000000000000090046dffffffffffffffffffffffffffff169173ffffffffffffffffffffffffffffffffffffffff8616916370a0823191602480820192602092909190829003018186803b1580156119ee57600080fd5b50506001600c5550565b60055473ffffffffffffffffffffffffffffffffffffffff1681565b60075473ffffffffffffffffffffffffffffffffffffffff1681565b42841015611b7b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f556e697377617056323a20455850495245440000000000000000000000000000604482015290519081900360640190fd5b60035473ffffffffffffffffffffffffffffffffffffffff80891660008181526004602090815260408083208054600180820190925582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98186015280840196909652958d166060860152608085018c905260a085019590955260c08085018b90528151808603909101815260e0850182528051908301207f19010000000000000000000000000000000000000000000000000000000000006101008601526101028501969096526101228085019690965280518085039096018652610142840180825286519683019690962095839052610162840180825286905260ff89166101828501526101a284018890526101c28401879052519193926101e2808201937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081019281900390910190855afa158015611cdc573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff811615801590611d5757508873ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b611dc257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f556e697377617056323a20494e56414c49445f5349474e415455524500000000604482015290519081900360640190fd5b611dcd89898961259c565b505050505050505050565b600260209081526000928352604080842090915290825290205481565b600c54600114611e6657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c55600654604080517f70a082310000000000000000000000000000000000000000000000000000000081523060048201529051611fd49273ffffffffffffffffffffffffffffffffffffffff16916370a08231916024808301926020929190829003018186803b158015611edd57600080fd5b505afa158015611ef1573d6000803e3d6000fd5b505050506040513d6020811015611f0757600080fd5b5051600754604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff909216916370a0823191602480820192602092909190829003018186803b158015611f7a57600080fd5b505afa158015611f8e573d6000803e3d6000fd5b505050506040513d6020811015611fa457600080fd5b50516008546dffffffffffffffffffffffffffff808216916e0100000000000000000000000000009004166122e0565b6001600c55565b604080518082018252601981527f7472616e7366657228616464726573732c75696e743235362900000000000000602091820152815173ffffffffffffffffffffffffffffffffffffffff85811660248301526044808301869052845180840390910181526064909201845291810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb000000000000000000000000000000000000000000000000000000001781529251815160009460609489169392918291908083835b602083106120e157805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016120a4565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114612143576040519150601f19603f3d011682016040523d82523d6000602084013e612148565b606091505b5091509150818015612176575080511580612176575080806020019051602081101561217357600080fd5b50515b6121e157604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f556e697377617056323a205452414e534645525f4641494c4544000000000000604482015290519081900360640190fd5b5050505050565b60008115806122035750508082028282828161220057fe5b04145b610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f64732d6d6174682d6d756c2d6f766572666c6f77000000000000000000000000604482015290519081900360640190fd5b80820382811115610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f64732d6d6174682d7375622d756e646572666c6f770000000000000000000000604482015290519081900360640190fd5b6dffffffffffffffffffffffffffff841180159061230c57506dffffffffffffffffffffffffffff8311155b61237757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f556e697377617056323a204f564552464c4f5700000000000000000000000000604482015290519081900360640190fd5b60085463ffffffff428116917c0100000000000000000000000000000000000000000000000000000000900481168203908116158015906123c757506dffffffffffffffffffffffffffff841615155b80156123e257506dffffffffffffffffffffffffffff831615155b15612492578063ffffffff16612425856123fb86612a57565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff169063ffffffff612a7b16565b600980547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff929092169290920201905563ffffffff8116612465846123fb87612a57565b600a80547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff92909216929092020190555b600880547fffffffffffffffffffffffffffffffffffff0000000000000000000000000000166dffffffffffffffffffffffffffff888116919091177fffffffff0000000000000000000000000000ffffffffffffffffffffffffffff166e0100000000000000000000000000008883168102919091177bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167c010000000000000000000000000000000000000000000000000000000063ffffffff871602179283905560408051848416815291909304909116602082015281517f1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1929181900390910190a1505050505050565b73ffffffffffffffffffffffffffffffffffffffff808416600081815260026020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b73ffffffffffffffffffffffffffffffffffffffff8316600090815260016020526040902054612641908263ffffffff61226e16565b73ffffffffffffffffffffffffffffffffffffffff8085166000908152600160205260408082209390935590841681522054612683908263ffffffff612abc16565b73ffffffffffffffffffffffffffffffffffffffff80841660008181526001602090815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600080600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663017e7e586040518163ffffffff1660e01b815260040160206040518083038186803b15801561275757600080fd5b505afa15801561276b573d6000803e3d6000fd5b505050506040513d602081101561278157600080fd5b5051600b5473ffffffffffffffffffffffffffffffffffffffff821615801594509192509061286457801561285f5760006127d86112576dffffffffffffffffffffffffffff88811690881663ffffffff6121e816565b905060006127e583612878565b90508082111561285c576000612813612804848463ffffffff61226e16565b6000549063ffffffff6121e816565b905060006128388361282c86600563ffffffff6121e816565b9063ffffffff612abc16565b9050600081838161284557fe5b04905080156128585761285887826128ca565b5050505b50505b612870565b8015612870576000600b555b505092915050565b600060038211156128bb575080600160028204015b818110156128b5578091506002818285816128a457fe5b0401816128ad57fe5b04905061288d565b506128c5565b81156128c5575060015b919050565b6000546128dd908263ffffffff612abc16565b600090815573ffffffffffffffffffffffffffffffffffffffff8316815260016020526040902054612915908263ffffffff612abc16565b73ffffffffffffffffffffffffffffffffffffffff831660008181526001602090815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b6000818310612989578161298b565b825b9392505050565b73ffffffffffffffffffffffffffffffffffffffff82166000908152600160205260409020546129c8908263ffffffff61226e16565b73ffffffffffffffffffffffffffffffffffffffff831660009081526001602052604081209190915554612a02908263ffffffff61226e16565b600090815560408051838152905173ffffffffffffffffffffffffffffffffffffffff8516917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef919081900360200190a35050565b6dffffffffffffffffffffffffffff166e0100000000000000000000000000000290565b60006dffffffffffffffffffffffffffff82167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff841681612ab457fe5b049392505050565b80820182811015610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f64732d6d6174682d6164642d6f766572666c6f77000000000000000000000000604482015290519081900360640190fdfe556e697377617056323a20494e53554646494349454e545f4f55545055545f414d4f554e54556e697377617056323a20494e53554646494349454e545f494e5055545f414d4f554e54556e697377617056323a20494e53554646494349454e545f4c4951554944495459556e697377617056323a20494e53554646494349454e545f4c49515549444954595f4255524e4544556e697377617056323a20494e53554646494349454e545f4c49515549444954595f4d494e544544a265627a7a723158207dca18479e58487606bf70c79e44d8dee62353c9ee6d01f9a9d70885b8765f2264736f6c634300051000320300ee6158611a510f7de2df7a2c4767d987778493b8e3b80910c7fead461bbe3f0058210390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56304b0314681367c295c3ff27fc0058200238e9689977d526f580531775c606e5a07c1ff4f40a9ee98e4cca4fe145a2f44106005820021540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af4d1eef57b0bce86debc4dd280c38021942000058210366cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c688054c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200582002575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5820215b786e4eba85d363599f0603a96206d9fe3a001a117bd4680bc44156f3ec7f005820025a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a85501f3f662916305da19deefd84fdc79e3923b00f41802184400582103f6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c70410100582002f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee3582065cf607b000000000007d33cd603004aedbf0001838810f254c24ce8a92f56600058200252222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f546d7497751656618fc38cfb5478994a20f7e235df0218480219b44505581e038de1d31fd0732b069ae68190eee17c0c586049e31a87a9de9222e718a00701192c1d0219d3e603704520d9a7f1d0f5ab13beb574fa85de11eb7dcc7aa98226a9ccb5801f75c362034e9df35d5bc61b600725ff1e3b9edc93735eed90a4aab1d0ad00f3616160ad20031176eebd55a02d80dd05460759ea58b48d39d6536b0ac765748a7a44410fc12f039da3c4c4508ecd179fc818cfd6c02b6e5d3d1e3047f69ecd21c37da15cb7d95c03dd42b7dc543187733702e6833e939411b2197a65cb76db423d59b475efae383a0315eb189197451955857f5707b837d7f14410752a1f0df757c7cb5863a9c6693b031aad9b030037232530ffb8b896db838425e0920dc8cdab47a182e2fe28b962c10365767c107ae03dfe02656af65be9ea4c30a65e8f9dfdb46295a452d5d381cdc903849f47f6dc1cc96fbdf17126d21d912c231b751f006d336884ce515a2681f93a032727c37bcf8b59e47634a27e11ee38eff8a05f80189c6c3d26722bc37bba30ce038177696feb2dbf2967cb4103efa2d507430711a197c2a738717c5b5543008abe032508f1a148f02172830fd482ea79cac02271a388af04019d6e49921a8a889c5d03e3b6225311b88e9bb4e2e9fa7a8ca0d03d20ac765b5f7ecdb3b183f7d7cae19303ee1760c7bd18c667ed1ed1d2d7c9bf2fb21df6ec20e0ee2dc9728cce12e9fb1b03d670e0d87c493b8041d362698dafdccc6291b498bfa6d94e5dc5d570c404140f0219ffff039a5d2aa9f89477b40b38a663ee233af208c20c47eaa78bbbabc145f2904fe69103b90caeab462eef9191d70fedb72ccf5a33cfeb057ee0fd17bbc43924a95fd89b03c3311ed3314a1499699434304684b7d306fc6f1358e8cad611a2895ac6e4291803b606ff6b93ae409276455be3f976d773b4336fa3f48cebeec8e7d522e9ffc77d032b6813577f217f060dd7ddfa16d8fd1394f5ac92b0c1b82d1ce3ed2f6e7b54050345aac4665b1fe48e2030bf0b64fa6a30ae1ccafe3dc13205189dfb7f0bf87bc40219ffff03bd2694cb32c5b310f2d4aefb513da5ba5f275c0b27abf93d3bf882a711be69e0030d23013cf35ab6c493b4afe0a730271f0f060b47cd5fbf77c501601a0ee33c0e034fb730c95cb500750ebaaa3c8d558a90071672bcca746ed2947d120c748b257f03a040fa78512205ba6ff18eaa5abdf5ca13d3f9b4e4c429bcc2e21e2c53349e8b0314412893c2f0f840fc03069f58fa267f5e29847a2715a440d21fadc59457ce9203cc8caead566ef34cbb50ddfa940a73e65fb2fa76e866521de5f66c24db31c7fb0219ffff031f58a293556a6886c08f3d6c768f622a47e151b3634f8f27f27d5ac65e5718fb03a101dde72c7df67bc737f261dab0779c02c0b83dc7cc111e94c471bda75630ab03ea33f18eaba3e67e57b55c8dee691f600f5e2a467e8133a6beb172c334b509dd03e50222ce226a8a103d385375f1341bfa321a721b0bdbcc6605fed526cd0821530323170395cb90f96cf5a6bd123f15830fb3e6bf21867d89a4bacc4643cf45d45803b316b8749165960c3c89c6e10d4998e4b40cc00e5a082bb7a07fbf9e679e8d2e0219ffff035542724e0ac64f0a9d2fb2104ed94363a57255f5680ee238de1474bc8486e44f03a6d185bd0e74535b72331e6dc9d2d19f53a4b4997b5c52f926ce79f26862099303f1f1571e19500730c0f62375fa7f8a7a90f6c4742bf538167be72a1432aadfb90310b9f42c477bf95e082653b147bee8737c9739cac14151019627bae2597ec4080370c11e4972fcab13d1050b9e54844ea1ff920d4470cbd2bf4e943b4d7bcde9e603d08c521f7580826bb146e476039ce572c0de596b9575ad6323d5947a0664f066030e7bf139f4acf4e2c3c37b1baced8754bc1ad4d28f6eec46b2afeaa843fb049b03e409a71cf61129f80b21eb96d4065506242fa36959967f5b797f0662a52f6d2603dcd03520ef6cdb737f6db64a695efa661cbcb74ced82a26e42f26cf6c85638ef03c4e66faec72346ccb69262b48aa4bbdd469944760209e8b91affed19198001f503edf450d44e9efc837fae58d704d8e6993a288e8ab19f430b496a6654f4fe4c4a03cd85c534ca4a6372058d36cbc405f13617bb5613c55942a3bbdec1a288797ae80353a0bacd823ddf1d179cb8bfa8e42020d2d6d89dd990ecf999ea42e9ca5b961d036036bdc180b8a1831a3e4376ee2b14fb762ee3b8013677f728cc40c17596c625032e5c4827174c4bf23d8d7782af6d6e69556233276422e18447afe6bc443cea3e03fc3f664a374c6cb7ebbc31143797767eb7f19a549fb18f5a79aee942f7f8a26a038c5ca361caa2475fef718e0a397f8d009e51ac3c6cc4621497fc31630d0ca24c03402e34ef9e39f1497e13e2190876faa9e01bd5a84075750a4d8d23a31527e3620388de86a97156e789845fca62671d0c79ebfadd3bc152e1f2947f9a30550bb58403901ed45ad80716634d8624f033d2070b0846facef7492686119fc2c631a58e9f0343b5ca4ec3c5ce5f4ca7fd15c9e94b832d2760e1519c6449212797fe9f63233a0339b21d7ec373b990bb1c57ce14123ac52ef5c7b88d20f76c1e35f7efcbf7c36003a2704b18c6dc0a318607ac2360f3991a67a92b944f41de6ef5a6e0e05afea94d036f216e7c7b3ea96690e6d0a685db725022227c311f46560481f4f49c094b0d1703abfe3d58af8f75d5d96bc1825a828bf810ab4dcc0c84a26df58228cd2331a20803c324ba644c40d8d6c2d47ae57b229a26e894ae7ea6ff3f9faf8e476f9692f37003433c8f733badd86057cb9a944bbb222eb992c5396a58e0a47f0d275b6e57fe2d038069108ff8e4b9e14c0c6bbbd884df679c273d1118a06e2b0e21eb9c93ecac0103d34a0eed061b8b168ba9abf29add6056d4e619688539cee6fe212dbcb47da6b70459202a608060405234801561001057600080fd5b50600436106101a95760003560e01c806370a08231116100f9578063c87b56dd11610097578063e0df5b6f11610071578063e0df5b6f14610400578063e985e9c514610413578063f28ca1dd14610441578063f2fde38b1461044957600080fd5b8063c87b56dd146103ba578063d547cfb7146103cd578063dd62ed3e146103d557600080fd5b80639b19251a116100d35780639b19251a1461035e578063a22cb46514610381578063a9059cbb14610394578063b88d4fde146103a757600080fd5b806370a08231146103235780638da5cb5b1461034357806395d89b411461035657600080fd5b80632b968958116101665780634f02c420116101405780634f02c420146102e1578063504334c2146102ea57806353d6fd59146102fd5780636352211e1461031057600080fd5b80632b9689581461028d578063313ce5671461029557806342842e0e146102ce57600080fd5b806306fdde03146101ae578063081812fc146101cc578063095ea7b31461020d57806318160ddd1461023057806318d217c31461026557806323b872dd1461027a575b600080fd5b6101b661045c565b6040516101c3919061172c565b60405180910390f35b6101f56101da36600461175f565b6006602052600090815260409020546001600160a01b031681565b6040516001600160a01b0390911681526020016101c3565b61022061021b36600461178f565b6104ea565b60405190151581526020016101c3565b6102577f00000000000000000000000000000000000000000000021e19e0c9bab240000081565b6040519081526020016101c3565b61027861027336600461185c565b61063b565b005b610278610288366004611899565b610675565b6102786109fe565b6102bc7f000000000000000000000000000000000000000000000000000000000000001281565b60405160ff90911681526020016101c3565b6102786102dc366004611899565b610a64565b61025760035481565b6102786102f83660046118d5565b610b39565b61027861030b366004611939565b610b6d565b6101f561031e36600461175f565b610bc2565b610257610331366004611975565b60046020526000908152604090205481565b6000546101f5906001600160a01b031681565b6101b6610bfd565b61022061036c366004611975565b600b6020526000908152604090205460ff1681565b61027861038f366004611939565b610c0a565b6102206103a236600461178f565b610c76565b6102786103b5366004611990565b610c8a565b6101b66103c836600461175f565b610d4d565b6101b6611085565b6102576103e3366004611a2b565b600560209081526000928352604080842090915290825290205481565b61027861040e36600461185c565b611092565b610220610421366004611a2b565b600760209081526000928352604080842090915290825290205460ff1681565b6101b66110c8565b610278610457366004611975565b6110d5565b6001805461046990611a5e565b80601f016020809104026020016040519081016040528092919081815260200182805461049590611a5e565b80156104e25780601f106104b7576101008083540402835291602001916104e2565b820191906000526020600020905b8154815290600101906020018083116104c557829003601f168201915b505050505081565b600060035482111580156104fe5750600082115b156105d5576000828152600860205260409020546001600160a01b031633811480159061054f57506001600160a01b038116600090815260076020908152604080832033845290915290205460ff16155b1561056c576040516282b42960e81b815260040160405180910390fd5b60008381526006602090815260409182902080546001600160a01b0319166001600160a01b038881169182179092559251868152908416917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a350610631565b3360008181526005602090815260408083206001600160a01b03881680855290835292819020869055518581529192917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a35b5060015b92915050565b6000546001600160a01b03163314610665576040516282b42960e81b815260040160405180910390fd5b600c6106718282611ae8565b5050565b600354811161098f576000818152600860205260409020546001600160a01b038481169116146106b857604051636edaef2f60e11b815260040160405180910390fd5b6001600160a01b0382166106df57604051634e46966960e11b815260040160405180910390fd5b336001600160a01b0384161480159061071c57506001600160a01b038316600090815260076020908152604080832033845290915290205460ff16155b801561073f57506000818152600660205260409020546001600160a01b03163314155b1561075c576040516282b42960e81b815260040160405180910390fd5b610764611171565b6001600160a01b0384166000908152600460205260408120805490919061078c908490611bbe565b9091555061079a9050611171565b6001600160a01b03808416600081815260046020908152604080832080549096019095558582526008815284822080546001600160a01b03199081169094179055600681528482208054909316909255918616825260099052908120805461080490600190611bbe565b8154811061081457610814611bd1565b60009182526020808320909101546001600160a01b0387168352600982526040808420868552600a9093529092205481549293508392811061085857610858611bd1565b60009182526020808320909101929092556001600160a01b038616815260099091526040902080548061088d5761088d611be7565b600082815260208082208301600019908101839055909201909255838252600a8152604080832054848452818420556001600160a01b0386168084526009835290832080546001818101835582865293852001869055925290546108f19190611bbe565b6000838152600a602052604080822092909255905183916001600160a01b0380871692908816917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91a4826001600160a01b0316846001600160a01b03167fe59fdd36d0d223c0c7d996db7ad796880f45e1936cb0bb7ac102e7082e031487610978611171565b60405190815260200160405180910390a350505050565b6001600160a01b038316600090815260056020908152604080832033845290915290205460001981146109eb576109c68282611bbe565b6001600160a01b03851660009081526005602090815260408083203384529091529020555b6109f68484846111a3565b50505b505050565b6000546001600160a01b03163314610a28576040516282b42960e81b815260040160405180910390fd5b600080546001600160a01b031916815560405133907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3565b610a6f838383610675565b6001600160a01b0382163b15801590610b1b5750604051630a85bd0160e11b8082523360048301526001600160a01b03858116602484015260448301849052608060648401526000608484015290919084169063150b7a029060a4016020604051808303816000875af1158015610aea573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b0e9190611bfd565b6001600160e01b03191614155b156109f957604051633da6393160e01b815260040160405180910390fd5b6000546001600160a01b03163314610b63576040516282b42960e81b815260040160405180910390fd5b6106718282611351565b6000546001600160a01b03163314610b97576040516282b42960e81b815260040160405180910390fd5b6001600160a01b03919091166000908152600b60205260409020805460ff1916911515919091179055565b6000818152600860205260409020546001600160a01b031680610bf85760405163c5723b5160e01b815260040160405180910390fd5b919050565b6002805461046990611a5e565b3360008181526007602090815260408083206001600160a01b03871680855290835292819020805460ff191686151590811790915590519081529192917f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a35050565b6000610c833384846111a3565b9392505050565b610c95858585610675565b6001600160a01b0384163b15801590610d2f5750604051630a85bd0160e11b808252906001600160a01b0386169063150b7a0290610cdf9033908a90899089908990600401611c27565b6020604051808303816000875af1158015610cfe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d229190611bfd565b6001600160e01b03191614155b156109f657604051633da6393160e01b815260040160405180910390fd5b60606000600d8054610d5e90611a5e565b90501115610d9857600d610d718361136a565b604051602001610d82929190611c7b565b6040516020818303038152906040529050919050565b600082604051602001610dad91815260200190565b6040516020818303038152906040528051906020012060f81c905060608060648360ff1611610e1b5760405180604001604052806005815260200164189733b4b360d91b81525091506040518060400160405280600581526020016423b932b2b760d91b8152509050610f56565b60a08360ff1611610e6a5760405180604001604052806005815260200164191733b4b360d91b815250915060405180604001604052806004815260200163426c756560e01b8152509050610f56565b60d28360ff1611610ebb5760405180604001604052806005815260200164199733b4b360d91b815250915060405180604001604052806006815260200165507572706c6560d01b8152509050610f56565b60f08360ff1611610f0c57604051806040016040528060058152602001641a1733b4b360d91b8152509150604051806040016040528060068152602001654f72616e676560d01b8152509050610f56565b60ff8360ff1611610f5657604051806040016040528060058152602001641a9733b4b360d91b81525091506040518060400160405280600381526020016214995960ea1b81525090505b6000610f618661136a565b604051602001610f719190611d02565b60408051601f1981840301815290829052610f8e91602001611d3e565b604051602081830303815290604052600c84604051602001610fb1929190611c7b565b60408051601f1981840301815290829052610fcf9291602001611e17565b6040516020818303038152906040529050600082604051602001610ff39190611e3d565b60408051601f1981840301815282820182526004835263227d5d7d60e01b602084810191909152915190935061102d918591859101611e17565b60408051601f198184030181529082905261104c918390602001611e17565b60408051601f198184030181529082905261106991602001611e9a565b6040516020818303038152906040529650505050505050919050565b600d805461046990611a5e565b6000546001600160a01b031633146110bc576040516282b42960e81b815260040160405180910390fd5b600d6106718282611ae8565b600c805461046990611a5e565b6000546001600160a01b031633146110ff576040516282b42960e81b815260040160405180910390fd5b6001600160a01b038116611126576040516349e27cff60e01b815260040160405180910390fd5b600080546001600160a01b0319166001600160a01b0383169081178255604051909133917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a350565b600061119e7f0000000000000000000000000000000000000000000000000000000000000012600a611fc3565b905090565b6000806111ae611171565b6001600160a01b038087166000818152600460205260408082208054948a16835290822054928252939450919290918691906111ea8386611bbe565b90915550506001600160a01b03808716600090815260046020908152604080832080548a019055928a168252600b9052205460ff1661127c576001600160a01b038716600090815260046020526040812054611247908590611fd2565b6112518585611fd2565b61125b9190611bbe565b905060005b8181101561127957611271896113fd565b600101611260565b50505b6001600160a01b0386166000908152600b602052604090205460ff166112f75760006112a88483611fd2565b6001600160a01b0388166000908152600460205260409020546112cc908690611fd2565b6112d69190611bbe565b905060005b818110156112f4576112ec88611525565b6001016112db565b50505b856001600160a01b0316876001600160a01b03167fe59fdd36d0d223c0c7d996db7ad796880f45e1936cb0bb7ac102e7082e0314878760405161133c91815260200190565b60405180910390a35060019695505050505050565b600161135d8382611ae8565b5060026109f98282611ae8565b6060600061137783611630565b600101905060008167ffffffffffffffff811115611397576113976117b9565b6040519080825280601f01601f1916602001820160405280156113c1576020820181803683370190505b5090508181016020015b600019016f181899199a1a9b1b9c1cb0b131b232b360811b600a86061a8153600a85049450846113cb57509392505050565b6001600160a01b03811661142457604051636edaef2f60e11b815260040160405180910390fd5b6001600160a01b0381166000908152600960205260408120805461144a90600190611bbe565b8154811061145a5761145a611bd1565b9060005260206000200154905060096000836001600160a01b03166001600160a01b0316815260200190815260200160002080548061149b5761149b611be7565b600082815260208082208301600019908101839055909201909255828252600a815260408083208390556008825280832080546001600160a01b031990811690915560069092528083208054909216909155518291906001600160a01b038516907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908390a45050565b6001600160a01b03811661154c57604051634e46966960e11b815260040160405180910390fd5b60038054600101908190556000818152600860205260409020546001600160a01b03161561158d5760405163119b4fd360e11b815260040160405180910390fd5b600081815260086020908152604080832080546001600160a01b0319166001600160a01b0387169081179091558084526009835290832080546001818101835582865293852001859055925290546115e59190611bbe565b6000828152600a602052604080822092909255905182916001600160a01b038516917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908290a45050565b60008072184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b831061166f5772184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b830492506040015b6d04ee2d6d415b85acef8100000000831061169b576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc1000083106116b957662386f26fc10000830492506010015b6305f5e10083106116d1576305f5e100830492506008015b61271083106116e557612710830492506004015b606483106116f7576064830492506002015b600a83106106355760010192915050565b60005b8381101561172357818101518382015260200161170b565b50506000910152565b602081526000825180602084015261174b816040850160208701611708565b601f01601f19169190910160400192915050565b60006020828403121561177157600080fd5b5035919050565b80356001600160a01b0381168114610bf857600080fd5b600080604083850312156117a257600080fd5b6117ab83611778565b946020939093013593505050565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126117e057600080fd5b813567ffffffffffffffff808211156117fb576117fb6117b9565b604051601f8301601f19908116603f01168101908282118183101715611823576118236117b9565b8160405283815286602085880101111561183c57600080fd5b836020870160208301376000602085830101528094505050505092915050565b60006020828403121561186e57600080fd5b813567ffffffffffffffff81111561188557600080fd5b611891848285016117cf565b949350505050565b6000806000606084860312156118ae57600080fd5b6118b784611778565b92506118c560208501611778565b9150604084013590509250925092565b600080604083850312156118e857600080fd5b823567ffffffffffffffff8082111561190057600080fd5b61190c868387016117cf565b9350602085013591508082111561192257600080fd5b5061192f858286016117cf565b9150509250929050565b6000806040838503121561194c57600080fd5b61195583611778565b91506020830135801515811461196a57600080fd5b809150509250929050565b60006020828403121561198757600080fd5b610c8382611778565b6000806000806000608086880312156119a857600080fd5b6119b186611778565b94506119bf60208701611778565b935060408601359250606086013567ffffffffffffffff808211156119e357600080fd5b818801915088601f8301126119f757600080fd5b813581811115611a0657600080fd5b896020828501011115611a1857600080fd5b9699959850939650602001949392505050565b60008060408385031215611a3e57600080fd5b611a4783611778565b9150611a5560208401611778565b90509250929050565b600181811c90821680611a7257607f821691505b602082108103611a9257634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156109f9576000816000526020600020601f850160051c81016020861015611ac15750805b601f850160051c820191505b81811015611ae057828155600101611acd565b505050505050565b815167ffffffffffffffff811115611b0257611b026117b9565b611b1681611b108454611a5e565b84611a98565b602080601f831160018114611b4b5760008415611b335750858301515b600019600386901b1c1916600185901b178555611ae0565b600085815260208120601f198616915b82811015611b7a57888601518255948401946001909101908401611b5b565b5085821015611b985787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b634e487b7160e01b600052601160045260246000fd5b8181038181111561063557610635611ba8565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052603160045260246000fd5b600060208284031215611c0f57600080fd5b81516001600160e01b031981168114610c8357600080fd5b6001600160a01b038681168252851660208201526040810184905260806060820181905281018290526000828460a0840137600060a0848401015260a0601f19601f85011683010190509695505050505050565b6000808454611c8981611a5e565b60018281168015611ca15760018114611cb657611ce5565b60ff1984168752821515830287019450611ce5565b8860005260208060002060005b85811015611cdc5781548a820152908401908201611cc3565b50505082870194505b505050508351611cf9818360208801611708565b01949350505050565b727b226e616d65223a202250616e646f7261202360681b81528151600090611d31816013850160208701611708565b9190910160130192915050565b60008251611d50818460208701611708565b7f222c226465736372697074696f6e223a224120636f6c6c656374696f6e206f669201918252507f2031302c303030205265706c6963616e747320656e61626c656420627920455260208201527f433430342c20616e206578706572696d656e74616c20746f6b656e207374616e60408201527f646172642e222c2265787465726e616c5f75726c223a2268747470733a2f2f7060608201527f616e646f72612e6275696c64222c22696d616765223a220000000000000000006080820152609701919050565b60008351611e29818460208801611708565b835190830190611cf9818360208801611708565b7f222c2261747472696275746573223a5b7b2274726169745f74797065223a224381526e37b637b91116113b30b63ab2911d1160891b602082015260008251611e8d81602f850160208701611708565b91909101602f0192915050565b7f646174613a6170706c69636174696f6e2f6a736f6e3b757466382c0000000000815260008251611ed281601b850160208701611708565b91909101601b0192915050565b600181815b80851115611f1a578160001904821115611f0057611f00611ba8565b80851615611f0d57918102915b93841c9390800290611ee4565b509250929050565b600082611f3157506001610635565b81611f3e57506000610635565b8160018114611f545760028114611f5e57611f7a565b6001915050610635565b60ff841115611f6f57611f6f611ba8565b50506001821b610635565b5060208310610133831016604e8410600b8410161715611f9d575081810a610635565b611fa78383611edf565b8060001904821115611fbb57611fbb611ba8565b029392505050565b6000610c8360ff841683611f22565b600082611fef57634e487b7160e01b600052601260045260246000fd5b50049056fea2646970667358221220d5cf2af665a707cf5fa8553c93e5404fb287bcd41313f3344f32d0bb6a72666664736f6c6343000817003303afb8816c4cbb69d1df7f5059c8646f739bdc1817a86586f82cd4543b55a07783038e056198349b75c9b9a2851928c6671134b3a28d9040008ec86b3da5d838499103aace8e5f96254bf5453630ba6fa4cdc8cde7f86bc2bddb01e42a1b888475ed5d0347c71c1f338cc73e8a463c9f0f2dc851fb8a3d29c9bd6d1529d4c2377c42de2f031f0fb94baff76e5ee7f7d75f27c6e2b6b5c8340d5547ae398d2ed700062d760b03eac10e4e75850fb781f4514a8aaad8f96b1b71d095d550697fcfc9586d75251803c619492583c6a3c872c33839d6f72ac0cc3e2865677e0ea0cb60e65ec9506771039affd3ea0720f53c544f1334f05faf65376d554554281bcca35f5c2871eedc8503cf9022b6c201b40807867336a288444e32df93bc2fddbb202e473e1f2351d44803e2dea544c7acf3c3d68e84ea42b2033de4dce351782729d2b1209df4f5f4ca7b03e002d88c0ff6a423bde25825060a68c1d33224da5da9127b494d663924574e6703d9630c91655bddf13e22545a7e58f1567777df8f64b4de6dbf6fd49b3f6be859038fc5ab4fbec404548cf127d320bbcfaa800874dcab588464dcce52fc8747ff890330d12872a989a652a34dd908b114ef593c53c19ffe34e22a6ffd7504df33d1a2033924eb6d6559eff37eccbcfd94782fe708b7e9b7ea1d4acd6219983c7420683b03fa494b54c69888d3f047882e041269bc44bec96b7c76f2b966858e0e16a13e760339edca7c8391b51d5aff3c834244cad508bddf6e60ce0b3b39c61c544abfd56c03cff8c0c53988ef18c8b769c73491561c34c77d596f18efc3ea65058a65278cce030c41d2f047a84162cd85fd26831453eced5545b17de49416bb5470e8fcc10e5e00581f02e3701882b4f087cfdc78abee4623bb59f835d466dcdb4ff432a8f5d5abc2410c03be77150d048b20b98659ed12f85c28a16f3efcf4a2a45216d3fd1d36f5a5a97700581f02775f66ffeb1f2e550ff9b71f8aa7ca573c9cae553005a0500111bc36fa5c54c43b6ffbb4781c354d7ea7198641ec4cdc4d8f5c00581f027a335fefd9be47e8bc2cb6453050aebe23d537b1768dfdf2010a85abd0af5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581f024b576da2277d0f3ef4f31a5e552b21b15014dd98f1dc1af9978fcc4523a648040cbd672250e32d00581f0269bdb3883436bde2846536fbc18fee58d5e21f361a5bfbb28be23834d0d042d10700581f028c6c4d6a2392b5fc8a9938ff0ae2d0f50bd92eca6fa0973e1bdb71eb52f4448a285ef2021930ee03edc7886ccbaadd1de4656b5e27f47268d7ea0b763b1f29ab2643af30499cfdb803f313ee58c42be5fbbeea7beff6bcf262bfb57f190a5507a7513971a343df8f6d0219ffff03df405c9d0d4d3e4fb368796734f23d0496cf38d5ab43bd167de49f437e7078cb035dd55be8715f265612ff3b9aac3207ac0d678528b9e9208eb2df59c8754bc846038b721f87a03cf95a23556c9a2f34025889bb152febebff81273c4a03e418e2fd032b5cc81ec353118fd2abed61ebe006bd0984835c5fd5e4716c8f598c0bd9b4a0033774d2d5872667a66c0e7231d14c7663efdb043f0463244b722003a483b7079603504f211b98470aa50f212213cd973251f0062e64b81b72ce07938279234ba0df03c25d438036663fc363ff53168ad90c44e119ec3ac35fa82a8c396a02fe52bc1303b5f499f58394fe98dcb7fba234c586be11e00084e82664d5ad5675df80c137fa00581f02b0656bc779b3f8000ade5ed2448f370c7a38beda3a9b7876f46cf2d4821754d371519e9789a5bbd84faf18928c80ab243f48c800581f020f83fba0715c7d6d1a6a69df54c8208d059a1c6c5c9fa73ceaff43ac83ed470c26dcc0adf96900581f0207d10a5ff625be797ed60a1524539ef9a27a10f079fe99fdcd3a8f16cdd0411500581f02177e67927941018ff62e60d1fe7d904bc57f4ea49d8304ac3dd63d698d4f54c189aed75a492fc5008e5b007d44b95803382c080219408a03ab2b0580bed43ab8c5465595fda58fb6051853b6f35915afc35307779010c0e1032ebfb925a833fbe9c7546b571c12048455da69c97c57109873443544ee71ab2603f8732bde2803092bcf70124f1fb7d589d3fb94f684f3b20dc9a64b5d21dfb44303087a83d2071bcb4f34d8cd038d6fc539d6b19578bc67289d0b55f0b90201ec35031a2e84f6251ccedae62fb19c2213a1cbfd71b44ff48752d5ee4a7b9761a1ac14030ecbea35a112be72647c6a983894167135d5e64f6a2434549c7d51c41aab8c1703a5a98598ee7877f4a8c462a6e7fcc3cbdd6901b93d9245f25c7e241978409a4a03dd7c568538321815198054b1ebfedbc635588efffb46c6b524a6d412f6c4630f032a3eca7218b391e2d38a67b3091855de846dec0de77086e0b93b4fd0f69668a403921e9848736d339a50ab2ed7d1f2cf1c5dd04f88b002e6ad4b42f3e31daa591f0219ffff0337273a33d5c14715eb8a36b57d93e6b435370d781d8ceae99a0fa41a85944e9a03ca0fd50d59aae1608d8867e01aa7c439f9bf99e7c4b0e7b9c269d4049da6ef9403ba5dd91f613c4882767a640da0752fe850b73b1db8ff62df46f4f8f172d8712b0337133aeaefea3eab255123fb16d3b60f84bb4b9d63eda43be2e1baa430e4ef96036358cea0b0b4693acd10a63674724e5c6257acb7fbe2e8143b5f595e72f70038032bba71886fc0281b091a81e8fa5ff55fc2cbb30c1fbda322bd956bf57f6bb03803f61425b6f4c6413945c685005cc015933b587b38b15feba29c052a88cc7ba1c80219ffff03a2bbb58e792a148022b2c2a0f18e455f1c5ce5b493c75cdb64943643ab97e63403c28957c904d5661cb39916050852b167a2497ba063b485afadbe4e41834760f40333b15d5664ee15356b4d2e32e4cdcf084f99a0a01eb48c3df97efab1220679d103d86fccda022cbf5ae9f722f6c3b9205b8474edd5adcf47a9428b518e325b98d600581f02f37f5113ad1b3ab93b023a19619372c3490a37dddb2b89865ff3b455c314410a00581f039c2658206e3d8ca06c209a6564533a873126c1cc9bc7f3b662a5ca455b60410d00581f03d0f839c327f30fdf0399abd58ea86e01198bcfc3df68c5312c519be877504901a17653070c4e80bb02186000581f0251dc258019cc2c05182012d6231b2b75c0208f04a33aaf6a63c27f2898a9410100581f02921ab63b159e1a70553147d65418610bd3560b6574598f3a967db4fdd62d470e8bc6eda9266d03166ccf211f5efc2b2be57fe8711de296c34285e44b543c280e948b8fe32efe1403e2cab55e7b151d5b94881f5e41dc3f1a2bc11c19db4922c78870a3f7869ac020021988d403b54b05f1d647d21596443934d716a110ebabc1e80f34379f7087e303f6a738dc035b298fb99310ffc24c47f85596f098bd71f75345dd2046010f080bffe26ccd9a0358c5ee1386dc31f6e94476cc0495cf523bff55771a3864388ad21440bb6f390203b0b5c2b2cf157ff56e9e48ed55566a8084c0aa7d2eeb9cfd7f62273af69a1f800300d5008aa75cb1ded85ed0aaf81a90c7237e181bad4db2e6b1d55a1eb8c35d9503693853d5d822664bb142706a4189ec827e044dacd028fae381f62de11d65f32203a65ef28b70438d460341b4ac5a512ece33baebe38c12abba58a52e0b77cb1c92038199399b5ff356d5c5c592c6cd3719420feeea79b59b46cd98eea7e00bfcd3be038c4815b10a894b8f75d913b83d37634a4f8b35f2539ba74e7eb6fb7f4a5a8d9d03ba1bc4d39d018c0bd10c0781dd961583159dc7ef84276ee6fd9cdc521cf90ce403859efdbf4caabc71b69890ee7052b674f646948bbaa91f2791a65cb32ab4c10203625b6ece50986abfa44457400d0c3d7961ed4df500cbe6e55c696170877eb1160219ffff03f7cf60b6233c218f2a71e3128b46f193ab0c1248466620cdae1975e91bb3357c03749cb58f13d374bde7ebc968bf1833bbd8c8fe92adaae6e3ce483f98ebe88439034da425150dcd2daedff75e90efcaec611efdb1bd2e99f809b9a58d368a692b4203920dc456c3e2df9a69b6e7ad1747f1990209ea95dc33d6acf7b6495df02a110003cc2cc9d00280f4d5c622293ce345e9ce0618caa75f61090a0ecd1b229d544c5d03cfada93750f51480f42eeff247eb14d6edd636d7b0869feffab3204c0ec492d1030652ac12a14f7290cfec908d2e9a2b0152585de4344ba1834ab7afb5aaf8f1e403b64e9d6005ad5285568b2c0aaaac2905c6a6617b78c1264727229aae887db265030d4719ebdd6d7aa612c40dcea6128956cb6c99209f2d45b5f530bc63d1c9400a0344378319e138ff3c41e437ca7ce8e0c2f40de06520a5ed7a54e080d7fa1f3ea903f55bed5d6c1f9d57569d0df22cb690f06d535bea2b3c237939f9eae28a1b512403cd42291a3f33a31cb8e34610b20d5065ea09942aebce0ea1d88f54c003824a23035ac76fd77cf8c8f23685059bc043f17a45561970a19cc33daa11293466e7ff1a031d11d4407c2683175b43fca13ac06dcd9fa1fea0d2f969a03955283664df81f80219ffff035e16e3e7296c880fffc1f2622c990b861385a625e68b14817ff04579668956c1030c191f2643e5d978a18cc15222a5d4aee7f57a7a498fa3d5e44effaaa1fb6996037612dcb363277bd094c269fbe84b559439349df8eee1619e5abcfcc11419497703b63a46a9c4e7fc047c0cc06477e6942c8d3049631783eda370a7a804501c4d97033590addf33a8e8a48c62c9e9e85a21e5feca7c83a3be22fa634bb8447e262adb0398ca9159f0806d0179542e8f9878bf66429890ba5571847177d3f2a38463c6b003ec36cf3a6aeae24928e379330dd2314d4bac14b4b7b89076212f2bc960de49f803e720272800c54b61c5a4db96d50e7f2e4393e897e3fa15347607ecb24999ffb200581f0298b4fe25ae4167140e938c162723f8346c7409f06e01f6db9881c16e3210483782ff7e6a51c9d00327b99076e51f6bd3fb3a50400abb068bff0a5f5e8b781d366a16f7f6fcc59e6703c606286ed78f5710d9e437f5d02c550d40e8ae44505b5757ba28e639efc75c2800581f02107c1425e4281608d778ea80f76fd042ffd239fb98d740b4c434a53a9f0d5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581f020d1b05d48a98f7719f2a1addbd341f6ccd7433effec725e3c83f2b77326154fafc9af3c0353d95244a53a3fc80e22295bec8a000581f02baabd0d17a2a95c49acf45f875e8b514b37b430c4f2f34c77a6251169f9942bc940219e88e03763d3ee21edcd999ec0622b176aa22b7d37514c5de6fcc7fc3d9ca46ddaa8a08036a965344563bd267ce1f505145690fd6164ff3450cccfc540caeedbd96f1c8d503381f27420f35bfcbabe18d784dbff6148988d9b837286efb368ec64e9c73bcd10387e23fca56d828956dcf7a2b61ad5e5b999fad3b4aab629e816407a3406663250384628f5152345968d622e1c45ad57a379663a47ab4edc533cbc30d1953d1152c03a8f72e7b6b0b85e34490f60dc9d3273c05e12ad774c55cee3694b69d09657bec0335cc4f51155d151f1e46599c1a2dcefe20a0451a268ff2ee5feb18415a473f580343ea4beb5dfd0c53a6a9739dca846aa95887f5ef2bfab660393a07e6ff14641903171dbee9ac0326cc27672fcaec5b976a2c94ec75cb5c6525d44d975115686b59031bb7fa96e90340e546f2701dbc8afbfd3197e2c430f0898ba00d0505cd0cfcd203854367aff76feb0cdd67177fa7a0ad21abe8e76a1df3676ff0d3fdd3d93c131e0317d22336b9f7c3431c7756b7987d497fd5defc3f49964d46c31c96fd9d40281f0219ffff032201a8a1b837cd47617e7dc14b6cd7731f8e2950c6a4bc020eed72252d12b66003bc30c7c319f705fad71e6a466c2ae6ac1ac71e2d0b7808fbdd72f50bd90f534a0385fdcf19a9ca5b30f93392a2c395227e51b1a80359c2c37e2589f1a364db78880318da6a0d3fda19c042cbc76ce033f240bc958db2e6643a18196bd67b77400e2803ca3dd179c84e108d867aecd7f28c61dedb4ab89b86f86dc6a680ddc26ab3bfdc037da3e3d74225ee44bcf9524c3879dcbfd1df63bbc7d6e7c8fb4ad18bf351ae77033a59a1d9b97c354803f3d52346033e570ae2853d0105c6bbad1829974267dff303c8304fd76d42d11501f9b8162149b22912ab4fbb7dbb65bfa3f4d39d18abfbf5034d3a39ef08e7b08d5b1529a2b5a60859b5eb0f1ba233ab0a8a168e44851e1e30005820039d6fe0e65970436430e9e1711f3c282c0a432c08b96520a64dfad2541c4b2042a1f203e093d73123d7f060681dd31ae522d32e5e42a0dd4165e59f9a9714e8cd905a820348a75626080bada971a2760e7e8871d6294ea86dee8578fb4e4f37d2b784d03903e85df9a0fac7374bd4f831f334f656d295544a60574cb9100ae4a070ecdbd3ab03f4c974465e1633fcec498a147d5d04f9dd40120faa3e5f298f2e705ba69629aa00581f024739f602fcf4fa97012237dfbf37a659119753af3fd9492a9b68bca5d75c480de0b6b3a764000000581f0274b3df945fb114e8da79163ab65c1a451d9f255e73dc52aec1fdb351ce135820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581f0202980399af25b0455eeba2ce1930f06f1ac19899b9a20e113d18f89e78fe410100581f02d879f1b973c2290ffa2c3f4389f275b409945b0d185e1286b3ce88fd4b6e46450a3af1eaf103bfce1249212996a2567b5d336b332e2ce576e4d03487d451ecf6e243a6b2a40200581f02b81d9d4676d5133fbf925b1d4fcc3fffac9bc1512f6d0c29e52cf20146135820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff02192545033e31cffec09dea24100b0f6ea9153289950c71e60d84894d06f6ee7fdc0649a403d5aa0af1d854925923a8181b0d75b504062aedc798c5ffac862fc11e61da0fbd0219ffff0308a95a7ef9c5259a793d23b36e335fe12b53d2b9cd3ec6463953b219726f814f033f80ac88ff26ddef6f0bebce5bd99a549f5bfc6c3dd696f41fd583da523b87430330259520d452087f2ee83166861dc63dc8116c6e15d0e3854cf04bc5dd700dac03ddb4cdbe0b687f874bfb646ec59dec7b26826859355bbced9e35785c9ed97eb700581f034a5de51f88d18f1af48faeda345cbdcb867b58a1ea361f548e70c7c043e04804a3a5ddbfd930dd00581f03e8d68f06979840048e1ea795f7fbe6c3bcbbb7147903dd9a3cb14f2aaf9041010219140000581f026b8f5bfa53fc53eb9188b91438b559361aa21a00d701d692285bab50369442711500581f02b0be825183b1319221a112f63c1029b1dda6ea65cda5de5d6956156eef434103034f2ae74c4e0689fd62d3a920e6e16d3644180616def7cf1757b65b78a00954d10304cc6cb8bdc14233a5987233bc8bb5dd4f2697fd7771072852cb3c0cfb08931400581f0214e59ad87c8048f7684ee76956306564d31b1f4dbb74f98cea8ab3a62dfc54384d0eeeab0b84641e450a4307f6f2de1c027fd00219aa490329ce0b10f31a2ba08d19483215342efe6d338b1ffe8373cf83de1f7931ef06c10369c08547f5884b17d445aedceba0fd85aef66d53f9dcaa34a1c489daf41d8c25037decc8bc8a507371e99df65e28037bc4eef88fa5d461337f43b224b3f8448787034026c6b08d082cc8c1895e74e66a7434773f238ff728131aae7a92d6ceb7b233034e692c473c53f5cf97ade5b1185b1961e7cb28df4a5284be8313e998f8b79baa036e8e4e559ceec00174a1e7dbe072e07d5a3c2ae48e80264b9182f94cc70a19ff03d5f13dfb1d36b00a82cfa7ee0ee2ba1e59e8ea02df31570f5e65891a966ea32c032e05dce72f3948598794e6f447965fcaac61b8c970fc0e8c2af0d170e119adde03ad31f51ce8a764dadd06933bf4e430ab46390621910b60d73b7538ad5acb16e003ede077a6abd29a3724dbd89c2141fd569e5aa17e9b450e3fd1f118798341502d035fdb8cdc1ddf9a277f74f1599f1d5a371f5dc9a3f97626fd161bfc6205f3006a032156617c7afe66029407fb60dcf6d0ec17d2129f6d4851b530f078d390f3357d03c9f166e694f5fd5a6b43392c3eb0060679e9675d16dad25ae254cb1c247033830219ffff03702e8bdc57fa309e34113307c35f18bdf1289dddc4bad770c3ccdbf2194e6fa503e6511465f0e736f839540177875a9cf9515fc419fbc56e4ed92de7518ea2c123037c810178f0cd382f0a76ac216cb3984f06744a9e2d74e1cf23b237ae15f9b887037aa8798f68e4c79e00daa625354cc7f3b5bd9132026f4b2b9ac86eeac4072a9d03965974ebec198d64e6bcad0304fb4f3be663213a6da1ac4469e3461b028645de03b670690b88c714461eab59209c43534525ffe6937ce7684d6ef2a74b4853d90403b8cf59fc88b34cf567751270cd7fa8478aa11a47e8923f03ceae293c87e4081e0368b12b03d54e60878db7959349875dfe93abd5784c31f0ef6da3db8e1c97caf003c12ae862cd4a7a6de7fe0786ebdd6825bd6f22765ae784b8e21063d51650e041031174f47970d79a136996fd196b858a3dbc6e9239fc62c3c600e1aac503eeb3930349df3c530498377c6bea965e07a07b146a3c2915ae788bc8b5d4eca3923b849c03836ac5af568d84f963ff409098f66788b5762b3de9ace549c76fb41ae97a196400581f03712bc8ff09eeaa3fc7e8f720b51e7eb50ca34e7079a470e22169ec338d705820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581f0382571082fe6d4fccb3aed4aad7f1c33fda122f1a3db84e1a08c4de6b8e30410400581f032cb7392b06aa80dc060f242a1f6af24d5a5a85dc9c33b99fd974b8017f00411e0219088200581f023a9e9e2d8eb3802fa33c2310af99ac4bc8e5ff708b2e34eef9e0fa4827fb46583afecd5c7700581f022907afac08b8c4ef5c9ecf42041a175c666f601f6db263c3f934b7c5dd93540b5eafba28226aa16b356ce3722a3dd3eadb1f8b00581f02a8ceff34fdcfdbcfbe09fc424cc927db17f73a36b56ad5d0a9b940556d664806f05b59d3b2000000581f0214f9b9cbc4695d32142af07f29f8c626aa2bc00045642174e4dfbe53ca5348023d9f7c59829a0d0219a82403ed6993ba5dd3afb82cac51e135f17332670d2c7884fb89d3938d8ab5aed0869403f85fedca8aeb93ada57f53295cbc0d816f88b06efaab78c761a5c0a3b7354a5803af469372f98350ad34c58ec0aacc8520addca49b0e9089ead503cffc978afe7b035c27fcc3ad9e9a476458397181ebf64619e60e0954ba02e93695e0c2a4bef44a0373cb54787e9349dc9ec388dc69222ad29a0b16890d917dd8ac8f116d38c4b30e0219ffff030f2755207cae01bc1ccb7de999d2da83e6686d92330d39986589d1c033d5f630034a379bdbb3a5adda4c4301e33a23cdfbdc3fb1f2724da3ba6220ab7b89cad4e1036bd0fbaa362c8438beb8c26147815da7d3e3b3b4b7500160a81ff1f99a9abc7d03f5e41438beacb0ce4ec1254eeba247af6b039b923b35dd71e0361882a54e0f7f033ac63c5eb42c6cc8ac40db3272219a4268c7a15eecce43c555bba6cd270ea42f0219ffff03b2f7371f3a0c844b51cb9508fc4ca188e2f0aea6af3112ff7d4a75f6b3365a7503a0b384a67e1181db331e4365ddee6bfcdf0e3a17bbb833e3c7840c709e97ceca03708349fad6e13d97fe2fec4c4f856cc3e107217b79f0a0c97d56d3f2d9178870032384a39db6f9a4b4e2ab79a1e1091fd4fd5c6402604b728c5d947cf5a226c631037f86cae18c5e155d4527fab70de1c1c1c098ad167cbc7ee6d21aab65ff216f2e0328e5a1188ea21207f65b32fa56f36c29f4e3ee5805cf49b948bbd9d06a42f9d8039d252f7bd41a23ee9ef2cf37ad78a20a8288bfeefc2922910c7be67a8dfa53080347fa16a85bc97f9b0d43e502e6f3e3ec13cda5d8843d16f442c482b99ff20d0903021e9fa592e3fdee2bd9845e46d27233ed1c38a16f302fbd4d29928d2ce7f38603ee2279880f7bd5b0a2e82931483fd7694cc454d4ed4818af7ab84356a96b27f3032f4f64680413c87f0c55ca50f9d872571434e32315b394fdece15afb034efdc50312e45334e0fbef194e8c6243b9a64c2c45ec6e26cc21e891cecf574d365bbd03030cb0c92f6ab9cb1334561892bf2dc00a3f769b76102357d28846ca03de8ec5140379695d004af012a3cd39b684fb5b05d3319ec94987fcf283705ec39a0fa02ed703f3b7bcfbd6171a62714e0b7d2751f54de61ee934394279266ba6e2bf5663bc0803183b7a187248818b5f51752ee3baec6b2861baaf52cb458ba47cea5b118c207200581f02f0f49c7293ad3beb83ec054deff24d135b9f305e87d891a0994920ab476a475cb4fa8418a10800581f0283b1befd6746797ebfd927a4eb9162e3af8226113c4dd3eee0caef172b5048014ba3c1f77640d8033ccf9bacd6221f571922150616b3da21fc8ba2070a4de5dc2ef3b9c66390a6d700581f03df1121e732477768a8935edf810bd85f583e05e5e17c6f709523b6e83b50471656943982828800581f03231fe4ea1910c0588f24b6d5273f6fc3c355852c198542bb9322a859304054dc900845732a53ee8df737efa282a6bc56976e6202189000581f02b7ebde36bd70504bec3ce1b84a96558c575fbb1aa096eabdb7bfa2de8434410e00581f028983462ec0e2f1476bb6ef3fe34bac93792310b2970ed70841a610ed283254bdfa4f4492dd7b7cf211209c4791af8d52bf5c500219c426038462a8a555a73182b7eb069d5f241dbc83fd5e2f4f5680ff41c5f45c8aa83708038c6613f8401ea0906d72d6ac9577810d26606d1a20a0d833980eee8f01c3172e03be663469e275d01c1bf217558116ef6134ed2c77b6211e48a05ba49092c12411034e7aaf6ae38920d1bc45df210fe28fc2bf5a7062ae1c74bf131484d86da75f9c036c11eeb819a0d3afccefa5b129b0b2f2bea688eec4316db85ac3da15c8910dc30318ff4560ea62955872a20d9adadd0802ec50cb408e04c8a71f35321d07a9faf503212bdf17083d431325990fbef3139c20a5bb658d10c9da1b63144ae64843a4d60363748a6b18cab29a7ca14c8c866066be0465cfe69272c5ab9f78d469b5155b4403d72b9c6009661b893e632f390daab3bd6535e4e453c469db0dda2d25f9c5c36003607a8bcd48bb2c3ad1da0be18778b447929790c4d7a661a57ff41143e751b74c0219ffff0371ed0eb820a8eaed34df18678e926543b795641a0b0289cd2a63973869d19fb503925d3aeee1ad01fa51bdd9de71d7651e2d133e3c11d815ad5562ae2ec0e0743903cf219322b17b869fab7333ae4dd02106f34101f191945be032e2447049d96b7f032aee989a67858c8aee497440018d63f81f3d2250d88e97465f039c81941f3c8e0219ffff03118fbf223e930225d098284d0639b03f64532a4105ead55ee5b9eb018fa723400377f0e30e2db7c44d70222026b2ec2f0aab18b5b4214c115baa6566029fb2d69e03a580ed20255da47be0bd309a2c93f9e706525bd881d7db50bd21f84aac1b12f103bd87c4e955b9192ba4ba22b82f918eb5815dd14b40711721650121ba51b4838303c4940d7860a939154dc2158cc33a37638445121c8201409f19aa24c7d323028c03b9fb1bcedcab1c6aedbc59a1300c272d271c1a631a39a4c97a68f7bebcda7e7a03966a0bb8adf026a8e8a8fb916fb2ec6e6f35ba43bfa3618aca42ac6f51bb0d5a00581f02f155d219fd4cb07bc1e937732ec8c6c6387f92daaf2ae62c6454838be2d44270260331d6e3bd78022fe8cd35c963ed1b7ea352b700de7d4032e7c591a99b75a84d51036e974adeb05a074e0e160dbac9ab64a155467e787c18c09520000c92831def5e00581f02250d8bde6e707cec3ca8e26fd2ef279ff6d29189ce951898fe724e4bc096546cfdc3643b69b3d8f5c2356be18bdf0d06f8d9540324d7cf05e62f4829531e7a0640fa43599150d3f8985ae3094367fba6f783084d00581f02833afe7fac8f77d2f4d64b177469b60a4c6dc40b5383ffaa1ca5590d2d4054d5c93950d64250c13642833d15627297124f7ec500581f02fa57ae41656b137ec7b116f1bf98790dcbb4dc114f89393931d69a12ffd5410b00581f0266c776a299387fb2fbaf85f852cc1561d90c6ce9f51b8d71517e9dac41755820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581f02f2752e407ca34f0f47b93026dbf6d90e4e5fe696ef996b921263f280a7e242bf4b00581f028c794db429a75801e58c70650ae08564008d5781be9d48c1d5f24f751f2c420db0021987fc03b88034eb3a4981202e3f0eceef1e6e506b6d48e83117cb8a49ac2cbe6d98125a03cc2a64477822f749da4393ca00ffd6b153b9d68fff411f57bd55354b8624d16303b9d42fa495edb34f56bfc3d348bb9d5b92f13bf1246e70c37c6c6b910b2b47a403fd8aa42599a120709424f668af5e40f6c5e51bd3e705df7962de3b1602c8020503b1c3e59e0ac0e24c2a25fe380502d751dff61500b393d9c056832bfbd63d93db039b95190d075f29355d2fcdc4f6daca871147dd231044e384f564fb2773911eaa033940272b88aa71cc8d76097464f067731a49cbede952bae3e93b04adf868baf003a2af8c9498e016803f4aeda8f748349f9cba8d68912e72763e8052c1021a662903aa635db77c7890c8ac22df7f39ed8599b51b0875e42816da1bee718a1e6af15f03a57d2fc08c3b61c18209462952437bc9474e3091fe18ca84dcb09f08f23c378c034f3edcc6e9b51a2cdfb09a678adf57205505ecf31b6972ba431c66f9ff59b8b70219ffff03bed2cccab6fde693c7c52ec39583be2b59273651df0d81910d512e633c6709260378fe79d7bb1e6fae2ea5c88c9c9bee617de54e2094abda4fc97904ac24d4c32503f53a1323c1703f901f5dfc9bf295206380dfb0140795863220169202ed27923e03e0d4853689f83b358626a612cf1632f9c8445777f1b52118862d132b25acaf6b03ee3c77cba39c299ae4e3bd3b84a4112bc554d035fc7cd0b1edab9667b8779500033b8c2145abda546a1c1ec6def3123ec1d4cccbb0fdaea5de380d3c7af6da263103e45e4e7067e4f6e2926ecd61a3c1460434f719c4051f6f4d7da30d21063d801903892e9555e27457d13310e1e7dc555a1104bda77aeb69da787483ad350e1672a203fc7f37fd3a6a52c6f4a87a3a4057a25f497fa6b7a36bc7c43ece469444ef35d20323188c32f892c0ab4fa5d453a120673387c3bcb8e18307937f96d85e09f4d34303b135344e97a0d4990d24a1d797fc5bd00747c54a5be9aebd321c572a34e7352303ea8aefd73ea2a5a58064fd7d900211e7739849fc20c8227ddb55eb85750cd11b03fb5a9bccbab77beb60ed8e8f0c980e0c5de869f51a14653b4f522e355b3cb8300219ffff034674dadc26eea308d87e34a054a96a8b5c937ed0a0231868452944be38e31231035ee2992cdc0270d1624feedd991c56603564953cc9ebbe09cee1305965b8c26803759e6b50b6949f059baa8c1ec201e861afa7df745edfb45515a70174cef3ad9703776a099bb8a998d883f44c801e422a8eb7fea0d25db49e5f4b55cc67d7896edd00581f02a16f8f5c4ce8a032c38ea410c5076bbc2b242616286df18fb3ab7fd65983413c00581f02f40586af5db5b2825b9814aa3fe912998ee950b34866cd45e3110289aebb54a23f54e0bb57a6114d831c080823f5fe2616cf9800581f027ec23f1723b114ffa5bfbb9a749f0153ce0e174a71205ffd05b96931b9d95820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0333d6caa8deea9b2f0ee819cf89711d6ebf65d3c1fd203f57b795f77a48813deb00581f021b58347d47308a81942270d339c37f0330664428efa82f3f4a26a1765e155820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581f029185c71de7e29b62d459e535e9bad37f1bac28ccf2c56e42f975833ed1d6540cc90076d388e260d550b720735d4efc41f98ffe00581f0259239bccf3ee8d122f783fa093c2bad708bea4206321ba11b8a0803f5c4742bd260219e2b203ceb6636ec5c1f8cd99e3df0127f4c8e35fbb060014e3371381ae55bf66ff84d303b3bc396d22cc8a4d156baa52f8822071747cf835db6a95cba92ee5e99ba281690357ad3f6b85aa41062de06393704f8d92b171433c8dda8ec458b23ec46757cada035891fb9799abf86b3cb68b5c38b3254f64d6c9447576f0e933ec4f2b43c9763403cbeb7de3b4df13feb43a216d3d2fc4d870ae96cc626837fe069dbd609d1ad09c03a24824b63af9fdaa3f1c09a664517efa1869855a2953ee5f78c97773917d640203741c6b42856b2bec55b98178ebbf8dfc89977011c442bfc133bb38261ea40beb034cc2c9a7c21448db287fa3ad1afab6885aa3b94bb1b1ab2ce8846e4052a32c3503d07142784f9bee8140a0dc68c99279f9ff791897d1c9132790ae8bd77faeae1203b80aed7c2eae8c75066749a1967b7dd9dac4b802d40d418ccac17bfc35e459ef0306fc68f6979849b8b4b203c2a3af9f6adf1bfa59d71c827a0de1b586cd3d7f81036d519e444b58ed383869fe4dfd26bf6401cd1c2b1cbe029f252fe69d4091067303655254b797175a53e2d47b2bba9773632a050782f43e8cac078ecfcd7fc8894c03811be60fe1a1e86861daa04d48d2f54f107378b0131a20aeb56ddb9c121b2fe8036719741df4f4c1a6db0cde726f01f3273762f65a48ba76d9a0d73d0efe0f87770219ffff03258a99d5b3a4969511367a6c6462e70731e200ccb568a08556bec0e4f361918e037bf5baf7a8b43470cc135827d24d5d73a595dd25fc49af90e91ad1ef60a11f0a03d1a9bc8a4ae584f539c048ef0d3a0bf216ac4dcb0270f3d73b415fcaa085e55303f398125f443d785b00d7d7c57b5ca010c0cc575739e873da70984ec3ddf3da9f00581f026b46ca68208dea7df94f6ff42b6a7917a25ee5b82f5a26a453ca5673bdb842887f03ca083267174b62154d47ae5e97eca25d9b1e77f2613557f81c35566d4f58816300581f020b157d7e836d7a96c737dd8071330dda6e168890bed092b2d7a0252d3a235820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581f0227c2ef0e589c72ee1e12cd8ccdb7c91c00d9f81ef9b0cd4606ebf1759e4c494014373fb51a80656100581f0291d5c6733fd326f10f7f0b5b89faddfe45942da8ca98f377d15f7c64d867411c00581f02993e9d715ee1ecc20fd8c6431f4c99ea7231375c30e604af4f0a8bbe0caf42c259035732d56266758cd0e03c22b40a5ebc1ed515f2494bb7f820df2cbd519ee69a9900581f02423e7048f85d775158323249c547213f109039906cd0221a12af83c0a156472c397a286eccbf0219a1d6031c19337622be53fa38765119d33ff225b1b733f17cc20434d88c190dab7ae0ab03db8d67efe33c1bcc95c82bbff11436ec70eeff1e994ffb3f864e8204d2002f750341eac02e036c4e9821420213f2c7c3662eaf13d82552910f6d311a2a31e6dfa103afed3e087ffc5300574b306340dfe64306430703c0d826cc02f992815fb6c9b9032e2c474dffb52ed27c9a74a9100190e84e27c6b10ff33943cc2ef068f1c85c86039030f9108ff8f453540f08e772ac13c91cf76e8d5a9764de8edf28e221e31c8e034deac2c1045ae741076904dda60bb28a99544570b0088a4908769dc7cfff40fc0396b73322795e557990ca8aeec20fad09563d32c7b67bf7bd36ffc4ec5cd8545003ea32b94557992f421d1c6300034b3654c1dbf82cff6a55d55d031e30042b2bba0384da90aed64d3bc5e1708e19e96f13a5d56a4a911f4713ed774478a25db2f0810329270802594a7ac5ec2786f9a2bcd92e3e51dbaddcad1366492cab4b5895e805033b73968f61a1311446a837e4ae5b748329f1ac4c8675f033eb477a3dca088ccf0219ffff03293d3e2ae4a6aaaa5b9c92f9752d1b61626b88c9a8317b70b9461d66e2343fbf03845e1dfec502ee559e9adf8ff5406afc0dc209a7e10b73bc30ac58b84db3af7a039bdcb89b905c1c65d95d544b3d7c72bbf858139ee34752188c7d34357d2f599b031192712c840994c787a1b5a9d75462ee474bc17e9dfce9c49b27b6897fc174ac03a16b8641219b805296aa54e36afed4ca500da6a50442e30d06e18323bb78fa1e036a837aaa66683bb558aba7503b8da10eb4e9de050617f0b940a2ba5821deeb2603f5a5d3c38cb9f184cf7c6a66b6485b4c9d15ea880357b546ff0a43727a30c1d70316f8c10596844901e3033880944d6d1190bc4cde580b04b525da4923b8fb7e9703b41b42f0d50ffa64b710690fe5b58ef200c27a964d2023d84391758120f8e7d603f967e4a50a60035b903cf3e622f3e118988f51ef6d13bd3703c0983583196c2c0219ffff0302933a59585e23139054f3fa361d1b11d9cbd5358ee5d7cbe4ae18533b96bc6303192216ef2ed65e830eb12bcd69618c1339d2d6be4e84cc2af666707a30e3a816033d344c19e758c634365c3a855cb7dbfe13ed9606272cbe05c1a9ac14c0842fe603fe7e4e26d10495c256e41e692afaecec308740ce734ebcbc1c50209827d0ee990305c997ac51e4c8aa54ab622690ce3260f2dde54d2c4f7c0659f14a4ccdcc61a90373c4c5d65460c9f51f7aa0781d1bee033b57b8a3e6f80fb4d7acd37c3044f65a038dcc7222b5ea0a5985462c2960dfbb7bdf4bd11735919348998f4a0397370cc9037c7f78116b96de11ab0c364167da129a45f326577657ceeff782b80b299ba12303b8aef0d4ce6625c3e246a0d6e8b07c9b96e8ee937eded0432039e480a71360870325513fcaa3b81c7e277e01d6076e62bda71015d1792955d25c071e6b5e1260240336f2f0ce2206191fc9bc8adfa0e2f7de71f19ce78407e27437294e4bbd53b82603aa5929dff076e6be0a6f69828ef680ca1b6710e584fb257dfd28cc6ad071edf803b88c409de7c3f8adec5ed90133707f2d939c50e67afb19790bd4ba92e52c85f403bb0929200904f4e637392570ac4d0b4c67defd85b09872d26bb92c462afc5d7100581f025de823c1dc39c190aa6e32946f4490ba05b0d0b35acf48e1ce7ba07e7417480df2fce32d73f59e00581f02a12bf686085fce9fd1e11630b3c0bf8f18a1c4cbaddfe9530b4332b7aab4410800581f0226661afe80cc2626c872a6a88c84ea363fbed12bcb63472ddcc85c3424fb410100581f02726a48a6efd020c3f88195182b90108871b76b7dce663e476db18dfae0a75820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581f02dbc3b79c8155a4a643fbae2da01400497eeb50354935eb64886b69ce6d4c4723ec720f2ccc1503e90453e222dbd33a1d2c4939faaa0a5062c9099b258528e3bc11a3e4e8cd790e00581f02ef0db8cca8c32d1eb6e6dc7ad3fb87d853bc4cf9588522ad9a4ae36541465820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581f02810645c871bef6c35f9063f3056846de45de144e8679d768a1cc958b559841030219493d03f20d40c3708deb8ec124f39a207097b97dc5ae8783aeb55e7408d37e85812b5403c4380410b26b2a972dfdcd7b1c5f362675f21052ed3e449e7d7dc079936b804003924169c0f9886d4c4e431c33de6e10ea2ff56ad58071460844d1191acea35d5903bbc1d0e7e902301b1a15f099aa5f4a8991d87d7c5d3724e5f1674747cc4c54b2039710b5fabbba71480375589c52e54e86f2321816761da9fb5e437b0bef107bb003d7a144b52ac453830f777cdcf9748d0b229d5b74b0c0ddd79cd93a83364c0b9b03db82373a20226adf484b6d3049b026f1ed09d83eee2ce7434fb264b011a5ea080336c81b7fa6ed54643656acf3f92c304fe829d77fe006eec9e6a1186374973eb70371d002d04f5d997e9454e8babd6261cb5f09e78c5afb213382eb133171d9e01103ea3625118091588cd6c335a1e3664eb95690f28ff806775bd40b48b5ed2534960309b9a4e69aace230a21b1559218500b79101c78a93f26e6c6032d27337060ec1038f81152e7198e13632ec746377c894f78b3fe3a3c1d75fca03c3c6c66b2d34bc03946ddf5e1eb2acc63c4dfa4cb7a5384449cf6edfcd182a3e9f33e9cf53992d0903d0fee8c7670195cab6fc1060704e9d67bc738775c70960f02d0d5abb6b8410b80219ffff03203d91faa71dde46441405bd1c75365cb6cb112124ff92ae7228f7348c12ad8903fb69c170b99fc9b86342ed9a41a9086cc7bf80d71d9fbcb6dbf67c3ad87179e80219ffff0378ef866aaec9e66e01462f7e7b9dbe97b5248e92e68e39f7d73ac95f3f2a40c10398df71dd6375cf87d05ff5aca21a2b6597c572e0a0c37d3998bf4a0266188613031b3bad7876659e37624cb24383e45a367a42c4893cfbc4ce216b7fcfd3a338a9030d56a78a6c3349ffacfd5f7d8aa74f5ffa89df3f2fa3e19a5723ba7e66adadb5031c3427f11b411ab87f0da47d4ab206d329594bfb6217cffe4f4baba36884c28203e4238e078e3543ae76eaca74224564bbff9af7d3137310a99731618253fc4dac0336dd77ea10955c26d955d385d77e659ba9e896b527090c05fe5ef6f5c5f0379f00581f02471114fcda013a1af0a227617f3ba051df0ff7cde2ebd627de47b676124f428bb100581f02920120bd3ce48da15c85f9fab211b438c13103328e1b64e37ccad45c01484611bca2bd186600581f0266c4d34b1773096e8b60c28a5a7dbe615891e65d40e75f00c83219c0f257410500581f029eb9cabd8f4fc844ee624d041c99d99ae5f36004c2b2560510875919a29a410e03164d17701af5e97dae976a7748bb092df32032a6f33d69f7ef2e7fdf1727868100581f02fb268d27179d99688439bd12006c2354599180462c5fde072e23fef652b4544ce96aacbf4a605084d757141be016de8431b28500581f029a5ad24c7c6ee443bdfa50b5411227414c9acb0e313e317bb256935d3114410d00581f027a4b78e1c32fcf7fdb76163e9eef041cd41e2ddc93982e5efefd47a6970c54e8cffe004c8f5bbed8d4c76ea1519c5105ba51780219df80034bf4559fa42bfd8ba9febe4ef75d0fdec851142c3362a63a639b43e47dfb5d7b03c85d9ecfad1b9f58a91963c946dfc09f1099c1b6ff7c6aced726ef2cbb4ecdd0030634fdd6b7a2339914809f1a1a754d98f2bd1f59ea4b09998ec833bd2f59ccb000582003cf08d88696f6f31f1b5e03a7d47c4cf729ba8bc02c1e9712d96d51f80e4300410103ac99d757812b828f00b90c3065ff53573efd3fc34b9e073b82814a39e6292e1503e75092987b118459d308fb6fa3f7f1a27c7589b521cdf36e7b301147987b1c4a03d59944f30ca2f83f6e68b1e854d8d627ed23c124f11735923c3b7822d50c5f00033c6e9390dd098955847afd5ded49b1c1cee08dbe39c19def81457b3bf198204e039826e46c358736a06cd2bc94fdeee393164953aaa24672f26512e6ae18f82328039b2086dc4ca1e1067b4ea374d80628255b054861f69fbef3142c1bdde875e4e603df2cd6b79510175a7ab97098a53f72c6b9e8e57706db343cb7706e556ece8d000219ffff03678a7664f1ca23c2a35dd1f2e4137e4360ad2ec680ae68da9242699d4336c86703317c64c5c79e227bf439d51af21aebe5b7a0335af76e507c8423db40e24e801703ce39254e8055b184d0e006709fefd0b9ee2e716ff25555f260dc808bf19b256003de13a8cc504dccb5fc7adbe8e2ae5a8aa03832723067e2564482e19b806195a503b4aa6ed6f789d494c6819b094381b1c14ce1f0640d8f6c2edd2dd52d07329450037eb0fe3e2551be914cfcd2b454a9d4cf9ff01a15e57dac03e532954d99307eaf0397b7104f468fe21a20e9238a9c9e050646964a605526fd043b250bd60b18c7de00581f028e2cce6bfffef641ec0f4ac40596c3d9cb95e62e545beb92282a908eceb1423f4503c09279fc2cde9c51f4dcaf1a8f02268073b2bd6a147545626ee18c515ef08fb500581f021b95d1b49bd9faae4c947aedfc52c070c6bc2e49ec3ae3a2ac252928ab6b5468808e7822a54c098fed2265b946013d1020ea6800581f033fb7423fab411702338ec9fedf41f262def894c097f81950fca96b7144f0411d00581f03852beaa7c37a973020369f24e35f45c4eeac0ca0624c94ca8a9eb7895ad0549f41a7e24fcd572079a08af2530ebe7d9bd5c7bb02186000581f021b41a4ae1325a8bac4184e73185bbd78e14c86ef7c3ad4524de1f549defb4501f6eed4eb00581f02f27939b9f512aa77d11bfeca6c9bd3ed457e81c238de9f704030fff23bcf5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581f02933aff1301c6ae9cf956f585271b0fc1617f32328c069ce83d439a2e5268410200581f02f5f121e21548f67e3e1209b18d2f8ab7fb535c578d3be7c022706a4e23b25820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff02194ea60398a309497d5946a149f5f533598132de8a2b7058a9c4f13fdf6e2f488d7bc6d400582003452bf5cf56d19600aebe0985b832f99211bb4c3ba606028ee84632f4126ef0410903f6c6f7afbe8c6056b4809bb5b11546bf039a2b5d7433bc75d0c37ab60520b2d0034d1db10df257dcb9eb1a4a47ed0d75c096bb07fe3a33c189c382dc76733c8f9603aa178b653ef12d90a22a91ad0d15d4826febe1a13d61e182d0251945131ca774035c3da35aebc70d15c460fba5c258b1c8abb97233baee07df984784ebc2ba5b9a03cc2eead7690fcc166a9fb7952924be642ef60309e24502ad4e0a692747a0ed3903688590ba272d3dbf78caafc270c59827a0869d85e31d3f6592d119511b0162140219ffff0371fde0406cb697c922ad82366c249f765c76ad124a817cd48597303c5345f28003fa0b15ba54540f2411156bd151bb9c7ef1cd03d5da76b7cc27158ac9d1bedf2d039e61bb4eb1c55261a5711a31d22b0041a902e0a255cda57c6d5d37c0724619ed03a8e52477b7188726d1808cab1c048d1fd913b7825a52eea04e53056a5b4224f0032e2f48dea5edde817d48684b1ec3d1158c5c2096c2941bbc690c4a96faa746f103bea1f317e45183ffa248a7536deeec904894ef3f8d7f71ba3644b59b53e63fec03656b29a7fc8270d4069eb49ccb1efb264a354d26b1be9af4aae6e0a24594d17303d3dfa42e9b77c4a21a5ccf606604b8f84a4ada1f0172369b7b4dcbfe2b6c85de03fb41ebd264546d5642e8164e2606590a08f7dcc6d84f29088159fea294b7f2ed038e64cc55b0615dc5ad2cc16881a27b2b5050cb08024d816a9d6a42d04c5baa0903e1e6a52cb9a0ac2de6126a5dd4f8b054708b040e30b26d596e1c28b6d0a261700219ffff030f88769df794d68aae3c9e65f8faf90c1a659dbdbeb0dee284061b81d7e10a42030f8a96252108c8c54250ee8b82d5fc2dd63979f65e0c58d1f913f0c87399061f03dd31d35a5d1a5f0ef46d741f73dd5c245d4a4b68ee5fb58dec029b001ff7451e033b445f4377a6f0cda78fc511575c3ee8d0e63607091c20dbbc9521d5ec5d214f03e2fcc5d568ee9e9e5ad2dc31fe4a6df850b0dd086e3b3f05bfa20644d160899b03892f1b22d56f1c8e47280d54bb339213c466bdafbc92076e5ad220140cc3319f03825924f86555e6d63249faf2b9eafed58ea0ab5c04c6fdc940315c596586b2b803c9e8eac7168764c8b70f3915d89a4bfc273b3d4906a1c97ab0b10a0bc61b2dfb03f3f976d291a82350fddd836bac387f77419e307cf9a6f070d7a02f9d425e5a3000581f023f414ceacf32fff17f5fcc778e6279a21c0649f58284d3da5a861f500613482b68ebc3f3ee8d4900581f0212f3b542812514136b611e5a1d7a376eef93210888e340a6d59e5361e153410203929dc327eb39d109b815a5d72455d750fca7e1e25218e12dc5ae3ea5b7076a4e00581f03a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b042d10700581f031ab664960f19d5b04d8bfb265f5d5198f0e55be951138b7c4aa848f378a0472386f26fc1000000581f0320533c2b96b1b0e92f3e0752c706f9db76c59f5964e80e5958483f019e40544554c4e5a971a25af0c29a162761d2a0cb855833021904a000581f023fc8e743015f63fe6838687e26900eef02eb75fe921b9ee91cdf3d54e6465820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581f02cb8c5ebf5244a0cd88e063d1a883ad7f80025be6be8b490cb2440c6a8d58415800581f0247abfabc9a2e441db9abc070516250e781d8ba5b23749f816ad010d5898b410100581f02298c44899077b2da6d440a94e801f6ec17c7eea5751286bbab864275f7fa42b4a302195ac30374b7ab0b8277e41e96e4e73e572c225d9530d4b2fd8744202f60e7d12d7e82b103e31ccab4d7b8b9cfb7d3bef282b1086c98c4aa9ad9f9aa10707a34935355cf20031215bae646c28eae3a87b45f08927b899b5908ebb8fdee3149be923f4d358e2903d6e681b38125841ddcc9ca52acf2834216ec6fcbb468f5d22743643ff94d3c550300f416e85bb57e13ddd571481dde5ff31bd6181ea0317320b6125d181e17516303c74df4fc939a9ac694bf450914222727fa79469bc6a29b4e76d76ad9a50df9d403052078053b751569426da5355284cec9047dd4c8f51c36f42ec441ec5fc84b1303546d146964a8fbf5598301981a5abf45fb45ff87156f32132f3ac3c90d4bb3f00347182d795dc93aa55bfb9ef642326a1a5a214158e61840fae79d3609cfbf50b003d978e6801113ff17b22363c9a11348724f0685fdf6aa59ff367e98149f6ebc0f0219ffff0335ba58953f823fbda866ab796fcd2e29213eea82f948b3955cec53e7bbe2ef0a03b7a1959c16b07dda158a173cfac837752f5a18a4c481602238760ac5d1680db703c297a0f953a0123a256f34ce8b2897d1abc9acf943ac9a141029ad2632b4fe2303bfc2983512d108dd897f3e10d3f0bb45c6c71005e3946af94372b0d27982406b03931fc5f5cd35d198e232fea9c050bb5f8175342a1a51ce5611d8672a67d3a8a0032b5334fd7c02a9a3e601de7ffd48f90ecaa7edb67423a5feb2a661c59bb33e9203eb2100091f55a28ad8a36fcecb8cb0ff2fba9a55d39c350522591923e92e56cf030ce552ba4994b1aa897b473634dac84a3b20717bfac31998b07ba1a8b1a6362800581f02aa6b03c9fdb1a9a0af933476dc80e79ddd92baf57dab20876398044a95a75820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581f026a380b953855c0a06274a60d270e6fd4ab664ccc146ee895f0aee8bcb7f9410100581f029596e8e46152251cddfa894b4b9a969cbfbed09d406a126d0808313090dc42cc53039251acd22588428edcef4679cb4530b08edcf6bf7efe34d6b9c1416f91149aef00581f021d2e572b06395b693c8c36a5a1f7e77a16cd77440d4971a44727436596f44289a700581f026b49f5dac4b6e6e3c9b373c9150ee50578361d0b9f39934c41bfd19597f34111021918ca03e207438db574939f4d2052d1e62e2065a39a281f7c1cabc96bd3bf283fbf79d50381c81df0355763f75a37a8259ffdc2991402216c4568ad5cf2fae32c5f14eb150326504c4300aa1d323a3730c7ddf6d329f2c24e72f83f8e1c4ebbe304e3bd0b270350f58606e3f0f3309d9eeefd22dfbc565809aecaa9d44beb70da2a7df9c0a959034f0b00a25ac2eeab2067919e03870f1b4e677efee5718f3c9ede896bf5e602c303117e28c4dc897753e5af0474835e781a4c9630091b6c34062027f58c1e3a94ce03c15de6cbff9531e383e8b556b4d0b41eedbc2ccfff45d53e865da03188582d0203b1cf1d77144a5f8a4631521ecbc063e2f2fcedc2529547205829e0bd32ab694f00582003a9f5f7871e0eac180e7dfaae3be3a79b447d9809aea2fb2edef79f1eff9d0054b34a45fb62ed69e0066d1b60406463255e1638ca036fe21c01f1b75a380014eb1acc8f453dc737feb18e15286cd6605716b3e922390320b2640ab6d3a746c539c345ab3cf8e48934d498f71be2c2b926294a0fb30731031a2c5e9326602f083b520a5d572e764477faae25f7eed58a44af37e78c5f99f60219ffff035b276b4b1dae3b641c40e00d282530094f89cc291c0ac642b27ab0e190db842b03a2b89fb05df43e24fd25cd6189b5746a20b3c4c25200f499b2c9eae1cfe39fd9031a5a31b4ba343683cc123cb32d09685bb359809f7c59fa267f48bbbdfa2a4923034fff688375db2633a68275992427586fac2c2b8aa0323289ef4d0fdb89476a2403c99c77bc0ef92cc0593e8f7306d086a1b5d0651e5579aadb02e25774918d67d9031e7f1012f881b2c98cfe351fe10cd58943757509490a0e9aa133683917404f6e035e0f7fdf77bfe733a3795766bc08ff71436c7d1bac7d30cc2d39105cd5aa6a7d03b8701fb9ee4c31a0c8d9bf92399701ad2823b901fdf12732672fa526847f2171032a19be148b7373bca4a8d7235b063de5d8ee9c1c8a62c5674a50db2f856f19e50058200328c9fd55ef3f12a282c0cfdcce633a22466ad809913c3c778f600d18968bf047113cd18bfbde4103a6f6d543766a6e9c71d037ce1c91092e85d5fbee4dbd73b38e69aa1f4fe91d020336e782939236d8eae5148113cf84d1275d21db445a03906390d5bceaf8aa45ba031cc0786aa26223ac1c94edc7725bcb495fb881a5040591922b0d3e3e2a09afee035f9d37f32e6f5f1867dd4e9cdcc427262300e26b57fa88489586d8d17e823751039834e2bce97e609e0c3c6abcd82c8abd23aca21bb749a92aec70a45a329f669303664971632b65816cb5123a9d3fb70e8674eacd5a000d2f7033e177b66c6af57403a541c4b03c1eb2072a4630a08c012615622a1f18b9522f26849ee8989e0dad5f03d15ffcf054f1ab4cb4645e6622c5a235541a17160441f939133a861a3930dd1b0345c99075a5163920f1fa2a5a673f56adf8ee78d308acb27b4f73f9e80b0e67750219ffff03ca430210688ea6b3cbc9815ed8894cc526812385feed13bc92b80190f6d0015d03318821af0523ce1620833e15549995ca801607f8e56a0f3867e220d37923ec3303b42c2f794fd6c38f0c285a2ea1ad1a545ec311fe23163829cac7b0f0b93b10880219ffff03cdd05920f2ea207af6c3d6b4b464cf8d1d7181d072c9f7c92712d8ac82e539fc0343b343b1bc2cea4a6c0c394752dce599c6366d20d83909fb6e0df8ae3795746803ca2b002e52273acc4d95ee45753139c65859002dfa31accb0d085586b4ce8fda03c1eb924d3ae51eb84ba16c0695aa352d3b73612e9f285254b6fa5e3fc64e3fcf038a2abcb7aae5cff965958f5715692f55fc41029c1909787b1769207af3af04a3030402245fe71bcc44001ca5ce0003cf345db2ce4ba45fc259b1f82a0da2f7cd2f03551f0395b54db7ef2c626e898e7ac956281b224f5e709b2372db8623073b340d03e56be1509cd275079244afa0c362821535d63ea6fb598abe8dcee3074536d6a6033a67dbfcaf4951fd71bcb4de6b45e755df365e94279e90c21189c4434627481200581f02737ca34fb6114161ec8245c8f7963d6f3bf2325aff8907f1c39a7e4b09ed5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581f023035c5f637e60e48eec97565d9f7b4800e071bb4a8625142462bd5f3c2cd5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03a86752199a16be89dd8c24ee324159ff991e26cf3d9d1b1a6cb45ad529e203d400581f02432f9455c9413d8cafd5d037c0158c68c8cc2a32461a8da7ae0b01933f784725803e30d08e8200581f0225328f4395e2bd6130b29f1a30afe77ee6fa43896b75c8be6c9aff9e610a42bf6600581f02c8790e98a22ac767ea0dfcba08f601f9f4e79f806fe9685595249dfbe95d5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03c5a37057cb7ece487c83b4231dda21eca33090b84797e534e584644d06d5ff0200581f025c67a0da73b02161ec58182b7069ab5df22262ffa172657e73ee0d5b6107410102197e8803d0f5c90e13ec4185a4ed416a4d9585e1466f3ac870c5d9b332071ac20d1049ae03329fec4a59c85559b80a67ec4062f70423bbbf54137c39798fa3065469f28c3f03009cd91ec1b7170b14f266e4907ec4e50c2c5c8fb3a47a5a9751f6c111907b4a00582003a97670be2590088a9a35f053bd00b423e5d8bb75be2ee795bbd6b53ef2daa041040314c0d5f541c6e26286712bc292b6a15d9cb8d83cce055a565643e1d27e95e4b203cbc74a078eb9b61921568d33d4c64260c77868141c44f54f6eb97781001bbe18039f244b362efd0eba010950b395f5b1ddb883ac204d7198168b754ebaeb3730430331aa5c8886d84e65e0048f1a8734858f4145e03bd24729f1ba3400fbdb118b0b0307dc5ba55317aebcff776ff29dd7250321b2d1d7fa0af0e43518e44cae952b6f0219ffff03faf6a32d6b702bf715d32a89ae476a9ade02d2dcd1a0a5cd578e1759156ec52b035e8e4345b591afa48a05ab6ae99a96f077fa82d02b7a9be835b3bc79f65c7c9f03c098301c481d6500d17135048e8b5939f4a62121e7b7500b9a61b1538b1d760e038762d7890eebe388425f7caacf61e4b941ab0191339b2c97348a98e7da80aa9103a6cb342487f148240cc96fa001a6dde12ea7e50b0ffa46502824cf53d17b98e803a3a7400685dbacdef3b8a3bc1a4925c068921a81b418031c496d911070cf3ff6038c0efe0212024b039cfb5ca04c7b2efbcc826dbee329a228d289b06eac264bc6032705110ae1a29799af9e5f8a39f1e033b569accc6bb64f762d9a0dec20fabf4d030de8e1687b4d892d2cf423dce37d2147ea8d71f41c1018f0843e54289e1ef331035bbcaa0619f99f84454b4531355c303ce96123c0f28e368889ac786d9c8fa48403c993283391df345dad85d14069bf90a36cbbd3f6965fe2fa1bd42218e5d4be7a03f0a138f3a825a3a10cba22f99ee7969212e22c9814c9e8e1a3f54bec2f13cc9d0219ffff032769143f77282ca6bf636ca33edd955766f55dd262cdcdf43871c412598ddaa603bc8b842beb575bdec8638d912b8671933f18ba7c365d55c2434d5b8163abce260219ffff05581e0369fb3acb688ec0833b76571adb8bec9d01192ee2c5bd41b51438b48280070119202a03a546b9a1f1b5222d221898a193f0546bd684da8b33f4cd85513383e58541461d05581e0375f6be4c94ec8db2ff43d669882ab0eec29bb8c0120b9f49a4dd503b600404030631bbde9b1f984125a7325a2a6a15403bc0250599094a57c034422ba83a62b705581e036bc29aae9ab1d575e9303ec7b8a7bccb66f352e16ffd847c2bbbc899c0040805581e03b241082d2135ac3ccec1e87a7f017ad6bf87934f77beb37a86d534ae700c014702f0b8f7a29b7803e0eda5d3a5480dc8ea5da0c85b538590b93e4d2f1f82da1f0961b8e2a8c7090c05581e03d442289d5db854fe2f45bf72f76ab99e937420745e213d4987526315b00c014677b0c5f2eb1705581e0362dcb6c71d7a63326527116995a809c8321c3d3d9ed190a5793aec0f100c04470be60cf3d5d5da0312595aba11f1bc6a8180d5051a4172b981b77cd82388b4475fea91ae328cdb21021999f6035798401a74e041e5bd23a00f2aca4913759e2bea42b7bb0502a086fc8f8eefe603db6f567a1cd62cbf00e6e4cba0c17eecd53765602f2a696c936c105a526f03fd03106918e83d1fe407b94f655299529fd05d72e273ecfbcc55d7058bace20a5e050376805a0317490cb460b2a955be780450f6836dd1bb05d8a0311534b86e4cbd8b0317e0520ee6cdbdf3000f978386f5e97130e317b013b2e637e52d52e70bba92d10219ffff03d1d4486e2c6bff5bdba36c57111df8161b46035ebe4a7feea12280efb7abadc203622246760650fc1eebadf40c41de6af008af31b75932edf66dd28a42cf6392b9039c80afa31006c3efef2127a7e68bbdce8aec15d88926e61d59c5868790abda2c0370d5462d7404481b04b145698b68aeb2231f6403e2368b89b416a31079713ee303836128bd50abaf5a48059e41f33c1cab36c01b7333b03270b06ce5e261ab630403aba33a9328146b5b64163d5ce01e95744d8b6ac6dcc3f2082bf9453e07fc359903e40e85229f7a4c2ff5f732f0007bb1244e1e4188260ab1abcf9687fdad76d10b03913a4690991815f6940d8befdc7176aa6ad9c18a08ac68e4419dee9b7c97835903a0a5d2719007da830314daa7aea494b3bf501dcf60ba746e3df67aad39d1db0a036cad949b8b70273203501eef9ecb81276415d4d21be66e2bfae218417c1342ad03554eae355e33b0f1ff9f4c04cfd8b0df1964d522cf1ab1a95a90ebb519ca123a034e8da133fbc1c4db5f5ec972e7f6b11a7ba43b57d5f3cbcc25efde12cf4974660327a87037846a9ad140b95de480c902ab097faf89bc4c8b701612cec33f38b6c70219ffff0382a4e2c4dfb758c117096d85533632ff5029373e9d8d952b129a41b1dedead650358d9656141e4dcbdd2f5f54390ea1afc7c5cb98f7766400013be155a5aa13a580356c4b7056fd10e48feb32c2dc3d2feefaee74d0524ad97b7dfec56dbdf0e23d403dbd626ed7b96456a5f896224b117de8b7ddc7b749c6b2d402d21d7438a8a0e3203845f6b71c30077546d85eed620eff91a7f45a2e3e2cf1f60c88e758cbc96315603e6f29ecbd7991b80570a28bb242c63bdd9cb653eb7596a113b99fdf14f97bd4003368c55f1fbf07342d8c323c738fb2ea9f2fabebc8352a279fd997722cb8fe14a03bdebdaa86a254e37a582155fb052c0f855a545e8ee61e91fd663b92dd5c53e1103c5f0447b2994fa864ec0df232eb80dcc384488291643071f774b9747f0ac26710219ffff038b015389fc84cda7709348e85846f4cffd6da02c1adabda10047c9b9da95a9ca03a3f9e82499e7674bb8c2e9f1a781e783a1dab71b19048e230bb58e5df5973a2d03333fb5bf5b23b1f86775a0f27aadcf9e79134ebec02fefff6051f9878c07f7c703692e61deb86e5b65787f5211be79b949ab6add0d6cdc4c81ebabe51f653dcf9703342d16fd6d25eec5f89690848c39f90db48e7630c455ba1822e24fdcefd8dede0357350819032baf0bfcb2089f1476b54cc4c185c32e30d20725b9dd45e6b5c21703dcf2cebccfb2c9b99f315428762e8487713edd7371ef4943d8faf8f81096755403bcd212c35ca52390cb70af312c0a2e8661c82402d9245a68961e28c9b60cfa41033335c588fe2460af946e5e4cf03edd7cd5888ea17c7e9e3955691cd7f6dafdef0219ffff03a163ea031c9ff8e641a73f154c1c71c9ffc513b6493c3f619a6200a0f4b25102035b4df2810be1536437ba2fcf506a03b9ac1ee5626c19f39a61e3f32ecb8b3641035e0f6b0543c94577d36ce472158db75597afe14df9f8725185b3ba9d4a9fa44203d369a2794f66e4bd041bf97e7dab817da7e83c3bd3581cb880bb6347636e5a930308f3158a611f591d497dbcac4462612e0d0f4dd07b14b124f72c0cda941b361a0370b32cdf3563202ae998180d3c1e78d0ac431450f193d88bdf7b4a005ef5a04d0219ffff0368d228a2bb6f501e5114668a22408ffe5bb23ca4abd6287073626b82b0cc7ab803c509156302f3d96b880a8b732eaacbf000f68350ed14f30eb207bcbd91dc33cf03b559b49383b2f2a8513a459b0ffa5039041233820ddb3beda9757fac8075904903827e2fb9c666b4acb2a77a11004e3a3f28e41e256328f1fd2aea356fbf95e284033bd5d1d8bd1bc47e3b0e5364e68b9a339b53d659a5c54f31b84d481441d9b765037c938a52efaf84f440f34c5e7083bc9ff3e10c463172fba369f45a660a44a0e303e8f34aa1a8d07cdf43a1bd3d33825a6db784ab7541a15153416748b70a8d45ff03af5e35086163e702a7babf11037a4edab68ed5818aa1ced9b8ef2e560c7dfb9603deaafd8cf800d4a3d27d08dd80e8b54b4e9cacd05678687cff6cee69798bbfd0030ad896cae91949a85c3e45fe83e99d91007ad9f03a11729d65cac061f583b19a03df17c3e88df7eb66e3bc42a6eff9226ad790b1acaceef30860193a69c371f135035f39e8f8e797cf94ff50c4d3e8612cc29ca20cd4d555bafd647fd750ce907e780328a438af36388cbb7355c4550a338d85fc90c9968ec8942c90047add703c8ba0038496b23088b87f4c247935c2dc0c117b79309cc8b7b429f6eb1d849e892522b10393990e42a7da40102323c7d777de80586e89dceb21cbcb4ebcb6074dc1780e3303f662ad4ac6060f9fa84ddc459c47e98009160b7a9c2a63fdbfd38d66f31072e103a6933d9379f9715caae168708081b65a233a6c2014eb525ee3917539d0f16ecb0355b5838de9624d29b324001d06d9d854210e1bc9b815d3cebb3730fb18da697203e17a5fd5f0258a79a7295fc349c35bcedc07d42f5cb371d822336abb513926190320954c325095c2fefc2201f0293f5f6f5240f85fcc052a5d8a82869a6172aa9a034570173bcc88ea2710d14ba572afa4f318427fea12278a73922298159cc5bcee032e0a36c7ef00d3650e2a628c2db0a90cf6348e8c442b4db3ac29f220276fbacc036e99d8d3ceb6afded473f5bc05e1c7b1c98c3f45222a1ecf01a99b9b4641e64103562d44997323f6b304ed9740a3025214a7afd24c44e13a26dd10ec7401440546030a3dbcac1201260432eaf194827eb27c6192a4884e34b7d8ec500c90a7db10ac0308c28be29d0cfe27ce4dcc22bb798c97692f50db909b2f809d8bbd97d6b95ea203b5f93339cb957efb1a03f248602d638122ff3ec5ab8cc5c9bdbea0ba48e30ae105581e038c0101236e268b917b5c0108f9f8370987816d98284c76b588798f19000c0147021bb96329ba0005581e03a9d5a946af9e33d51d87cee40544ebbfc39c5d3ad52b77e1b30786b1a00c02462155aa030b580459567e608060405234801561001057600080fd5b50600436106101ae5760003560e01c806370cf754a116100ee578063c45a015511610097578063ddca3f4311610071578063ddca3f4314610800578063f305839914610820578063f30dba9314610828578063f637731d146108aa576101ae565b8063c45a0155146107d1578063d0c93a7c146107d9578063d21220a7146107f8576101ae565b8063883bdbfd116100c8578063883bdbfd14610633578063a34123a71461073c578063a38807f214610776576101ae565b806370cf754a146105c65780638206a4d1146105ce57806385b66729146105f6576101ae565b80633850c7bd1161015b578063490e6cbc11610135578063490e6cbc146104705780634f1eb3d8146104fc578063514ea4bf1461054d5780635339c296146105a6576101ae565b80633850c7bd1461035b5780633c8a7d8d146103b45780634614131914610456576101ae565b80631ad8b03b1161018c5780631ad8b03b146102aa578063252c09d7146102e157806332148f6714610338576101ae565b80630dfe1681146101b3578063128acb08146101d75780631a68650214610286575b600080fd5b6101bb6108d0565b604080516001600160a01b039092168252519081900360200190f35b61026d600480360360a08110156101ed57600080fd5b6001600160a01b0382358116926020810135151592604082013592606083013516919081019060a08101608082013564010000000081111561022e57600080fd5b82018360208201111561024057600080fd5b8035906020019184600183028401116401000000008311171561026257600080fd5b5090925090506108f4565b6040805192835260208301919091528051918290030190f35b61028e6114ad565b604080516001600160801b039092168252519081900360200190f35b6102b26114bc565b60405180836001600160801b03168152602001826001600160801b031681526020019250505060405180910390f35b6102fe600480360360208110156102f757600080fd5b50356114d6565b6040805163ffffffff909516855260069390930b60208501526001600160a01b039091168383015215156060830152519081900360800190f35b6103596004803603602081101561034e57600080fd5b503561ffff1661151c565b005b610363611616565b604080516001600160a01b03909816885260029690960b602088015261ffff9485168787015292841660608701529216608085015260ff90911660a0840152151560c0830152519081900360e00190f35b61026d600480360360a08110156103ca57600080fd5b6001600160a01b03823516916020810135600290810b92604083013590910b916001600160801b036060820135169181019060a08101608082013564010000000081111561041757600080fd5b82018360208201111561042957600080fd5b8035906020019184600183028401116401000000008311171561044b57600080fd5b509092509050611666565b61045e611922565b60408051918252519081900360200190f35b6103596004803603608081101561048657600080fd5b6001600160a01b0382351691602081013591604082013591908101906080810160608201356401000000008111156104bd57600080fd5b8201836020820111156104cf57600080fd5b803590602001918460018302840111640100000000831117156104f157600080fd5b509092509050611928565b6102b2600480360360a081101561051257600080fd5b506001600160a01b03813516906020810135600290810b91604081013590910b906001600160801b0360608201358116916080013516611d83565b61056a6004803603602081101561056357600080fd5b5035611f9d565b604080516001600160801b0396871681526020810195909552848101939093529084166060840152909216608082015290519081900360a00190f35b61045e600480360360208110156105bc57600080fd5b503560010b611fda565b61028e611fec565b610359600480360360408110156105e457600080fd5b5060ff81358116916020013516612010565b6102b26004803603606081101561060c57600080fd5b506001600160a01b03813516906001600160801b036020820135811691604001351661220f565b6106a36004803603602081101561064957600080fd5b81019060208101813564010000000081111561066457600080fd5b82018360208201111561067657600080fd5b8035906020019184602083028401116401000000008311171561069857600080fd5b5090925090506124dc565b604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b838110156106e75781810151838201526020016106cf565b50505050905001838103825284818151815260200191508051906020019060200280838360005b8381101561072657818101518382015260200161070e565b5050505090500194505050505060405180910390f35b61026d6004803603606081101561075257600080fd5b508035600290810b91602081013590910b90604001356001600160801b0316612569565b6107a06004803603604081101561078c57600080fd5b508035600290810b9160200135900b6126e0565b6040805160069490940b84526001600160a01b03909216602084015263ffffffff1682820152519081900360600190f35b6101bb6128d7565b6107e16128fb565b6040805160029290920b8252519081900360200190f35b6101bb61291f565b610808612943565b6040805162ffffff9092168252519081900360200190f35b61045e612967565b6108486004803603602081101561083e57600080fd5b503560020b61296d565b604080516001600160801b039099168952600f9790970b602089015287870195909552606087019390935260069190910b60808601526001600160a01b031660a085015263ffffffff1660c0840152151560e083015251908190036101000190f35b610359600480360360208110156108c057600080fd5b50356001600160a01b03166129db565b7f0000000000000000000000009e9fbde7c7a83c43913bddc8779158f1368f041381565b6000806108ff612bf0565b85610936576040805162461bcd60e51b8152602060048201526002602482015261415360f01b604482015290519081900360640190fd5b6040805160e0810182526000546001600160a01b0381168252600160a01b8104600290810b810b900b602083015261ffff600160b81b8204811693830193909352600160c81b810483166060830152600160d81b8104909216608082015260ff600160e81b8304811660a0830152600160f01b909204909116151560c082018190526109ef576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b87610a3a5780600001516001600160a01b0316866001600160a01b0316118015610a35575073fffd8963efd1fc6a506488495d951d5263988d266001600160a01b038716105b610a6c565b80600001516001600160a01b0316866001600160a01b0316108015610a6c57506401000276a36001600160a01b038716115b610aa3576040805162461bcd60e51b815260206004820152600360248201526214d41360ea1b604482015290519081900360640190fd5b6000805460ff60f01b191681556040805160c08101909152808a610ad25760048460a0015160ff16901c610ae5565b60108460a0015160ff1681610ae357fe5b065b60ff1681526004546001600160801b03166020820152604001610b06612c27565b63ffffffff168152602001600060060b815260200160006001600160a01b031681526020016000151581525090506000808913905060006040518060e001604052808b81526020016000815260200185600001516001600160a01b03168152602001856020015160020b81526020018c610b8257600254610b86565b6001545b815260200160006001600160801b0316815260200184602001516001600160801b031681525090505b805115801590610bd55750886001600160a01b031681604001516001600160a01b031614155b15610f9f57610be261560e565b60408201516001600160a01b031681526060820151610c25906006907f00000000000000000000000000000000000000000000000000000000000000c88f612c2b565b15156040830152600290810b810b60208301819052620d89e719910b1215610c5657620d89e7196020820152610c75565b6020810151620d89e860029190910b1315610c7557620d89e860208201525b610c828160200151612d6d565b6001600160a01b031660608201526040820151610d13908d610cbc578b6001600160a01b031683606001516001600160a01b031611610cd6565b8b6001600160a01b031683606001516001600160a01b0316105b610ce4578260600151610ce6565b8b5b60c085015185517f000000000000000000000000000000000000000000000000000000000000271061309f565b60c085015260a084015260808301526001600160a01b031660408301528215610d7557610d498160c00151826080015101613291565b825103825260a0810151610d6b90610d6090613291565b6020840151906132a7565b6020830152610db0565b610d828160a00151613291565b825101825260c08101516080820151610daa91610d9f9101613291565b6020840151906132c3565b60208301525b835160ff1615610df6576000846000015160ff168260c0015181610dd057fe5b60c0840180519290910491829003905260a0840180519091016001600160801b03169052505b60c08201516001600160801b031615610e3557610e298160c00151600160801b8460c001516001600160801b03166132d9565b60808301805190910190525b80606001516001600160a01b031682604001516001600160a01b03161415610f5e57806040015115610f35578360a00151610ebf57610e9d846040015160008760200151886040015188602001518a606001516008613389909695949392919063ffffffff16565b6001600160a01b03166080860152600690810b900b6060850152600160a08501525b6000610f0b82602001518e610ed657600154610edc565b84608001515b8f610eeb578560800151610eef565b6002545b608089015160608a015160408b0151600595949392919061351c565b90508c15610f17576000035b610f258360c00151826135ef565b6001600160801b031660c0840152505b8b610f44578060200151610f4d565b60018160200151035b600290810b900b6060830152610f99565b80600001516001600160a01b031682604001516001600160a01b031614610f9957610f8c82604001516136a5565b600290810b900b60608301525b50610baf565b836020015160020b816060015160020b1461107a57600080610fed86604001518660400151886020015188602001518a606001518b6080015160086139d1909695949392919063ffffffff16565b604085015160608601516000805461ffff60c81b1916600160c81b61ffff958616021761ffff60b81b1916600160b81b95909416949094029290921762ffffff60a01b1916600160a01b62ffffff60029490940b93909316929092029190911773ffffffffffffffffffffffffffffffffffffffff19166001600160a01b03909116179055506110ac9050565b60408101516000805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b039092169190911790555b8060c001516001600160801b031683602001516001600160801b0316146110f25760c0810151600480546001600160801b0319166001600160801b039092169190911790555b8a1561114257608081015160015560a08101516001600160801b03161561113d5760a0810151600380546001600160801b031981166001600160801b03918216909301169190911790555b611188565b608081015160025560a08101516001600160801b0316156111885760a0810151600380546001600160801b03808216600160801b92839004821690940116029190911790555b8115158b1515146111a157602081015181518b036111ae565b80600001518a0381602001515b90965094508a156112e75760008512156111f0576111f07f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28d87600003613b86565b60006111fa613cd4565b9050336001600160a01b031663fa461e3388888c8c6040518563ffffffff1660e01b815260040180858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b15801561127e57600080fd5b505af1158015611292573d6000803e3d6000fd5b5050505061129e613cd4565b6112a88289613e0d565b11156112e1576040805162461bcd60e51b815260206004820152600360248201526249494160e81b604482015290519081900360640190fd5b50611411565b600086121561131e5761131e7f0000000000000000000000009e9fbde7c7a83c43913bddc8779158f1368f04138d88600003613b86565b6000611328613e1d565b9050336001600160a01b031663fa461e3388888c8c6040518563ffffffff1660e01b815260040180858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b1580156113ac57600080fd5b505af11580156113c0573d6000803e3d6000fd5b505050506113cc613e1d565b6113d68288613e0d565b111561140f576040805162461bcd60e51b815260206004820152600360248201526249494160e81b604482015290519081900360640190fd5b505b60408082015160c083015160608085015184518b8152602081018b90526001600160a01b03948516818701526001600160801b039093169183019190915260020b60808201529151908e169133917fc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca679181900360a00190a350506000805460ff60f01b1916600160f01b17905550919890975095505050505050565b6004546001600160801b031681565b6003546001600160801b0380821691600160801b90041682565b60088161ffff81106114e757600080fd5b015463ffffffff81169150640100000000810460060b90600160581b81046001600160a01b031690600160f81b900460ff1684565b600054600160f01b900460ff16611560576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b19169055611575612bf0565b60008054600160d81b900461ffff169061159160088385613eb5565b6000805461ffff808416600160d81b810261ffff60d81b19909316929092179092559192508316146115fe576040805161ffff80851682528316602082015281517fac49e518f90a358f652e4400164f05a5d8f7e35e7747279bc3a93dbf584e125a929181900390910190a15b50506000805460ff60f01b1916600160f01b17905550565b6000546001600160a01b03811690600160a01b810460020b9061ffff600160b81b8204811691600160c81b8104821691600160d81b8204169060ff600160e81b8204811691600160f01b90041687565b600080548190600160f01b900460ff166116ad576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b191690556001600160801b0385166116cd57600080fd5b60008061171b60405180608001604052808c6001600160a01b031681526020018b60020b81526020018a60020b81526020016117118a6001600160801b0316613f58565b600f0b9052613f69565b9250925050819350809250600080600086111561173d5761173a613cd4565b91505b841561174e5761174b613e1d565b90505b336001600160a01b031663d348799787878b8b6040518563ffffffff1660e01b815260040180858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b1580156117d057600080fd5b505af11580156117e4573d6000803e3d6000fd5b50505050600086111561183b576117f9613cd4565b6118038388613e0d565b111561183b576040805162461bcd60e51b815260206004820152600260248201526104d360f41b604482015290519081900360640190fd5b841561188b57611849613e1d565b6118538287613e0d565b111561188b576040805162461bcd60e51b81526020600482015260026024820152614d3160f01b604482015290519081900360640190fd5b8960020b8b60020b8d6001600160a01b03167f7a53080ba414158be7ec69b987b5fb7d07dee101fe85488f0853ae16239d0bde338d8b8b60405180856001600160a01b03168152602001846001600160801b0316815260200183815260200182815260200194505050505060405180910390a450506000805460ff60f01b1916600160f01b17905550919890975095505050505050565b60025481565b600054600160f01b900460ff1661196c576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b19169055611981612bf0565b6004546001600160801b0316806119c3576040805162461bcd60e51b81526020600482015260016024820152601360fa1b604482015290519081900360640190fd5b60006119f8867f000000000000000000000000000000000000000000000000000000000000271062ffffff16620f42406141a9565b90506000611a2f867f000000000000000000000000000000000000000000000000000000000000271062ffffff16620f42406141a9565b90506000611a3b613cd4565b90506000611a47613e1d565b90508815611a7a57611a7a7f0000000000000000000000009e9fbde7c7a83c43913bddc8779158f1368f04138b8b613b86565b8715611aab57611aab7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28b8a613b86565b336001600160a01b031663e9cbafb085858a8a6040518563ffffffff1660e01b815260040180858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b158015611b2d57600080fd5b505af1158015611b41573d6000803e3d6000fd5b505050506000611b4f613cd4565b90506000611b5b613e1d565b905081611b688588613e0d565b1115611ba0576040805162461bcd60e51b8152602060048201526002602482015261046360f41b604482015290519081900360640190fd5b80611bab8487613e0d565b1115611be3576040805162461bcd60e51b8152602060048201526002602482015261463160f01b604482015290519081900360640190fd5b8382038382038115611c725760008054600160e81b9004600f16908115611c16578160ff168481611c1057fe5b04611c19565b60005b90506001600160801b03811615611c4c57600380546001600160801b038082168401166001600160801b03199091161790555b611c66818503600160801b8d6001600160801b03166132d9565b60018054909101905550505b8015611cfd5760008054600160e81b900460041c600f16908115611ca2578160ff168381611c9c57fe5b04611ca5565b60005b90506001600160801b03811615611cd757600380546001600160801b03600160801b8083048216850182160291161790555b611cf1818403600160801b8d6001600160801b03166132d9565b60028054909101905550505b8d6001600160a01b0316336001600160a01b03167fbdbdb71d7860376ba52b25a5028beea23581364a40522f6bcfb86bb1f2dca6338f8f86866040518085815260200184815260200183815260200182815260200194505050505060405180910390a350506000805460ff60f01b1916600160f01b179055505050505050505050505050565b600080548190600160f01b900460ff16611dca576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b19168155611de460073389896141e3565b60038101549091506001600160801b0390811690861611611e055784611e14565b60038101546001600160801b03165b60038201549093506001600160801b03600160801b909104811690851611611e3c5783611e52565b6003810154600160801b90046001600160801b03165b91506001600160801b03831615611eb7576003810180546001600160801b031981166001600160801b03918216869003821617909155611eb7907f0000000000000000000000009e9fbde7c7a83c43913bddc8779158f1368f0413908a908616613b86565b6001600160801b03821615611f1d576003810180546001600160801b03600160801b808304821686900382160291811691909117909155611f1d907f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2908a908516613b86565b604080516001600160a01b038a1681526001600160801b0380861660208301528416818301529051600288810b92908a900b9133917f70935338e69775456a85ddef226c395fb668b63fa0115f5f20610b388e6ca9c0919081900360600190a4506000805460ff60f01b1916600160f01b17905590969095509350505050565b60076020526000908152604090208054600182015460028301546003909301546001600160801b0392831693919281811691600160801b90041685565b60066020526000908152604090205481565b7f00000000000000000000000000000000000762d10ef955d55b7d038c7a7231cc81565b600054600160f01b900460ff16612054576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b1916905560408051638da5cb5b60e01b815290516001600160a01b037f0000000000000000000000001f98431c8ad98523631ae4a59f267346ea31f9841691638da5cb5b916004808301926020929190829003018186803b1580156120c157600080fd5b505afa1580156120d5573d6000803e3d6000fd5b505050506040513d60208110156120eb57600080fd5b50516001600160a01b0316331461210157600080fd5b60ff82161580612124575060048260ff16101580156121245750600a8260ff1611155b801561214e575060ff8116158061214e575060048160ff161015801561214e5750600a8160ff1611155b61215757600080fd5b60008054610ff0600484901b16840160ff908116600160e81b9081027fffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff841617909355919004167f973d8d92bb299f4af6ce49b52a8adb85ae46b9f214c4c4fc06ac77401237b1336010826040805160ff9390920683168252600f600486901c16602083015286831682820152918516606082015290519081900360800190a150506000805460ff60f01b1916600160f01b17905550565b600080548190600160f01b900460ff16612256576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b1916905560408051638da5cb5b60e01b815290516001600160a01b037f0000000000000000000000001f98431c8ad98523631ae4a59f267346ea31f9841691638da5cb5b916004808301926020929190829003018186803b1580156122c357600080fd5b505afa1580156122d7573d6000803e3d6000fd5b505050506040513d60208110156122ed57600080fd5b50516001600160a01b0316331461230357600080fd5b6003546001600160801b039081169085161161231f578361232c565b6003546001600160801b03165b6003549092506001600160801b03600160801b9091048116908416116123525782612366565b600354600160801b90046001600160801b03165b90506001600160801b038216156123e7576003546001600160801b038381169116141561239557600019909101905b600380546001600160801b031981166001600160801b039182168590038216179091556123e7907f0000000000000000000000009e9fbde7c7a83c43913bddc8779158f1368f04139087908516613b86565b6001600160801b0381161561246d576003546001600160801b03828116600160801b90920416141561241857600019015b600380546001600160801b03600160801b80830482168590038216029181169190911790915561246d907f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc29087908416613b86565b604080516001600160801b0380851682528316602082015281516001600160a01b0388169233927f596b573906218d3411850b26a6b437d6c4522fdb43d2d2386263f86d50b8b151929081900390910190a36000805460ff60f01b1916600160f01b1790559094909350915050565b6060806124e7612bf0565b61255e6124f2612c27565b858580806020026020016040519081016040528093929190818152602001838360200280828437600092018290525054600454600896959450600160a01b820460020b935061ffff600160b81b8304811693506001600160801b0390911691600160c81b900416614247565b915091509250929050565b600080548190600160f01b900460ff166125b0576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b1916815560408051608081018252338152600288810b602083015287900b918101919091528190819061260990606081016125fc6001600160801b038a16613f58565b600003600f0b9052613f69565b925092509250816000039450806000039350600085118061262a5750600084115b15612669576003830180546001600160801b038082168089018216600160801b93849004831689019092169092029091176001600160801b0319161790555b604080516001600160801b0388168152602081018790528082018690529051600289810b92908b900b9133917f0c396cd989a39f4459b5fa1aed6a9a8dcdbc45908acfd67e028cd568da98982c919081900360600190a450506000805460ff60f01b1916600160f01b179055509094909350915050565b60008060006126ed612bf0565b6126f785856143a1565b600285810b810b60009081526005602052604080822087840b90930b825281206003830154600681900b9367010000000000000082046001600160a01b0316928492600160d81b810463ffffffff169284929091600160f81b900460ff168061275f57600080fd5b6003820154600681900b985067010000000000000081046001600160a01b03169650600160d81b810463ffffffff169450600160f81b900460ff16806127a457600080fd5b50506040805160e0810182526000546001600160a01b0381168252600160a01b8104600290810b810b810b6020840181905261ffff600160b81b8404811695850195909552600160c81b830485166060850152600160d81b8304909416608084015260ff600160e81b8304811660a0850152600160f01b909204909116151560c08301529093508e810b91900b1215905061284d575093909403965090039350900390506128d0565b8a60020b816020015160020b12156128c1576000612869612c27565b602083015160408401516004546060860151939450600093849361289f936008938893879392916001600160801b031690613389565b9a9003989098039b5050949096039290920396509091030392506128d0915050565b50949093039650039350900390505b9250925092565b7f0000000000000000000000001f98431c8ad98523631ae4a59f267346ea31f98481565b7f00000000000000000000000000000000000000000000000000000000000000c881565b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc281565b7f000000000000000000000000000000000000000000000000000000000000271081565b60015481565b60056020526000908152604090208054600182015460028301546003909301546001600160801b03831693600160801b909304600f0b9290600681900b9067010000000000000081046001600160a01b031690600160d81b810463ffffffff1690600160f81b900460ff1688565b6000546001600160a01b031615612a1e576040805162461bcd60e51b8152602060048201526002602482015261414960f01b604482015290519081900360640190fd5b6000612a29826136a5565b9050600080612a41612a39612c27565b60089061446a565b6040805160e0810182526001600160a01b038816808252600288810b6020808501829052600085870181905261ffff898116606088018190529089166080880181905260a08801839052600160c0909801979097528154600160f01b73ffffffffffffffffffffffffffffffffffffffff19909116871762ffffff60a01b1916600160a01b62ffffff9787900b9790971696909602959095177fffffffffff00000000ffffffffffffffffffffffffffffffffffffffffffffff16600160c81b9091021761ffff60d81b1916600160d81b909602959095177fff0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1692909217909355835191825281019190915281519395509193507f98636036cb66a9c19a37435efc1e90142190214e8abeb821bdba3f2990dd4c9592918290030190a150505050565b60008082600281900b620d89e71981612b9957fe5b05029050600083600281900b620d89e881612bb057fe5b0502905060008460020b83830360020b81612bc757fe5b0560010190508062ffffff166001600160801b03801681612be457fe5b0493505050505b919050565b306001600160a01b037f0000000000000000000000001df4c6e36d61416813b42fe32724ef11e363eddc1614612c2557600080fd5b565b4290565b60008060008460020b8660020b81612c3f57fe5b05905060008660020b128015612c6657508460020b8660020b81612c5f57fe5b0760020b15155b15612c7057600019015b8315612ce557600080612c82836144b6565b600182810b810b600090815260208d9052604090205460ff83169190911b80016000190190811680151597509294509092509085612cc757888360ff16860302612cda565b88612cd1826144c8565b840360ff168603025b965050505050612d63565b600080612cf4836001016144b6565b91509150600060018260ff166001901b031990506000818b60008660010b60010b8152602001908152602001600020541690508060001415955085612d4657888360ff0360ff16866001010102612d5c565b8883612d5183614568565b0360ff168660010101025b9650505050505b5094509492505050565b60008060008360020b12612d84578260020b612d8c565b8260020b6000035b9050620d89e8811115612dca576040805162461bcd60e51b81526020600482015260016024820152601560fa1b604482015290519081900360640190fd5b600060018216612dde57600160801b612df0565b6ffffcb933bd6fad37aa2d162d1a5940015b70ffffffffffffffffffffffffffffffffff1690506002821615612e24576ffff97272373d413259a46990580e213a0260801c5b6004821615612e43576ffff2e50f5f656932ef12357cf3c7fdcc0260801c5b6008821615612e62576fffe5caca7e10e4e61c3624eaa0941cd00260801c5b6010821615612e81576fffcb9843d60f6159c9db58835c9266440260801c5b6020821615612ea0576fff973b41fa98c081472e6896dfb254c00260801c5b6040821615612ebf576fff2ea16466c96a3843ec78b326b528610260801c5b6080821615612ede576ffe5dee046a99a2a811c461f1969c30530260801c5b610100821615612efe576ffcbe86c7900a88aedcffc83b479aa3a40260801c5b610200821615612f1e576ff987a7253ac413176f2b074cf7815e540260801c5b610400821615612f3e576ff3392b0822b70005940c7a398e4b70f30260801c5b610800821615612f5e576fe7159475a2c29b7443b29c7fa6e889d90260801c5b611000821615612f7e576fd097f3bdfd2022b8845ad8f792aa58250260801c5b612000821615612f9e576fa9f746462d870fdf8a65dc1f90e061e50260801c5b614000821615612fbe576f70d869a156d2a1b890bb3df62baf32f70260801c5b618000821615612fde576f31be135f97d08fd981231505542fcfa60260801c5b62010000821615612fff576f09aa508b5b7a84e1c677de54f3e99bc90260801c5b6202000082161561301f576e5d6af8dedb81196699c329225ee6040260801c5b6204000082161561303e576d2216e584f5fa1ea926041bedfe980260801c5b6208000082161561305b576b048a170391f7dc42444e8fa20260801c5b60008460020b131561307657806000198161307257fe5b0490505b64010000000081061561308a57600161308d565b60005b60ff16602082901c0192505050919050565b60008080806001600160a01b03808916908a1610158187128015906131245760006130d88989620f42400362ffffff16620f42406132d9565b9050826130f1576130ec8c8c8c6001614652565b6130fe565b6130fe8b8d8c60016146cd565b955085811061310f578a965061311e565b61311b8c8b838661478a565b96505b5061316e565b8161313b576131368b8b8b60006146cd565b613148565b6131488a8c8b6000614652565b935083886000031061315c5789955061316e565b61316b8b8a8a600003856147d6565b95505b6001600160a01b038a81169087161482156131d15780801561318d5750815b6131a35761319e878d8c60016146cd565b6131a5565b855b95508080156131b2575081155b6131c8576131c3878d8c6000614652565b6131ca565b845b945061321b565b8080156131db5750815b6131f1576131ec8c888c6001614652565b6131f3565b855b9550808015613200575081155b613216576132118c888c60006146cd565b613218565b845b94505b8115801561322b57508860000385115b15613237578860000394505b81801561325657508a6001600160a01b0316876001600160a01b031614155b15613265578589039350613282565b61327f868962ffffff168a620f42400362ffffff166141a9565b93505b50505095509550955095915050565b6000600160ff1b82106132a357600080fd5b5090565b808203828113156000831215146132bd57600080fd5b92915050565b818101828112156000831215146132bd57600080fd5b600080806000198587098686029250828110908390030390508061330f576000841161330457600080fd5b508290049050613382565b80841161331b57600080fd5b6000848688096000868103871696879004966002600389028118808a02820302808a02820302808a02820302808a02820302808a02820302808a02909103029181900381900460010186841190950394909402919094039290920491909117919091029150505b9392505050565b60008063ffffffff8716613430576000898661ffff1661ffff81106133aa57fe5b60408051608081018252919092015463ffffffff8082168084526401000000008304600690810b810b900b6020850152600160581b83046001600160a01b031694840194909452600160f81b90910460ff16151560608301529092508a161461341c57613419818a8988614822565b90505b806020015181604001519250925050613510565b8688036000806134458c8c858c8c8c8c6148d2565b91509150816000015163ffffffff168363ffffffff161415613477578160200151826040015194509450505050613510565b805163ffffffff8481169116141561349f578060200151816040015194509450505050613510565b8151815160208085015190840151918390039286039163ffffffff80841692908516910360060b816134cd57fe5b05028460200151018263ffffffff168263ffffffff1686604001518660400151036001600160a01b031602816134ff57fe5b048560400151019650965050505050505b97509795505050505050565b600295860b860b60009081526020979097526040909620600181018054909503909455938301805490920390915560038201805463ffffffff600160d81b6001600160a01b036701000000000000008085048216909603169094027fffffffffff0000000000000000000000000000000000000000ffffffffffffff90921691909117600681810b90960390950b66ffffffffffffff1666ffffffffffffff199095169490941782810485169095039093160263ffffffff60d81b1990931692909217905554600160801b9004600f0b90565b60008082600f0b121561365457826001600160801b03168260000384039150816001600160801b03161061364f576040805162461bcd60e51b81526020600482015260026024820152614c5360f01b604482015290519081900360640190fd5b6132bd565b826001600160801b03168284019150816001600160801b031610156132bd576040805162461bcd60e51b81526020600482015260026024820152614c4160f01b604482015290519081900360640190fd5b60006401000276a36001600160a01b038316108015906136e1575073fffd8963efd1fc6a506488495d951d5263988d266001600160a01b038316105b613716576040805162461bcd60e51b81526020600482015260016024820152602960f91b604482015290519081900360640190fd5b77ffffffffffffffffffffffffffffffffffffffff00000000602083901b166001600160801b03811160071b81811c67ffffffffffffffff811160061b90811c63ffffffff811160051b90811c61ffff811160041b90811c60ff8111600390811b91821c600f811160021b90811c918211600190811b92831c979088119617909417909217179091171717608081106137b757607f810383901c91506137c1565b80607f0383901b91505b908002607f81811c60ff83811c9190911c800280831c81831c1c800280841c81841c1c800280851c81851c1c800280861c81861c1c800280871c81871c1c800280881c81881c1c800280891c81891c1c8002808a1c818a1c1c8002808b1c818b1c1c8002808c1c818c1c1c8002808d1c818d1c1c8002808e1c9c81901c9c909c1c80029c8d901c9e9d607f198f0160401b60c09190911c678000000000000000161760c19b909b1c674000000000000000169a909a1760c29990991c672000000000000000169890981760c39790971c671000000000000000169690961760c49590951c670800000000000000169490941760c59390931c670400000000000000169290921760c69190911c670200000000000000161760c79190911c670100000000000000161760c89190911c6680000000000000161760c99190911c6640000000000000161760ca9190911c6620000000000000161760cb9190911c6610000000000000161760cc9190911c6608000000000000161760cd9190911c66040000000000001617693627a301d71055774c8581026f028f6481ab7f045a5af012a19d003aa9198101608090811d906fdb2df09e81959a81455e260799a0632f8301901d600281810b9083900b146139c257886001600160a01b03166139a682612d6d565b6001600160a01b031611156139bb57816139bd565b805b6139c4565b815b9998505050505050505050565b6000806000898961ffff1661ffff81106139e757fe5b60408051608081018252919092015463ffffffff8082168084526401000000008304600690810b810b900b6020850152600160581b83046001600160a01b031694840194909452600160f81b90910460ff161515606083015290925089161415613a575788859250925050613510565b8461ffff168461ffff16118015613a7857506001850361ffff168961ffff16145b15613a8557839150613a89565b8491505b8161ffff168960010161ffff1681613a9d57fe5b069250613aac81898989614822565b8a8461ffff1661ffff8110613abd57fe5b825191018054602084015160408501516060909501511515600160f81b027effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6001600160a01b03909616600160581b027fff0000000000000000000000000000000000000000ffffffffffffffffffffff60069390930b66ffffffffffffff16640100000000026affffffffffffff000000001963ffffffff90971663ffffffff199095169490941795909516929092171692909217929092161790555097509795505050505050565b604080516001600160a01b038481166024830152604480830185905283518084039091018152606490920183526020820180516001600160e01b031663a9059cbb60e01b1781529251825160009485949389169392918291908083835b60208310613c025780518252601f199092019160209182019101613be3565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114613c64576040519150601f19603f3d011682016040523d82523d6000602084013e613c69565b606091505b5091509150818015613c97575080511580613c975750808060200190516020811015613c9457600080fd5b50515b613ccd576040805162461bcd60e51b81526020600482015260026024820152612a2360f11b604482015290519081900360640190fd5b5050505050565b604080513060248083019190915282518083039091018152604490910182526020810180516001600160e01b03166370a0823160e01b17815291518151600093849384936001600160a01b037f0000000000000000000000009e9fbde7c7a83c43913bddc8779158f1368f04131693919290918291908083835b60208310613d6d5780518252601f199092019160209182019101613d4e565b6001836020036101000a038019825116818451168082178552505050505050905001915050600060405180830381855afa9150503d8060008114613dcd576040519150601f19603f3d011682016040523d82523d6000602084013e613dd2565b606091505b5091509150818015613de657506020815110155b613def57600080fd5b808060200190516020811015613e0457600080fd5b50519250505090565b808201828110156132bd57600080fd5b604080513060248083019190915282518083039091018152604490910182526020810180516001600160e01b03166370a0823160e01b17815291518151600093849384936001600160a01b037f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc216939192909182919080838360208310613d6d5780518252601f199092019160209182019101613d4e565b6000808361ffff1611613ef3576040805162461bcd60e51b81526020600482015260016024820152604960f81b604482015290519081900360640190fd5b8261ffff168261ffff1611613f09575081613382565b825b8261ffff168161ffff161015613f4f576001858261ffff1661ffff8110613f2e57fe5b01805463ffffffff191663ffffffff92909216919091179055600101613f0b565b50909392505050565b80600f81900b8114612beb57600080fd5b6000806000613f76612bf0565b613f88846020015185604001516143a1565b6040805160e0810182526000546001600160a01b0381168252600160a01b8104600290810b810b900b602080840182905261ffff600160b81b8404811685870152600160c81b84048116606080870191909152600160d81b8504909116608086015260ff600160e81b8504811660a0870152600160f01b909404909316151560c08501528851908901519489015192890151939461402c9491939092909190614acf565b93508460600151600f0b6000146141a157846020015160020b816020015160020b12156140815761407a6140638660200151612d6d565b6140708760400151612d6d565b8760600151614c84565b92506141a1565b846040015160020b816020015160020b12156141775760045460408201516001600160801b03909116906140d3906140b7612c27565b60208501516060860151608087015160089493929187916139d1565b6000805461ffff60c81b1916600160c81b61ffff938416021761ffff60b81b1916600160b81b939092169290920217905581516040870151614123919061411990612d6d565b8860600151614c84565b93506141416141358760200151612d6d565b83516060890151614cc8565b92506141518187606001516135ef565b600480546001600160801b0319166001600160801b0392909216919091179055506141a1565b61419e6141878660200151612d6d565b6141948760400151612d6d565b8760600151614cc8565b91505b509193909250565b60006141b68484846132d9565b9050600082806141c257fe5b84860911156133825760001981106141d957600080fd5b6001019392505050565b6040805160609490941b6bffffffffffffffffffffffff1916602080860191909152600293840b60e890811b60348701529290930b90911b60378401528051808403601a018152603a90930181528251928201929092206000908152929052902090565b60608060008361ffff1611614287576040805162461bcd60e51b81526020600482015260016024820152604960f81b604482015290519081900360640190fd5b865167ffffffffffffffff8111801561429f57600080fd5b506040519080825280602002602001820160405280156142c9578160200160208202803683370190505b509150865167ffffffffffffffff811180156142e457600080fd5b5060405190808252806020026020018201604052801561430e578160200160208202803683370190505b50905060005b87518110156143945761433f8a8a8a848151811061432e57fe5b60200260200101518a8a8a8a613389565b84838151811061434b57fe5b6020026020010184848151811061435e57fe5b60200260200101826001600160a01b03166001600160a01b03168152508260060b60060b81525050508080600101915050614314565b5097509795505050505050565b8060020b8260020b126143e1576040805162461bcd60e51b8152602060048201526003602482015262544c5560e81b604482015290519081900360640190fd5b620d89e719600283900b1215614424576040805162461bcd60e51b8152602060048201526003602482015262544c4d60e81b604482015290519081900360640190fd5b620d89e8600282900b1315614466576040805162461bcd60e51b815260206004820152600360248201526254554d60e81b604482015290519081900360640190fd5b5050565b6040805160808101825263ffffffff9283168082526000602083018190529282019290925260016060909101819052835463ffffffff1916909117909116600160f81b17909155908190565b60020b600881901d9161010090910790565b60008082116144d657600080fd5b600160801b82106144e957608091821c91015b68010000000000000000821061450157604091821c91015b640100000000821061451557602091821c91015b62010000821061452757601091821c91015b610100821061453857600891821c91015b6010821061454857600491821c91015b6004821061455857600291821c91015b60028210612beb57600101919050565b600080821161457657600080fd5b5060ff6001600160801b0382161561459157607f1901614599565b608082901c91505b67ffffffffffffffff8216156145b257603f19016145ba565b604082901c91505b63ffffffff8216156145cf57601f19016145d7565b602082901c91505b61ffff8216156145ea57600f19016145f2565b601082901c91505b60ff821615614604576007190161460c565b600882901c91505b600f82161561461e5760031901614626565b600482901c91505b60038216156146385760011901614640565b600282901c91505b6001821615612beb5760001901919050565b6000836001600160a01b0316856001600160a01b03161115614672579293925b8161469f5761469a836001600160801b03168686036001600160a01b0316600160601b6132d9565b6146c2565b6146c2836001600160801b03168686036001600160a01b0316600160601b6141a9565b90505b949350505050565b6000836001600160a01b0316856001600160a01b031611156146ed579293925b7bffffffffffffffffffffffffffffffff000000000000000000000000606084901b166001600160a01b03868603811690871661472957600080fd5b8361475957866001600160a01b031661474c8383896001600160a01b03166132d9565b8161475357fe5b0461477f565b61477f6147708383896001600160a01b03166141a9565b886001600160a01b0316614cf7565b979650505050505050565b600080856001600160a01b0316116147a157600080fd5b6000846001600160801b0316116147b757600080fd5b816147c95761469a8585856001614d02565b6146c28585856001614de3565b600080856001600160a01b0316116147ed57600080fd5b6000846001600160801b03161161480357600080fd5b816148155761469a8585856000614de3565b6146c28585856000614d02565b61482a61564a565b600085600001518503905060405180608001604052808663ffffffff1681526020018263ffffffff168660020b0288602001510160060b81526020016000856001600160801b03161161487e576001614880565b845b6001600160801b031673ffffffff00000000000000000000000000000000608085901b16816148ab57fe5b048860400151016001600160a01b0316815260200160011515815250915050949350505050565b6148da61564a565b6148e261564a565b888561ffff1661ffff81106148f357fe5b60408051608081018252919092015463ffffffff81168083526401000000008204600690810b810b900b6020840152600160581b82046001600160a01b031693830193909352600160f81b900460ff1615156060820152925061495890899089614ed8565b15614990578663ffffffff16826000015163ffffffff16141561497a57613510565b8161498783898988614822565b91509150613510565b888361ffff168660010161ffff16816149a557fe5b0661ffff1661ffff81106149b557fe5b60408051608081018252929091015463ffffffff811683526401000000008104600690810b810b900b60208401526001600160a01b03600160581b8204169183019190915260ff600160f81b90910416151560608201819052909250614a6c57604080516080810182528a5463ffffffff811682526401000000008104600690810b810b900b6020830152600160581b81046001600160a01b031692820192909252600160f81b90910460ff161515606082015291505b614a7b88836000015189614ed8565b614ab2576040805162461bcd60e51b815260206004820152600360248201526213d31160ea1b604482015290519081900360640190fd5b614abf8989898887614f9b565b9150915097509795505050505050565b6000614ade60078787876141e3565b60015460025491925090600080600f87900b15614c24576000614aff612c27565b6000805460045492935090918291614b499160089186918591600160a01b810460020b9161ffff600160b81b83048116926001600160801b0390921691600160c81b900416613389565b9092509050614b8360058d8b8d8b8b87898b60007f00000000000000000000000000000000000762d10ef955d55b7d038c7a7231cc61513b565b9450614bba60058c8b8d8b8b87898b60017f00000000000000000000000000000000000762d10ef955d55b7d038c7a7231cc61513b565b93508415614bee57614bee60068d7f00000000000000000000000000000000000000000000000000000000000000c8615325565b8315614c2057614c2060068c7f00000000000000000000000000000000000000000000000000000000000000c8615325565b5050505b600080614c3660058c8c8b8a8a61538b565b9092509050614c47878a8484615437565b600089600f0b1215614c75578315614c6457614c6460058c6155cc565b8215614c7557614c7560058b6155cc565b50505050505095945050505050565b60008082600f0b12614caa57614ca5614ca085858560016146cd565b613291565b6146c5565b614cbd614ca085858560000360006146cd565b600003949350505050565b60008082600f0b12614ce457614ca5614ca08585856001614652565b614cbd614ca08585856000036000614652565b808204910615150190565b60008115614d755760006001600160a01b03841115614d3857614d3384600160601b876001600160801b03166132d9565b614d50565b6001600160801b038516606085901b81614d4e57fe5b045b9050614d6d614d686001600160a01b03881683613e0d565b6155f8565b9150506146c5565b60006001600160a01b03841115614da357614d9e84600160601b876001600160801b03166141a9565b614dba565b614dba606085901b6001600160801b038716614cf7565b905080866001600160a01b031611614dd157600080fd5b6001600160a01b0386160390506146c5565b600082614df15750836146c5565b7bffffffffffffffffffffffffffffffff000000000000000000000000606085901b168215614e91576001600160a01b03861684810290858281614e3157fe5b041415614e6257818101828110614e6057614e5683896001600160a01b0316836141a9565b93505050506146c5565b505b614e8882614e83878a6001600160a01b03168681614e7c57fe5b0490613e0d565b614cf7565b925050506146c5565b6001600160a01b03861684810290858281614ea857fe5b04148015614eb557508082115b614ebe57600080fd5b808203614e56614d68846001600160a01b038b16846141a9565b60008363ffffffff168363ffffffff1611158015614f0257508363ffffffff168263ffffffff1611155b15614f1e578163ffffffff168363ffffffff1611159050613382565b60008463ffffffff168463ffffffff1611614f46578363ffffffff1664010000000001614f4e565b8363ffffffff165b64ffffffffff16905060008563ffffffff168463ffffffff1611614f7f578363ffffffff1664010000000001614f87565b8363ffffffff165b64ffffffffff169091111595945050505050565b614fa361564a565b614fab61564a565b60008361ffff168560010161ffff1681614fc157fe5b0661ffff169050600060018561ffff16830103905060005b506002818301048961ffff87168281614fee57fe5b0661ffff8110614ffa57fe5b60408051608081018252929091015463ffffffff811683526401000000008104600690810b810b900b60208401526001600160a01b03600160581b8204169183019190915260ff600160f81b9091041615156060820181905290955061506557806001019250614fd9565b898661ffff16826001018161507657fe5b0661ffff811061508257fe5b60408051608081018252929091015463ffffffff811683526401000000008104600690810b810b900b60208401526001600160a01b03600160581b8204169183019190915260ff600160f81b909104161515606082015285519094506000906150ed908b908b614ed8565b905080801561510657506151068a8a8760000151614ed8565b15615111575061512e565b8061512157600182039250615128565b8160010193505b50614fd9565b5050509550959350505050565b60028a810b900b600090815260208c90526040812080546001600160801b031682615166828d6135ef565b9050846001600160801b0316816001600160801b031611156151b4576040805162461bcd60e51b81526020600482015260026024820152614c4f60f01b604482015290519081900360640190fd5b6001600160801b03828116159082161581141594501561528a578c60020b8e60020b1361525a57600183018b9055600283018a90556003830180547fffffffffff0000000000000000000000000000000000000000ffffffffffffff166701000000000000006001600160a01b038c16021766ffffffffffffff191666ffffffffffffff60068b900b161763ffffffff60d81b1916600160d81b63ffffffff8a16021790555b6003830180547effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790555b82546001600160801b0319166001600160801b038216178355856152d35782546152ce906152c990600160801b9004600f90810b810b908f900b6132c3565b613f58565b6152f4565b82546152f4906152c990600160801b9004600f90810b810b908f900b6132a7565b8354600f9190910b6001600160801b03908116600160801b0291161790925550909c9b505050505050505050505050565b8060020b8260020b8161533457fe5b0760020b1561534257600080fd5b60008061535d8360020b8560020b8161535757fe5b056144b6565b600191820b820b60009081526020979097526040909620805460ff9097169190911b90951890945550505050565b600285810b80820b60009081526020899052604080822088850b850b83529082209193849391929184918291908a900b126153d1575050600182015460028301546153e4565b8360010154880391508360020154870390505b6000808b60020b8b60020b121561540657505060018301546002840154615419565b84600101548a0391508460020154890390505b92909803979097039b96909503949094039850939650505050505050565b6040805160a08101825285546001600160801b0390811682526001870154602083015260028701549282019290925260038601548083166060830152600160801b900490911660808201526000600f85900b6154d65781516001600160801b03166154ce576040805162461bcd60e51b815260206004820152600260248201526104e560f41b604482015290519081900360640190fd5b5080516154e5565b81516154e290866135ef565b90505b60006155098360200151860384600001516001600160801b0316600160801b6132d9565b9050600061552f8460400151860385600001516001600160801b0316600160801b6132d9565b905086600f0b6000146155565787546001600160801b0319166001600160801b0384161788555b60018801869055600288018590556001600160801b03821615158061558457506000816001600160801b0316115b156155c2576003880180546001600160801b031981166001600160801b039182168501821617808216600160801b9182900483168501909216021790555b5050505050505050565b600290810b810b6000908152602092909252604082208281556001810183905590810182905560030155565b806001600160a01b0381168114612beb57600080fd5b6040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c081019190915290565b6040805160808101825260008082526020820181905291810182905260608101919091529056fea164736f6c6343000706000a03d17782c4cd36d05cdd223274e6f11f0bae006ace5dc893fe20d6aa29a4a3803903d1bb547bd6b5cbbea0f2fc3cc2aacbc58aabb8ba8656a4edcade9b7cff7bac7d03338a98e9989d7321d955cb2712ef2b05aef2828ed20e037a1ff05acd11b25d440337c09af6df0572cab700d07a6d4e17094409a90a622762065c74960eef6776ff0386de23cebd3b70eac96010c074dc8c528f7cb6c1925e0ef1d06a376f7bd3659a03c14777cc17916270deb514fee8cc463ff65551890a7c9816b5fe4338b703e6c00361066613e1f94d0d4345de64943b0be36fa2c8eeeef30209a965b84963ca8ff8031b54c9f810cd83202cee9ec361facb891cb4cfe6c08b6c1a77417e0852b03bd4033fb46b08ba86d1852f036d8c589578b66658493ecfc0a0784733a2ac2c610b1f037faa4d4eb9f99114a31dad6303c681191f6c2f22156e3d6d62ed421777380e97037d9f1fe27819233f385f571cb1b7f0df12930c9e9116143d509fb1911768c68b00581f02cb626d87046d8f6ee8ce20a08a646569bc433816e3507aa4385bbed101ae4e37e392663b4284210f7cbc6e8fab00581f02ecd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563581f010000010001000000441d0000000000000002642f000ae0305c97f5ccc3480219202000582003045a568ef3306cfd478deedd5ad151bbd143fdaf9abd51ca5c9bc2910cbcd05820ffffffffffffffffffffffffffffffffffabc8aaaa7faa5075e689c651ee00a9005820035fdd5b26e05594ab7f4573059734bc150752e47a50ac3c87b293ff60884b605004622dfafcdc2afb0d7aba966d2fca53005820039b80675edea1c97981c9ebe0973eb1fad5c785805af8052c3296461b55c4d0502cab60911e85c3d6b0eb5e9d0a1f2cd600582003e0745acaf73ddb5cc0b50544b65d05b21296b9f949a5c57f6839ee35cc7c105016a1f203601b8f04f1af74efd612172200582003f394fd8f898c934a91ee099b20c4220642002a802449a005ec6ecd04e1bae04f100157e1f8f82dd92e4f9c64dbe93803273cbc88dd8f6fef719fdcecb81a354c550cdd80fa750b74a202e9dc195346ad005820038ba7a39de05855ed18b20804f6c109c88c4654881536f73b8780815e9680f04fb3b56ab5d750c9877845ce6de3e93c0219766103a2588a2798a643741f7229005c93d8d7e0378887d7e019180f0671fb7b0daa3603efc27a949a5d727ccc7b4c152371d523f333c3e3c6ec41315b359729b8e77c30037b8984fe467633cc7930279b4952931018aeeb2c03335ef388287ad63b67c1ab039651e9a084d45fe30e8cfe4828a82b675176a58c84ae923e4907b00dc6302528039a2f98d9985f887de8fcafe355f0cb4e8c4737c6ab82be4d61e4fbebed3bc74d032917e09a215b532e77e7f11460834bb236c003d85da1d389208718ce58e81e750219ffff03112c019ac03a479740d3190de2e5786bab7fe86d3a87ec7f0cbb7bfdb37386f303e5d3f5040a637aa060111977bbbb22b1cf0c6c051dc4c99d0f56fae02ea3d98400582003fb44e3814ad2f97c355d30510921889d3b51c59bb2c833fe3f1b8834c989a05001e56d003d951e9f4eec6cefcd2c567800582003787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace050687e96f3eda637b1b9355b3d8a3de8e00058200350e533c0869bb8b21a47620a45b5269e041d538307ced18c6907c4112797605005cba62c28a4c1f716c9e25c1654b42a0058200324de1ff205b1e2e88f2a8b50d7d4b26235466bd23a5e71ea522f6579d988905820fffffffffffffffffffffffffffffffffdd0da2dad5a4673eb52547fb9791f12005820033714666fd6ff655a0ecd1548849423e04708014489371bf1d236d94bf6d3004805cd2318edbd14eb00582003434c8f71a411e0406d20ca91402a2f8017a7d300541032b08acf5de524e4e050021fd8d596112d85211cdc49a52addc20058200302d66bd6c59e40f948d57f037f489a5c9593dc8d50d50335be0622b98720805003005e435cce05767f425575c6c2fa8500582003f6a3a292d84b9b790282e53631d17b65405330585aaaf9ec67ad69f3d90f304f681354b920daf8d920149a09a86d0602199ce303a3e2828797e362aecae7f10515a1b12872e9f9e6f3ad68efb08fa9cdbe49461e030e1ac2a40e1773010a5b1dff658b6d0944134c347159a4f0feacd1dc1d46aba603ba036f38146114b9a61e7ab4f3c9a5118109e5045675f3e8fa1c1d68cb015462037fe35171803c405a480f91c7d2d986657c43f5c08467b049b78e378172b7b193030cccba7b57f509c3cb416c1afcae972b474f0da620aa5110725ad4d1ac4ececf03023508b931c7981574fefc7a77e14f29efb8c2fcb0c3816e53eb8f76c5e6291103a1e5e82f80fcaed0cb0ab828114df618fc6f0d62442f9b845935841646c1dc1d035196e7799facc0c78e231475f707c75dfc9b0e1141d7394d438ad30a31ffd98e036b8b867f824c816767e95b09f2a54763af3b807f387530e950806bf7552145b00303db908b36e289ae7b6f0873c7b0191a56a87668a9242ab2f6d12b2d137bcb3f034b47319eda20016069524f90ec904f3f97a4af2102a329d1769bbc8fdc19007b030822662fd4036dc70dfe7695626dbbd73dcbfe2007579afc6884849d3f6a4c7b0359d60a4ebbcf1f1b9aec2693e4208f9c2b9658f7ce07127d8043bc3ab6a547b603f619032baeb8f496342bd6b9eebb4e5a915de1a8e348f629a36c91bf8db0333c036b4d14477a4f27b352754db65610e600f9c0b4c0b71e3d9d793c238795731c370219ffff031bd47238d8bddcf384ad8566ec0d6b0d74b3450592c2a0982a974456a1e8a0f603f55a996c733d89c68ab21720e0e8180edc292b3d0c65c86357e81f64d2d7ddd803d2d47f1512a8f2d838b3d259e12b64d1ba99450d512642991440182c05aabc64037dcc6ca7e612eae155d5501a9f8d2f07c50f226d93b5ccb49eb0ab1cb3a35e160397fc2f4af53cb44f081df2f58de1ac7bdbf4c68b100dc1c9c2e4715b88cd2990030719f2233b134ea32f14094f33d137a8cdfb281384586ed7b28c128c4d6c4b9f03bca80a83dc94e4b907bc490772c462c6d62ff7c8bb3e6b1d42bc754495d2b16403496d83088e932f9f91cda9726e6a5f2e299bf8dc773a35b1603d948c9c5189850326a9ce54fc6c699cb12bec15ea9211e3206fd0a819cf5dc59f505f4353791896033fc4ad7d6497abfbf2ea5fbeae7c34f202ef965582c361ab8716eb9cd7eb6ad80320adaef509459786c17d48fcbaba1f2dce59d3dc9a229225b131db24a9b55d6303d8073b72e0142845326fec7ff529d7caa30389a009e093c4d9d5a5a81fd38b78033f2d7f373352561557d592b1ff25be725a740e079b79d8d8f723a8fb9f83687a031ab5d62d0d4b544adc462aa3d1e8f954d307cb7fc01147f0a2c5e56a6ce0b1b500582003fd805f52096804894e5a07e68210bceeab84539df9acc559aa8373666a25e04f726038fcdd9f9e0ef4408cd3310bdc005820035acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b049aab081aa14a00c686a005820039f5f0aa038d5e8d2a5704c0956593eb3f091c784484a4231b0fe5dd11cc9d05009c27cb3c2f65b10014179fae2db407c00582003e0fcf3cb208b9648f2f8742498b435f3f4cebca69579418d6ae60deaa7f8f05820fffffffffffffffffffffffffffffffffd2c8cb564e4737c9f1dbef942bed4ed0058200324cf913afe092ac0156af50cf4910626db47dc326445733a6776ff81dbe4305820ffffffffffffffffffffffffffffffffffb9659f3c63cc526d27961af312db0c00582003ddad8ca21662bbaf4b9eea3b3c567b39d6a9843ffe60ca8f5c4d27976042a05820fffffffffffffffffffffffffffffffff87f57bc2a0d4ae30fcfc078ecfbff5800582003a35ec2c8f1db4c0403b1102bd8d17267c562b1524f4ccc233a24959eb92a7050046d115139f7a497c793dcac8c6e8c72021924ee03fd84f52b565e0a75c6a32cff6479409a53b94c534a5c7d4570aa66b813566e9603cc4e859bb275929c9b83792e421a885df29b844f9b4c15d73055485d24a6d68d03c3daa779b945c1d000f5a925e3f09a1f115970cc2b6207b7c5758351382c1f60034f6fc5a0b4648cb2f725e4173c9508c41461f5e57d7e4f7cf8642dbd1474ccb10331c2111911e565873a4caaf841a5d9d3018a598b42e21736ef834a42c25f5abc0219ffff03cf06a6af59aac9ba0400b7c54caef60b3190ed01f63f190576e7f6788ad9976403f935fcdc07af3f5d0e74dc89f7dbfee7bb0b30155395dc3da8c7e992d3b8b079037f7010cfc513aeaaa019f6ac35f1e4390536ed6555caf3edbe7b7083e7870e5703f78e594a9e6c6a5c9cc4c450151e7081a898fc9324027b691ea5c5e06f6dcc0103e72db0dc7229be30cbc045f1fa7a2e1ddf45df55df4a7cbbbd9edcda63657af3038495915d832495e8b0423b8d2d88145f5d715105aee06e60aab1b9ef34ac7bf403fae3b6b0d6e1afb318939237aa7d38555b9bb126bbe89ad2878fbbbe91d92db0005820039b48f25d6245f73a6d02c822f16a3b341b9ee27473e32010a6ab0b428d4de048ab214e0f14da828b00582003198ec01d37a5c120bd4fe5160209d9a3fde2c9346fc768866de6f8715cbe105820fffffffffffffffffffffffffffffffff7bbe8d081ace812456729549b18106c030d10d363b46ce6ee11991d012aac6fa446e899f0f752ba93f7d76d084140570a00582003a0f4808f7f0c52440675894727c9e66265266cd1e1f5015f8b745ca2de5f30581c4000801081042502a1f7f99f57fefffbffefdfd73c80441912002a00005820030410f131d8cddc28b24725376dab161b7915d4683b8e007ea2b615f9850ab050020bad51054d791edbfb0f299cf327740219906403cdf49b5d76105b920232c5f6818018d2560318b3bd72b2e3a38ffddd231d31db005820036668a7514ff38d59f59871e4b6fc175cba51a1f1828673e9e8eb9aaf3e98b04f1a417dbc3160ad6995ebccc0eafbb700582003271299ab0ee12c543b81dbd4b4a3a97053fed5361578e4b11f8a19493bc9505820ffffffffffffffffffffffffffffffffddc99551dcbea3e4fcb0e86e5f1b68b803187d867583a70f83f6688bda2c62c139372760fc02d814dc8e84cceea9a8575e00582003778d56331fe985ae74ac425470f1ff1e13fa167a12a9fc4b0fa065ff2ae6605011757dba76046a475379d62c3def4d11005820035351d8672387b37d86550fdfc0070a44a82d0b6515e3e6fbe4e310bdbe37b04f047406d9e371eba09c51786d4a22cd00582003bc9dcfc21b7bdd18f4eaa4700547a41911ad8607abc59ac8a2935e9ee3ef40500b2e68606443fff717db43d196e13d1c005820030fab288735963648f7a27a02d18ffe056a8396518819d2520bf75923272cb04e48458fe5302ca815e9b7dead40f500581f028e06f13e7919f19acf2a388bbba12c34c25c3edb38325e8c24be4ba318a05006f80e36ca6a5280038bf2e001ebac1e00581f02a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee3582001000001b000000000000020406a780e664d939858000003b2fd004065cf60270218c00219ccc5032ad796c18ef05ebb09657ff58fd63a4c451a7c3738f81ccb74cb89777a010dc103948d17c4cbfb5da1876891aa354068d2b2c08803719112bfb8163af00314a7f803413c6b4ce2b493b2250be11e00e7e71807d9b9be73cdd5207ec50634f4eb5991036dafe2c70d779072cc0727d2559c2d7e8b4ea11d52c6e938811e3530d2f7dd9a032e887a24ceca763b439e0ad3e3d6fd2e4b3660acb9e767582369450dfb2ae7f903b0dc4fc9641c999040d29cab56f176b2bcbff1ffe4e486c1518d2722f5198a2e033e6ca7e7cd045ed42b561775aebdcf4267e70991ade486514379a7b7840df7350372c145bebab08c9d89fa49b563aaf715acf9dc0d9e341c1cadf281ad0e22eb62038a6424a48cd52e972e39c994a54d493a69b93a8f7b9d276168d9e521d2f85a58035fe7fd0775b8f8b2f17239e86670fdb0f55714a3f0d7a5b69c123c6df38ca001035d9b3e20327c516ec9381d93ead23870134a1f421f694c991d1f2ce955a7c7df03923193e139a98d3ca0b7997e2777cfe77d67b1d7c6c224e7e912d94dac5155520219ffff0219ffff05581e03c63a2b39d3291db69424bed53d4edb18e94c1ba6115e52cfe72b31f2d0070119567e031b460c826a854d61dca82f718e088b8b4c4082ffeb93752d7691bc62c51dc0280605581e039eb67ea0b38e576078f35745444edb6b6e3e019574e1d2e56b0390fa5007011bffffffffffffffff031d93f60f105899172f7255c030301c3af4564edd4a48577dbdc448aec7ddb0ac0605581e03ccc849355732a0e83ed860e8e920243a9d3ae3e7cdca85673da010c20007011bffffffffffffffff03f2bf302610e990e0039b8b855a5f692932a2a20c6651cc866a1daee56ccbd57d031b460c826a854d61dca82f718e088b8b4c4082ffeb93752d7691bc62c51dc0280605581e037bc189fdf2ef1af5046626b500dce9595590ead4c1076f50d33880adc007011bffffffffffffffff05581e03bf7c22281b5791656fa5dbdc49952c0913dfb009497ad9512ebf4ca2c00c1841471e8e6c4337658d05581e03073bc5ec910e0655b4f432c018ac43cab6cdad628de03eff36f961cde0040102198ec703fe71f6f923c7be6dae9f9d758b69d418ffa1b1377dad2c97ed67c30cd585560c03794ae0c876dacd580e09bb89fdfff9d6d2c2568a3ba3c3d3af6a7e6368c2a3b503a46f94d8422db47f7ba34ade932ea9183ac91f09da501eb965f977b49247146b031c2eeb76b3262bbb0da3cf65cbe9ad22629bb1e32d3f43c6a825300a9155d0e403fcf6008b49a07304e20f4c40d5b0053b8f55a5a113755484fdb8ca5503de8c4a03d6328c614b778654039fa290b4a639307affee8cee4febaccf402920238e2ddd03a481c6eed67d93cc9ba1f3d5047150ea1fd5607fa5473e5eea507d297d3d014a0219ffff03977dc21575479ec24f0498941309561b4211e2548ca4fd6f9ef4864c4f2b99d9033536226bcdb1ef0b4af07084d7b56dfd5e9446fdafbaec7f9869b0674bc0f12a030f18d7c6021382a8d5680fec483b86ee0a1a52444b0c1c00d38f3a8e3fa8b5ee03da1b86ac746e92a6630199116a1482362feb638dbecde99d0ed9b347a3056455031e48c3afbbc5fd1bbfed53563580122c757d9575008fe5efcc4180dfd23c2d2d03b306993e2937c87061b1657d0450ebb66563558e52364b60ffc57593bc48f1f803b8235ff301f6d2dd225fd7019d99d8c71cba1ca725fcfb2b0dc1c5b87c13deec03a28e060988ec47791d2867be167b6f1b69cd5e9d36e073a97a137aa70f0be52503cab803cb75245ba30dce44660d29e707dbdd2009fd313a205358acec342339790396e8ab3c4a77e852f1a070e88de49b736105f02654fb4cc929b76bf11a5d5e670394b182ac301057c49d7dbd072513443ce8ad770ffc4da0a2e67bf1178056d81b0219ffff031dc4ad382a0820474944ecfa57c09d28959aff2c9fe5c6a5b49ebee6f0f1efc7037a67147e1c00ed66c4d1559bae7f02b52f17c7bf41c3c60dbdb1bcb2d39ee5c003d76cad2198d33449990f7477a7492b420bba303bbd871d8e0d4dd5bbea364b7c031c3f9bf2b1cdf9be6124abc2bb1269c25142149dab3ec6b0e9a5897fd67ecac503a0c63d0b3dac49820d4c32d10871dff681d650916d63b5e635c8cec332fb0bd703d25d4ecbe9bc37338b68d39b42418132b99fd3b75c152aa1028861c556c616f90360507ccea9f4d3d41db19c6735cf671dcb95e9bd06babf4937a73f06f9b5b82f038630b0eb1ebb36bbea2cc4342975e087412f7a1b0ecc2abdd8667b93d7d5e33703c734819e0b99baa3e1a0cc561c3a712fd5998956e669869eb84747da9af1cfa70371af6fbb0dc265ebb2c426a93ce71083623d58f72bb01677dc5f3c090f58a1d90219ffff0311685a7370180524cb1df19157a555234792aa4cd7f8640a96bae6ca1ab6070d032bf840fa28d97c3d2c11f351334dd3b1d1e006e27098513cc82df0e95e8162e503f795215251d3961912625390ef0931881a93b79ef83dd76ac120dfb570c7e3b803c7c5553ffb7def0babee9bd6e908ef8a5b0bdec17323c5170cd151ffc208476303b6a88dd2e04093960b267fc15ec2bc7c7098949ebfc5e89aa3379f9e653674c803cfa7e4e5628410de9ded9a94a3bfc0efa92364a66c45364346572ad17e76c14c031224eabf1041a6876d5b159d7bb859849b147e7ce2df27c3cb2b556007f54cfa0219ffff03364b6559c7690ea7a691588295f4f746a03791a95d929abf056a905e62cba94b031767f87c1747bc72bf20749d1630b4d8a6a3f3ec2c5efb0397c8d2b1882f2b9f033640ec5bfd1d31b099f28bf24ee00207681696bb5b5f1686c38d6f51d9c60f2103833873354ef23e5031de39206fd93c894f41e492be5abdc5b50d875b146f94be0368eeeb37450f0ab1647ea98248e195454722ad72924ae1702b76c2e665488e7c034b2504d65afe59cf239935f20ff0cc4d21238e98d56ed8e62ecb23bd9a98d82703b099bb79c2d8139ba62553f8d15b9b91d82dcf44cb9eb0463b1834154ac7cd91039333459f6ef0e57274cb85160033e40afe10fe12e0debeb996e2f2152c755a3e03a54acdca2baf6d193f4d2cdf0e3b1dec7481b7cae3827400d569fab81b236902032dfa9d212b30d11c5f0fe283ea6bb1f96a6ff17c8285094579b3dafc73c19d9303a6e20dad8ee3ac1e603fd82adf0ddf1cb46543a3abcd4e15d9d1b3bb60fd694503579461dc80c5bdd7be4240d7bec05a9118bb71ebdb48b65f41ce800aac27c95503d380579902d6b06bd5a41c9b54bf02048fa108f947d33ac139920c967ec1f0a003a788fdd42968f19b864304838a0be2392a57e18797fbf53a45e51cd417cbe50403b1c82d8c79fd1fae374f7a0253bbce1e08781d1e052f6e1b5258a6999b78e48703ae6e8ae8f84895ecf8923ba1e17a11ede67a0d32c197559b84ce80da1287696903e12082db6e1d9cf3b96e752ea4d505d02338da98d4f410d0cf582fc1f5804d41032002b0b0c2875cbde643ca757693fbaab3824fbd77be1d06e30422b55d031fd103043d0abceb8917e06bac76c366bd55330b2ea4702a9b7f11a73a14d93f2002bb03f797e1b101c4362da9a1b995a34bb4025a1c01bbe55811ed72f9bff2b4c7b4e203f8a3867cf1a6407d98075813b8b6504529dedc2bb0de505153c63f8b302199480316dcc2557c0182f5e5404e880e6c992fa4507896661fa7c541b5ba5b39dd795603de80770a5f69882a420b750e8b8f1ee042d1a71592173a1652bea7327f5d4f2a03d0a0fa4fea3a3b3c5c75aa7850bd3ed496680c327cb1db823d80bd0e42759df50328a148496cccaa374b36e98e88fb5c43e7c0b9ee5b0c7ab5bf6513352ea369e003ebc919b2b16e61d5022bc8502cc057fdf904d86d1312e6ac7dea952855d2c28103c612830f92e60b444d863bf182dba243d9061a2e374ddeba6b967b2cde1ae64d05581e0354746d6a091df4f940a2b869ce99873412140bf1ab5f16dae3b9a5f8900c0247016d70140db4f005581e03d7b4a7cba46d7b5224a93107596bb581653437eef3ef3d56a3c63231100c18394705216b6d7811ee05581e03ef25d3ec4c58ee05f86e4a201af1c17b5872aa69753b5d8fd07e499cd00c01471fd0bb9557655005581e039c5fe0cea5a718c6ac75f30d923843dbbdc140b5f06b6b36de9eaf2410040105581e032569341a494cb9aed9e2db5ce56a022162d0bf4206eef5336c9868ef200c19212a4826d09457b5a35a1305581e03cabe03eed53acf52ce1dc5ba8dfb1cd4fd167db22130bb4e40260f8b300402037cd4f0e9b8cf24b193c82a5b57e2d4b6dc215ba7e1a9a99c02dc35322045360c05581e035d63d992a0085927b0496407f5229bddb79b8dbd20d5fa6df29284b620040405581e03fe3a78cc7ae3c777f68a7c7dd74df344c5fc4952438796029602f33460040105581e0302c3738e36db439ba782af9948ed23f65d580ea8a7f27b424be874c4a0040103476ce0e26df74c8ebad1ab70192f5370e100e91d4e7f6416d50fadd12210723f05581e03f4cf097ad987ae738f3fd87a06b8b7de34b60cca553357313fc8a340700c03466cfe6d0f398d02197b7d039df44fa133f95e83bf4a90bc231aa2384cc41e6b1ff26ff6176e1097d9fd263b0345a5164166d7ca748add83d08a1a4a275e2056c3bef82d07a477ec061d1e040903bad73c92171efac5a520e4b51d7f012bfca64170d6437a3b7472dc63c5ecdee903ecb7f681173349607dfc4f08cce57a4d78cd9f2132ad791a6632ffd4d0efbda60357c7b3e63e9311d7d564e98ba06f83c8d9e9a3764fcc66d30ac569f53bb125bb0336549a7d84b0cca3ea5fe0b786ec3f3cfdc31fe165c9b0640d506caceff3ddda039205c02f90636159c69417e236e213d720a9b993773601fc942a8c8b475a01a8034572550d3f6d1de6860cfd6f02b053c38265704f31bf0566a2467026dcfd8d8c03fb1feec02a7335d3d61800d5532c0fe16ac80d50313a572e32162c78b05acbe00318738d58dff4671d2513a6302d83e20e10cdaf9809261294471b6152b3af023703a3bb603b0685f06e5cd93241339ea86446a92f12019e9949849f1e1928e268f90314f965c748ddfe77998987b77d51593ad96f1cdb6efad907f6a87f1d6f6a1eac03b9ddf8d13b6701c3a0867cae25e04379e96abc55b46e8ad785d5183fe20c20ae0219ffff037be1a9fb687073d612cbe33712871c72def307b689961c7c9bedf18304513a8003ee2b78cb8249a0d4d3055d50fd4eda167eacf9b722628851275e4a61a9efab1b0379db70d0e387d4338503a930c2f3efab6abfe7b1361c3b3504cc4eaf51cbf7610343d34c8c4c4be5723ccd42f61aa9e21339e1d27d0d6c2f76f99394e6a0de72320219ffff038e4f84f6195cc1938876df332e852970ea245127c2e363336da5f9b6a7221f5f0338a6da18e54a7c8cbd7f28dc1e96e6f0e705d0f009eafee982702834c9a3672803e18e5a68ff8c052e14b65ff4cdfc70febafc37246d52c47ea0775b211bc1ac4303a7e8d3c1a03b8b4878b6561de6719acaf9059e0359a7790dce2205d2caa3c83403e37545af314b26ebc1749eefbaa44d553dc183a83c712a06f914e6a0e0a0338e03d2296853913d9824987ebd935db84ce8f41f9ef8b1a78ce7daebafb0a79e296c033a225fa4377ad3d9fb72a61ba1f8c2efd4a2b7d82d469eccdd921047d9dff4030219ffff039777e7d98e1a15a70eb2a1b3c9ee58157c2fdf6e7f009bed4560ca8b11999d8603ab405f879c7e8c28154307d99dc11211264511d6d8c499452fbf477531e4c6de0352325782268b2e879f0ebfb29978df9d64e9f2fee1c3858beed953dee806b666031376ed85e106a7585758f2a7b8f1fc57a6250d8cc05c2b9df7ead9edd2f5b9fb03153dd4a6cb1d67f24e7dae95baa2cd1bf832304b43f7e003647ff365231ee0cc031281da51c1347c69f936229528efb15b1e3f386af7dca5a622d6034917d2543103226bf9a5ff46025a636a99ec1c07c68b3c4ea6e982c2d64fad53197588662486038c420da56d0edf7a84d928d828379e77a9a61d5c9aa22a5fcf9685a82c2bc717037bc4349b8fe5674c2f77ab8cf787da1b544af7dad7c7100a75e2fcacb4d8b0ab03e1a91d6327a510c419bb9eb480a7603f051ff356dcc7370e0700150056d2226f03d402e585d9d671310e9b66c5327f9fcb63c342ae4a5d104c175c933c2f4aa02003fc3922b7dfa2b5a649b771c922df839df68a712bee8806cfefbd14f5952bc358033d2ac8255991c176e9b0aab3c83507a3ecaf083c2e46f26ed04ed98b19787423033e3284ce8ce1108f7cf14b8cee7e235c514b1981dbe6171546585ec7a67d3873034d967638893a1a112568f517f41f15df732a81e1a27815b3c96345c1339240c803b91a1e9482cdca8caff7708ea722ac02cc69675d7cd91e7bd9759f6865a511df05581e035e10ff5e818b9387c9db668b17580ebad2ce2ae40b04a6fa2b318e8e60040105581e0362738fb5147d892c08b0d65b02ff8c3bf028b09c157d98fc6d1a31dff004010328635b9b2ad0413203f351ce87de544b80858f67181e10f894b7eee66f296b71031d93f60f105899172f7255c030301c3af4564edd4a48577dbdc448aec7ddb0ac0605581e03acbb55262039bdb50143383dc5c2c9018815bccf85e42c859557fdd46007011bffffffffffffffff05581e034ed7e7b9290e2366a749c2a65fef417dca73889ce344f53f504a74e5900c01421400036215a63275c53615160db0395ca2e5919c1739a4f0d57e7e64ab3a3dd741b58b05581e03895c3319e9c6c03ae3146c40240ffd65ba105d40f09d1da211efc46e50040205581e036b55ecd060a1b87fff27d57ab897e969059e096cc1a69ffe500d58d260040305581d02fff4f457f33dec751c06c3bad17bd28bbeb00499198355f345d4203c0849152de40539242f9e7e0338ac76c6b894b21842674517d8e9f72ab3926490a188f091e2a96084cd5dac4103bfa6f85242f2d9dd359c732732b53a9b0a9dd82bae0397b59b4120453134ada905581d022ec333acf02cbcdf3d719776a20a66883537123429184eb29f685fbc07011bffffffffffffffff02183005581e031c8ac17bd18c0ac59cd803c7bf2e9819307235169f2503ade3dda4e1c00c01471dde3a765ec20002197f92036ffc24be38d4a7cddefe7b00c6adbc83d818f5125ffc2fb697dda7b7564ff04e03cb50aa8845608636596143b3f6bd78dc603064fa95bcd36aca250673a0ab3373036a4d3d4739cfccd1f95405a2928a565bade9f964f642f4ae051949d6dcf89f6403ad15af9ef2228169ed5e56c37b50ad96b49e845177f5b4d010ff9173807f401103b1815d03175990d53d3bb54a390645a08545ce2ac83213327ff2e7843b49748e0365f047ded3ce3c5e811a51d708037aa9d7ff214ee714898d79664f23e8e67a6b036288f1e2b8c4edc28a00562e8596d9fd8a501d0001cad5303b370470db47f832036ae5ca5112cae34e8eb7dadd64bdd02a6dfd5b3e570618d78ab898552e98413b039c67d015188a13ab81cbc92ef694739f697a78d45e2942186e8726682b876a050316023d1bde4ef10f10d04cb3ab15510446dfc94ffea24f8261fd26513aa7727c035e829f145141b06124f4f46462ccabc8a9fdb1f7855e3a1e346bdfb08f87679503ccfac1f8355676751ff85cbe6ae1703fc2ca4cfcc33a80e466182dd55c3bc01d035327d9a3b8eb6cfdbe26a4c2c78597488139a5386b440be951bb636dfa93d74303c25cfdad9b35441134741eb8aba198ede579324c14da9b8432c30ea9f75caf1903ebdca6964c9d382504be3f2d4aa27104c26dee5f09dbd6fe9e857d62a11b670e0219ffff03b538060ce209273b322661ec5ca2e0b3ceca19678486272438189716899c4ffa03f2ba31e6d6fc83904c4039060bfad864e5034e2729c9c032e37b10e116ef21d9039b756b32a4ff2de285080994c8ce20ef4f8b422fe53686999b9638e42b071fd903c2517c71395213d9757444b81f62f9d648fee471315625a7b71087030fb606940386e87a2ca1342c617712768367f896e17a7dc1f46a66a3028467d6cb0367307803c027d634174a616d51f531d7120d77307ff15d8dfb3858ba6f1c124d06cecc350219ffff0304974c24f184cc3ac6b5c213ae5f2b4e920cb1afbbeb8d56a46505b9ff044ef70330ae87fcd4abd16cbc379495e479111a156c46abb3e53fb973d08fd82088f37203e494c982b7ccae6500cca80e6f09d4f96a5c714baf8e838ff251c099f09fb4fb037cba6bd53998591ad52a21d1bb7219878ad777e6eb2376c357548acb62d0b412039df6eb33d7b1ddd3ecd6a64b6007b5c906f4c6fcef21f6285365b47ace73fe95035a1963c24fcb5a6bc49c50c7743aec2cf59bbf129963040b6f5d3203d00929b3034cfa5eedfac55af9ac406824e5cc6846b0dcb9b62d93336a54c2f6e2bbcf74c003931de92f69c5ed20cba72e577cab4485ae3279432221110236182e4881662d7b03de9256ae3deda6eca5a956c62ec0cde32baf832b3d3622c68568dfe9f361e54803809399dc7754323ad003dab6d6e9c64683de650ace53e5e1c25479e386db6bd703788ad1f6248924a8a45ce462d7507254bcccd3776c96f9ab4d76b34c0294e372030295e531f41304acba79464ae6360ffe5f9af4ac502728c9f0f4fb8e535bb5cf03b8ff8dc28758b91e4df6c0f4b698a090d085ba8f692f4a901a52be54a5ee8d9b03cd062f1e61d91a810f358d89f6f5451e22252f9d9725c8876a33e5ee6dd635c80219ffff03396ac8dfd43ca5d01413bc6f8df65025133f0c01dbd7a0d8a704e089d3a0d2c303e23134108131d7cb0f65f56ab836262fab3c87b6e7cb2d438985093fb9b1aed80219ffff03179d8b1563fa8a5680d0b6c97e173fccb267e66dbc63188f30b42f60d81d02bb035ecb9a9cd095d029e5005fc4c0eea8864c2877668942633a372947380d493255033fa36fc5639c13f1dc4a7a16fb09fd92db8fc4db3f457a2a473a4dbc4735e0de037a5d9023ac6e87ebdad673236f3566589d793577e789b042da2ed5cf83b6581203d20c2d9b5b87923d7fdcf6e244a679a7379272bdab59f43cd173538102253d3a033db8efc8c221fe1f24bca291900adef5d9b32346261bfa1769eea16da75d28b0032640b075d2b6c362828fbf9559d61151e84d2e05dde525cba9cdc7f9aede24ba030011f9dd04d04658217da6f1caf48df20559f5e0b64978a309bd66c62fd258f303ab3ad083e63a3a4af637e0b2f300120071339a46a093fb2af292338e0fccedc003a87f12207f8030b45d1c78b17d2d9a449ee2cc54d1189794ac8797ebdce8a2b50337d5f260ca675994a7f39492b6a62c9ee685e2c7c2dee1f326657bed34d039e2032ad03a5db7b1aa15164a1044c75059f51d79f8b83b7a591e9a8e604e9257d6ff037b952ad125c30efeb0990ec3e9bc65ae6f0eb422490d45d5fc4092d36a967ded03ee5641774d1aa19c0945643c8e828c1063c658cb214e71bdb0d5336d65774d13032fc920942582fe8200b5228d4968f835799c5824659abb316bbb9ea91a066cc003609bfd3984a82f25c2f4faac561f101a4a46ba4b8500e1b5f280c5c2281abc55035fbb8896e284b6215e15e66d7e8ad6d54f4210a7deb2a8a8ac62be969cb017570321b42fc3e00d3c6a7829e0bf6b7016347ee78ec9d7369f7049d4f1d8251c06810303b303ec83ef8a1cbb53c3f9f74c73fbf62405b70a9149b01cb15ecf6a9045f1038509ff8dac14e41b233e39fc3c0736657e20183c42f7744891c06abd8f0652190372276fe284394b92af71a521c89d95949582153046e0be2717fe63d1b27bb8f903be0091b83cce2b6d8d0bc358025ca6a0e07f426205e662490786da5a95ec32cc03d93234ed3a82b09d3cbafa02562b56faa216e786701ebcc09cbdf959bd8a467403b54f1b4131fc4f441424069d2e0cca3321755b5ad1faea1eec21f277541f3ab2034a5d87d6c9ffe713516b77770d4073b02ad7f206f5c426ff93fc9690c54968e603c70a8010104e670988536f7e591355ee3bbb074cd33e5edef03c2c59fd353bdc039580312c4e71785e78736943d44242b373ecacb33ee03677dfeb1bc4fd63006903dee5e17a9d94b4adec65d4c445f8603acfbd99ad4aa9ae8eddfc86144abe979203c484df6afe59c7ea1d3377964d84f771355b1d3301472a1b97044d479f620fb005581e03bd37fd152ca264eb62ca42c9762fe745c21220c74b28a045255cf6d5b00c014709faa779c79000045955b760806040526004361061018f5760003560e01c80638803dbee116100d6578063c45a01551161007f578063e8e3370011610059578063e8e3370014610c71578063f305d71914610cfe578063fb3bdb4114610d51576101d5565b8063c45a015514610b25578063d06ca61f14610b3a578063ded9382a14610bf1576101d5565b8063af2979eb116100b0578063af2979eb146109c8578063b6f9de9514610a28578063baa2abde14610abb576101d5565b80638803dbee146108af578063ad5c464814610954578063ad615dec14610992576101d5565b80634a25d94a11610138578063791ac94711610112578063791ac947146107415780637ff36ab5146107e657806385f8c25914610879576101d5565b80634a25d94a146105775780635b0d59841461061c5780635c11d7951461069c576101d5565b80631f00ca74116101695780631f00ca74146103905780632195995c1461044757806338ed1739146104d2576101d5565b806302751cec146101da578063054d50d41461025357806318cbafe51461029b576101d5565b366101d5573373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc216146101d357fe5b005b600080fd5b3480156101e657600080fd5b5061023a600480360360c08110156101fd57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020810135916040820135916060810135916080820135169060a00135610de4565b6040805192835260208301919091528051918290030190f35b34801561025f57600080fd5b506102896004803603606081101561027657600080fd5b5080359060208101359060400135610f37565b60408051918252519081900360200190f35b3480156102a757600080fd5b50610340600480360360a08110156102be57600080fd5b8135916020810135918101906060810160408201356401000000008111156102e557600080fd5b8201836020820111156102f757600080fd5b8035906020019184602083028401116401000000008311171561031957600080fd5b919350915073ffffffffffffffffffffffffffffffffffffffff8135169060200135610f4c565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561037c578181015183820152602001610364565b505050509050019250505060405180910390f35b34801561039c57600080fd5b50610340600480360360408110156103b357600080fd5b813591908101906040810160208201356401000000008111156103d557600080fd5b8201836020820111156103e757600080fd5b8035906020019184602083028401116401000000008311171561040957600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550611364945050505050565b34801561045357600080fd5b5061023a600480360361016081101561046b57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135811691602081013582169160408201359160608101359160808201359160a08101359091169060c08101359060e081013515159060ff610100820135169061012081013590610140013561139a565b3480156104de57600080fd5b50610340600480360360a08110156104f557600080fd5b81359160208101359181019060608101604082013564010000000081111561051c57600080fd5b82018360208201111561052e57600080fd5b8035906020019184602083028401116401000000008311171561055057600080fd5b919350915073ffffffffffffffffffffffffffffffffffffffff81351690602001356114d8565b34801561058357600080fd5b50610340600480360360a081101561059a57600080fd5b8135916020810135918101906060810160408201356401000000008111156105c157600080fd5b8201836020820111156105d357600080fd5b803590602001918460208302840111640100000000831117156105f557600080fd5b919350915073ffffffffffffffffffffffffffffffffffffffff8135169060200135611669565b34801561062857600080fd5b50610289600480360361014081101561064057600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020810135916040820135916060810135916080820135169060a08101359060c081013515159060ff60e082013516906101008101359061012001356118ac565b3480156106a857600080fd5b506101d3600480360360a08110156106bf57600080fd5b8135916020810135918101906060810160408201356401000000008111156106e657600080fd5b8201836020820111156106f857600080fd5b8035906020019184602083028401116401000000008311171561071a57600080fd5b919350915073ffffffffffffffffffffffffffffffffffffffff81351690602001356119fe565b34801561074d57600080fd5b506101d3600480360360a081101561076457600080fd5b81359160208101359181019060608101604082013564010000000081111561078b57600080fd5b82018360208201111561079d57600080fd5b803590602001918460208302840111640100000000831117156107bf57600080fd5b919350915073ffffffffffffffffffffffffffffffffffffffff8135169060200135611d97565b610340600480360360808110156107fc57600080fd5b8135919081019060408101602082013564010000000081111561081e57600080fd5b82018360208201111561083057600080fd5b8035906020019184602083028401116401000000008311171561085257600080fd5b919350915073ffffffffffffffffffffffffffffffffffffffff8135169060200135612105565b34801561088557600080fd5b506102896004803603606081101561089c57600080fd5b5080359060208101359060400135612525565b3480156108bb57600080fd5b50610340600480360360a08110156108d257600080fd5b8135916020810135918101906060810160408201356401000000008111156108f957600080fd5b82018360208201111561090b57600080fd5b8035906020019184602083028401116401000000008311171561092d57600080fd5b919350915073ffffffffffffffffffffffffffffffffffffffff8135169060200135612532565b34801561096057600080fd5b50610969612671565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b34801561099e57600080fd5b50610289600480360360608110156109b557600080fd5b5080359060208101359060400135612695565b3480156109d457600080fd5b50610289600480360360c08110156109eb57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020810135916040820135916060810135916080820135169060a001356126a2565b6101d360048036036080811015610a3e57600080fd5b81359190810190604081016020820135640100000000811115610a6057600080fd5b820183602082011115610a7257600080fd5b80359060200191846020830284011164010000000083111715610a9457600080fd5b919350915073ffffffffffffffffffffffffffffffffffffffff8135169060200135612882565b348015610ac757600080fd5b5061023a600480360360e0811015610ade57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135811691602081013582169160408201359160608101359160808201359160a08101359091169060c00135612d65565b348015610b3157600080fd5b5061096961306f565b348015610b4657600080fd5b5061034060048036036040811015610b5d57600080fd5b81359190810190604081016020820135640100000000811115610b7f57600080fd5b820183602082011115610b9157600080fd5b80359060200191846020830284011164010000000083111715610bb357600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550613093945050505050565b348015610bfd57600080fd5b5061023a6004803603610140811015610c1557600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020810135916040820135916060810135916080820135169060a08101359060c081013515159060ff60e082013516906101008101359061012001356130c0565b348015610c7d57600080fd5b50610ce06004803603610100811015610c9557600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135811691602081013582169160408201359160608101359160808201359160a08101359160c0820135169060e00135613218565b60408051938452602084019290925282820152519081900360600190f35b610ce0600480360360c0811015610d1457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020810135916040820135916060810135916080820135169060a001356133a7565b61034060048036036080811015610d6757600080fd5b81359190810190604081016020820135640100000000811115610d8957600080fd5b820183602082011115610d9b57600080fd5b80359060200191846020830284011164010000000083111715610dbd57600080fd5b919350915073ffffffffffffffffffffffffffffffffffffffff81351690602001356136d3565b6000808242811015610e5757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b610e86897f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28a8a8a308a612d65565b9093509150610e96898685613b22565b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff16632e1a7d4d836040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b158015610f0957600080fd5b505af1158015610f1d573d6000803e3d6000fd5b50505050610f2b8583613cff565b50965096945050505050565b6000610f44848484613e3c565b949350505050565b60608142811015610fbe57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc21686867fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff810181811061102357fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146110c257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f556e69737761705632526f757465723a20494e56414c49445f50415448000000604482015290519081900360640190fd5b6111207f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f89888880806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250613f6092505050565b9150868260018451038151811061113357fe5b60200260200101511015611192576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b815260200180615508602b913960400191505060405180910390fd5b611257868660008181106111a257fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff163361123d7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8a8a60008181106111f157fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff168b8b600181811061121b57fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff166140c6565b8560008151811061124a57fe5b60200260200101516141b1565b61129682878780806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250309250614381915050565b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff16632e1a7d4d836001855103815181106112e257fe5b60200260200101516040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b15801561132057600080fd5b505af1158015611334573d6000803e3d6000fd5b50505050611359848360018551038151811061134c57fe5b6020026020010151613cff565b509695505050505050565b60606113917f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8484614608565b90505b92915050565b60008060006113ca7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8f8f6140c6565b90506000876113d9578c6113fb565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b604080517fd505accf00000000000000000000000000000000000000000000000000000000815233600482015230602482015260448101839052606481018c905260ff8a16608482015260a4810189905260c48101889052905191925073ffffffffffffffffffffffffffffffffffffffff84169163d505accf9160e48082019260009290919082900301818387803b15801561149757600080fd5b505af11580156114ab573d6000803e3d6000fd5b505050506114be8f8f8f8f8f8f8f612d65565b809450819550505050509b509b9950505050505050505050565b6060814281101561154a57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b6115a87f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f89888880806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250613f6092505050565b915086826001845103815181106115bb57fe5b6020026020010151101561161a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b815260200180615508602b913960400191505060405180910390fd5b61162a868660008181106111a257fe5b61135982878780806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250899250614381915050565b606081428110156116db57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc21686867fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff810181811061174057fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146117df57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f556e69737761705632526f757465723a20494e56414c49445f50415448000000604482015290519081900360640190fd5b61183d7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8988888080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525061460892505050565b9150868260008151811061184d57fe5b60200260200101511115611192576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260278152602001806154986027913960400191505060405180910390fd5b6000806118fa7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8d7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26140c6565b9050600086611909578b61192b565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b604080517fd505accf00000000000000000000000000000000000000000000000000000000815233600482015230602482015260448101839052606481018b905260ff8916608482015260a4810188905260c48101879052905191925073ffffffffffffffffffffffffffffffffffffffff84169163d505accf9160e48082019260009290919082900301818387803b1580156119c757600080fd5b505af11580156119db573d6000803e3d6000fd5b505050506119ed8d8d8d8d8d8d6126a2565b9d9c50505050505050505050505050565b8042811015611a6e57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b611afd85856000818110611a7e57fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1633611af77f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f89896000818110611acd57fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff168a8a600181811061121b57fe5b8a6141b1565b600085857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101818110611b2d57fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166370a08231856040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b158015611bc657600080fd5b505afa158015611bda573d6000803e3d6000fd5b505050506040513d6020811015611bf057600080fd5b50516040805160208881028281018201909352888252929350611c32929091899189918291850190849080828437600092019190915250889250614796915050565b86611d368288887fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101818110611c6557fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166370a08231886040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b158015611cfe57600080fd5b505afa158015611d12573d6000803e3d6000fd5b505050506040513d6020811015611d2857600080fd5b50519063ffffffff614b2916565b1015611d8d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b815260200180615508602b913960400191505060405180910390fd5b5050505050505050565b8042811015611e0757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc21685857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101818110611e6c57fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614611f0b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f556e69737761705632526f757465723a20494e56414c49445f50415448000000604482015290519081900360640190fd5b611f1b85856000818110611a7e57fe5b611f59858580806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250309250614796915050565b604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905160009173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc216916370a0823191602480820192602092909190829003018186803b158015611fe957600080fd5b505afa158015611ffd573d6000803e3d6000fd5b505050506040513d602081101561201357600080fd5b5051905086811015612070576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b815260200180615508602b913960400191505060405180910390fd5b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff16632e1a7d4d826040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b1580156120e357600080fd5b505af11580156120f7573d6000803e3d6000fd5b50505050611d8d8482613cff565b6060814281101561217757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff16868660008181106121bb57fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461225a57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f556e69737761705632526f757465723a20494e56414c49445f50415448000000604482015290519081900360640190fd5b6122b87f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f34888880806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250613f6092505050565b915086826001845103815181106122cb57fe5b6020026020010151101561232a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b815260200180615508602b913960400191505060405180910390fd5b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1663d0e30db08360008151811061237357fe5b60200260200101516040518263ffffffff1660e01b81526004016000604051808303818588803b1580156123a657600080fd5b505af11580156123ba573d6000803e3d6000fd5b50505050507f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1663a9059cbb61242c7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f89896000818110611acd57fe5b8460008151811061243957fe5b60200260200101516040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b1580156124aa57600080fd5b505af11580156124be573d6000803e3d6000fd5b505050506040513d60208110156124d457600080fd5b50516124dc57fe5b61251b82878780806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250899250614381915050565b5095945050505050565b6000610f44848484614b9b565b606081428110156125a457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b6126027f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8988888080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525061460892505050565b9150868260008151811061261257fe5b6020026020010151111561161a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260278152602001806154986027913960400191505060405180910390fd5b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc281565b6000610f44848484614cbf565b6000814281101561271457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b612743887f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28989893089612d65565b604080517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015290519194506127ed92508a91879173ffffffffffffffffffffffffffffffffffffffff8416916370a0823191602480820192602092909190829003018186803b1580156127bc57600080fd5b505afa1580156127d0573d6000803e3d6000fd5b505050506040513d60208110156127e657600080fd5b5051613b22565b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff16632e1a7d4d836040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b15801561286057600080fd5b505af1158015612874573d6000803e3d6000fd5b505050506113598483613cff565b80428110156128f257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff168585600081811061293657fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146129d557604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f556e69737761705632526f757465723a20494e56414c49445f50415448000000604482015290519081900360640190fd5b60003490507f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b158015612a4257600080fd5b505af1158015612a56573d6000803e3d6000fd5b50505050507f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1663a9059cbb612ac87f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f89896000818110611acd57fe5b836040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b158015612b3257600080fd5b505af1158015612b46573d6000803e3d6000fd5b505050506040513d6020811015612b5c57600080fd5b5051612b6457fe5b600086867fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101818110612b9457fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166370a08231866040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b158015612c2d57600080fd5b505afa158015612c41573d6000803e3d6000fd5b505050506040513d6020811015612c5757600080fd5b50516040805160208981028281018201909352898252929350612c999290918a918a918291850190849080828437600092019190915250899250614796915050565b87611d368289897fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101818110612ccc57fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166370a08231896040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b158015611cfe57600080fd5b6000808242811015612dd857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b6000612e057f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8c8c6140c6565b604080517f23b872dd00000000000000000000000000000000000000000000000000000000815233600482015273ffffffffffffffffffffffffffffffffffffffff831660248201819052604482018d9052915192935090916323b872dd916064808201926020929091908290030181600087803b158015612e8657600080fd5b505af1158015612e9a573d6000803e3d6000fd5b505050506040513d6020811015612eb057600080fd5b5050604080517f89afcb4400000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff888116600483015282516000938493928616926389afcb44926024808301939282900301818787803b158015612f2357600080fd5b505af1158015612f37573d6000803e3d6000fd5b505050506040513d6040811015612f4d57600080fd5b50805160209091015190925090506000612f678e8e614d9f565b5090508073ffffffffffffffffffffffffffffffffffffffff168e73ffffffffffffffffffffffffffffffffffffffff1614612fa4578183612fa7565b82825b90975095508a871015613005576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806154bf6026913960400191505060405180910390fd5b8986101561305e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806154256026913960400191505060405180910390fd5b505050505097509795505050505050565b7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f81565b60606113917f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8484613f60565b60008060006131107f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8e7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26140c6565b905060008761311f578c613141565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b604080517fd505accf00000000000000000000000000000000000000000000000000000000815233600482015230602482015260448101839052606481018c905260ff8a16608482015260a4810189905260c48101889052905191925073ffffffffffffffffffffffffffffffffffffffff84169163d505accf9160e48082019260009290919082900301818387803b1580156131dd57600080fd5b505af11580156131f1573d6000803e3d6000fd5b505050506132038e8e8e8e8e8e610de4565b909f909e509c50505050505050505050505050565b6000806000834281101561328d57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b61329b8c8c8c8c8c8c614ef2565b909450925060006132cd7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8e8e6140c6565b90506132db8d3383886141b1565b6132e78c3383876141b1565b8073ffffffffffffffffffffffffffffffffffffffff16636a627842886040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050602060405180830381600087803b15801561336657600080fd5b505af115801561337a573d6000803e3d6000fd5b505050506040513d602081101561339057600080fd5b5051949d939c50939a509198505050505050505050565b6000806000834281101561341c57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b61344a8a7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28b348c8c614ef2565b9094509250600061349c7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8c7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26140c6565b90506134aa8b3383886141b1565b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1663d0e30db0856040518263ffffffff1660e01b81526004016000604051808303818588803b15801561351257600080fd5b505af1158015613526573d6000803e3d6000fd5b50505050507f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1663a9059cbb82866040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b1580156135d257600080fd5b505af11580156135e6573d6000803e3d6000fd5b505050506040513d60208110156135fc57600080fd5b505161360457fe5b8073ffffffffffffffffffffffffffffffffffffffff16636a627842886040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050602060405180830381600087803b15801561368357600080fd5b505af1158015613697573d6000803e3d6000fd5b505050506040513d60208110156136ad57600080fd5b50519250348410156136c5576136c533853403613cff565b505096509650969350505050565b6060814281101561374557604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff168686600081811061378957fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461382857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f556e69737761705632526f757465723a20494e56414c49445f50415448000000604482015290519081900360640190fd5b6138867f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8888888080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525061460892505050565b9150348260008151811061389657fe5b602002602001015111156138f5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260278152602001806154986027913960400191505060405180910390fd5b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1663d0e30db08360008151811061393e57fe5b60200260200101516040518263ffffffff1660e01b81526004016000604051808303818588803b15801561397157600080fd5b505af1158015613985573d6000803e3d6000fd5b50505050507f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1663a9059cbb6139f77f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f89896000818110611acd57fe5b84600081518110613a0457fe5b60200260200101516040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b158015613a7557600080fd5b505af1158015613a89573d6000803e3d6000fd5b505050506040513d6020811015613a9f57600080fd5b5051613aa757fe5b613ae682878780806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250899250614381915050565b81600081518110613af357fe5b602002602001015134111561251b5761251b3383600081518110613b1357fe5b60200260200101513403613cff565b6040805173ffffffffffffffffffffffffffffffffffffffff8481166024830152604480830185905283518084039091018152606490920183526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb00000000000000000000000000000000000000000000000000000000178152925182516000946060949389169392918291908083835b60208310613bf857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101613bbb565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114613c5a576040519150601f19603f3d011682016040523d82523d6000602084013e613c5f565b606091505b5091509150818015613c8d575080511580613c8d5750808060200190516020811015613c8a57600080fd5b50515b613cf857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5472616e7366657248656c7065723a205452414e534645525f4641494c454400604482015290519081900360640190fd5b5050505050565b6040805160008082526020820190925273ffffffffffffffffffffffffffffffffffffffff84169083906040518082805190602001908083835b60208310613d7657805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101613d39565b6001836020036101000a03801982511681845116808217855250505050505090500191505060006040518083038185875af1925050503d8060008114613dd8576040519150601f19603f3d011682016040523d82523d6000602084013e613ddd565b606091505b5050905080613e37576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260238152602001806154e56023913960400191505060405180910390fd5b505050565b6000808411613e96576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b815260200180615557602b913960400191505060405180910390fd5b600083118015613ea65750600082115b613efb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602881526020018061544b6028913960400191505060405180910390fd5b6000613f0f856103e563ffffffff6151f316565b90506000613f23828563ffffffff6151f316565b90506000613f4983613f3d886103e863ffffffff6151f316565b9063ffffffff61527916565b9050808281613f5457fe5b04979650505050505050565b6060600282511015613fd357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f556e697377617056324c6962726172793a20494e56414c49445f504154480000604482015290519081900360640190fd5b815167ffffffffffffffff81118015613feb57600080fd5b50604051908082528060200260200182016040528015614015578160200160208202803683370190505b509050828160008151811061402657fe5b60200260200101818152505060005b60018351038110156140be576000806140788786858151811061405457fe5b602002602001015187866001018151811061406b57fe5b60200260200101516152eb565b9150915061409a84848151811061408b57fe5b60200260200101518383613e3c565b8484600101815181106140a957fe5b60209081029190910101525050600101614035565b509392505050565b60008060006140d58585614d9f565b604080517fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606094851b811660208084019190915293851b81166034830152825160288184030181526048830184528051908501207fff0000000000000000000000000000000000000000000000000000000000000060688401529a90941b9093166069840152607d8301989098527f96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f609d808401919091528851808403909101815260bd909201909752805196019590952095945050505050565b6040805173ffffffffffffffffffffffffffffffffffffffff85811660248301528481166044830152606480830185905283518084039091018152608490920183526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f23b872dd0000000000000000000000000000000000000000000000000000000017815292518251600094606094938a169392918291908083835b6020831061428f57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101614252565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d80600081146142f1576040519150601f19603f3d011682016040523d82523d6000602084013e6142f6565b606091505b5091509150818015614324575080511580614324575080806020019051602081101561432157600080fd5b50515b614379576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260248152602001806155336024913960400191505060405180910390fd5b505050505050565b60005b60018351038110156146025760008084838151811061439f57fe5b60200260200101518584600101815181106143b657fe5b60200260200101519150915060006143ce8383614d9f565b50905060008785600101815181106143e257fe5b602002602001015190506000808373ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff161461442a5782600061442e565b6000835b91509150600060028a510388106144455788614486565b6144867f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f878c8b6002018151811061447957fe5b60200260200101516140c6565b90506144b37f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f88886140c6565b73ffffffffffffffffffffffffffffffffffffffff1663022c0d9f84848460006040519080825280601f01601f1916602001820160405280156144fd576020820181803683370190505b506040518563ffffffff1660e01b8152600401808581526020018481526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200180602001828103825283818151815260200191508051906020019080838360005b83811015614588578181015183820152602001614570565b50505050905090810190601f1680156145b55780820380516001836020036101000a031916815260200191505b5095505050505050600060405180830381600087803b1580156145d757600080fd5b505af11580156145eb573d6000803e3d6000fd5b505060019099019850614384975050505050505050565b50505050565b606060028251101561467b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f556e697377617056324c6962726172793a20494e56414c49445f504154480000604482015290519081900360640190fd5b815167ffffffffffffffff8111801561469357600080fd5b506040519080825280602002602001820160405280156146bd578160200160208202803683370190505b50905082816001835103815181106146d157fe5b602090810291909101015281517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff015b80156140be576000806147318786600186038151811061471d57fe5b602002602001015187868151811061406b57fe5b9150915061475384848151811061474457fe5b60200260200101518383614b9b565b84600185038151811061476257fe5b602090810291909101015250507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01614701565b60005b6001835103811015613e37576000808483815181106147b457fe5b60200260200101518584600101815181106147cb57fe5b60200260200101519150915060006147e38383614d9f565b50905060006148137f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f85856140c6565b90506000806000808473ffffffffffffffffffffffffffffffffffffffff16630902f1ac6040518163ffffffff1660e01b815260040160606040518083038186803b15801561486157600080fd5b505afa158015614875573d6000803e3d6000fd5b505050506040513d606081101561488b57600080fd5b5080516020909101516dffffffffffffffffffffffffffff918216935016905060008073ffffffffffffffffffffffffffffffffffffffff8a8116908916146148d55782846148d8565b83835b9150915061495d828b73ffffffffffffffffffffffffffffffffffffffff166370a082318a6040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b158015611cfe57600080fd5b955061496a868383613e3c565b9450505050506000808573ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff16146149ae578260006149b2565b6000835b91509150600060028c51038a106149c9578a6149fd565b6149fd7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f898e8d6002018151811061447957fe5b60408051600080825260208201928390527f022c0d9f000000000000000000000000000000000000000000000000000000008352602482018781526044830187905273ffffffffffffffffffffffffffffffffffffffff8086166064850152608060848501908152845160a48601819052969750908c169563022c0d9f958a958a958a9591949193919260c486019290918190849084905b83811015614aad578181015183820152602001614a95565b50505050905090810190601f168015614ada5780820380516001836020036101000a031916815260200191505b5095505050505050600060405180830381600087803b158015614afc57600080fd5b505af1158015614b10573d6000803e3d6000fd5b50506001909b019a506147999950505050505050505050565b8082038281111561139457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f64732d6d6174682d7375622d756e646572666c6f770000000000000000000000604482015290519081900360640190fd5b6000808411614bf5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c8152602001806153d4602c913960400191505060405180910390fd5b600083118015614c055750600082115b614c5a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602881526020018061544b6028913960400191505060405180910390fd5b6000614c7e6103e8614c72868863ffffffff6151f316565b9063ffffffff6151f316565b90506000614c986103e5614c72868963ffffffff614b2916565b9050614cb56001828481614ca857fe5b049063ffffffff61527916565b9695505050505050565b6000808411614d19576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806154736025913960400191505060405180910390fd5b600083118015614d295750600082115b614d7e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602881526020018061544b6028913960400191505060405180910390fd5b82614d8f858463ffffffff6151f316565b81614d9657fe5b04949350505050565b6000808273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161415614e27576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806154006025913960400191505060405180910390fd5b8273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1610614e61578284614e64565b83835b909250905073ffffffffffffffffffffffffffffffffffffffff8216614eeb57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f556e697377617056324c6962726172793a205a45524f5f414444524553530000604482015290519081900360640190fd5b9250929050565b604080517fe6a4390500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff888116600483015287811660248301529151600092839283927f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f9092169163e6a4390591604480820192602092909190829003018186803b158015614f9257600080fd5b505afa158015614fa6573d6000803e3d6000fd5b505050506040513d6020811015614fbc57600080fd5b505173ffffffffffffffffffffffffffffffffffffffff1614156150a257604080517fc9c6539600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8a81166004830152898116602483015291517f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f9092169163c9c65396916044808201926020929091908290030181600087803b15801561507557600080fd5b505af1158015615089573d6000803e3d6000fd5b505050506040513d602081101561509f57600080fd5b50505b6000806150d07f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8b8b6152eb565b915091508160001480156150e2575080155b156150f2578793508692506151e6565b60006150ff898484614cbf565b905087811161516c5785811015615161576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806154256026913960400191505060405180910390fd5b8894509250826151e4565b6000615179898486614cbf565b90508981111561518557fe5b878110156151de576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806154bf6026913960400191505060405180910390fd5b94508793505b505b5050965096945050505050565b600081158061520e5750508082028282828161520b57fe5b04145b61139457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f64732d6d6174682d6d756c2d6f766572666c6f77000000000000000000000000604482015290519081900360640190fd5b8082018281101561139457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f64732d6d6174682d6164642d6f766572666c6f77000000000000000000000000604482015290519081900360640190fd5b60008060006152fa8585614d9f565b50905060008061530b8888886140c6565b73ffffffffffffffffffffffffffffffffffffffff16630902f1ac6040518163ffffffff1660e01b815260040160606040518083038186803b15801561535057600080fd5b505afa158015615364573d6000803e3d6000fd5b505050506040513d606081101561537a57600080fd5b5080516020909101516dffffffffffffffffffffffffffff918216935016905073ffffffffffffffffffffffffffffffffffffffff878116908416146153c15780826153c4565b81815b9099909850965050505050505056fe556e697377617056324c6962726172793a20494e53554646494349454e545f4f55545055545f414d4f554e54556e697377617056324c6962726172793a204944454e544943414c5f414444524553534553556e69737761705632526f757465723a20494e53554646494349454e545f425f414d4f554e54556e697377617056324c6962726172793a20494e53554646494349454e545f4c4951554944495459556e697377617056324c6962726172793a20494e53554646494349454e545f414d4f554e54556e69737761705632526f757465723a204558434553534956455f494e5055545f414d4f554e54556e69737761705632526f757465723a20494e53554646494349454e545f415f414d4f554e545472616e7366657248656c7065723a204554485f5452414e534645525f4641494c4544556e69737761705632526f757465723a20494e53554646494349454e545f4f55545055545f414d4f554e545472616e7366657248656c7065723a205452414e534645525f46524f4d5f4641494c4544556e697377617056324c6962726172793a20494e53554646494349454e545f494e5055545f414d4f554e54a26469706673582212206dd6e03c4b2c0a8e55214926227ae9e2d6f9fec2ce74a6446d615afa355c84f364736f6c634300060600330605581d02a7e8b8234a7992da2173c0d9eb404853fe85a65a0881149522e12b2a0f014758d15e176280001955b705581d024ace4a9cf9849185c4b4cd9d2d43e5360f36d915f743ac51fcc9c7f80c02450135f1b4000219042005581e03dc8fc5260164a847b23d38a881b05ec9e601d397d1188021168d7c0da00c024701923a550aa0c803fe15c6a27562a27ae904cb1b828dd828705d1507183fdaa98bcdd0bb1025ee5305581e03f23857a9fa3d0da9ae7310c160afcf49158a232640406bae60c29650500c01472296500eeb160005581e037f46e0bb2789267d0a30b8d43a6d3e2a2e289f0fcb20ea1bc8ee7216a00c024744d7c56d940be603da1a91bd88ab16e57730db064e35880953edf7a3e2f40ec22e887166b95f4b18032be8a5093959bbacdaa2d02b061939a3a07681b5a5351eef8de8b286f8df80ba0399cc8746fb4acef4aa25ad53d2d12e8af29520040e86729cf0fe867839a233a005581e031d2088553eaa5823aa330be942a76137ea4d7416fda40c5c72917239f00c0147028114c7f49fc0034277b3bbc296a60f66b308aceaf2f9ad4e766e1984e39f2bb1256df0c51448a70219cbde03821c17b99606f4ef0404c1e66f9b5847282857f6d43c1d1e2282e3e20358c1a203db467c50e815cada5a92eff2a2eacd9ea814c7a9ef7e9360b114df767836e0da03454a7715e43a8f5665f3175574c0f374cd68bdd08d8d39beda757efe065a1d01031b6cddd0884a393f6d5bcfee003d6dc52eca04e3674b628c1218c9df32da11c00319e2bf440d2b4f680aaafa688da47d7a6bb06838e97a74e1417448325e4dc2160353551a267211a18f437178d0c23472dbe9821cf53390b4846f5824c4479362ee03bd0a711bff7996b670c2882e6431d8feede57819c03c580eaa092d1a8f5351940380cbe31fb0e2a43ba18c77d189091732df11cdeae4b9271100d3c69cdaf18eb40219ffff03b903cc550db702ad3a61ba3345dc7be08f65f036ca37eb75de4257af49f2a5b50310081833d47de439dc5ca64722ddbe5b89d19154c69209fc433bc3f9be784edf0219ffff03574964b9c2c92f0c167e483ad13048a696603f618fe6161ff49526562d063c0b0398d864f93383e440938855e216914a7657f791096683dc5e1a95247526b21b7c030aa0afd682a694188ba1e959debc39df4164c06fb62c972b1efc58a37e5e34f30355696aeaa907d644a62566418115cff57fdbe7ea48ac28e27f4869a444b1dbe7036175a74384427131cd1f76af4f6acbaf7394eaf522902cc13990da252fce6c8c0309968c0b60d1a2dbc7fb50a199f6b0429722075e6b0899f82013e64c4990cf780367ea5b99fc8b8a0bc5441573d9f1e554e9ca7c73adbd4a0da858b344f296e887039bb8ef7a73bff7718ddfeec785fb40a054c0f139e1b4c27f9e005be644e7ae2503bb48e7ccccf38660763a13aee09fb1204a1d92f7eb889acd039aa2c639980e49031d0bc64efa9a6be7279538de3feb0257ccc453be6dc0bec1c82b6d02a71be63f03d1411b2200827453b7ecda10c407e4c2c75923be1eff4b5e2228c1a86027edbd0219ffff030c1697859a1f4f97a4a721f5db02c8071bb9db7ad42cfde08cf094475c85612b03497e80dbb9deb11a709e2012232d0d7be9790369fc329796a5a59b1884918c68039f3db4a2d6621cda2c5d7e47febac283f043f4c0050ce4de6e89c13e4f5a5b54036f6ad3356f4cabefd72b78e447397678dcac34b0372490dc9221ff9e4a99b8540302984fb9591a0315eab425106659d397b387d8b60cd55acb8c12d79c56aa095e03f4b9f2b1fd6f197d24401fc0cc88c43c80852e52262958dc28dd6baf71c5d5b60344316f26f6f472f93d4e822166523fa45b86896bdd678adebbce04734a9d458703c786754dfe02834ef85e4e92c4e1d8d8544c46577c2f635542f68713d591757e03f7fe46ee0cc93b9bbc66dafc8b1f953c5ce3b9ad93da258f962aa09cdc2c500b03dae4f6257c9870f274c2266fc3378b1335876eaf8c33d10a25a77918ba5e454b0219ffff03d56fdb0840a81c81618896f260607fd182de84071cf2c9c8a2e735b96020bff6031da5779b3491195e5b9bb6e453b2ae41e6335528aecbd9b2fe471334208aec260399b64342fb9a5620bfe08d6489b86a459469230ea6080730e64eb3fcad2381f60367f82a624ee855d72b7b5872c86d5d5bae35536e35f822fa3c77e72077d54e8903fd50202e175f774fb9d80e165c751df97702a69fbbcf8492007960e8e7cd83d003a3c387ed6e3b746e8a9a9f2cebc1de1745be32be6672fc7c633968a4cc848afc03c11c527aa48f8261cadb4cb77bd7decc6ec5b645073f5311f8a71489be3ded8703506b75f394ee75ea319cb6ce7833f057a59f044f02f3fe041ca6bac480f1bae903e694a6aa98d9326a2f6b852e10d16d017ebe1b4cd86eac0e32f5ededfa0ba6410314c24cee32e96a03e014c92582b9b06b1fdf7965d25d6991ba95c3cafb5d28cd0322628c54fe449f70317d8c59a87ccc316d72fbda30d91450e1965c0d53a4d39f031ea382ccac791c7ed22c500eabe92324592932054ebf167c7a68a9fa7e4c241303883f75505a8bf0feaba512d0273c135fae4c2296a998431c18806a701ca74c7003034536bafb25eb4772c1a534a41e122dac9db06e10c5b344064a858331dad01c037da1892aa2eddb9456e97acec022a46a4b9b25345e8ec3ea9adf4b57e0795d99036d6213a34cf76219b071fb27b4b8a223f354f9bf61afe9a59eb581d35fbf56b9035c35211ce01a56f4a4df3acc2d8352f400caaa2b8add891e0ab14b1dbffcb33a0367601633f60d748bc186c6a51094178512173ae94bdf88c57f1a2068c8ef41c403c23385e32ba4b204bb7a44de616b087c2fa9b145556a05a1cac7a53905a1458f03718c8a3151f1d369163217db26a6bf96c8554bfc20a42de18244dd4aa78d700e03348e158e9e1d18d0c341b7a84f07b9c569d32e557d28cadb8d493eff1a35daf303bcee5953df5b754319c5e5b9e7420a5ddfa0bdc966a818d96a76c72adac85ff2036b9c76d773a53fe0f704f73faa589e4c9e0f594082439a6aa92e87c3d7f5bf5f032c3fafda76b41c6cd2cd8698d986b6eb1e9cbbd1d60ae2132ccdfbf78f948364032d2517993d3478f3fd26bcaaedf64ac715a758ab75cccc3d743af3224a33e1b503a87c4e7c0a3a06211fca303ebd71048a6a0583ff27458f26783e8eb408d3070d03c79a87da9666b2ac91a5aff7f5c2a1ef6e2d5581942929201acc44e50fbfd13403a2cc83b1c270a01e2ba337c6986d590af10e1b8e5fb7a83ef89b78234ec4b2ae0333eec8a9f11f30c552fe67c43769813a16b6ce89171bba2273886fcbb6b1e43003a535da2c835bc890aa629151771ee140e34ba540272882365130815f6215f04205581e03419307bbc38dafaec9d5a761fc2f6c63052591bc312213f6e73ba3d4a00c181c4701341065474000031d93f60f105899172f7255c030301c3af4564edd4a48577dbdc448aec7ddb0ac0605581e033e3d3d31cd7e38553f13806a7892506a5e8227c3ca736cde5fe6f04a4007011bffffffffffffffff05581e035ac5be7033a1b22852896cedd2815b74dc857fc3347364818d356fee700c024734a9467026ad8003feeba8c0dc8c17c9e5b8ef65192d9895aa6f9cea2784f0c91da8ffef94e861b701410a038438e76d30c9789d7bd66d5b07f0025d57003dd9561e5d04b18888148450017405581e033d9438c9a7425c7bf95be9c6d056c4849318b3fb13a4020516c244a60004010323481efecfc7ea5580455535ac2aba13cebe77f80ced15bc908b7e870a13378105581d0200861828887e89d9886d049a802c915a7dd4bde40f01e1b2677ab5140c1901d04819d059fa26efcc5205581d028a1edf8919783af87cb5df1282e89f7b084fca90eb8bf743647eabce0c01470433d6a6128cf70219022005581e03591c0c07edb44b3526d4d5e83a87429b7e768410120ed4a5456fbfa6f00401021973cf03157473ac6d46081b1e902a660187f2837c417be9bba14455562c146ac5c0512403528cfd81a298da6f87441c44503f8854d9f1229add613b0e003d3ee679c032250219ffff031e7dad53c0835eb2f5fe2025f4854cd284851c812681835191bf08be0597a867034d526cb6e4ab3f77102fbcb0a268edd8563573d417ba6257a31b22c413e5e8260348f9f87edd58a4bc7c4bccbb1ace39acf93e487a6e1b9f09c120cf5ec9c7b82d030fc42acb6988c001cbc747fb060ffa2315e2a7e9f3858aacccc6045196670c6d038ede8ca57d89a0a97313c0815cb3241b7efe2bef8d7192f8976d0a0249518c0d032997bca7fa388de1fc5093059489071c5b1a28f1d45386a533b4fef71c7e4d4d0331c0b59f27ad21f521bf37738b4f6cb7e505aa79294696871680d98ad7a7cd8d03e999f11a6f5a89a542bec94951106e13561d791c54f714417c57ed04cf15c220030d725e38a69fe8015dde8be5f41bd45bad1da682ed126c6c2fca3d1654c076500330929c4223b2bf5852f3c69108b61e594dd2155e5993f260ff092724986ef40403ddad99eb8da0d6e498a11312d7539a3a36ddef8ea4da8f1dd83902d2f98a4d8703c393af2ceb55007ff36919cfd1adc9c792521940399b3cdff94c80a3a49d032303f061a7fee8cda1bada88835ea5a2b5f63b57c862fc31524d578da0d9f72466020219ffff03e950be9bf95656c93c6ae66ab70ee0c598a851d713b58ddaf9a37e34c60d14b503539afecaae32ec02c7df84f22d916f1d3a8f956a5bb374d41825c30214032425031ab3e13120968b7c89ec3f8dbd92117a2b9c7391645a3a1e25e9288ff7f464f20393da1487b94e19943ff6796aa3e9a1ae02e824297080bca3944fa2e6d7c7ca36039f1b09aa41c736693ef8a9f90d85cd15c9b651ca086781b534d4e04e9d2950e803229153fc868c8249c724b09f003e12043cb6c2d72b509cdbfe3c4b571ab8d57003a5336fc45d92838893ac2873a0b8432b69c360e04e50851ff7686ce353c7c84103259a41555d11c7e98e5eb5c59482f6c8e703a999adebd1b78f01d143b074f7ab031b88251269605bca25915c2f0cfe0ff60ac06609b9ad04a261ebb132f44a6f2b03b1ca108b6a1b156938c59e8d7f7e08037ac060a54045bca05b9b1786c60e477903e03318c169afd3b31fcaf79907b35760a71114e70388d5b451b267dbdec5cfd6038c41dcd5ee1c89bbe4ad2c4bf73361bf0f620f8f3a2c0244a5fba44c50eee65b0397e258045b6a8c10cea2b5e1e77463ed432c801aaa3e2b8b59f9ce60c37a4a33032a244bd691c84ca1237008e7039c6a388d80cb0dd59c6b8b172a4be206b6a0940219ffff036cf790a1cc2af37b416c6ced67e1b54bcfeb1a8358a10ed1ddb917a966c448c7036737d67f986616f7045e96a75fe283f5783109b962920c3fdd7698e65e661f020379906848b81a9b245590a1d6d4930cee0acfdc6a8f5825d6c39c32029f765ded03530cd52fab10018d5873fe6d6b24eb327e3c5f2c583ec52f9ae179aa69d8e7ad0328fde01e3d9c0a81bbb45c67013d9deae840204b0c90de4fa7052d94b62ffca30219ffff0392828d0673719a493348d3f7ac791e8653b99e6e031901d07ae0c57c1ac8d77603960708659d4b82694dfe60de7152b336e6572190548bd386c0b928fff1fe4bcc03d3bd095b5713f6e6c15bd53d3c4494cca0a204b3c413cd00e7b679e82e580ee2036113bcd717855633f80d58c72a8bc2ffe9cd03701279d3271bde31c3194e98c903f26e130f1e43f4cad27ed07ca29f6858979f910da41d87e7992bb0020a3d5a49032593d5f99161e1d3bb3aea12b8194d6f317fad4576418bd30df04c5cd3bc7d91036356c8d669a925ad3a0cc47d990a6f7c1f305d4ffa0b1e51a8ad7f6fa52b272803fff56f4887fbdc44e04e0048f3208e3f59835171e6881f513c1f76367ce5149103fa753ef40632f08297aa7b2531eb58b131919ce804459515b410296dd9dccb0f03071795b6907aecb9192c1cd1cbdc5a38513a4e99e195491598658ace0c96c54a0394aab02b7c592620eb85d1c78770192dba63070b8edfc7cb9231e597f66f6e62035f131f2046a50e16124ee7a82e8bd400b780da480924e680d88589a47ba5995303746c60068bceb5e847b8d807399debf9e20ca096dd7616d83b6c9c76f7df624203196cdd63eb142a5e4a30941888ac1bfd4aa55f338e4e7fcae71c3b157dae6af303a03240d602e94981102988a51ac0ad251d33e4eb64a118f2f4d2c694de7aa00f035c0598d574729d2d5ff70fb4df61f6cfc873139bf72155cbd90178302a00547f033c70d85319fac84a8ec0d1c665a785afd4bd13a1f1ecde4b8081f58171bb8d2c03c34ef342310f5630cdbe147fa1b48b48961fe134927a80f7c7e71e53156658e30364491870a4b733deceade048efca43e5267a3c4c296f4bc31d4d68a7c8b1659803f4af757870e6d965532386fe3bed87fef39c3ab113271733efac9a2729bbb40e035daadbbc20f1348650139be122db61e3e51785934a77b201a94ac930d68ad16603ec75c771a0e9f56ac875d8dd2f4838dd7251644e7431f9725e563992ca201a0e034c09ac2a11815e3fb42072b4508b313d3a9c5eab342833bbc9ba1848893a0fa1038388c5291c1969ccf1d6ff774ab9fec7286ec83b989a2e5d63d57e123c585d8403590cc9f8da2fead06f74d6d1bcd43b7a07ecfedbcf0d2a30124f575da1a586ed036018ecfb7ab41f5680184a29cf60f4107b11ffd7b5a7b99b7656c2dcb43c265903422610b6b652ab5dad44612a758aa7cd7b1109f8245293a66cb647f1143793380327817f61bd8fbae8cc0c6b912e9ccdefc11d1826a6180ee24271f3b712f44df003e3bfcdcd9f142fb6b051af72650b128b0b097d5a9bb6e61da36126933191296905581e036d484e86de5e03dcba47f4dc308188c37142acb756dab33ba0b59af050040a05581e03cb1c3c5d37502d0aa13d62b6424910ab952978d0b11fec6ccc3004c7e0040103d10c50a8f35a113076846ce372a64d64408d9bbc8a21f8c5de5b93a1ec720cb003eab6d090ba3fbe9d3b561c6f50e146a8465834dcc21748f143244185f48fc56003562d59a51820d47f520c975e0b2bcffac644a509749a3161f481f57b6e826d210605581e031979e8fff074daaad04439623f1b536865bdfaac7f39c16055f50143c007011bffffffffffffffff03d8be73740597e0432b0d60ad70443a41237ea3e32666f8b7a16b7e242f10dee205581e0317bbf8ba657e39871ab5852bb5d8ace9de7fe3ac599410ee2d50d0a040040104590795608060405234801561001057600080fd5b50600436106100a95760003560e01c80638129fc1c116100715780638129fc1c1461014a5780638da5cb5b14610152578063be116c3b14610163578063cc5183a114610176578063ef921f8f14610189578063f2fde38b146101ac57600080fd5b80630a5ea466146100ae5780631c6eced5146100c357806323b11d8d146100f357806348a4f99314610106578063715018a614610142575b600080fd5b6100c16100bc366004610694565b6101bf565b005b6066546100d6906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6100c1610101366004610672565b61029f565b610132610114366004610672565b6001600160a01b031660009081526065602052604090205460ff1690565b60405190151581526020016100ea565b6100c1610324565b6100c161035a565b6033546001600160a01b03166100d6565b6100c1610171366004610672565b61041b565b6100c1610184366004610672565b610496565b610132610197366004610672565b60656020526000908152604090205460ff1681565b6100c16101ba366004610672565b6104e2565b3360009081526065602052604090205460ff166102235760405162461bcd60e51b815260206004820152601f60248201527f417070726f766550726f78793a2041636365737320726573747269637465640060448201526064015b60405180910390fd5b60665460405163052f523360e11b81526001600160a01b038681166004830152858116602483015284811660448301526064820184905290911690630a5ea46690608401600060405180830381600087803b15801561028157600080fd5b505af1158015610295573d6000803e3d6000fd5b5050505050505050565b6033546001600160a01b031633146102c95760405162461bcd60e51b815260040161021a906106df565b6001600160a01b038116600081815260656020908152604091829020805460ff1916600117905590519182527f102d162d8690811b17006e6e7529e0e7ec6c7f357d1d74d3d7ceff35fd75ce0f91015b60405180910390a150565b6033546001600160a01b0316331461034e5760405162461bcd60e51b815260040161021a906106df565b6103586000610576565b565b600054610100900460ff166103755760005460ff1615610379565b303b155b6103dc5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840161021a565b600054610100900460ff161580156103fe576000805461ffff19166101011790555b6104066105c8565b8015610418576000805461ff00191690555b50565b6033546001600160a01b031633146104455760405162461bcd60e51b815260040161021a906106df565b6001600160a01b038116600081815260656020908152604091829020805460ff1916905590519182527fb24cbb8005827c88afc2851884e834493cae4ef0d1302bf939281696bd6ae7599101610319565b6033546001600160a01b031633146104c05760405162461bcd60e51b815260040161021a906106df565b606680546001600160a01b0319166001600160a01b0392909216919091179055565b6033546001600160a01b0316331461050c5760405162461bcd60e51b815260040161021a906106df565b6001600160a01b0381166105715760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b606482015260840161021a565b610418815b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b600054610100900460ff166105ef5760405162461bcd60e51b815260040161021a90610714565b6105f76105ff565b610358610626565b600054610100900460ff166103585760405162461bcd60e51b815260040161021a90610714565b600054610100900460ff1661064d5760405162461bcd60e51b815260040161021a90610714565b61035833610576565b80356001600160a01b038116811461066d57600080fd5b919050565b60006020828403121561068457600080fd5b61068d82610656565b9392505050565b600080600080608085870312156106aa57600080fd5b6106b385610656565b93506106c160208601610656565b92506106cf60408601610656565b9396929550929360600135925050565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b60608201526080019056fea2646970667358221220cef0361026d143789d3a650723c24c713525ee432d868f9508fc5fe410faf15464736f6c6343000806003300582103164135aff1c548a9982cce22ebb8a532443ff7b3b6e3a417c5234f7974c9c46041010058210390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5630410100582002501879b8ca8525e8c2fd519e2fbfcfa2ebea26501294aa02cbfcfb12e943545440aa958dd87fc8305b97f2ba922cddca374bcd7f00582003e5af7989af34160053c740614b9ddb3802b3053daf83245a1bb265a8bcafe0410100582003b2639b17bef0515027b53aeaf7e6afe7d7e132e00c9abd9bbdd0f1be746cc0410102190420021901400058210345c78e6cce5cffdec145e73ecd97300c6fd3c324449c80fc66d92f4c6bfbad704101031dacac7dec919df3e6de7ac4aacd07fad0b2afb26a93a66596e35672d9e4ec20005821032a75bdeeae8604d839476ae9efd8b0e15aa447e21bfd7f41283bb54e22c9a82054f2f0829018452a00435edb9cf6fd5158d2e92bf500582103fd6c600593df7046d05407c1e5011ba9e7b831a4becdfc4e22a3f9c313d38a00410100582103a012d18e3ab1ec8e0601d9139d23395f6626362a923c5a7c2e6839fe7f5f18c04101021945b605581e03f4984e1d911878a0ddd7f3a4c56797a98c41c9e65626fb4ae0becc9cf0070119079505581e0321f22ad905c506331d7f77323648114d3988003fbbf8da25af93037a700c0245236fd3b2000360f9c8120dca62dd1c723fbfad26b16f48c0799c1bea1205b363df3d8802aefd03e341c22cc41099ee3c0f967a9d5e94af7bc906464204782df8baa1259de0a4b80219b56f03fc39247169d68387fe8f2d369c8e98607eb364efc82e1033596d18048b7527d203e2a45e68759e1dd926792be7bf83dcd856d4dd670f974ae206ee6a4d5627dcfa032abc13497d9fc711f804b073617b9d21a594d4d5a4deb876e540b80929ea18a7031f94b1d7576f2582eb0191ef837530d6d1feebda22f870d136988de0a07412a403694aa123047e9ad525f9cdbcbb674b941e25772c1134d00eff1caaf9b1392fa503ca626fc94f8c99d78e79d3ea497f11cded8aa614a1ecbda86f398e8b4848e94103edd82fef0870913fd1d73b5e14b6950a8e0bcbf06097f4b3f5c435f45537135903e81381385db7a7dc530dea9fbc6c59e9d3f51ec69a9c654302d8379db9738a2d03c562a9d42afccb384670f065e0287167d809b1e537db57badc0ca1fd6e12a872031dcaf274ffbd3cc4c5096eb427a1bac8e71053d33979ee4983374ac5674c99c8031b25881fe51e16803e7fbfcbac12d3b25fdba9d8c3b5c7cd658da84c4276bbc303486a41219b6cbf03e815837fc2ce7bc2bb3f0c65e8cad72ef944cb95715b6fc60219ffff030122934164aa33fe3986851a13a079c54fdb7da9a63e1a4e4bf3e8ec74a8214a03bd0a2e4e1439ab57fd37c24cd33ce6837d5e254d54c7e88062da31c446417a3c0372c7689d8cedf4a68726f7c43c731eae4eba6a89495ea932a7ff09d94949f1f30383bb030500655869f5f7af9b038c9470630b34936e655e29360fae48fc12cc9f034f2c8ba7ae6bf8f4d46a780ca64de3c373922d2de3d4e32435dfd4bb84d930d3038fd0f00991c5137af676688421187712405e0fe4b657dfbd5d36c41658ec5f2b035c880e7a33da2550cfab9b40c9084cf944cf023e58221c8b0dd07fa12bec108b038d0f10a1ff8bad2c3289702a41d7c15ef442880ecc11ec84636181148cde56d303ecc19cc255272f7651cce2a83b6d9f7c3188b41145c0113c32c2a42d6dfd113a031090949f42ac5df0b0288574e3cb2ebc34d436e05466670a8c673161764590840219ffff033b5ea417612b816eb2e27559a01f474d4dcea6c4c0137512737e10ca86e576dd03fe78149b5346621279469911874510ecb529124dc198a00e8f6dab4ab98827b8030a642e46989b438621e525efd2b5c91a131099482c267894b34ac282678d2de70219ffff03cc7aa690272741c06dd2c0bacb46b20d8593c038e93027fd87aacf1dc60bd9a803e1eacde9ab111632be6d099fdd45c32e2e8d7e9b6bbbc5cfcae3d7800f1ed8a1032b1bdd7e77fa5eaf49c31e69d6297a85a27e0ba7f5f1653f2f4d77fa6feaf9b3030c1f30f385e6e7fadd3c080452bb1830f3fccb6d51943c7e3c39cbdaa3d900ad039b0a33545680a03e132c49b5199567c54ee3fe3d23664579b0a668e41f9f3701033cf2a552666499ad3ebb1ead95a3386987f48069de5c6114faa72b11a09b997b03eb0a5196b9046119dcace0cc0a671452b3710a48affbddbd9cc980df9e245913033a36ff92ceccefd9b136cf851f7dce6a8981528939559b0e6ada61f5a3806c040300f671dbc968f94cb7624a7b3f688337e4fdb64744c353dcd7f51955b9a8afde0344a6993e44ef397a4ac783cdd411cb5c00c55a3a5cd69490f455fe707b2d9b1f03933a1ae386027c9473af7e55dd034f21a22feeb8664f9aacda8cd5ab1b60d44c0354fcbb11430f75ee96323e498f115c41fc8c58f7ce10cc459252118b8362d8630219ffff0317f1d75f02289bedbd7e2ffa92e0039284fcaa96ddf79ecb0ebc6a28fe03eb5603b79d2e7990e83d1114544c8be30a90e55551b5c35105c36f8099ce20c78948660344143267c6955ea84b69da894b6ca6d4b17ddd34f4ba7ed9227f650cd971a5c103bc3a2d2a12d22ebbfc8a69081264ce6e61dcca60a707902192c588b70773df970392201e7e8bd2b81302184730c351bbe303f33a9bf27cf88555775ce043dd1a8803063cdd4d4c96dde3fc359f1c3b7e3f0761c696a8bad14aea7421313f02c0aee803031fa4d408dd95a87b9662357866e4efc80dfefb86d5aa95a3cca16c1ce405e0031279a24f16b3850bd07dad54754dd7d0953b4aec2cacb4825213514eb1c0f91303660a2dee8752fec8f33d533082034fd1749b9a15f76abb3f0220518dc751396903ae71c41f58f26059e0d50b96e4c9e4ec83e1d3ab71d862c1456edca4da109f0503bfbd33f5057c0832a2d28e2220088c52c8908954bf87e5f81391ec80faec1e8803ec86ff64b1bfe6f7775c4a8a00fb1b4e0a05f6c6db7d1804881c3b746e2861970385eac1ef69d9e4da96773c544852a23e718bae08410e6593475ecb17a490f89e03bb7e2f288fcdad39ea8e7c608daed81b75e920e60b5f74ccd4d7b4d6027a42d303df2beaaad1babcbaf35e8f59ab771434c9610487d2a36805b8b02e4e3523d2f803c5bf6a07fbcd88232a42444c59816b8672fa6a5916fd0493e7bfb2fd2ee28b1503b4274c7e527ede63c995a7d176506bf6b1fe929358d9d6aeea3b2fe5d72826cf03a1e2b7b4f627ce1ab92d777f4670f56bad48bd52ac39ff9eb9b309aeaefd2a7103f327f4c25afc8b4f7bd4ea1aef52ff8917ae1276c9e907b9f603e06d49a7afa003d5b607c3c91a574a6438fba7aa8e7a1a2a953e271ed0124f3c2f03c80fcf89540362f1ac5b1d016fbc7835544300b6244bde1d81326c511630202f877e2689a0f00381e4af3edeef59f2cf3f571a01f6129d634cc60f7f1a2a547ae426146bd401aa03168037d5caafe3a9373312d47bdc227c1a63aee2e150ad4d595c7c884ea042f403a57e9c016b928802d57b3e57db76ead5d41df4c3ab36321a3eb9d0ae76629ad20351f15bae1b2ee7dc7bb14e2b3908d42de86fe3a26089ec7bbaf502843d582ef7031068bb0daf30ab05600da7d3f48becf8aa5fb09531fda6121f578119f489decc0362ea0a8e481ea3c55adcf86f4ae5e7c4367eac629c3cd25f075d3eb62704c2d503981e91f1495425f666003a83d85b5afb13de3220618ea60decd6391e1166c8a103ac51e311939faccbdf933980b6bcbd99705caba9b4fe8059f726cf4888d49e17036635764f91aa7cf1eeb192af4b9457e95ffbd8eab658e87b880ac73e6a9e5c9a039087e809d48b949d64615b72346d874b97471298c26e2de2803017d9207d9f5c03da8cd7368a3e7fb6969cce2da56b0b68a87b970b68675b2c7c9371f762625baa03fa9e62c69d7b5ff3e8a02df2ef9f0f5f38e107edef839219d17179cf252287f203c585ed2e2c0bb6ca73cc8c2248b67bf0f1664b860180c5ec3cdddca9bb77db830382338bb913050382a0bc7737cd5046bffc95024c5add048b62594ab94facb47803d29ede6626dceb566c13b2b1e40f9acf41bd6e6cedeed721aa1e0e94b962723c03351997ef5bc48f6be427b910116d139c78c546919f03fe2eb7f2cb3b3cf05804039b9a8efdb4595f983b753d75024c79f3e31d9d112f0e9e95966cd548d19e4bc803a9702d2cde95a500901b6b6da8c3a9f50771690838daa6535beebb735451bacd03d5840232c74158a9a14d7b58b06c1550bd36b5151bca68561e6e0013f3b5a60a03135dc465a2a361eff7c35ca34dea3aaf1584077fe45cafbae059bc43c1d529490398149f677be4362234d2ea63618757368bd5ea43fd6b07de04d79b7a70318c7e03f345f29b2db301ee1067f97494dfd2efcdd41d67b0e4271eee9ff9afcab78875031a0821df0b458874072c96dc30a461eb5d92ea8225485cec4636953606da0d9003d81304c77a7d80401c94548ef364333dc271b14b103a6884651033eb0c2da97105581e038871115f4aa4f77693f19eb9849d4a71a2f6a0f7158b75a5da530f44c0040105581e03dd440cefb41d4fe7be24b1055857af4e4d7ce5284b20fe1eca0cfa0d000c014664459416240005581e03884211d834de3d11a80cd0aa7dcb1723d6569780353b2b8706c0a6c3900847025014bfbfb00005581e03fbef4edc995dfcdfe1c4be1ae4ed0a33fd4dd67096a5b898369ec92d00040105581d02cf56e7b9ab0f0368bcd6c9c91aed6b3d9d4ec1971397fe5c378ae83f0402045902e0608060405234801561000f575f80fd5b5060043610610034575f3560e01c8063199f72601461003857806341c0e1b51461004d575b5f80fd5b61004b610046366004610262565b610055565b005b61004b6100e9565b33733328f7f4a1d1c57c35df56bbf0c9dcafca309c49146100d7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f41646472657373206973206e6f742077686974656c697374656400000000000060448201526064015b60405180910390fd5b6100e3848484846101a3565b50505050565b337337aab97476ba8dc785476611006fd5dda4eed66b1461018c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f4f6e6c792074686520636f6e7472616374206f776e65722063616e2063616c6c60448201527f20746869732066756e6374696f6e00000000000000000000000000000000000060648201526084016100ce565b7337aab97476ba8dc785476611006fd5dda4eed66bff5b5f6323b872dd60e01b90505f60405182815285600482015284602482015283604482015260205f6064835f8b5af191505080156101fb573d80156101f25760015f5114601f3d111691506101f9565b5f873b1191505b505b80610232576040517ff405907100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505050505050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461025d575f80fd5b919050565b5f805f8060808587031215610275575f80fd5b61027e8561023a565b935061028c6020860161023a565b925061029a6040860161023a565b939692955092936060013592505056fea2646970667358221220e1e0e24429d1e60a36a247a324ced67999a9e96a064cd7f127808cf22be29fcb64736f6c634300081700330605581d0255ac7c678e81a913bf853f5551a6bb0741a5c148c177e38869e6a92607011902e00219200805581e03ee7d86dc28e98a1a94067f177aee63e0e3603c812938bd0ca60a188df00c034504e3b2920002193a8b031f4ea396c544385575f7317f648795e8b46c58c88e63286b6d7cdb49c9c3c1a803db3f1a5be8536c50a796471cd3a45ceacd91a027c35246b92ef2ca358cd2deae0219ffff0367abee778da185894018330a9b993a0ced9075e19716f2a2c8fdd0faba4b4cea035d2f73bd1a95d8e49533b2724d3e36a4eb9fdb3d5175f4cec1c786199b939fab03387bd806613062fa40a63b9353022d151739c49a2b55e1686779f7fb302ec2c303891f81da2b6c93837993aad9b3232dfe4f93d64c9552c8bf3f1af806d93abcca0326f15b650c43cd2e0456972566f002d04b55f640f7e4867ed95aa9daffac4e2e0219ffff0380cdfb4f3c718d06c88d0f2895a92c79e5a88067189476166dffa0b73a4bd0a603f858f5432ee3ba1a47f0cacd00dc9bab165ededc3035f375b276b6a5ccfe71cb039d3c061db293c93958f66cf82a08b00cdaa7865a29c0a68a028e049519c5274d034d867d9c19d067babc065f20726ff1e9c6e05805149e519c6016f07894a09fc003115e2a8560bf1cdf5d5481770e927a28ba860dc1888e432a9e5ba3b5582c428f0379cc7a92c9810d6c8289fcd81323357b5c4304d58aca6c4b8c68fbdbfd06d68703e098c63d5f20b1b33cf5d02c6f88b5fab96fca2f5ae310c144b7e882b0177a7f03e581ff362d772c5b4405136673b1d49b973726b6605335c9171006ebb0b233e30219ffff036d100c89da2e31cc4d3042e3ca24cab018e80207ae34641ccdf590aa3e8e9e4f03e1dbfe9780d374748ffc030cd2d9d1492008d95e182a20b71955a2f308ef21660219ffff0219ffff0397dfa101fde59d7a873fd990067871a26228726da88ed82217e3082ae0de9d8a034c6330889e479e0437ec18ad4221495ce093fe8604ff53615a54fbea804c051203c5b2f63f9a5a5cd66618806c193ca68fda574c5a2acce1ed016e8e80111b1fe5032d204460a4f390bb930344eca9cb78f197f1d73a30cff59173fc4c9118776c1b03ce3b8e21738ec00090bab67c0e56aba701f1ffc91ddd75971e8d05dd66804dad03ec7b3addeff25a874d06a1823987b7e739f53b8f18dc7cd09854084f7c4a480c034e5b05d9793394f441511084b00cd7ccff4f9207d8f7be27350b6b51c4b9834503ff61f62f38c0852b0c058ae5a37e5d6b4528add10bceddc6ed38bc2ff6b1f17403b05058f7de824628c4ecc041b3b1c502f92ec252850c58da6be29dfbe4bc405703be0b479b2cbf58363b88356f31d1067cfc06ed036a05a4f25aa29a2e73128e37032fe6479680414852100ea1896a2a1a31b538828412971c0944e9ed52ee9110150397b83d111a61768b3a7487748d8fcd3cb37bafa09bd88bc052e03b49e02e7dec0317605621ec49fe11b49d03a0fcb10f835fe7d288078be6b914d4565f9a114bea0356f99e41ed032708486527a36e3c998b9f42fc60f1c6a0f9c17b0a302a16537f03f4eebbeab1524ef7bcb20a30ce700431bfd8fbfa7aa09d472c10d704bd3e4967038d0e099a44f7f94546d79cf2bc08f9624998397189445f243f46332f84f0603f03d05250ea90479b253866120e5392b2b2d868e340657627c07bf99a10a5f65cca03050921a1e651703ca31b8579a026e9156270ae517c8249a1c185d861e75d5794039778ab010a0c170994e2568b7a74323405413c121da2146dfcd06854b976d95003af2edf1c12a65a6a5653a836da344214334fc18125b1e63bb65d7471d8c90ed903e17e89808bbbe14f5d187c548b09996169580f5a86b3e8e76ca051a12be8d2f603172a2b898855b435c0138b47ff7b835c7ea32d144f279af470e0d48eff8fb46c03e000d2ad2e91a07ad7fb1c40401ee7ac59c21d4bbde16c36cc05b0a1b0fd39ff032e0291f83f615fc2ac9ab68d158a2293bdc9338d4809ee467f8df0f1c112859a03bcd5bf62f604e285404479e944ade8ea7d3b9c2d21eb70a4c1571e15b0e86b8b0398a30a8778f0ded40305ff797aca464ce00a3f9f9f1bd2297e14ac40d335171403a41ff41dd7940030fda9502f8b9a53a3defafb850ab91965a9e3f49e1fb7e5e403774102fbd2afc19911bbdb4ba13dafcefc8e32f587a39acda10619fa3bf38c8303b82abff063c918fd489a5f382932ac32fbc683ecdbe87a5194521b697c0bc35903d6d462a3c91aca34f28e8b63fdcbc28b529b2ce8372da43b6af554565e08540703a79cd05a1f27dc92c6e4320d86987c4cd923f0f6b7170081eadee52096da34fd0330118fa5d8f20113dcf45f6e6e4ee9bc57511caac6ad6e5bb21f2f6331cfbb8e0330a5490d0d74cdafc07896ad493593c24c3a83af3764ab2ac45c7dc3f2780c300321018bba01709f429872107909c38efef0e53e71816b05edeb20af661d0191e30332ac4c48ca2b7ea7bef1df4cdd25d3369565fa6c33677e5aa4254c7289da87a60317f64686f9e80b29c5e086d6650879d2e10b03e76f1909dab35c065f74b1e18103ff4188c921d9b6f1bee6dfed6f7a49bfccabccc6bcdb8b58d1499eefd6c2a028033046c8c8a219c4c65d33095a44510122196a261ddca5f310d7c30c660070c4f603307ed868f1a796746f0a45d2c164bf06618ff25eca391c4bb92ea3d11d11b33003509d73eae7e6b8a705afad9c8c08b58e8013349667835aa89602cbec28c468080376131808cf1f70f19ffb7034be1db0ee00656912fa99de431ff955106f6e298f038d5c83f672a553c76c6d3b8fb64113dcf651c5b0172fb0fb6de6385306e2999b03265f87ebf0f1e77f65743101e01179f2b651258e7e671a023fbb3a154a40a8d703337c29fd9976d67b66b28034c1414c04861ce13b19a267c6e01d66f2cdb6bfba03387d1af7fb6734d678da59d1d8eb7aa81f851120b36bed51bdd5ea46b2a2d62e05581e0374a9d461b6a9d2c0b4b8eec628b533d580ac7a40dcb6d6d1d9e2c0e8f007011bffffffffffffffff031b460c826a854d61dca82f718e088b8b4c4082ffeb93752d7691bc62c51dc0280605581e03aea6a37014af491452adb5d70fa3a144f2893d475b7d03d22a07b08dc007011bffffffffffffffff05581e039009f9fb7f3600a0fd6cce71411b0b2fc393098cf0f18c99ed4c3fdcf00418280372a061fe6d5ad391d8e81fccdd10fa823507f7b7728bfb7810690b10aae2e44f05581e03468b1bad7d17d14b3da7bf86b7ac6239887e52714e679390842b97d0b00401034c45c04bbc4da5675b38332226f2ebc2b47b1e0c09e31f5e33c98c37eae7b14404592c1d608060405234801561001057600080fd5b50600436106101b95760003560e01c80636a627842116100f9578063ba9a7a5611610097578063d21220a711610071578063d21220a7146105da578063d505accf146105e2578063dd62ed3e14610640578063fff6cae91461067b576101b9565b8063ba9a7a5614610597578063bc25cf771461059f578063c45a0155146105d2576101b9565b80637ecebe00116100d35780637ecebe00146104d757806389afcb441461050a57806395d89b4114610556578063a9059cbb1461055e576101b9565b80636a6278421461046957806370a082311461049c5780637464fc3d146104cf576101b9565b806323b872dd116101665780633644e515116101405780633644e51514610416578063485cc9551461041e5780635909c0d5146104595780635a3d549314610461576101b9565b806323b872dd146103ad57806330adf81f146103f0578063313ce567146103f8576101b9565b8063095ea7b311610197578063095ea7b3146103155780630dfe16811461036257806318160ddd14610393576101b9565b8063022c0d9f146101be57806306fdde03146102595780630902f1ac146102d6575b600080fd5b610257600480360360808110156101d457600080fd5b81359160208101359173ffffffffffffffffffffffffffffffffffffffff604083013516919081019060808101606082013564010000000081111561021857600080fd5b82018360208201111561022a57600080fd5b8035906020019184600183028401116401000000008311171561024c57600080fd5b509092509050610683565b005b610261610d57565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561029b578181015183820152602001610283565b50505050905090810190601f1680156102c85780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6102de610d90565b604080516dffffffffffffffffffffffffffff948516815292909316602083015263ffffffff168183015290519081900360600190f35b61034e6004803603604081101561032b57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610de5565b604080519115158252519081900360200190f35b61036a610dfc565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b61039b610e18565b60408051918252519081900360200190f35b61034e600480360360608110156103c357600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060400135610e1e565b61039b610efd565b610400610f21565b6040805160ff9092168252519081900360200190f35b61039b610f26565b6102576004803603604081101561043457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020013516610f2c565b61039b611005565b61039b61100b565b61039b6004803603602081101561047f57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16611011565b61039b600480360360208110156104b257600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113cb565b61039b6113dd565b61039b600480360360208110156104ed57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113e3565b61053d6004803603602081101561052057600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113f5565b6040805192835260208301919091528051918290030190f35b610261611892565b61034e6004803603604081101561057457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81351690602001356118cb565b61039b6118d8565b610257600480360360208110156105b557600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166118de565b61036a611ad4565b61036a611af0565b610257600480360360e08110156105f857600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060408101359060608101359060ff6080820135169060a08101359060c00135611b0c565b61039b6004803603604081101561065657600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020013516611dd8565b610257611df5565b600c546001146106f457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c55841515806107075750600084115b61075c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180612b2f6025913960400191505060405180910390fd5b600080610767610d90565b5091509150816dffffffffffffffffffffffffffff168710801561079a5750806dffffffffffffffffffffffffffff1686105b6107ef576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526021815260200180612b786021913960400191505060405180910390fd5b600654600754600091829173ffffffffffffffffffffffffffffffffffffffff91821691908116908916821480159061085457508073ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff1614155b6108bf57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f556e697377617056323a20494e56414c49445f544f0000000000000000000000604482015290519081900360640190fd5b8a156108d0576108d0828a8d611fdb565b89156108e1576108e1818a8c611fdb565b86156109c3578873ffffffffffffffffffffffffffffffffffffffff166310d1e85c338d8d8c8c6040518663ffffffff1660e01b8152600401808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b1580156109aa57600080fd5b505af11580156109be573d6000803e3d6000fd5b505050505b604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff8416916370a08231916024808301926020929190829003018186803b158015610a2f57600080fd5b505afa158015610a43573d6000803e3d6000fd5b505050506040513d6020811015610a5957600080fd5b5051604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905191955073ffffffffffffffffffffffffffffffffffffffff8316916370a0823191602480820192602092909190829003018186803b158015610acb57600080fd5b505afa158015610adf573d6000803e3d6000fd5b505050506040513d6020811015610af557600080fd5b5051925060009150506dffffffffffffffffffffffffffff85168a90038311610b1f576000610b35565b89856dffffffffffffffffffffffffffff160383035b9050600089856dffffffffffffffffffffffffffff16038311610b59576000610b6f565b89856dffffffffffffffffffffffffffff160383035b90506000821180610b805750600081115b610bd5576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526024815260200180612b546024913960400191505060405180910390fd5b6000610c09610beb84600363ffffffff6121e816565b610bfd876103e863ffffffff6121e816565b9063ffffffff61226e16565b90506000610c21610beb84600363ffffffff6121e816565b9050610c59620f4240610c4d6dffffffffffffffffffffffffffff8b8116908b1663ffffffff6121e816565b9063ffffffff6121e816565b610c69838363ffffffff6121e816565b1015610cd657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f556e697377617056323a204b0000000000000000000000000000000000000000604482015290519081900360640190fd5b5050610ce4848488886122e0565b60408051838152602081018390528082018d9052606081018c9052905173ffffffffffffffffffffffffffffffffffffffff8b169133917fd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d8229181900360800190a350506001600c55505050505050505050565b6040518060400160405280600a81526020017f556e69737761702056320000000000000000000000000000000000000000000081525081565b6008546dffffffffffffffffffffffffffff808216926e0100000000000000000000000000008304909116917c0100000000000000000000000000000000000000000000000000000000900463ffffffff1690565b6000610df233848461259c565b5060015b92915050565b60065473ffffffffffffffffffffffffffffffffffffffff1681565b60005481565b73ffffffffffffffffffffffffffffffffffffffff831660009081526002602090815260408083203384529091528120547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14610ee85773ffffffffffffffffffffffffffffffffffffffff84166000908152600260209081526040808320338452909152902054610eb6908363ffffffff61226e16565b73ffffffffffffffffffffffffffffffffffffffff851660009081526002602090815260408083203384529091529020555b610ef384848461260b565b5060019392505050565b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b601281565b60035481565b60055473ffffffffffffffffffffffffffffffffffffffff163314610fb257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f556e697377617056323a20464f5242494444454e000000000000000000000000604482015290519081900360640190fd5b6006805473ffffffffffffffffffffffffffffffffffffffff9384167fffffffffffffffffffffffff00000000000000000000000000000000000000009182161790915560078054929093169116179055565b60095481565b600a5481565b6000600c5460011461108457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c81905580611094610d90565b50600654604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905193955091935060009273ffffffffffffffffffffffffffffffffffffffff909116916370a08231916024808301926020929190829003018186803b15801561110e57600080fd5b505afa158015611122573d6000803e3d6000fd5b505050506040513d602081101561113857600080fd5b5051600754604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905192935060009273ffffffffffffffffffffffffffffffffffffffff909216916370a0823191602480820192602092909190829003018186803b1580156111b157600080fd5b505afa1580156111c5573d6000803e3d6000fd5b505050506040513d60208110156111db57600080fd5b505190506000611201836dffffffffffffffffffffffffffff871663ffffffff61226e16565b90506000611225836dffffffffffffffffffffffffffff871663ffffffff61226e16565b9050600061123387876126ec565b600054909150806112705761125c6103e8610bfd611257878763ffffffff6121e816565b612878565b985061126b60006103e86128ca565b6112cd565b6112ca6dffffffffffffffffffffffffffff8916611294868463ffffffff6121e816565b8161129b57fe5b046dffffffffffffffffffffffffffff89166112bd868563ffffffff6121e816565b816112c457fe5b0461297a565b98505b60008911611326576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180612bc16028913960400191505060405180910390fd5b6113308a8a6128ca565b61133c86868a8a6122e0565b811561137e5760085461137a906dffffffffffffffffffffffffffff808216916e01000000000000000000000000000090041663ffffffff6121e816565b600b555b6040805185815260208101859052815133927f4c209b5fc8ad50758f13e2e1088ba56a560dff690a1c6fef26394f4c03821c4f928290030190a250506001600c5550949695505050505050565b60016020526000908152604090205481565b600b5481565b60046020526000908152604090205481565b600080600c5460011461146957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c81905580611479610d90565b50600654600754604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905194965092945073ffffffffffffffffffffffffffffffffffffffff9182169391169160009184916370a08231916024808301926020929190829003018186803b1580156114fb57600080fd5b505afa15801561150f573d6000803e3d6000fd5b505050506040513d602081101561152557600080fd5b5051604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905191925060009173ffffffffffffffffffffffffffffffffffffffff8516916370a08231916024808301926020929190829003018186803b15801561159957600080fd5b505afa1580156115ad573d6000803e3d6000fd5b505050506040513d60208110156115c357600080fd5b5051306000908152600160205260408120549192506115e288886126ec565b600054909150806115f9848763ffffffff6121e816565b8161160057fe5b049a5080611614848663ffffffff6121e816565b8161161b57fe5b04995060008b11801561162e575060008a115b611683576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180612b996028913960400191505060405180910390fd5b61168d3084612992565b611698878d8d611fdb565b6116a3868d8c611fdb565b604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff8916916370a08231916024808301926020929190829003018186803b15801561170f57600080fd5b505afa158015611723573d6000803e3d6000fd5b505050506040513d602081101561173957600080fd5b5051604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905191965073ffffffffffffffffffffffffffffffffffffffff8816916370a0823191602480820192602092909190829003018186803b1580156117ab57600080fd5b505afa1580156117bf573d6000803e3d6000fd5b505050506040513d60208110156117d557600080fd5b505193506117e585858b8b6122e0565b811561182757600854611823906dffffffffffffffffffffffffffff808216916e01000000000000000000000000000090041663ffffffff6121e816565b600b555b604080518c8152602081018c9052815173ffffffffffffffffffffffffffffffffffffffff8f169233927fdccd412f0b1252819cb1fd330b93224ca42612892bb3f4f789976e6d81936496929081900390910190a35050505050505050506001600c81905550915091565b6040518060400160405280600681526020017f554e492d5632000000000000000000000000000000000000000000000000000081525081565b6000610df233848461260b565b6103e881565b600c5460011461194f57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c55600654600754600854604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff9485169490931692611a2b9285928792611a26926dffffffffffffffffffffffffffff169185916370a0823191602480820192602092909190829003018186803b1580156119ee57600080fd5b505afa158015611a02573d6000803e3d6000fd5b505050506040513d6020811015611a1857600080fd5b50519063ffffffff61226e16565b611fdb565b600854604080517f70a082310000000000000000000000000000000000000000000000000000000081523060048201529051611aca9284928792611a26926e01000000000000000000000000000090046dffffffffffffffffffffffffffff169173ffffffffffffffffffffffffffffffffffffffff8616916370a0823191602480820192602092909190829003018186803b1580156119ee57600080fd5b50506001600c5550565b60055473ffffffffffffffffffffffffffffffffffffffff1681565b60075473ffffffffffffffffffffffffffffffffffffffff1681565b42841015611b7b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f556e697377617056323a20455850495245440000000000000000000000000000604482015290519081900360640190fd5b60035473ffffffffffffffffffffffffffffffffffffffff80891660008181526004602090815260408083208054600180820190925582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98186015280840196909652958d166060860152608085018c905260a085019590955260c08085018b90528151808603909101815260e0850182528051908301207f19010000000000000000000000000000000000000000000000000000000000006101008601526101028501969096526101228085019690965280518085039096018652610142840180825286519683019690962095839052610162840180825286905260ff89166101828501526101a284018890526101c28401879052519193926101e2808201937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081019281900390910190855afa158015611cdc573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff811615801590611d5757508873ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b611dc257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f556e697377617056323a20494e56414c49445f5349474e415455524500000000604482015290519081900360640190fd5b611dcd89898961259c565b505050505050505050565b600260209081526000928352604080842090915290825290205481565b600c54600114611e6657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c55600654604080517f70a082310000000000000000000000000000000000000000000000000000000081523060048201529051611fd49273ffffffffffffffffffffffffffffffffffffffff16916370a08231916024808301926020929190829003018186803b158015611edd57600080fd5b505afa158015611ef1573d6000803e3d6000fd5b505050506040513d6020811015611f0757600080fd5b5051600754604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff909216916370a0823191602480820192602092909190829003018186803b158015611f7a57600080fd5b505afa158015611f8e573d6000803e3d6000fd5b505050506040513d6020811015611fa457600080fd5b50516008546dffffffffffffffffffffffffffff808216916e0100000000000000000000000000009004166122e0565b6001600c55565b604080518082018252601981527f7472616e7366657228616464726573732c75696e743235362900000000000000602091820152815173ffffffffffffffffffffffffffffffffffffffff85811660248301526044808301869052845180840390910181526064909201845291810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb000000000000000000000000000000000000000000000000000000001781529251815160009460609489169392918291908083835b602083106120e157805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016120a4565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114612143576040519150601f19603f3d011682016040523d82523d6000602084013e612148565b606091505b5091509150818015612176575080511580612176575080806020019051602081101561217357600080fd5b50515b6121e157604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f556e697377617056323a205452414e534645525f4641494c4544000000000000604482015290519081900360640190fd5b5050505050565b60008115806122035750508082028282828161220057fe5b04145b610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f64732d6d6174682d6d756c2d6f766572666c6f77000000000000000000000000604482015290519081900360640190fd5b80820382811115610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f64732d6d6174682d7375622d756e646572666c6f770000000000000000000000604482015290519081900360640190fd5b6dffffffffffffffffffffffffffff841180159061230c57506dffffffffffffffffffffffffffff8311155b61237757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f556e697377617056323a204f564552464c4f5700000000000000000000000000604482015290519081900360640190fd5b60085463ffffffff428116917c0100000000000000000000000000000000000000000000000000000000900481168203908116158015906123c757506dffffffffffffffffffffffffffff841615155b80156123e257506dffffffffffffffffffffffffffff831615155b15612492578063ffffffff16612425856123fb86612a57565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff169063ffffffff612a7b16565b600980547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff929092169290920201905563ffffffff8116612465846123fb87612a57565b600a80547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff92909216929092020190555b600880547fffffffffffffffffffffffffffffffffffff0000000000000000000000000000166dffffffffffffffffffffffffffff888116919091177fffffffff0000000000000000000000000000ffffffffffffffffffffffffffff166e0100000000000000000000000000008883168102919091177bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167c010000000000000000000000000000000000000000000000000000000063ffffffff871602179283905560408051848416815291909304909116602082015281517f1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1929181900390910190a1505050505050565b73ffffffffffffffffffffffffffffffffffffffff808416600081815260026020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b73ffffffffffffffffffffffffffffffffffffffff8316600090815260016020526040902054612641908263ffffffff61226e16565b73ffffffffffffffffffffffffffffffffffffffff8085166000908152600160205260408082209390935590841681522054612683908263ffffffff612abc16565b73ffffffffffffffffffffffffffffffffffffffff80841660008181526001602090815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600080600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663017e7e586040518163ffffffff1660e01b815260040160206040518083038186803b15801561275757600080fd5b505afa15801561276b573d6000803e3d6000fd5b505050506040513d602081101561278157600080fd5b5051600b5473ffffffffffffffffffffffffffffffffffffffff821615801594509192509061286457801561285f5760006127d86112576dffffffffffffffffffffffffffff88811690881663ffffffff6121e816565b905060006127e583612878565b90508082111561285c576000612813612804848463ffffffff61226e16565b6000549063ffffffff6121e816565b905060006128388361282c86600563ffffffff6121e816565b9063ffffffff612abc16565b9050600081838161284557fe5b04905080156128585761285887826128ca565b5050505b50505b612870565b8015612870576000600b555b505092915050565b600060038211156128bb575080600160028204015b818110156128b5578091506002818285816128a457fe5b0401816128ad57fe5b04905061288d565b506128c5565b81156128c5575060015b919050565b6000546128dd908263ffffffff612abc16565b600090815573ffffffffffffffffffffffffffffffffffffffff8316815260016020526040902054612915908263ffffffff612abc16565b73ffffffffffffffffffffffffffffffffffffffff831660008181526001602090815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b6000818310612989578161298b565b825b9392505050565b73ffffffffffffffffffffffffffffffffffffffff82166000908152600160205260409020546129c8908263ffffffff61226e16565b73ffffffffffffffffffffffffffffffffffffffff831660009081526001602052604081209190915554612a02908263ffffffff61226e16565b600090815560408051838152905173ffffffffffffffffffffffffffffffffffffffff8516917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef919081900360200190a35050565b6dffffffffffffffffffffffffffff166e0100000000000000000000000000000290565b60006dffffffffffffffffffffffffffff82167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff841681612ab457fe5b049392505050565b80820182811015610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f64732d6d6174682d6164642d6f766572666c6f77000000000000000000000000604482015290519081900360640190fdfe556e697377617056323a20494e53554646494349454e545f4f55545055545f414d4f554e54556e697377617056323a20494e53554646494349454e545f494e5055545f414d4f554e54556e697377617056323a20494e53554646494349454e545f4c4951554944495459556e697377617056323a20494e53554646494349454e545f4c49515549444954595f4255524e4544556e697377617056323a20494e53554646494349454e545f4c49515549444954595f4d494e544544a265627a7a723158207dca18479e58487606bf70c79e44d8dee62353c9ee6d01f9a9d70885b8765f2264736f6c63430005100032032e2bc0c0ff22609eac8f10e1c8736f3e780dcb85055451e7ac674e2667ce4b570058210390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56304501db03ecf300582103e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af0552bc82346b125e21af9685fa629b9bd9d56348d32440058210366cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c688054c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200582002575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5820b73002977f7a921d00ea2ceae62e84aa6c739ed1af5d2e827341affa6f651e99005820025a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a85502ef738cbf907356636685fa416c08696ac5c309b402184400581f02e257e98749920f079c806953e0129df22aca1b5060fd814f8b684a2728b3410100581f0266c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7410102190204014200f600582103432903a0d939005005c35d6b3c91f991de7aaab2b261dabdd70424fcc29326004501db03e90b00582002f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee3582065cf5fa30000000000093dafbfc84712a218000000000001a17653070c4e80bb0058200252222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f549e9fbde7c7a83c43913bddc8779158f1368f04130218480219f44505581e0376d2eac2897b11b32dac81b81fcb6fa245e571f0c1c23e6d28e7ed00500701192c1d05581e03e3596cca07971bee28d4f7363be4d8e6c720a382a8292af73ded53fdb0040105581e03366941e9597fc49d153609a888bdc75e5450deea087c5f0611f91827300c1821471485b42652f89e05581e0349b55ace9b37869628798c01d3a6cf4accfa2ced1c83e8ae740820c89004110373efca8acfbe544c8ecb7016b28cc776fd50a99ff19b848bc15cf31b5dc8ed0d02195f9e03e8c46238406f0d1b98ef136d648c28e2e8b8263391c554e9880c161fff165cbc0219ffff0219ffff03c47d5275fe855b7d46701da078bec769e285a35a64d77d91b25bb4bf251ef7bb03297c29d0f390b5b65f6ae34cd85557acd49def0190974947409845cb46a105b003d600f6b67ffb988d6770719115ac25d166a5618f731c2941b05c46257f0f765503ae450d3a7931a3c1a2d797fecb8b532b2a416b011c5b66eac4c7a83c5ae15daf03733ac70afb68cedd7251f39375e8dc794165c34d8992a28c1a6e5a01cb42f41803ffbb9d63f88897c6ffbe2aa9fda8b87dffec04851e6d8d3e0775d7b396c584130311d88e5c395d975fbe216255c16ca42c588a712276c49586d4ff3cf9fae1688a0219ffff033ef8ae4cd7a240131254d9907625ad2d3616109d08815fbbfd5aee912acab2b2036aa54ca1e4f12f7f0207d3e512b4ce190bc18ccae85dd73565d9242eb5e7c6c103319913649e465932fc2243ed0047dfea864723fcf47b7de527bc4e974e9f1d6e03cfbd4d08fd9e1de875ff093bb45e8a218788f5970a14a1e90c80b02c4e854aa803100eb15c41005be0b31fdd22d44d97c2377cd4f1c8d95741532724c06be01905036417964ffa371c57e6f395e400bed05db58f626794555697abaf5e16745ff885030831b0dbf4d1c827c664a972dd16c58cc551f8874c30ab70fefdc66525cea43e031ad2f3f9d09fe09f04845ed7ad6b8e1a4fc5647dd25b8b8304fcb7668938d37203bcae8b659ce47717542c4c1749f0a2c34be0d5fe9c46d103cdd66852fff07ba703cbc536abb71f20e84af17bea2cc3ab6eac3c2fe56962d098c942b1b1578616960362649e2b63c97826ba896d600683c001438d9b10adb8436c8e91073570fb444f03c5577f2182fa52a3b43ce239d0d5af1dd880d26d8175d0a8525c0732d8b96610033f7c3082081fbe4203fd093095d9ff976f09a8346eb87e2afb5c33ed617007c20219ffff03263e78c2c2ecb01c326d913ec44010a5949ec8d8e2b0483cd67d11d7b93e991d03cf8f99b839001afd8fca3d13b0bed66b9c96375bea0cc5ec0a1344c88ed421e703c03d2180f03d00062beee405ebab84c5be230cd8100f0de54866251f12157a9603d7ebd4ca0460d8102685c92e5d03bc95a785a25c63e2bc34a8886ff642ff10a5035a366cb6a84f62aef78724cb22841a25098531cef93c0dfbc987eab478aa8e040314f43643e29c03c21c20958f4db255d2b3a2f1888708ce8be6fc00256de8862003808f5b817c9ef3c39a4f17c1871432671bead67a2f433934348aa261ac0be77603f8a6aae111f30d1f0ed6666bcdb9f835266685113bc61459b35127d6887ba4c40316c1b019f7cd665ce6b3252c67a0858b88a147a7a8291870d780585b7a92847d03094265900c976454fe42db08b4d728fc462700183a2b3d446d195c55f4c7251103209b8c33ca4f1acfb8fa7091164c40b38a03a24510c24832ea0d7f8ba37467f603ff1422c5fce899621581ef95eab73efb46e5fd530f4c0cf0447d81a30956df6c03e2f6538d24ae024739b843d26699c7df0947549b27b8f2380d8cf0e55dca58ee031dd09da6c3f2c1e38b6f91f10f829c61a759e62bb9aa3b6d8f1de7ea08db46060398589bf5bd087a3fa0c0b2dec3a1089336cb7846696a0a392fee264ddba1cea003a735abd8fc0586fbceac1ac1a7472a2f4898b4c601a9a9b3f1590a3f69f4a1e903396ef29935567ea809496029d14d2027e8ec312ecf7499f280a203f4aac7014d03ea7f236df86cb2aabb31337600179807caef5bdea8c405ed8676f1d4bfedee0403623cbf159197c45f809d7167e876912c42640aa6629fdcad4cda3a0de803905d033d60fca5c0df5a9d9a70427488679edceeff74b88fb4a71e5256ca89b36011af03c1159efe6b581dd8726a3f5a892cf99a681662bafdb5432442537be893bef21c033e3f138dfd37ea5fda09897df32334d848ab83d9f95a5f305cf15b4ce1f90e25033f20574ac973dc6157547709cbac0cb7c31baa2da17dfab35e9c8da6b92134cc031c6bf571f4a6d789d4be859c7bf5d2a0ebb03f319b10b04496d622391d9d2632031fc1badfee7a6b662a27e0b182a3362d8dc0dffccb42eb4c827911c156482ebf03b8defcbc8f20b79fdce12b4f0c3a2e13ed8aeb9c4ee0621a411c8717cb50505803ed4ae5fa47b5e981e3c4d006dfe2170de7fca39862936d8910a2142ef0500fc203f339b60b343ff744c8f79351ab7b107230b38a99680343d862ec0d9f7429b9350373945e16f1ea6b13c393411c087e6d69571ff68e9578c5f2ebb0a3d8cee01b9403eb1dc98b4de131e954727735765515f50b9438fc94b08ef5a7c815e70a903a5103b3da505310032b1cef8b5f2292a85cc526e7f85e1ed1d195ad081dd85468e2d203f88cc9e4a2338b6b02e694b1e4056b464444eaf1d445175e6b4625ca0798cb21034c7a79fd5fa2bd35628d79c6a2f03c8535b634f4d74081f4ab6af320c412f59603a7a293a1699408e03be2f671049d440b8437a643fa20be6066fb5dbc0cc7b3ff05581e03db6fa8971ad16c5dff0df61c983a6c5735b13dc7d2ef55a5dff37dc6100c0147012ef35e16650005581e03829b8b434022a63cba8fe236649a2f2cc1c1569878795dc6ccc39487b0040103562d59a51820d47f520c975e0b2bcffac644a509749a3161f481f57b6e826d210605581e031cc3c1014051f7421eb11f39d4bdfafd216f161720ceaf06e74cda37f007011bffffffffffffffff03d9341e9a79236fcf78e02523d3655b8297ec4328c6f1577d949e7d07765db30205581e03d87c983935083aaf8af636694de96453713cee0fc83260aed3eb33b4a00c01470124efdd56e98b0459258f3460ff164360ff161473ae2fc483527b8ef99eb5d9b44875f005ba1fae13331416602857612418565b5f355f1a60061b5600000000000000000000000000000000000000000000000000000000000000000000000000000000022c0d9f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000005b00005b5f5f60a45f5f60246101825f393460415260023560601c806024525f5f60645f5f73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25af150608460595f396004601646355f1a375af160fd575f5ffd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b0023b872dd0000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a9059cbb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b5f5f60a45f5f60246101825f393460415260023560601c806024525f5f60645f5f73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25af150608460595f396004601646355f1a375af1601a9081355f1a60061b575f5ffd0000000000000000000000000000000000000000000000000000000000000000000000000000005b5f5f60a45f5f60646101825f39600486601b0187601a01355f1a37856002013560601c806024525f5f60645f5f73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25af15060a460595f39600487601601884601355f1a375af190601f019081355f1a60061b575f5ffd0000000000000000000000000000000000000000005b5f5f60a45f5f60046101e65f393460255260023560601c806004525f5f60445f5f73a0b86991c6218b36c1d19d4a2e9eb0ce3606eb485af150608460595f396004601646355f1a375af160fd575f5ffd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b5f5f60a45f5f60046101e65f393460255260023560601c806004525f5f60445f5f73a0b86991c6218b36c1d19d4a2e9eb0ce3606eb485af150608460595f396004601646355f1a375af1601a9081355f1a60061b575f5ffd0000000000000000000000000000000000000000000000000000000000000000000000000000005b5f5f60a45f5f60446101e65f39600486601b0187601a01355f1a37856002013560601c806004525f5f60445f5f73a0b86991c6218b36c1d19d4a2e9eb0ce3606eb485af15060a460595f39600487601601884601355f1a375af190601f019081355f1a60061b575f5ffd0000000000000000000000000000000000000000005b5f5f60a45f5f60046101e65f393460255260023560601c806004525f5f60445f5f73dac17f958d2ee523a2206206994597c13d831ec75af150608460595f396004601646355f1a375af160fd575f5ffd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b5f5f60a45f5f60046101e65f393460255260023560601c806004525f5f60445f5f73dac17f958d2ee523a2206206994597c13d831ec75af150608460595f396004601646355f1a375af1601a9081355f1a60061b575f5ffd0000000000000000000000000000000000000000000000000000000000000000000000000000005b5f5f60a45f5f60446101e65f39600486601b0187601a01355f1a37856002013560601c806004525f5f60445f5f73dac17f958d2ee523a2206206994597c13d831ec75af15060a460595f39600487601601884601355f1a375af190601f019081355f1a60061b575f5ffd0000000000000000000000000000000000000000005b5f5f60a45f5f60046101e65f396004602a46355f1a3760023560601c806004525f5f60445f5f60163560601c5af150608460595f39346021525af160fd575f5ffd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b5f5f60a45f5f60046101e65f396004602a46355f1a3760023560601c806004525f5f60445f5f60163560601c5af1506080605d600439344652600460595f395af160fd575f5ffd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b5f5f60a45f5f60046101e65f396004602a46355f1a3760023560601c806004525f5f60445f5f60163560601c5af150608460595f39346021525af1602e9081355f1a60061b575f5ffd0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b5f5f60a45f5f60046101e65f396004602a46355f1a3760023560601c806004525f5f60445f5f60163560601c5af1506080605d600439344652600460595f395af1602e9081355f1a60061b575f5ffd0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b5f5f60a45f5f60446101e65f39600486602a01874601355f1a37856002013560601c806004525f5f60445f5f8b6016013560601c5af15060a460595f39600487602f0188602e01355f1a375af1906033019081355f1a60061b575f5ffd000000000000000000000000000000000000000000000000000000000000000000005b5f5f60a45f5f60046101e65f396004602a46355f1a3760023560601c806004525f5f60445f5f60163560601c5af150608460595f39346025525af160fd575f5ffd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b5f5f60a45f5f60046101e65f396004602a46355f1a3760023560601c806004525f5f60445f5f60163560601c5af150608460595f39346025525af1602e9081355f1a60061b575f5ffd0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b5f5f60a45f5f60046101e65f396004602a46355f1a3760023560601c806004525f5f60445f5f60163560601c5af150608460595f393460081c6004525af160fd575f5ffd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b5f5f60a45f5f60046101e65f396004602a46355f1a3760023560601c806004525f5f60445f5f60163560601c5af150608460595f393460081c6004525af1602e9081355f1a60061b575f5ffd0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b4646355f1a60061b562e1a7d4d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b60446101e65f39600481601601824601355f1a37601481601c016010375f5f60445f5f856002013560601c5af190601a019081355f1a60061b575f5ffd00005b3446526004610aca5f395f5f60245f5f73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25af15f5f5f5f47335af11660fd575f5ffd0000000000000000005b33ff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b60646101825f39601481600801603037600481600201824601355f1a375f5f60645f5f73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25af1906006019081355f1a60061b575f5ffd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b60446101e65f39601481600801601037600481600201824601355f1a375f5f60445f5f73a0b86991c6218b36c1d19d4a2e9eb0ce3606eb485af1906006019081355f1a60061b575f5ffd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b60446101e65f39601481600801601037600481600201824601355f1a375f5f60445f5f73dac17f958d2ee523a2206206994597c13d831ec75af1906006019081355f1a60061b575f5ffd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b60a460595f39600481601601824601355f1a375f5f60a45f5f856002013560601c5af190601a019081355f1a60061b575f5ffd0000000000000000000000005b60a460595f39600481601601824601355f1a37601481601c016050375f5f60a45f5f856002013560601c5af190601a019081355f1a60061b575f5ffd095ea7b3000000000000000000000000ba12222222228d8ba445958a75a0704d566bf2c8ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff52bbbe2900000000000000000000000000000000000000000000000000000000000000e00000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b6044610dbd5f395f5f60445f5f856022013560601c5af19046019081355f1a60061b575f5ffd000000000000000000000000000000000000000000000000005b6004610dbd5f39806015013560601c6004525f6024525f5f60445f5f8546013560601c5af1906029019081355f1a60061b575f5ffd000000000000000000005b6101c4610e015f398046013560e4526014816021016101303760148160350161015037600481604a0182604901355f1a60a40137600481604f0182604e01355f1a61016401375f5f6101c45f5f73ba12222222228d8ba445958a75a0704d566bf2c85af1906053019081355f1a60061b575f5ffd00000000000000000000005b6101c4610e015f398046013560e4526014816021016101303760148160350161015037600481604a0182604901355f1a60a40137600481604f0182604e01355f1a61016401376014816055016070375f5f6101c45f5f73ba12222222228d8ba445958a75a0704d566bf2c85af1906053019081355f1a60061b575f5ffd00005b6044610dbd5f396014816002016010375f5f60445f5f856016013560601c5af19046019081355f1a60061b575f5ffd7c5e9ea40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b60a46111b05f39601481601501601037601481602e01605037600481602a0182602901355f1a3760048160430182604201355f1a375f5f60a45f5f8546013560601c5af1906047019081355f1a60061b575f5ffd128acb080000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd8963efd1fc6a506488495d951d5263988d2500000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000062000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000000000000128acb080000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a800000000000000000000000000000000000000000000000000000000000000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000001000276a400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000062000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000000005b60246101825f39336024526004356044525f5f60645f5f73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25af100005b60246101825f39336024526024356044525f5f60645f5f73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25af10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b60e46113fb5f39600460166015355f1a376014601a60f037602e357d152100000000000000000000000000000000000000000000000000000000016101225260405f6101265f5f463560601c5af15f513460181b1160205160445114161660fd575f5ffd0000000000000000000000000000000000000000000000000000005b6101046112d55f393460181b6044526014601a60d037602e357d155200000000000000000000000000000000000000000000000000000000016101225260405f6101265f5f463560601c5af15f515f036016356015355f1a1c101660fd575f5ffd5b60046101e65f39336004526004356024525f5f60445f5f6084355af100005b60046101e65f39336004526024356024525f5f60445f5f60a4355af1000000000000000000000000000000000000000000000000000000000000000000005b6100e46112d55f39600460166015355f1a376014601a60f037602e357d16c100000000000000000000000000000000000000000000000000000000016101225260405f6101265f5f463560601c5af15f515f033460181b101660fd575f5ffd00000000000000000000000000000000000000000000000000000000000000005b6101046113fb5f393460181b5f036044526014601a60d037602e357d16a200000000000000000000000000000000000000000000000000000000016101225260405f6101265f5f463560601c5af160205160445114165f516016356015355f1a1c111660fd575f5ffd000000000000000000000000000000000000000000005b6101246113fb5f39600481601b01468103355f1a37601481601f0160d03760148160330160f03780604701357fffff000000000000000000000000000000000000000000000000000000000000167d16a200000000000000000000000000000000000000000000000000000000016101225260405f6101265f5f8546013560601c5af15f51826016013583601501355f1a1c11166020516044511416906049019081355f1a60061b575f5ffd000000000000000000000000000000000000005b6101246113fb5f39601481604b01601037600481601b01468103355f1a37601481601f0160d03760148160330160f03780604701357fffff000000000000000000000000000000000000000000000000000000000000167d16a200000000000000000000000000000000000000000000000000000000016101225260405f6101265f5f8546013560601c5af15f51826016013583601501355f1a1c11166020516044511416906049019081355f1a60061b575f5ffd000000000000000000005b6101246112d55f39600481601601468103355f1a37601481601f0160d03760148160330160f03780604701357fffff000000000000000000000000000000000000000000000000000000000000167d16c100000000000000000000000000000000000000000000000000000000016101225260405f6101265f5f8546013560601c5af15f515f0382601b013583601a01355f1a1c1016906049019081355f1a60061b575f5ffd000000000000000000000000000000000000000000000000005b6101246112d55f39601481604b01601037600481601601468103355f1a37601481601f0160d03760148160330160f03780604701357fffff000000000000000000000000000000000000000000000000000000000000167d16c100000000000000000000000000000000000000000000000000000000016101225260405f6101265f5f8546013560601c5af15f515f0382601b013583601a01355f1a1c1016906049019081355f1a60061b575f5ffd5b60046101e65f39336004526004358015611b13576024525f5f60445f5f6084355af15b602435801560fd576024525f5f60445f5f60a4355af1003c8a7d8d0000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000620000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001af000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b6084611b2b5f398046013560e81c60020b602452806004013560e81c60020b604452600481600801468103355f1a377f1af00000000000000000000000000000000000000000000000000000000000006101245260405f6101265f5f604886033560601c5af15f5182600d013583600c01355f1a1c11602051836012013584601101355f1a1c111616906016019081355f1a60061b575f5ffda34123a700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000004f1eb3d80000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a800000000000000000000000000000000000000000000000000000000000005b6084611d1a602039806015013560e81c60020b602452806018013560e81c60020b604452600481601c01468103355f1a378046013560601c60405f60a45f5f5f5f606460205f8a5af1956024611dbe5f396fffffffffffffffffffffffffffffffff6064525af1165f51826021013583602001355f1a1c801591111716602051826026013583602501355f1a1c80159111171690602a019081355f1a60061b575f5ffdff5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f000000000000000000000000000000000000000000000000000000000000000096e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f000000000000005b5f5f60a45f5f60605f5f60445f5f600235805f527fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000060145260285f206055611ea45f3960155260555f2073ffffffffffffffffffffffffffffffffffffffff1660446101e65f3980600452961c6004601646355f1a375af150608460595f39346021525af160fd575f5ffd000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b5f5f60a45f5f60605f5f60445f5f600235805f527fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000060145260285f206055611ea45f3960155260555f2073ffffffffffffffffffffffffffffffffffffffff1660446101e65f3980600452961c6004601646355f1a375af150608460595f39346021525af1601a9081355f1a60061b575f5ffd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b5f5f60a45f5f60605f5f60445f5f600235807fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000005f5260145260285f206055611ea45f3960155260555f2073ffffffffffffffffffffffffffffffffffffffff1660446101e65f3980600452961c6004601646355f1a375af1506080605d600439344652600460595f395af160fd575f5ffd000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b5f5f60a45f5f60605f5f60445f5f600235807fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000005f5260145260285f206055611ea45f3960155260555f2073ffffffffffffffffffffffffffffffffffffffff1660446101e65f3980600452961c6004601646355f1a375af1506080605d600439344652600460595f395af1601a9081355f1a60061b575f5ffd00000000000000000000000000000000000000000000000000000000000000000000000000005b5f5f60a45f5f60605f5f60445f5f8b60020135807fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000005f5260145260285f206055611ea45f3960155260555f2073ffffffffffffffffffffffffffffffffffffffff1660446101e65f3980600452961c60048d6016018e4601355f1a375af15060a460595f39600487601b0188601a01355f1a375af190601f019081355f1a60061b575f5ffd00000000000000000000000000000000000000000000000000005b5f5f60a45f5f60605f5f60445f5f8b60020135805f527fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000060145260285f206055611ea45f3960155260555f2073ffffffffffffffffffffffffffffffffffffffff1660446101e65f3980600452961c60048d6016018e4601355f1a375af15060a460595f39600487601b0188601a01355f1a375af190601f019081355f1a60061b575f5ffd00000000000000000000000000000000000000000000000000005b610126611b2b5f39806015013560601c60c452806029013560601c60e45280603d013560f01c6101045280603f013560e81c60020b602452806042013560e81c60020b604452600481604601468103355f1a3760405f6101265f5f8546013560601c5af15f5182604b013583604a01355f1a1c1116602051826050013583604f01355f1a1c1116906054019081355f1a60061b575f5ffd5b3273ae2fc483527b8ef99eb5d9b44875f005ba1fae1314606060845f3760605f2074ff1f98431c8ad98523631ae4a59f267346ea31f9845f526020527fe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b546040526055600b2073ffffffffffffffffffffffffffffffffffffffff1633141660e43560f01c5774ff41ff9aa7e16b8b1a8a8dc4f0efacd93d02d071c95f527f6ce8eb472fa82df5469c6ab6d485f17c3ad13c8cd7af59b3d4a8026c5ce0f7e26040526055600b2073ffffffffffffffffffffffffffffffffffffffff1633143273ae2fc483527b8ef99eb5d9b44875f005ba1fae13141660e43560f01c5774ffbaceb8ec6b9355dfc0269c18bac9d6e2bdc29c4f5f527fe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b546040526055600b2073ffffffffffffffffffffffffffffffffffffffff16331473ae2fc483527b8ef99eb5d9b44875f005ba1fae1332141660e43560f01c57000605581e0301f6c3949c69f9617d8cada9fa07b6fda3ca62cbb047761f4b81f57f500f014701539d60bab84819258f05581e039d23217cc1d0e25898a08368730ce1c3b413b7c4726dac94a5bb635c300c01461319718a50000219b7960344f69efc96b5d710a1abbd3de340aa0c5b19f185f5f5af0aafc63564c49470ae037f71f69d7ffe6eae84c543710b1e63632992124d6efa05b9c9eea1cecde51c3803d30be93feae6122c5c83fcee4ade19d8593dc0e77a70a1352fbb785c9bf740e303408ff80ceb10dfee2c5172ac2b00b97725e08b421f8afbfa60eb18f29dfb64380300254560b9f93c0ae534fc8ef874e98b7bd17b8fbb1ff4749952add33408696503d16f183da84481cfbd79c3b6c17a2a7640a29d64af2305d38bded0603756548903afbbd44d1035934b2a5e002b0a9d4da379ca6bf12a26733a4a21b72bf308688d03d9904ae3e52c3563d7f895ec9d4c69693cbd56916be499561bea193442a13db30393eb3d942ea02eb0b8981a284886f44b6a1f2ca9529f288a0afe64f20925809b0346ca8f23a3aa58be5e6f8b317d0e816294011f3e17463ed636aaea1ea255f3fe035290850ee2daa65453f3c475815e68b6d6d7fc8cfe02ab05ce6a365b55f7e5580219ffff0308bcbe1e77152c55aeb289421609c72d4840c442f4814f04e70dc7562d1bf9db039d81891c5d2f490c3476628bd0dee50f0b2c59b04936e14b6504d3bc38de6ea5037e36898afbf03a5b22da3a7a344dab864c5197cc41f6fa21d21bd9b0bf1396150396767989914d58a550d23b6f74deaea46038390f3dbc86b11d27d8aaadeb38370219ffff0219ffff03e02e7a625c1d2b58c584c3faaa9d8d9a805a86cba7348de8f107045a566dfbe0038a21425582d206c56c0299fb1ec2ce460d7efc93c74782c4f0ddb8d4759b92d003ce0381cef54e33fd2879682803e2b3b61820073ad8267542b8f95dbfead60a0c0339c2ff07b8adb3428f1b927a455d7f08f8d18ea6bed4425fefb7b2ac5857393403a93670b3765ce75c4b84af1b1ee8dbf50a097a54cd03d7f2a0bc1170a21b458e03b15e47487a3e6b993f6eab695f5cec6d04e49eded15e09cdfa03eba94ab16dcc03b65efcb505f6da7cfca1ccc4547383e15462e2a415db205862d41658143a475e035fba676c58b437a5a5a64249fdb3fa7dbec751f54f167e240ded0a2324ce38880395973bb87b5d88ab4778580f0cd48d7698cb5bad10d3a72504e1fec61dd24da403405649a7a763314cd97968b58df25cdafd75a746478baa157bc483bbe3c7a1df03bb9920ebae59b43b3bccd50d7d160e8fb7f06ea9eff1c3c979f7c5a49153b43c03e5129ef9780100869a9faaf43d334c19b45707ef34c914834667d1ac03eece33031f359bfad93710c88f17c4170fdbcc623277b952dbd4d62748c6fc268f9047980381afdfa7774209fcb4bc07f91ec04ebfe1b89c69a9ae1ebe2bc43536e45d46030314ec32550afaeab1ed605a3a72100325cb76c0e8fa24e3c69438aa77a1a4f74d03c76fc44ec84125b9611cfe7b65ae97a94e4c3be48cc6d956ab81073e3ed5d0fa030bbf75bf642c80da34b73dc943a30c7f1b3be348bcb6d1e2529fdd51449e08a103cf160104d02da84a41724719c27da09ceb1a51b00928fefcda317edcf309825903430344155e34189a33e11dc4c8ad6a9404df9870a2e18ad0865721f7c1f1147a03bce449afc50e44019e3aeb75e9f2d6c72042d0a20464b69d61eb2d3f2ad96843030531eb4a0afef3cb404d7675870c3c49efd29b9a4b859ddaae94c36310b7ae0603e752d314fe049bd7c6ed39c67d2f1f639c45eabfeb257b9147fdd57481857e3c03cb64018e30deabc41ab7512bd25329679bec782bc7628f4f0a116407316e648303894b6c455abd1c361917145aa5eec85d66b43da4327f92f9abe6b738ba9323c1037231449890315a74a2d29846f6c25d573b12d5c1222989c4588d400044406d2e0368ebaf5ceb22e2bc333432b76718d6c36166d1444ea4dbb2dd5d53b77494290c030207bd1b438590dc2ea4773092ff6840b379c1206859eb63b419a18280273513034c42088bd69508dc161cf6548f23a976f09ba9bec3c446d4a2cb7bdbc95a323203c286dab82a74c887eb5d2eb399eb414c81f1083efb4af7d185086646598e62fc035b9e9801cb4418702c6b8d3be24d9ee539bee1721f92fe233a0d95ba588e81db033661a6bd0c24511a05b40f8ae74c737f47d0df70bbf3c7a0816810ed90a8442603d6b82b5896be46433e7349f5005e0d5958dbc3747435483d956149bf9a41740b036af61ac19cd4496fb6d376dc38a8165a2561022866ce6fef41f8ade0fd2a407b031d942c5f793bd295e61894015b65c2a0b772da5420e868784603db7d45fc38ab034f96708c335dd6364f8c5b4fc62edf32f5fd9a7107a99be1e9f6914b4093b88c03e3981f17dbc1a00258432c0e34232f267d05e271590ee7a9783a51870f9873c8033a7c4e1b76aa60dc9d631272c2ce6a72e82914e0cd8ffc759889d5f24cdedeeb03ca06c2b4c97d9941e56c3c752abe4c2b0b2cd162e22a5d25f61774dc453deedf05581e03da7d3b581a6515e83a053ac10dee614e37c74397b0001f77359de74100040305581e03e08b15e58c41c3c9db35bc5268e123e5e61f633bd60f8dd66ce320d4d0040105581e03961ea71a2092ede541a5313d58a72b669c614b45d7c9e73ab7f40b85d0040105581e036be6c90bff6b129f08f2d3732f40a37dbc9a95e1c60843ed138e4969d00c1836470135ec955aed3303235db60b9fecfc721d53cb6624da22433e765569a8312e86a6f0b47faf4a2a2305581d024eee85039fc9d8260336899b904d35911f52859f57c1e664281186f20c0147a7a5422d062ab804590c346060604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100b9578063095ea7b31461014757806318160ddd146101a157806323b872dd146101ca5780632e1a7d4d14610243578063313ce5671461026657806370a082311461029557806395d89b41146102e2578063a9059cbb14610370578063d0e30db0146103ca578063dd62ed3e146103d4575b6100b7610440565b005b34156100c457600080fd5b6100cc6104dd565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561010c5780820151818401526020810190506100f1565b50505050905090810190601f1680156101395780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561015257600080fd5b610187600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061057b565b604051808215151515815260200191505060405180910390f35b34156101ac57600080fd5b6101b461066d565b6040518082815260200191505060405180910390f35b34156101d557600080fd5b610229600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061068c565b604051808215151515815260200191505060405180910390f35b341561024e57600080fd5b61026460048080359060200190919050506109d9565b005b341561027157600080fd5b610279610b05565b604051808260ff1660ff16815260200191505060405180910390f35b34156102a057600080fd5b6102cc600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610b18565b6040518082815260200191505060405180910390f35b34156102ed57600080fd5b6102f5610b30565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561033557808201518184015260208101905061031a565b50505050905090810190601f1680156103625780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561037b57600080fd5b6103b0600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610bce565b604051808215151515815260200191505060405180910390f35b6103d2610440565b005b34156103df57600080fd5b61042a600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610be3565b6040518082815260200191505060405180910390f35b34600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055503373ffffffffffffffffffffffffffffffffffffffff167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c346040518082815260200191505060405180910390a2565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156105735780601f1061054857610100808354040283529160200191610573565b820191906000526020600020905b81548152906001019060200180831161055657829003601f168201915b505050505081565b600081600460003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b60003073ffffffffffffffffffffffffffffffffffffffff1631905090565b600081600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101515156106dc57600080fd5b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16141580156107b457507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205414155b156108cf5781600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561084457600080fd5b81600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055505b81600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190509392505050565b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151515610a2757600080fd5b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f193505050501515610ab457600080fd5b3373ffffffffffffffffffffffffffffffffffffffff167f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65826040518082815260200191505060405180910390a250565b600260009054906101000a900460ff1681565b60036020528060005260406000206000915090505481565b60018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610bc65780601f10610b9b57610100808354040283529160200191610bc6565b820191906000526020600020905b815481529060010190602001808311610ba957829003601f168201915b505050505081565b6000610bdb33848461068c565b905092915050565b60046020528160005260406000206020528060005260406000206000915091505054815600a165627a7a72305820deb4c2ccab3c2fdca32ab3f46728389c2fe2c165d5fafa07661e4e004f6c344a0029036aaeb1e0dd249b087cd1c940635994a67018a3efc2936343a1d5683dfb2222a003148c0f2304aec3353cf5ed7568766ce494a21fe011985cddb407f939e9e3784603b1a4e93c311e947a63c609299e2ba2d7c4366b66b7ff9f76a47a9f664e12ae8803977ff6ee86449fd9ebac8463ff545fdb4a38e11385e958bf6277974047b1d236036c4c151180a39edb4fc33f328395429f008873fbfb4e59af8b3efd262acb84e103089710c7a16e171320a10b6bfa611bcca0d1c600363d8b0e93026e86ccf00eb303f43a6e3544d2c39c8401dabf9a56c3833b0a6f076bb84531f56a458fc067e2af039c1e0323f514f23b03257afde58fa72cbe2b64f4b1fbc02ba92903634d4af557033c9ce9384b1306ecaf25eafd1ac442076e44ee9e517bb0309557f1dbbaebcdcd030a66e92eeaf801092919fe5c726811e2e2afc96552fc69f880d0375d795c2c070364e28106c325c81a6deef5895ded24763fa571f68be656b87c26f0ef0e73bd89035e65546b978b0a40324014c9c99e4c5cc66ede9d8c6f06a52b76da37e2dff7a403e6d2a3cf8466027d8ce6c3fd8b8137fcb5d3dc34221837a3c0052493d6d6aea003bc60844ba0c0f950e431ba1ae8f48914bd3615b148b2be4f0b205c62218dd5e603a0ad1e2e5f67d5d490f8de1a408c30dca01085e318971df5c131e63df2a836e403cb985a6e5f969d91cd70fd13601a14e12389fd9838cc92d23bc36973e136359003b24bba13c7f0dd576eebbd075439e3c822e4f06a125e1d43b71197e098555374032daa3bb00175af3b8e2990396fff10297352bd5469719dc25d3919f285399978030b91957a4270f52c4bef2a9148d90d5d7ac07d2edf289a0bb1187768c942a92e0384c1c153254938bcf5833e3266c69d11863f01beebc2172ef140895ab5e8137f036c73c87058593f7a4c5f34ed7a844533bf2ae21f0dcc75be2305cb26502e27d803a7116b013767ee61d04cfc7ee0ef0fa6eb0c6e0b7d9b21b78302c7452509581003a0555fa89a6ac0d1f753c5bc430ca1bd74595c40928b8eb150567bdc1a85892403a2f2c2f85b117e77fbfee593bc7f1a87d965760ab4ceb77e2bdc085c22b2c46403e87a120a0a27451b6271d538a807bb0085c31bfcf491f46298bf79e07f1561b1039504a9cb5fc23525872531ecc2f65ab0bb5b29115c62bc024a8912f1fba1cc4c03202d28845054f6fe6a50978ee6537cc8f29ea9d08a2b9d1c560a3a044f9e8be603ab12089e9f6f349abbebe4f7498455ee8410c5cf24aa2586edea6446e9593ab3036d7c1259ed661bf224d1f6954d7a13a9112f7eac5ea9541142fc8327142c1185034302512bd497d6c6691c4c6d78fdf99b51252c4cf34b284987001796c063680d00581f031843a5260f50654be60454888c12e1df9a8f5beca305d3643235bb2410105820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0314ac2a4037bb9c454b5ff1d341e61930d98f8bbf8d3879fb16bd9c75c9b180540391d0c3f554b53449edcce8701fb7941e35a59dc2ec26b8d30a92a204b5e6f6df03d3da91b634b1a7274b216a230bbcba5e24d92dcb0acdd9600e61d9d3e6a0e6ce034453e289c78c92dd80619ded94bf85359c38c30b8ccfbad693f38d7b6762751003a648a3a6eb8bd75eb1e2494b42f22a94eca6d6f608b5b7aa2ee9b5c0e9ca547d030feb2f389db71094872ccb0acb1ccff2f1aec23fe78ff388eccfd0000edba3ac0306c41f230bca0bff2ef0f2eee1e9f31dc74fdb942d2744b67227fac91ceee0ba03d05faf394c9234ac4e98e507310fdbd59c2580cd987c354cc84105e56caeaea5038101d8390f58e6e7da14d23c8005b95989c7ebde1a3c6982158464c2aaae60a5032e72b7f4dce792eaeeab1c5033624a348c0511b1cc58e47802ebc95995d428db038c233253a52c8d290c1cc6d7784cbcb618436a5b351f4d65c5ec53e05c7b2abb00581e02e45fa4a838b134c920a70c634c9ca44bca01a924b0040860770d1aed8947726d75c93f880000581e02920e6cbb93e03175c156b6576e7c589a7b95ec4418551adbe64a3944845820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0218280393d772a69697218f224549a9e49fe5d9bc8b9c7fd49e7ea79f9713960c9076380219fdff03125272869e4a25821c1cfbea8c449006295201c8a3b3dc92aa3406d39104c8a90349d115a4a345f8a268d6aee0628a37f606c4cd02adc99bee0d669911de16ff68031cac3ecf09fdfd75a2d143901f6954071b1174b117f8f904143cf0421911a13b0398a46da7dd993981a9a8dcaf22ade3299be2a26ef780714b027d49f654e022850219ffff03df92241811a831787efcac462fd390fe5b43ba9dff23ff8a7c31535e602b6ddf03132b10049085bc9411c024dcf21327de2162f0af350e374686035cd48fd66e540315d16c487ef535d0f173a49c98dee30280d70f178dcab63c169934921b6a88fb0340fb1c046cffa79a46c8dfd65d13fe4b7734d4f0de8e5db9b56e0a3c74e0f3ce03ccab9c6c1f535a5c35a33a1cf64e366963750b0fca755702b3b5c1417dbb2dfa03cbb86d45dd9af8dbb00aceafcc979fc30e5aa0cd791c9d803edf2c899c3ffbae031285699058db64b8b3582b409e3336dbbfdc4d9c652586f8071df03934d17e7903982f0f8eaf23a1e6644fd286aa80994e30b4776d9673217bfe49330d9430d6c40219ffff03148ce59153c3b6d3a51adff6fed05e04856b41a1b213cf618eb02a0464013c6003907445856d4e7bd1e631857782665c201d5eaf44a89e6dcc56cb91f6710699a9039b08d40fbd4f420c2c9ce0bc56f0bb68851bed1077a3ef9a09227b4d52e7f34303b079f07a552409694a69868e97b41a31d05b058048fc2b22c31afda30a32e4d2032def77a99f3cea53463e061361a4a5c2a6ebd51b4bce0183b47a2487bed4b9dc03c2b80bb8433cb5be1729135b7cd381feca3c71ee3f8d3081dd78e395521f43b103ec922eb0dd7dddd163f9662050404d3f517b59359c4e488a1ade52b41beb8b290219ffff03f9b1a3de1ddf51fcd02852b050cd82a44af42a31e4ca0431201232bab87fb2b903d662475b9dd7bc4fb29a113d47688b1610d5ca1d97cbe41a09e3c45d776e13c103e43ce858d3bb97be3f11d30381e4b0b5b5d495d4b60a720c4c76977075740a230342b92a6a02d43f2969169120f474850868a376a6f3baeee2b8ef27d72d00d5a70314f0018f0ea7622f10c9c16904d69aa082d2e697bf910dd5bd22c11e4343da810390fb0cdb6d73176293d1bd512a8eea879edc3f7761cfe65860cd54d5eb05da360323bc64835d47eba09af91d3d699dcdfc662f646e18e27614efa1cda0d15a885003d44a7b9d01e902fb5cea241748244a1ca359cd7b01b2c93c3561c6b8d715812a031d5dcc5bec9cee90eb95da8c3e5ae9554113c5620a4042832dc2f2a0d38bf7ff0393b1047e44528ddf116e8ed492a6c212860705c19664cdc6a239ac6bd165999a034b78d73443410fe7e26a2651584ebfd40f664016e64de6eb1ee5512ce60b8ab9035f58b8d1c6795c7d004b3a0d7d66693db1d42470826e7904aa11ac26ba5be1d903ada595f73bd0c03b8674369a0b872da7a416403a407fcaba17b7cccf8c441f0a035147c2b7234b7f98df9b329bffdef77beaed68d805393270b0bca31c0974e20b0341f585d1afa7058de9c4ff75e578cfee0547740d52761109429e1a500048b8cc036e000c659ba7400fa969a30b46bc8456a87ea85d4e8dde9a6d0a9f2b362220de033a4e8dae42372574bb9b956bed5599265ca7f77ef6ebc02fa4422734dd492e22036eee0803e5c6cdcdfcfe2d5d87413d3da3a75b981e35cee98f1b116321f6696c03432cbd7efbaa89401c5517539e8d5fba8caa121bc6bdcbd305cc8caefa1d05580345f92df33a0fd133c2dfc6d1d13eb684f07677d672c5f645508a620d143bd26e0323d9ebad29e4e7895ad2185bac3cbc7ce6fed69f4c9145fe7f2c7b25009789b903519e65d019ed3c6cef529cadefbebb066c1290e7d2ce262e11daf1fddcb1beca03c5bfea76dd60de9c0c9c74275e1780cf1803d2c1432e214659eceaa9ccd8fe3103f6ed084f430bffa399efad0fec00183805450aa056dbfdadc0101fac40bfcf33037b812d3d2422b715d9011dd10dc45cfb80de8d0e2d036c8a985d695bf236f88003730022e3f37dabd44ebae9f0b8ba59d9e9bf5d9557641ee8813b57f57b312d2b03b9cc9bb14776136d03fcb066764e3d75df42bed1fc4c2618fc0db7e3f472c0bb03268e78e75296e051471665ff51a2c6ee66688091ce14d4e4962d238cf183556403c161d822f7e345d1a803ecec4dee41f647129dd2e4b3e121b41eacbc90334e93039fdcf29c144697781256d8c58c8d5a4647f379da8b38c80046ab5e613452d68503563c67c676cbb02b118096b4986ba589cc4a0c80776bb11d0946f42769e815ad03adfef6aa619a9c61fafc73488a137b2b9067ef7f3084b589a2c36d7d85088a9b03192fb8d2fd62532e258d48a708c613ab0d6f749dec3a705ebb70f0980bffd6d5036ce7b69c284e4d5ff7aa7591172bad92c5ab6539ca0a7cf8bd5bab695707badd03cf71e2f66aaa87e44b06737375fdf40912da232d600f267924e7adeaef734d4e037fe6aa9028fcb17c4218228fa2205bad1d1265207801557f6b71143937908f0a031fce0e35b7afb0734361df0242ee908a9e77ac2e6eb64313dd6123339c4726d6035f95eec9ac279db4f8cbd8c6d73591e534db823119bf835b09e6dc0f91df7eed039a0640a47039e93a2cfda190fd8a488905fcf7bed4c9409e6ffb3ab864ae05ec03de3c56e17f75a7134788e2b302612a64e9cbdfcf2f6b80f576da8eb48e6c92fb034dbebd109c98fafc3ceccfd8d2f5a1620f174b97c667b83a89937ca04f9e0e6603a0a5fe05a71f0bf6cf1ea4b15415e886df2db11aba30e57a1d7a9cffb1d360f000581f03c0989249a6e0cc82ed453bf7ef7c1dc3d5a2f80aaf8ae6c92eb1e4b275304b4a817c417bd859b65ba95600581e020e15399fd39d541ab913f704b29f2ac5a0a1d7c8de1df5fc284ad5bc8e491df6e71091139771710375c66964d75b3c09922d520522ccc74fed954a49cce59678beffd1d6219548ae0219804003897b04fee6fa11c85ccc358e065296b5167fc4f1f98dd3f441739a1bf4f31a9a03df6e21134b4f253174b754122369e4a968a58a80ab8cd11a3ef309b19c4f13d103de9555841e4d38f19a907cef279ed0305a3b40f31b94bc562044c603f21c86bd03d282d00380b4627f3343a7bd6b304f79e9d11e4f2ffa6d682ac37eea2157144803f581a7918745d765bdd33da58b8352443b12956c816c6615b6bf5d46f973968d031c2ac99060a723d5528838eb2bd045c0f26dc8d79db971353ccc45e0c1cdfede00581f03a54502883d504147946f04c0bb6f530de60ef10da9c004d4d863c9e7f5805820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581f03d43c201d1d6d4b455cc27e248ac5edf9245e40430e0454431678796079e05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03689ed8ccdb1d6c4d4f631a5783a46562d2ea4b506e0457329cb7e9e29ac7f24203748330133ace753c29d8bd2f4ac82b0a5dab4921cc373a94f99abe6554caddba0219f7ff03b9d58c6d54102f5afbd5ea3a11c75bfb789610fb1e0abd8759f55da391b2588c0219ffff033d0a0631da22140d9980b77198f93966c465732de1316f8e2079845add63833c03308f161e020fdd7549f9640621f1e75125002a27384c8cdd8e272d369acef5b60336f600d07000daed56ae4c73defe117b1a1c408e3ffda727d5120f2d1bac1b98035e7e8b146d15f0d9d5fe1866172ec6ab12738733364add3b5fec64932efbe69a034d3ad34130edbbd33810686af87380324b01411066c8b5bce9caf44aafe16a080219ffff0391753630219fa061da2f5bbe0ef5c8349f57b3babd94191807e262f1f828502a0359bd38fd590136438472abd7c29a908fa9661f015ce766de74144aa903fcb2790219ffff031b4f97035b883df174e26857227b2e9129fdb24b36deaa5a40eca236f51a94d603a732c44b4dc3a649b48836843a6198adaed7c237ffc671eafc13d7e64fe505fa03ba8d847864e282a991307f028f9b7281a7d7c586f4d165dcb09f56f77cfe78f6035c9e8f32e0b80f324d127c1758218bea79b6eeb81118e4188ffdcd3a81af3f7c032a0a0f882de87cddba79fabc4d0ba428d42f81d84b0b8a1a55fbd0ae63c5ecd303c2229cc1619df92fbc32dbdff9f0bf6dd4f0dd22208f9b05f5a7ef3c26467015037aae15e597f24850cd76419f4d056cd60790f99bc9e4f85c0a1082c850e065ca034a14ba77c23dffaf345269dfacaa6ef720cd9639d0850089be0cfb8a5c4ea70503b93a185db5df4e8a695fa9a15cc14bdf440116e6ec9e102adcc9bee22b93dec8032b6079aea46d6c438bb050bf5963f870e0dd4c28e9a43d877e623ac77e68a11a0310c1d6d60eaf244100f27a39d49930133dc409eb9258daa3ee72fb956ecfb57b037a8c38e90a9d3084f29661f023194abd5e179f7ac382372d4181fd11d6248b52033a7472560c188f46db9f91529eabccb22c24b91a87c8bdb67cbb2020f044ed5f031c92c2de4f649c5eb7fef1f2dd4a07b967ab986d7812f88eb85aef63ba7e60cd031ddaf463cc5cd0afd40d8405313b3841df1b66bcd27f84ad3d83a9765c44ca24036b30f6fb68ee486958d2908882abbaeec2e8215b507a05dadb0203df41b2e65703628c77db566123d1c06e8c8e61064897b53b6cea5c92bfc6b0590a83e78d17dd03e6a08caae2e5a19a6534699eaa45126089718006fadd56f2d9a00d729cf59e6b0367a2335b37fe2c18ab910b5192609091f4cf83bea3a3eff7aa1c8f65ae7710d303f39a7571154bce8d4066e6a8ade370697e1647a658257511b21a6133b6e6d3b00315d2e9a20cfbe215473da0212f0c12369a1f33a4108da61c7fea3ff06db3c7a70394afd1fa51d4b995f81c07c01c85fdcac96aa2a9ddec53ebe795e833c35e992903ac1cc00f3fe79ffe8d00762c41c60e76267e271fcc53f0238cf3559aadc86dbb038e184234b198d6818c586664b1e53c1e6d0fda0e337d14955a1fa07eb268a50d03fbd188de5e001d9973e5e272e8dcc3ed3186e90800b0776d3130ace932690fb003bc6dba6372569631c952556be8ca7cde85f5b5e9de0108b890be17ae8cc2c2e800581f03294f295afb1fb36ed7571fe230a68b51c5c3c4c821457dcc320532d1a5005820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03e0613854285ed12abaaf7127ff70f49a63802daac845d4557be198856d9ea9b90378cc22eee8530d3b50daa2823cfad4b4e5acfed898b9c28f95947beb99872ca800581f034b9a6da6cd1bcd3a9de67e96d96524297c2e634e18f3d3c1e138eabd25c05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03c3eb294e14a1bd5ad7baf12d63c1ad76056b46288afd261e3d8eae3a491d5e2a03d70c61dd6483b5bc2e54aeee9617f7cf1ca628305bfc238521ee56875f1713560384646eb05e060a331358a63f775212368d63e5a3985ab960a1f97443eaa99f0f036ad2d024007efbe9dcedf1b8458628baae16bb17b309b264cca6bad64217adb703bd15489bcef2e9989ef1298d9bcf70d3fcbc829f5718932d6d541d70cf373d8400581f0341e2803d7e5d925debf58dea57c47b45d238828f50f5a4d818f8ea3eb6104723bc46c94c540e00581f037b894c13efe501d77927ffae71a82cac6051a36300e9aaf3cf22b1b2ea005820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03b3eefa67d5ec7a16ae007f84831c4808e90ecec458a93473b25d75fc06da8ded00581f037a0bf43238cbbbcb4003c84bd021dc80e136a57d38690daa4456ddb1e2105820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03f4d344b699fc1351d2d5f811ab43e2955f8083919dc072cabd0f73a546b4036b03c38d53019c4c8a58fd13f428b88ebcc41669a692369557a1885c3bd4969e16c800581e02c1ffff3533ff4434a3aa1ff6351a7237cb7d56a977c7f3c5fc6096621e5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e022d362f510f31d6a1c57f07a71fec7b413c9eee230b8a1631f1d21b9ffb5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e02b9a1bf707ee90667e23724f818453bc614d7e73dbd09ae8188c15214f44718de76816d800000581e0260c7e41aefd4f58ccebd698a2a4697a60d7dd9f37aa82afc17df08d6124a010eb8ff5c6ec42160fa0219c4480219ffff0381395b5b5fbc64d007974cbb411f14107821e0e4557d1fa9d1f15f6f11838f0703041892c1a86f45af727708f25863a0f5eb4cbecb7e4e4d5481db08c1e91573b0038d1f618fd079e76b67453db30ca563a9dc8ce7d42a6e4696cc5b906b83ce867703535c5bf3fe78626f07f85b8cc4f8236f1e4605de32a4b4b0f1459aa1f2395d90039f16b03bf2313e5fdc28dc859bae6824a6a1ebb609333d02b57c947de0c2762303d735b32cd1ab8993546a9f1c2da70b5d2f4758e9d580edfdab69545d12642bd2035f0c20453cab9d341d43604d67cb307569de9d9a45db2da03f696b8c3a8779df032689569be474a9a0f3d3313569234152f66c72a070261385e34865ee7eb16d7b032c07a96120fe4b00dc4f9a37b591a4fbc868352fa8880ec26d7fb7e8e725318c0219ffff0328d5997a2f8a381f43c2416233c7ad8e701fe7585a4ee9db2eafbc7619377bdb035308063446ca37dfee4ca6ecdd2b8a34b32bdf9d3c619468c7616ccb2446719b0312808edbcf03cdf71ba104c290b2222267b6437808b56e622e7f04fcf7f2781d035fbb6b7da7674abd79ec91a54337ce3cc8dd56a9920aac8f2d5862e36c46bda3032df05b3184fdfccfe950c5ca598b7870485656088fffc039590ec179bbe65abc03fa8568eb7a92fb2ec1396a3db34e28cab406031a411255321561a951f79574aa0312b0ba806aaf97816ba1107c8145a89294b4231cb48c88ea01562550a20e5c380318a7ac9e45ea8b80cafcc027831be415e6a219d0d055dac81f909378ddbc17f20219ffff03868664f06f2a5d68459163e168ad49abcd02ff97c45aaf00f6f93f6d61fc10c703a64769496f3f7f7a8d57a4626fdeaa73ba07c90aa25a878625e9f1ce63cd85b103d5a703cac1cf5dd5a81ad74e710bc8f48f66505b61bb20f361ce141fa53bbd0b0219ffff03f30f1db5d3a12552bba64d6e65873c89bf66da484742888fb4962ff4b97db4720341ac2d2df50f3196b691e14c3419de2c89e7f6b9da7be46e5d231de653b87ba403143e864aefa356946424dfffbcaa3c8a34d7552df001d2f93acb23ffcdb736a4032df267b1c58f1f13f86e0a84c4c1ed96731f853f4d58b835a55e930ee8d6e552038a696f36cc00865896affc2d7319a996c92d0b1ff29492868b8b5165f8d497d403a4c9a4448576278e481da8a55ce80cc8623adafff302276e0dc2fae9f02206fe0349609864e05a84269fe86659ebed5d97f831a8dc1b59567b64fbccb0206e18e8030cd72b209304f0e48b7460a1f3085bcaad25729efdfafb492e6efcfcb28de82103e218ec85a367b38d2e76111a1fab3d5ec3e8331ed48f6569af25528b9007675003fc6d8918df4e4caf8adff445099aff73c359e079f7f404db3804b5f6f1d1a64603b8b94dfa9bbd3c7ef07f6c604a1732afec013f501239155eba01de2a1bac14060397d0dca58d635f5296cfdf2547f135a3eb2484fee07345a431d7cd55e7255eba00581e02857cce08c663c4b566cb624ed8465aaa43a6829c5d385696eac1ba1ec25820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e02001ba58aca587e5115aaf3ad7cc007018ff8061fbd30a715b71ef8959c5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e024ea07105559d27a7c0a085be03577d0b3be26521e7977b65a6a1e730765820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e02c93de13262b30376bdeff424384505a1328e1f2f1ef28ebe40b171ee13491e35893f50e01f680702193804032eb60713a2a32f49d6f54454eafaeb9f270bed84de8501cbe10c76bf412508420312ae8a6ec23cfe12fb64c0445d566e763cf7e5a5ed89fcd52d51dc61d3b76e3f0345e0abb791a9203ea0ad1f00a6448bd2ff06de439bf15095873b93a7a4b48fc003c87edc3b1e07399a45c4e89c9ecaecb87bf107a408dfe5ec144c2d83a640fe1c03d74dd26e53e94e3b49548464fe1798b6704a9e5ad10b00069d67932385bd48c60358727b9aaed8c8de68b1483c2beefe7d50a67e12cb7dc93ca895afeb0cbcd1b20388ee36f1f7d375bfd12bc53efa34b34ae3f2012277ffd71b2e708795f8c2fd93038c0c39bafcf14b6468a868ebb540a2a32b05280295cc64c2998c0d1c9283848203a304a13714ed4c6c39ca2b649e1491da3ea0f09b9f069b06425707ef114e128003bbbe9f5fb48a04275c333683e1e2c859d52f17e0a4c8fa11f07060b47febab7902197fff038eb5d79437ae4753f3a6f7bd8f8dd625067bc48d0a30bce56863841d36e7e2c003ac5ecbb13e65cdaa794f562f372c4f03f2a1fc3a96ab47b2ac4a67757599d82e03e593b2132975c139cb7a3f15b1424dbc1dd432236a98e92a3541aae7e9e7924403fd39061f3e76fee5ac39908af4631359e0ada38938f2a91b01e5eeb04e7fa866030c279ed1d48a4dcc65f1117aef611aaa1371b26441abc123dace170716f263bd03759161ae64aef9ea7243d45a3d1c92c895172be221cc472760dd89c20a0d5a99036c6d8d4068c06c7be7676f2251004a09ef858d748dadd6a600105cc4d989a025030bee7369420d4df0b42b12cb91ea4390067c646b1fae232b4f5cbf62b7c31d0c03358259513b499facbbd32ac6a5a57a5191722d02cdc0c4aa38dcc07a1901272103d3d5dfd8b648bde2fc5c7708510de7acf7f81d687ba25930f752ff3c2889c90b031b45f6115b837e012a5b447d135ebdd153e893d0d3fb414262f6dfe598d14202037e6b8c0389015943d7a640799ef029e67dd93bef918d028b5e2708d1b36297550345ca8a38666165313ee4c99bddbcebc8315f2b23d44863e5b3c55eda4e00471d03e2dae0de86bef66aaf65c03259a46ef89afbe2ee582a6e69cf7819db0fb43bee0219ffff038225ea9e703d8bbb4f6b14051225fd3c46f2cda48d988e792d7cd6544ddc5bd20388c1e701504e631c9504100c66ee5f907fbd37691eadd5e0cce51a670e0835f10331e7fa9afe57fddb017453c3462c3b33f2876d7f488560d6df71913cbbcf2b2203ccfdc1bea81f23ff6f66f6ce7d1877991888b624cb9693e0f8a553f3987d17b60316193561c88dff80996927c305b461f0abb824513f57aa7d44b5efe2236dbf9903d89dc04c7a6fdec1db6068838377ef94533b1ea6d92e96eb15f874253020ebe80382a265fde8df439f535d8580d21215af31265f916867b8a9ea940af81d2fd094037871ef639c7c31c92357bc2e9b234cb80df700511f8765348c4f1808a052334a030c55f471f23d2a26e81a6d49f0ed32558417990024fde3b2609355e0dccc0146030afb0a3715f3b2372999d28054589bbb3c776f65c3498a8d5ba26b018a8b08e903b6a16df56eb6c2f25c244b1805554ecd417bb79f67c4723ad68a1d584e0a6bad0219ffff03abdc40a40d74c909d6c5969ca704a2c47de796e6eb989dbce5fb87abc8d48249036fd93fd4698121574a30b52e86d91cbe42f240cbc117e259f8f0311839e7032b03515ac5697ce3707644b69cf4206246c5517669520f654377275ea49bc4b2a8d103d0adb200f169d166e9e5e9d81af4e093b9041f3d0d1a958a01b0ee569b9dbf6703f94f2d084434d37ddeef352707684367afb4d47958181cfe051740bccbabe8bb03f0fbb7dd9b53dd7723dd25af7ab3de673e600ec3fe173d89367e5815ef07460f036c249ff03bedbe861f0739826cf58c51b2aa49d585abc685db16830172d2bcd9031af8f3406d416763f5a454c3badd4f2a46c6718ff1dbbf88c6236dc087f6a8e7033121a3e0234e42053ab25224b4542b6b0acd3747df58d5847fbb17449c67e845034326716a338902c7a9ab2fc3ea2f3246d127c3f93c431d9f131c813db570fc4b03ec0ae9cd3838fe503bc1b95f4ef6bceb787b61af6c35acaf17985ba22f10f23a035602f7ac1b3624f8a4ad0e267bde5d93cffa770f7692b569fce9041fb0aa5c8a0342346751f6e3da33637657399f21e36b01589b73c0892c5fa47d0cb8021bd7ea0219ffff03d1b98a2af1109990035484b831c6ac45c0fd5984d9d2f521ae969ff0a6a185fa032cac2a24829b353773ed48630305f361398623dda70988310944477c00b8cc5a036a8174032b088afd567906821b0d552929a62cd6abf5867a05c71f543cb25bfa032985f35ac048b808bafb691c38e0be54a43dddd59bca0e7b90cb3fb9d59c6c7b0329870c783a7db76ba69910b51b8824b8d3883cf91c777660dad478359bf9f3ae03b96f9cf6ef94a0ccc8559b5b1d07563d2b1e7b5c997eb4a2c1ddcaad242ee7c5038e5dc4cee2719f0ff12479753f2cb032bfe96eeed75edb399fa8aba6b32eef33036b085a429ae07a3324cb8a84c786b761d95206dd466ce3c8a9b24c03db04596703bc85b450a89aedd07d872bd64a380f41ac8c7d3b4b078b859e2ea6aeaa82338803539c4a0eb0d06a1e7265dd47029b1040de0df55af1c0d03203844500a09853dc03c34dbdbf1534b851000ed8704a263c51393a28f9d7236e2e29d5318b91726eec03631710005e4c266415ae96480ed91265996ca3f9db83de0537a72740a12f6b0c0317f5d82c586888f62692345f5c0a19daa9d6282a2f7e5b70d5e6405d61cf6c9b03965d7be8ecec83fb96be522c9977c8bdba99bd3122184808e3a69d2327f668190360320d27bb5fa06d28e1a1444c3fc79127232fcf89751968d304b01afe50605d037eeba4a194af59c1a397969be20fca14151ccbe8671110426680268276f4475b030ffa72f516be0d7c246c938e08b62f3029b22eb5bc2470e509364b0aa98167f9034af6aa1143bd52da01a5f674d6fd056eb3d64c4404c799da32aecc45d6a1daa0034db9dfc6c07be23ec3bacbbb30bd65da8b4c3375ebf67143a3f78efa8b6838ca0355b89c799b7367a12e3f36916c580a7c05c692c2d7454081332e73c1fa57fba203f426007ed69b6c7184c35c5a09961ea52c877882752c20e696713c89d3863b22038a046a9a03e046600ef64d62c4347fb174190cc68eb5c81d8680aaf6caa4e96b03e14da8e677d7bf1367cf4f82affb1d08dde4beae34d0d521989560196042014103b651ec2c50ab012c0ae178bcf78d72bd621eb995a4223a82cce3a2b37efe3d6d038c40aa81e345d742ac16f1847b9e923d01ba1071da92df0aa577accd62f2ce8e03cbbd81ab6eaafe10f46f41d716a332e8735dc6e282d130cbae6b2696c1f827fa0365e3c2140950f1882fa2861c42442ec159616b1c650b4201b9c5116ed271263d0384457af785b22b9927ed644b78e149e88d3e2f07c2665584210f69625824c1830323854148a2abec26ab9ea4ae470f21ceb558744f45ea8ec4c2190a97a08c13ff00581f034775b60761b266b809fe7d7ad099f545463806f29558e5339d6f60fd39b05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03dba806f7854e36e2d5a172bdad964f8c2ebe8494897a7f9201dc9d9617861f4900581f03369df2687b35cba76c7755753e5601d4bba7cffa868a0b7d5192914dd6405820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e02379ca9937fcd73b8cbb154d52f12bb04c1c24981a6281db4d50dbf4d684755c19bc993344800581e02cdc6f4fbaf1017b1c49cf6308a872edbd711870c25826a213a8280fb015820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e0263462a3822bf8e70995fa3e5afb487c6ab731941329426ec54703d35395820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e022d20f4b110ec13d9092915f0bed4a9dfb442bdaf885300b5a183287b85485c318124cef2ce8b0219121803f17e745ef63ebd72f822d01488cc0904536de816367dc56f0876d6922a783633038b5469cc0b44d2e9db0b955f797446486be8fdcd9394e3774a948e833e7d24bb03072cdc30ac11e9c80b5de8e4abdf9bd36f24346bc6b2d15be33af85d85559065032ba8750770e5a4c551c4c1837bacc16df16f760c54f83e6d434955ef84379b3203860222e57f686c9ab066d727165c4dc48c74360fb76329bab343bfc0fd0f65eb036c3dae84239b7b12d856e7639b4f5919c4366fae4112938ef721bf8fb063b0f60380fa2331e74be8763f9943e30785578e8d6fb354db499af617ac315e2b08830a0219ffff030a6ce6fe02bff1b75de7cf089b9f07ec04fea3806600549d7a97b932ba71988803af8bde051d20c605930c1a02adaa96e2e050a242c72bd6052b313473dbca192f0390328e18d1816b34757e05be47f31b577730daeab53124da1113016c3d68a487036fa977c74341cce882febd2520aa642944b59395cb70de209cfcb125beb7984703477c7c06b93d5abe9f95777186600364782a600215f11c343dcc1816c609b3df033f0ff1dc59fc71313d4b6d3b01a04dbdd2d54ed5da0da9f348c0c06fcf3fe941036d9bc80807c8076b4414bc4a5af1d61ebbc018e182f5f515dcd2ebd0a14a78540339999a2010055e3a757800b6093475ecc0f408e01b671f3cbd0ba2dfe27e6a2a0219ffff036fd3ef41defb086eec251560fe6d9be4c6a2c5d85c4acc43818bcae23678b3e103f0464b03cccf064b6ba7c78abb1a444cae24d5b091069e8f82fc8e68387c6d20037746cda5e593d69d211fad2607a3701eb1b500a88b6e157f9ae8a43686e2bc000309e12e8ee728c66e0adf1545fa238afe1778136d7451b9719534e2f9abe1a90f0219ffff0334798a85a8a4acacd23dc115fc06d5bfccbf9f4c4fee0c2fa2daec26d2feeb910306b56e43eec239a9e51c0383d0862f74e379f437edbe1711e021dece3b101c76032e48ab5d19f621487d5aa22c8c3267c68284a97b8e631475d322d8e7b017789f03b2df841d181768846614ade1ced23ee1da3a820cb4950862d5d1883281a32c37030f610584c8d33201e59983875f6efc4b83531cd9df633c0b2e6a8ac20f9790e203c5ae38c7e72bb0d5d0b37e61e6608969ba0323a70bbf3ee8d78f5ad3c3531a6103161f940fa3be226289aa4c980589ed25620077f6f43de6c7ff780ef18edbd1bf039adee46b226a029846ead94999b4b8099f5e94fa01fbb5355ccdefb5efb14e090319ca36df4724f0c8caf8f31cdd13612e952dedd6418803ea9b4e011be5026d2a032c55c2dc1036b12f076fa6b992ec236feca41f26b30b05bd0f5d3b2c010e7a8003caefafc905c6482552d526f390e7fd7614e59c2ee750f25c381909a60c58547203fe247c1a6b28cff79a5b68288d61f141fdac830f8a108b512276310d37bf720d0319cb78c3c056be4a2ee7714342e0c2802779aee2f99757f0de6eac341918695403c18f7a552ea135f4f2554039ada3c883666a490247390699969e59f2bfbf592603e64d8444d9e807adac51cce58f39114762f56b7ed5d30cb7421198055576e90a00581f038b4aa533fabde72e405fe4adbf787c0d19b3136008a570a3edd8fdce05c0412a031429e6c8bfc0bf939d05c76e4948bb5f5576e5e53d58f301b747d8474d62a2ea01410a03cfa54c9c989a9d371dd8c0bdbae19816d7962a56cf291e9f75bd2abbeb88d8a203087cd2d74d19cd5b71bbdad6da34ba1a9d6bf232d331debfeb8ee8d8048fa90800581e022a00d6dd8fbf187cdac84c31a747540b69c14d0dd18e2cbc14a7ddb8b95820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e02b530d6bb8fda542e57acc1c11c8ba1ed193508f93eb9e9da973e35c3245820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e02edb846397b821658f7d758560f1cae33066b31cf7fde36d3692a5d26434907d33cd603004aedbf0219800603f1a4b63ad4ce62990d7e586e28918b0ae63876053e4a51a6e90c6519ad76dc160322fb464ca8582b6992a768ee706e9c97bbcd8ff9dc49e3811d34ea9925ce64fc032f0bcb70e1abe5ca44d6334625fed63282e90c091387b54cf011a0baecce7f7a03b699d748b04b3f8cd87ea98f4682176bc632756e1ce6df20d1c35ce77881ed97039fdfe7653b043f4a2b30896d98115586432b94c93c4d9058efd8797325dc91ce03e46ae2235f88f4559b28ebd9111d0544ed99e50570536a364d9be10063c2e4770374350f3cb77409d14b21070871db39e8bca8ce30fab0180e4edd2fe71e5771b9030c989f5f7329323ba31e737984c3ad1842b90acfcb292683abf228277a2d2e720219ffff03440341ebd49a41b3750e6fadad6b0111fd88987b5edf1c2a43701ece64995107038689b35440dbdf37d4c24931e4ee7f70729753d6d06fd72e286fd81e92b420ea034aa624ae3efe87cea4a0d70b5c994036de97c7a69c420085397af86c94c99a0503b103e0260c3aa0cb520906bbe7578e2a12201c715fa148b44d8499d3a47dd8fe03e466152c67b4fa77498bc599f85adacba8b17da66c9cb05e2bf7afc9b890588f0398c42f9ab9199b3acdf4d71d9315d5910366673e521a4b126c912f1fcabec6160219ffff034e6da7d288848fc9f0692e238c79442524c8629cc91db5e381dd2a3cc825dc6a03e75ee3343719d4eb14454785e4932fbc2ce32b6f56ccf5037ccc4cc9787863bc038b49c4fb5938072372edc3f07597e7625d0431c54927da065e66b6ece6d9bdf803f7f23f0b0dd52cb4ac90f64a78e7028d6f129c27f47a20f45315e57e2edb976b033ac46e89ed5ed4700af778961404aaa4f5fe9154a5969a42843ef43115a1be220311bc23d116d66567a018fe812f7ec1244541ace448316b670fcbf90d719f46e703058ec124a8ba88e15a4642e3d04f5c966eea80304cdc0064a04f87e5fde9de0c03a5f01eccc6c7d153e5815a603e3bfe5876dd2ed0cbaca481ba9d603cc05d202f035b9c5728daa8e3034b4987036eea0b66b4cbc5155fe6beda6e7b61bc6347fb7e037eaef7433e560ded6e234b96831cb8a299f6bfe0aa1672eb3f98510e523e0bb40307b626f0dde902ca6f074828cdd814064deddf6b0d575edf91a288f6c9488aa30395736d062244ae5f7341a95a209c2407e5fe9fdf5d867573288c38018277355203be7e31be9fdda6357c98c7f4853921cfaffbee74c23cfaafb375a05b1e15a9650219ffff0376ad06843397ef66cf3051ffbd895348e4f119285be382c7b0d57346056d5b100300f08fa889f8efd51a5a1861eaa865c32843ec0dbb99a2c467d59a2608466d1f034aeb2b91e2bed2cc16e45e894e4106c38d6ff4ad1e59a1866cf141df2a6cc25803a58eae89c1246c442ff5ed99d911fa33774c6456c77ad9a975a34cb266350bd50399360b1090000d036fc06eb0241d65ede1e22ed53e30b5f64d5911839b3f4f19039ed380b1fb30fdebe37e745e30ae555fa301efd04511798a2d64e5f9d806a5f4039e47b993b882c5284a354aa688abc1ac03780dcc06a00da2f93d16caeb5782d10219ffff03ac87e36e5319e1c55ea222d8bbc3cfd6d61b3fca4204db19becb37bcc451fd530315cf47f0538aca517ea532be21790318a2497f25330fe1fc2a4c0510eb2763a103d005574b8cecaa1e4d9020ed2be3bf92e3963ca43bf7f9085fa1c26f27d34b9d03c95110d5ede1b68fd2d86286ac891c691432ad13e85f22aab18faa1eb107d4bb03e3cc69ac2c2c22fbe8e9d77b32c9d75ac06bc9e83ed5c550af8111565d42c2ff03bbd0f4bba80ad8bcea192f20928ae6552fc48ec518ab9ede4b641e3f100ff8d10300f698b4cae7fc2b75b02c76adad4746ddec09d9afbda9aa690daf90112a130403986eb4f22080b9280bb83c62024c12c97cfb3a2e6a5526c9b6e498ba7c071a78039a8f171c4e5c11deebfe5ad3508f46a68424ca0d2616ac3df12355765bc7b631034989925510847f56bd09c145707380adce35187415f5e105e560b4b2aaca51a803cf3fd5ede6d1db43437bc4ac094848b2e2a34b90af4ba42da5c08117d351a59a03128bb3bccaffdbaa5ffda65658b5b08d7f774bc8ff81ff8e8e28e4d3aac7b03b03307fda99e8f58c120c563c670fe7c2f0ad101bf7f5d6a89aaf22053442b8778503aac1c3818b00e9abaff21a0112744c16198572117a018e20bd10859cfa9e5ddd03ca4a7d4366cc6abe4f706fd6455bde9278d37b59fc261eda35551b61520897d60330603e90563fc3e07272ce5c3871380a177c3082e0aa127caed87991b83af45a0333e96424605cafb689b3f0254626ee94a3e32d41c100effdf034643a4ed0809c03d73bc4af1f2c30dec11c4f2127f93d82b2a3f115be149655964f1be54c4b7add032c2d59312df5b0c914656274a3b45dd0fa4a1b36189db319dab2eb78d11bd790033934a79507275815e8afec659a1e59385809f95687728da50d63ef2ba60d1f7603ef44482550df95fc65f1337ec178e490dce4d0abee7856b472263a89006069fa034665419d82307b0978a6b7eabcd95a085e7cf9340312d414658ad93ab115ae010325d48c247517f7ff07647921f64636164cfd36fd0628cac68452edd1f6c92867030baa45aea7db304b53ac53851ee1dd6ef1106b8faf5519e468c5823e8ec7afda03d4e7a777adaca7a7033905e4cc9919712165bdc232d0779b1a0069ded4a53ffa039c4f9c6946a2018e67d459586519108179557da575955c41a9116acd4babd92c03a74d51eada7cc555f32e6d06f2b6b65462f63eceda9446d6b489a5e84f15bd3003ded394ca4fb7425ab85f2b64f4a9a1ad50e769ad160b6c4abb242fe2302125b3030b2feb92c8f89d1fd4025b93577577106b6b1cbe33a4ac2378d36ed96e7cd3d400581e02bd9d4728554bcbd11ee64fef531a3622b099953d0d71ee541aa1d878a64706c00a3912c00000581e0262b8218fc769b3ee83f01d809e12a5d65ebfd8369ea90806c48057a93c49093dafbfc84712a218021982020335db81c736691f8496a34dd621e525a010ec7ad11ac9fcaef2dbd3725c05328b035743b9e11402254c87ec03f9ef1db818f8b03b57bb76e536c8d6aee2b37f8aa403154c167ea3200785ec18df7f3761c5f0196bc4e179dcf7e95868a21785ae831f03b25db63b38b7932b8b081cd68e356792a24ce23cb4f8fbc179155b3931cac1610219ffff03da750471e3ba4d760089ba23cf7c57f3a37d74fd20a0c8617f6faa45674c0b450374045cb59a5cce2c414144522708a6b01d53bfdd7c9a143604129552e0af2cc0039732be93edb7e1468d085a5086e922dd56790acec23c5633b628f4c91122484e03d6cde307dd180285b82bd662e39a9057f864540394342e0c7a3de2c57a60f73e03df00b2d3cc9aaf7cf3878464cc9d7a14f61c7a8666481f18f7b355860f06f83503ebbba500edd5e0d799f678b7a28e42873e7c4ce400273b68d26c4680f24dff8c03f06cf93cece3769a3f458ce042494d2d7563c69a10575fba35a0355531463c520219ffff03605369d2326789307d9e9cbe34a0447ac06d1bd8cd7e290d08cdb06ab043f4550330057cd576f23de643915f853f1a956809ba703fd7e1ddd8ac2b8522b0aee3ab03a408f882d2b589b5ebed914d522c7f2644a96b0ad3f1492c5747d615ff3aed9e03581a9f6c9a9ffebf499a5a737e8c6e3751cded1a3fa4923a92aace00405b375c034596194b49e83ffa76a715aa8cf195918294c338ba5984f5c572001e9c2c5ee2031f7cfa7f83d6841a7e1fc616342945564fa285b9798d2cd433ee8d3ba53a9f6e03c9ad4200354ef9cbbaf9d937bfafc89d1c70bd64b57feac214797ef494a45d1e03ea93fcb2ab53463f68ea1aaae813d9694a878c8831f38e708986aef73c5717520345c2a4341ca212fa64da70bd29bb1bade3359622257d90cbee46f95d2bd65c6b0219ffff032a0b3467f188c5dc257b3f5027762867ea0c8e22f456b834bd0e8d41c32ee09b038cfc902c1f9952b277fbb430bc80c0a4b35df7b073f854b49355b17fbfe36edc03ddd7e926707a0b7ee5115a82668cc7e2ad8e1aec8752d80b6e840e0ecc57c1f703b9284afa5f001baf4c6829083ce0527f19c3875ed1b0b2cda94e4c62ec04bdb0035f4f3b66f94d1daec51651e5b9a33eb8dbc0ea90f0a88f23dfe36005236a47510305de4330bf407d7e333d22cb367cc9e40da789b71d74ebbb906ac5272e110c3403ca26f3f2134469b7c3e0b4c71a4befdda217bdc6da8a2331e9c72bd85959fb4503b35005e34eccc3d639b90785263ea33d9e02e8634c70b5d76f3f8f9c37b91cfa03f09490d56b9721f7e5cf1b817de0388ae73814bb8ad975cda7525f207c7ae0ff03d2f0691e78eaac4302c7dcb1aa81e997941d8d7a121a37d7c0be91bf03d46ac3039b2b8e335cdde04f21804cb124b7a3269680fca8294e6fca53fa74b729b6efc50383fc3cab9e24b99e91b3e98105e29975ad7deb3712756c02a08cbcedb519f562030019d418897f88c1b72bbcfb3b156a2c5d9e9601a3fdd927241f2a20dd9a73fc03b4fb4ac48971e0c21a1e4e77602697b4c8add96964e4984cde5d0ed8cb2caf440301a7af6d4770b9591cdb9115fce837f1c32a6d1a1ac2df8ccb5fb634299fb7ac038122973066dbc6f3b8d5266f5b1e0d71c669f766e43d67dd021c53b2e81ab7d803512346813a9fab291dc9c87125b0b84c905ad7779f9e6b1fcc00ffa91a5e5bcd03c3964da07c5d9a5f441c5c4ea3fa85458a9de17a4377a733c0bf350ca313a1ca0302f48797c0b0df1a974a9aa027d4f5715c1d3f14d9a1f584d3d3b025a5ee5bae0333f8a1b6bb14d23cc94fc8dddb95e10002f1db6066132372a666406ab5e20f8103490e8879fc4f8a90265b865d5dd58f7fd5a5b8650793beeeee1d822fc9d7985803f2fe190efffb9379d26227926c6b843388580ada0b1ac97707073b8590ab9d1503197557c84c324d352229b51fc3490f858bc0118ed0333fbcd5dd23d1e4072f9103ca80b505aa62463c9182cfa3e9f0c28292112664d8fd3ea753896c79f6626d4203ef540ba75184e69b0cc004d495f23b89ee474939fee3f25b105c5b04f7a2127a03c75bf5c5e7fe7ca77a153124fbb30a4f0747251a934f89942b0b6007e7429e0703c83fe45ca5c6bbfae8e8f08f2dd2764fbc69afa3add4f1982b3e1b746963a21603cc7b26e623070ec97b3e645d4a4e32258bff6b0597b13923dba3450501a1e1ba03a72571c1a7548f7e4d279cb5673acc2ce560f0e49b4474e6fc250813a33df499034a589c1ff7c954c05f7551736df580c4e5c1813679fef8b09e13bc2028e6826b037c522072578d99241a8cc402e952bd4bf4e70dcf7f7a30d8eace2f084eac40e403a68d56d07484a77a04ffde6195121db303e2733c6296aad526784d1fb73d01db00581e023c8e0285cf1900e405e4bd465e219a261ce68dfc8bb46bae3a7ec61bb25820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e02a8075e9efe61170e277642bc016106cb05fbbf530bb940c05351b2d9e95820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff02030340c3eaa78234b5a8d7b6984550b7aef4dcddf380f80fd41bdd8f57c7a34bccb903f066e73a5ab76f356582aeb52015e96dda876e378db9b82a07ee3c11c925852203d528008d14b0ea68d1459f570ef0f1e2e6cf1ae4fd51ef8249636dd9dc2b8cd6038f2e37593761dcd15cda6c816d6491084b2d00ad3c1fdf1deb2abab4522ce3ed0361d9e735c3026a7766ebf912d3aebee68b957be15a2ca91ef96781cd527d0a320219ffbf038435cac3808c37786c5115fa740d8f4559645332fb324bfcfa5a0a2eea6e64090327cf05b796df0db9d7d45f3b0170992c4fbd32069fe722cc894340ecae0280080346c6ca1fc27c397199c13cd957220a4d2617afebb773ff19d6f36486b76c0d5c0369c03bdc9c9541c21db422d9fe988e3bc61936555787ddfaef8dff6648b2bf220219ffff03e75c34280a1e1a0130e7aca3e8672d1a3bbcee1d77d44b1fd22a72974ca9e40703f0a73deab7990a4ef86a318cc01a26746704d936b0b0dc140ffb3e195bef06a703c4f3db34293882bbd4c735c48ac39a4e2585a61e323d8e6f71e4394ff863917103f6f723d8186b7ca6cd8f7b552bd18ca43b70dbffa812da9692845a9d484d833203f406e06e559bba217b95d19a281dad191745d1f36baed4c23c7ff8d4e82aade003871cad120c9f525378edf9a628a263c745a78bd4157a599beb30808a69bed50d0219ffff0371a332e4e5fcba54eb9513711d6e5685c0bc49580912c50d8a0a329621f2fb4d0385c456d6052bafb3bf7819655386ba3d910f492c2910a5111f90a0d19d25c6540339308781ad8bba05feb2fa8448d8ce898fb892e3a48cd020fca1a5b914b8c0890373385fdf1d4cf0db13bd7b90b8de76d16225ddac8c8b5a2c7f587c271125df9d03d057cf424f960ff9ff7100981f9e78d44c4eed75c72691814fe7655ceab1e5cf03ed7fc30365b94dc3117660dc1ff1b6789d6d5f69c783e607e2d4cd5847c812f00377fdeb53cb20bbb58cbeabd6ae9dd945425e031379426655fdbec582d2552e8403e9d17e85f6f306f1a2cb53589f236a9010da57697a22055661a069ac9c23bdd60219ffff03de55e752f85277160ca0c958b626d215dda2b2212d932a332ae210d513f420220393420ee59f9e46e777f22f2358d112c200fbb0f857c38cd49ca1743f67c8e11703098d5cb8af66482cbd329f87a5d36a565123ea86e1b84c053b7da2f8a3df91560387a34ccc7a808911c771e3c6e0f5ce337c532a15f33acb467e187759b488a9ab03b2bf7e8e41c5ebc14f6bb25502940f7602f6401d83008ea5c632284b1b2ab51703a5e5608821359e2dd7899f2ccebc5996a15104708b6d0c176432961944d5d98a033a2a07a4149f7abcc62ccc216d108236eaa6ca25d8995492d3df2c9393e2464a0366cbf45c2030fc6292e3e3ff8d284ddf14dd52877d8b70e993377084239cb60d037d8d41815206c5f0cad4c7efa74b25b017fe4e5dbb78602a2ac33ec1f6506f830310bff45f08d6a3e5fd85c78e86f91ceed9e687c6b1caefba6c9822d98d37581b030e9e87024dfe942f3b54c682455583cde6a346774e0f5cf51d6d4b3a847374fe0319841636fd348885958e3098a09a95a190606045a882596044c74916b03cd757034b5ce9ea738066a055dfdb67fe913c40460dcbbad771a4c817de558bdb93693503ac86b5dd4c37b7cbb4174d27ae198c911eb843f2efaf8471d9c751bda4ca4e6a03fafb58f2da9159118ca12d3fa5e548ed08a3b97dddb0d0a23ca28abd9c825235039d01197a47fb946a137122d34dba1f2088bd3a645c0f25bea9d91f9979f451fa03af32d65d8776aa64f530b288a53827ad0a68b6b8157f1a823c6d23976baf863a031f6c3c8629190b2e0c674bc3526e5bc6ec2953df6d69b81276b88a5c6914949403c0d57044b0fcd1b905d766f8c4a370225a9c0cf1dbea99831bdaa530f0ef998c03fe67c8924abbba53dc3fd138e374dae4bd48c787de1e3c458f8180b4d99bbdb103fb9584fcee4828d57051389fd7b0f858d3a384e1d1ea2708d3dfdaf5f2b80ea203b1f0e0252caf7e384e90ec52e8c1088183cac7417ad0b3e28c0e044a5bc14f6303fb41e91bbdcc5e1aaae8df9700c1c4e7819a4746b2c96370b9ad4c2eb73b1f1f03a9170a06029ee85bbfa1a4425671ba86df9ffac37e893f852610d2f1e9d0b82b033aee03ea2757fd5fb1c6b2be9a825a13e9964d77d39faea9e74fadf47da10bd30302a018df65c3e244c004f44dfb8f6a437edad43b9d07ae320d798459dedf70b900581e03113852dee74ad8febfa99ddea6af9ad2796f28f1631cbf4791ee4b4ef05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e03f1a75bcbe79e3098dc1337e44ab01ae65d8210ebf501375c1c9014b3304878f317b5af08a25d02188400581e022ed51b8a614df515870318e87c7f683fa5cca9037b05d7b8f57e449c835820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e02caf77d292916f019559fa894479f878f9f7fae27bcfc38a5954a7ed69c47366144e47e735e0219020900581f03d1c48fb50e219353cfb47172db25c0c309d78b14d3ba912ff01fffb798d05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03d29663957ee0dae4aa956da6ed2655e5edbed1c8a415ea18992553ffbe0c0eec03cb5c06ca8a269e346691a9a5b5903b3521bf2b08cb0e6661b7406a2953e1c13f039f66cb897dd12ce94dc946ee9277c9609d94911a5dda51adbe78d4ed46694b1e03532dea9a2d486a38e9238aa125fa46ee5cc58beb6d7a140f930f915112d6c0db031f1ece742481bae0698023fd5886cf5be8b7efc86b6f3fa1c8b78c04f9abf6ac038622397967cf62d43a0bc5a799a1c0a160d875cb0c63c2246d45fe0e9dead2840328dad53e94e600e16331fba00fa2e30a45d764f3fe65ce9a537955d5c327dab1033ecea7ac7af0166e5acab7f99de2092ae2ed28d4015a6bc72b73dcae4572e6d30351dbed0078607a2932fb8fac6b051c9c4150a861b920f6e3e2bdaa7a41905f1903e99f98c6f3057b4118a9fa16b370da882c431a6b5d86a02c96cd040604240c550219bf6f0379670dcb9e05d5abb8f0ff91dd92591197c03b8f3050666e87a62e6b06f763ed03b2af213d7f943824d7745f371bfd477c502954958fcdb432e90768b73e6eda980342776febfff8238bea4a5d07080bd59e7dc2920558b4f034c2d3b5e1b190cc9e03b2ee05bf74b2778eb42aa92c2684dfd96ceef1c13945e2ee25f7892ddf580e97034887b53329e1649f98c8ee08051065ea3b6c3208996b9cd777f604e6f28d51fe03a2fcdc451317b41a57f41c642f5707440bd1f6cd382a34679d78575c5213ccac03c4ec75b0dc750b522c5c275a92615aae28a91c304bcea4166118519bde6e0dd203aed03054438d58d9df8ec7379daa953507733b8588edc30ba8218a53a2941b110346a749e9c085bdae32057a23737e9ed4a2920181c86b095a77b8fece6bfc3a180219ffff035ec2a3bdefc77a6e28a5a9c9e0cd2f44e4dad8541a45dd7b19eaabfd059da102039781c273b709f60c5e002faf2f2e575086140368fff0d5a78e55e875551e406b0219ffff03f6505eec45a3333e967dd07a2fca6c4c46022ce3ef7f1f8284c48c2b2f652af6036fec3b9b8e8424b1f5b734a10d70b5c57bf279903ae7d6fa809c9945f7384ad0030dab9bfa75821e67f2d346e5fef4f99c9ff4fb9ebd9f3a2fa96e54116c72ce03039d99969e57379077a76ac460eaf793d3ae5dca1a363d0fc6ae9943faed37aada03af2c278a9c2a5f9faad7fabb5118899aeba061c7496d5004b5f992d8ed99c4b70373f9e7ce8f604e3cfd98b6450327c48fc4e21e0723fe24cb36f307bfe1d6f7c403e9ce92ccebae7e91013a85b5f9e5d9c214b8b10458e506b444763e41888079d3034abc70ae670a98f36b74e3270a7d835d2062a8c808475c9556f6d587af86519d03ea39af5155a5e6756def4154244450d0b92db15ce575eb8a374e0c64d03d9e6203e0fb998c3862acdc676f1b2894ead0ac1c3806268a0223751c26aa528fbf7eff03f162f10c99b896604049cb0ccb2ea774a6c2f3076348a00166c18541fb8e851d03798160a5346fef9a5a70373c7f67f81224e0471e9b86ed337d38ede03bc6dd0f03a8a267999fcb620a60c89fc18cb70c8703e0ce206065f37dcc94e99d44d71f810337a13ff13fd898079357e9bfbfc3da1969b18da365c250fbccd565cb97ca34e700581f03ce7b1f7a132ac95b7202aabb486604ad8af10d8d747dcc1dce47302965105820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03c60801b4048bc95de2b37eff6b641d56a07b4ab863938759f922b53be9677be200581e02988ed9ca2b0ba1f0c0d9f294b947e56f4ee65613fa7c968917f01d51c448019e2e804b251b3a00581e026cc869e4f53917c40b9bc6711be7923f6b86e5538f1a6ac40f8b0e28a05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e028ea50b7964fc041a4159fa03658732c3b4a06d3b2aeeda165050c706655820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e02cfda133e13b02bd74fbdd9dfde0c7f9494d88c6ab54613416b0ecaa9925820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff021908700219ffff0353e1e00ae1dd51e6ab3a5fde1ea00a8ff2b0994c2a745397ab9fcae937a1fd5903a30c74e204c070ca575e639e41212a182219513cd6dce7ad60874b5e6fad2b07033f0437d0ebb7a6a456fdd7db2df487a65c79e0c91ea0e7fb519b2ac25e204b49031cbd5a1a4732ef718f81684227017c2b4a7ce3ad3b2bd20fdd5091a7906d303503c93000deb024f09068d8a9463ed48ff7a096f648f1e62e2455cf16be94d3f75603f53525a2b0f99fc11945e3c1d81053b2d33c70f0e631877a8a52be10b348718a0348b75a5f195d3c23748bc35234850ba86d51e1def297fac3fae5605d05b891b9038aa1b95eb47dd925991ed5fd636394c36fcb697700a51b45ff314bb721d7de110310fddb29756d0105eafa0e69e6d351f111a5cfad8c16ec9f796e654cc01a32db03f0060441275f6fdd158cad5d7e817b4d02de140251b437260052a94854d41e3103dba02deea77fef3f1b7a6835c27cf1e3a81b573de948b52246f68577a16c889903fb6b7e10d8c011e0393959dff2c1abcfa3ffe6dc464f3b7a924176163010f01e03866aee14e2ad5db877be4c4351a929d4d9a5dfa6cb9c808c5f00c28268f5938e035c111587844d5280e8c28f1e91380e48707b766df0e2c7e4bcb5724d9e2020280219ffff03b562d57b698138248caad5fe7d788f137086bbb7e702f65f8cc0b6da6cf9f7f8033b83220e9a4fe0fe402aa51973f429f72c4e3b953e953effc69d8d1a9c1cc0dd03e0033db505862409002f07627372b672d3b457dd4d86009318e2285f7f43280a036f6a677a6c0e41718528e36f72fd7b664921d5e5d22668156a67d31646da7c80036c3d735c842d0135fad8c3e1219ee373442eb6914893883715fa0522a9b3c759036b5838c406ae0ef2ff13983ef1deefa05bd67408bc8cc80ac33f2d82008cc1660368a400940a558d6d57b4b7f290d0b4669f3653c7c470d86e5d7ffdc351a58901034efba55309c9c3002d965639e077fd37b9398b4e07637b6b490479279043167703f91e8b11ea7761c23ca9b96640f9981bf2b2dd055a1e8948d591b1d1b4b72b4f034cedb855c749a35ae794e2adc0defc755b377f9562ed9826fe753b8b96dcf76603102d61ef180da5d8db7935707a75cadad9201ad415eb3a6815d204736e6acab5035fc353e2cc24154b48ae2993ca7726fad24c813a0005b02d69bd49b622a95a2b03f755c3b5f5a853db34fe9a5c31d752203d9a644a76221de7f4e433edb1a77aab031fdcb4556e393069eb4f7aae10f491ee67a3c89958a49b5308ee1be3894174b8031096cbeb607394ec2434509c8b42e35ca9644f553924415077e307811c61850c0219ffff039c2395aa57704810c6bd7ea9100be41361e694277785d873815628627e2d5c000304dd5145b61bf1f675c1fc74aedfb1aae39d1830195aac4770d141a5fabe1b1b036034608003c389afd9e1b7e398ed580417eaf7e873e6df88d3061fe2441cfe6903edc737da248f5341e69dfab21930ce0a771192006622408e7da9b0762d078a53032ae50479991369f968359a1df049a4cd1b03283f2c90a2be9addda0f36f37f0003da02bac192c1ed52df9220552db01501bf269f350f724e728bab0d6dc8f0b3ab035191ae25e23d4de6345142ce1e195a474bbe11f9e31cf4a74c284a0f3c2ee233038ad125d0706137c066c450f6d4404f86f009b4d6f753a0e6d9e4e894e69c89d303d09c7a0985eeebf3f9027155156efa71aa0cdfed7ff11a35165cae6f75b14acb03f69dc43cd31b14202764670122d65e612917f6c5900cb46bd91d6725bcee27c703148bbb8d4258920138ed95efb7a12a2bbc03efb059d37bda0adbdcadd14bf7b603188b308f812d3f0672ebc7bc63c3f53bbec09a1dc319bee573194d3a543cbe150350db007108a5880682fd05bcbcd3af967afc8192c59cd2d4a96d7baa5149c3a10322ac04c0ff22460f8a6ef7d1b6604a53a2f688724023ba23ad882978f16cd6c9032e5faef295dfd37a99fd1b71078697e66a0b10d8c7cf7d4faf4dd81235315f6a0390043452ebec4718702e627de30bade7e35a106b5834c9de26de64ebcd41270a03ef86792dcac18b4e4c020bce4c0ea5987ed54d16bf66dc38196be5e8d95d6853034535f06eb1130ac507e8810ef9a9a092f4b9acfc52a67cd20d4579f19cc1bc6a038d2834e8e8a9cec36e516e62b90071707408df491f37e9244a28e2108c44db7c037c7ca4181c617674a7c655ecd186078c969798ca2fe9039b92460d0930680f77038713b7a825a71321dcf7c292f2c40be7414c7ec024e286ea3d0d116ffc509e90030736740e39e719e21361c3c2e3f81dccbc4ecff17565a1bd66dd3ae483c3f8d2032c231475bbb652d4f007b0bee4293963a50a478924fee75be1d20f98697826a603acfd551c78ae25cc2fd9c455124950709a62368fe8f90081b4477ac83080cd6f03988162eeb7c20d15a4c8757ab1a942c89f19e600aaff9140e87f22bca2b0882d00581f03bd2e8969365b59e138a43bd6028aebb6e1ae6c1bf688b47f5690eca068405820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03cd2cdbdd277317f0879de0e587972741b1d5ea30cbdb41922dbebb8dcd8ca9ba0394fc8ddd4575c57ef2f26e86ddb0da30663f2e3f9439fd25edf36ebf1116522f03abd03e0c212f185e288982fede0707be506c43ed17f8025345c507b2456f9058037db57be4d023741d7ec4c9f988abcdfd47e9f7f71ee39f0658cfc508aef7c57e03a5b3a9fdeb39ca6dccf0273e45af7eb7b1702712feb305c26f1c8a6d3b387362033d4894abd5c32c34694f0bd84f6c73a35b96b8436241bacafb1c03a9fd998fc900581e0217e0c219b58f64a42415630cf12d88372e03bc059c78d09e53929164ce5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e0276792580cc0a0d0d92d8bfddd4b046a897d146dd1c3ed2adf47ee91219410103b4ca22451d6cebb0403e28af7e0d10f8e1a4c586cbf72b3aec3fb58e22ea5eb601410500581e0209c41b130d387db64e89c263f03fc31592b58296965f22926c6e65e7205820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e02477dfb476be7a7b7076f810f756bc4a4ca1428cb07b43fb00fc96b66225820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff02196c01038a7f2ea0249ef9d9932004ca6ae817d60dcef0de7a5b779b5e18fa3b261fa64d031c5b9a77c3a56ea1a7c67fc85a6c459acc694e21f656a6ccfb4ae23aa93fabb9038cd47fc87a17d1053b17e8d16337624a8b4f7a365baca77e7c7e1a53ae000f2b03b2b714554ab9a03fe0ab43171830c8e70062ee9ea5afb293b930255ca075074003ee242a4525c914fd13a36f9f74cac20134639aee171169323ab143f8b50642ca0219ffff0366358a9127e5e31cbdcdb9728077c4745a895fd9ea393a1b61e977f963a8222b03f727cb9c56f63545e244a546199ccde51b1048210d2ee77ede7fb497f641d12e035795e1fa4a049bd2d272fae96e6ff6f62961bbe0a7f7001673602dafb20d454003e479f1b45a2243143459afcb42fad806c42ee7207bb69c6229cfc12d38b874a003dc27c605cf69616e2768afab44a8b68e8ce8e6b1e3664de3076be1a161c6e44703db17c079d690656965851ba06985243b71d9abb8f579b0c7dd489eb1694dc4fc0320c3042795c65620661ed9cb43b2e002222bd6f48d90002b24c6c093e5b8c8940303d55cbb7723636f728f64a3baccb17ed429b0f2f4fbb610a159f808e099a59803c0ac67952593fb4325ea46fe54e3b06624fe55f3aae0b301ee25fc22907fff1c031137dc6a2c2e4be039123610f21f16baf855c5d96b16da4cb61100bc5413593e03df213d5d4d8b5190cbc8b1f4b174c00d94687e58f1faaa75a75a29f71d293eb903746467d98eeee27c6b363904c85ed02406963503b32e2265976dac34ad03539703e69113cd7a565a6e46ace0d88d15d6d6893538bd734b909160ac6a6cc29223ea03df988bdb52b7ca0f8f22bac490b8e717a40eb6979cba13397401956ace3c85910219ffff0219ffff039c91d5ba33ffbb5d6718d31eff358060eb8b6a8c8f4ad666117293e0e01fc6f8030c4c048d99e777514ec10b6ecba5558a5152e67b92da4e8189eac88059c4456a0219ffff039e731799f3d5234faecbacbc47b76fd3541b8cd20f05e9b08188b85248cc284803d4467e5aed6b9be8da472b53710151e59f8ee6d0fb89dbe2cf0369a7c7b6e9b40348de182ea2c070654bc20284b1f8444c8c521890e389e3d3cbd18f7849e1721c0389e7a6bca3cba67abcf573d394d98e7b7a854ae30a28cb3ab57f7aa611a0356303d8389a7c55e94c40782429c1eb1f233dcd9974c17b809cf8ea4b850fc78e28ff035bc900f5ae94420e9cfa583aa6baa555aa0e5f04905905f625936adba2497a3e03b8cc707f5d3552363afd64c3756e3f6c6a23cc827bfc524e9d546c68d7ce5c2c030e78ca90708bcd69efb3c822d7dffa9a02d0b66aee417a5c05e441874a2307520378e0baceaebdf2b11d212f273d1f31b4ea9a0db34276bc291915166e3a76f66f03b096515a642d0422bb3e675280674692800833ba308f6175a3c3a5f511a7c9660393a8b74d9af3c2eb2ba928c3f09ff0199f44274fe1b8bbf80e6b00bd2e0bb28303cef340e78e55fe909ab1cfc7a0392b09dd51f54c3d79e6194ad8880fef0541b603b72ff83fa705f04ec1b81dd4554f64a3e33f1d4004007d23b978466f9a36b4830364f72fa1c2622a567c0d3831526e42ded6c4a6f2344d604a5dba80d6a181fd4003cffed49459425b0d2da7f272441098c41282ee65b532143924353989cde8d1ea037e0be922c58d532e35b8cb330c97cd2c028be4cc65a66628df8f663dc5dbd81b03e0d4dc333d4ffea0224bc146974cebefd76edbe6ac28c2cff2e6367b936780d103240529d170cff5d9d4c54fe762757d24d0cb8aee37f756e31b7865f8ffd59588035b6f1e49eac2a2e0f25a23ceed3427eedd124ad8117e2a0508aef9b7138515f103b52155e3c1ea78517da7f0d3844e824db72df6e5a01e1d2518ff6a02845d55660379f983383f08c8dd59cd430952e40476e95ea6a23e19dc2fa9e81a397973718003172d0eda34b35f60a6d5395fea8760c024d2ae393f58cb782f67aa2d05bd4cb00371812d80ed0ea3f04f8f2843a0b140a15a8934864fd740bc0f8881ab77875e97034f7116037000ea9230be83925d5edb323c31b48868f9679d80c98bf6092a81d30385aa49c12cdad808fbd9a2d1692cced2e17984f731edee63ce7d6649c3cdf016033330cc1dff7e05f7b6ee23c730968fd49674c20d3982e99ad6be3f62c4fe1b0b034e77ce884ffc656c434d2299effd3e211499df5c963d9121c4dcab6a153285b900581e020b1755f3810eea0ab86fb07ec9cdb847e45e49803dc72c82a7d66f3afc5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0342d061d07c0396c02cab4f74cd41f448c48c43434582c32fa002b229b105692400581e02ee713f4e03e4a0d7f2a90ae4834f72106e1e65e42f3b2ec4f517d6b367470e35fa931a006400581e0238d98e4c3b9cbcc40eef1ee76986b6877984a0e7b6956830dd2f3455af410100581e027ebfd882c0b8a8b5c05eef33dfd0345ca219820a3cd0af31450dc691f15820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e0238a5bda3c70246f0914c50983e21f20a805be98e6276310dee8b8495c14b4a817c7f2a2bf142ec876b0219160b03c8e521e04254386e003f7bbd99556ba574c609b0e200d359604b0cff7866af3103237f5a097edad31f1d89d2cbaafbf83169f6eeb404e7905b712930d926270bf803ecffbdae71e859f97752b3d56e3c39565782f8bc17b669aa3ed61efbfb5161d003b5022cdda3df9bc44253c9bad5ebb86c3e3160898cf0416a98136e59dc7f7c380219ffff03d5e1b77ddebb082898e4e23a96213d93e1eb466fc26a6fde217b80525b2dc1c1032112e772020f83d545a62ceec7a1168c695f36325b9c3039812afd53d43b5bba03f0368e6ab233f0e7ce6aa4cf462e55df6998b01f54015d2079dd8ede97bb816a03c0a8c690ec681fc2e26239bf1a1047f52326a3b8315dfa726039ef901a3cd074036d35208b96ff5be93018275342e980f6bbd89ccad3d048f824f5cdd625df1ffd033d865cc0f302bd91d0e0ce07d0af72c0e2e107bb2de7538216fa025e9d83e8b103c82c48fcabdc7541207d54fffbf2cc608a0fd984551c05c188651757081fbe0c0309e04c9e6909ad7dd039762f4e30c83f7f20e0dd5d98d9b9c91dc0cc984572c2032a62254e826db842766950715f6c93927e4b49d055802d6396f613c00091623c033778ef8d23a6da68967ac5470a395c4e26d3de8eb442f511693d302f4a3cacbf0219ffff03ce89a44639931f8f1fb8962782ad52d56c979306c090d5043e04192320888e5503f17e89d8d1202c099d84a67fa39c4fb07af2de100ba4e4ec1949532582b54c2b03724e93400753ee59606ec1f83c8b3838f76180391db0df52d98161e2ac91272603b8872eee1c1c88f0b890e9d1804673547b13bc750b60acb2a10349d38ae22a750219ffff03539ea2fd00723098869d28018a3b05fd4a7fa5693296792c4294a7524e39a4bb0398cbb06ff100e9ea192dcb248c1e4e9cd44dda37370e9d94aa039cad5158a0ba038fc6c323e79dc6ffa32384532ca8df63d366d203852aef524efc5b0b1f2fdc2b034216bd61b4676f39a50e9abe414960ed3b79daee20398525467e6489cd0f29b003b6c58b078bdb68dd95486753aebbd418e8ee8bbc65fec3756d1c25e23b49e457036b6ad326b5def7225704c129d9af40f4642951813d1507e319fa727ace71a05803e6c42ebc271bcae4d7c8cfa012cc434d0ac312b27c4a0df426270393e89136b0035e4082705a1fd46fae9698c01fa449b296acaf29118af7cb109b2ff5e135322b03bd26937e13ce92f3911d1d5e1fac601b933e0a2a6a57e95434010807b867f340034cb41f2bff31b91eac04c1a4c2a3f84f8978b1e803eb859993bdd96187653a510356ae8434c3df25974936e160002949712d0b8dbab2d5850ec750bf490c779417036ad1a806b81b3d7b46f13bde63547d8f5a828ab8ea7a616066982f14761aee6c03ff0ab0fbb69bbfbe938fd7de76f40774049d1c379882a13193f944456cf6cdef0372b29bf841025a0ff6e03499ba81204a17fa79a70a325227d6b7937d45ab8f6903e4821aa975f63f622a5beea12e61130e06144114d296c6de21b81a1f426db4fa0219ffff03d7c6b3fa4aae186f4d4be482827cf8d5334f21f7113b6fe392414c7b8fa1b9ea0219ffff05581d02a65bd257638cf8cf09b8238888947cc3c0bea2aa2cc3f1c4ac7a30020f014b02987192535af43b35a8e0190c34021920020219702b03c07c4048481c07b118748b2a30e851cf5c6c87af6212257f8845eb9ce07a02e403012fab999f402d6b302a07d3f7973ee8c357d5db8c7991903bd54d54d507cc23035923155adc8cd3aceaf27c07858d290a3b85fc378fdd98e9f4f7f70d157f28ba03c55b2981a6fe08260cb6a076c76858d56aafdf255d0a12a2c50abe35d468c7e7037975ea5712162b9b7339943572a7180adc20238024a429eb2e0928128e39556d033909e6c87be78bf54588e521ebdded1d467e8104d18958f7dd079010ee22155703867f6119f66c88787520dc8899d07d0e49598fa8dde1f33e611871eff6cd04960219ffff033307aa27831bf49ba78f1d47a092c180664e709902579fa9b13f147523cb8d0b0219ffff0329d6f7d3c1260b8be12e0e506b6f090a20038b4006ff80babc6c798e50891df503e9c47c8bccfa7364ec6c3d5dd71ec05a15da0ac8111ec3a218a963843ca4f44e03c26d4fc6efabc1fa16b6b91e18f6ad576e156cc6506af88d5858488a5d4546eb0346df579e8e01b9f5eabcf5a8fb9ab122c02677630ebb8b718ce561efff51cf770375030fc96c9b2aaac9c3270ba8b807966d58e1033d6bc3636bdb330f148cd22e0395ca7850aa3c51492680ccc0d0847a4ef39d48df817e4e66b6fd64c35ca4485a0219ffff036c9c5ea6492f79beb757ba4a5a86cc21ecb10dfec704dbc42196f01fc3bd8a18037e2386574bdde6a6241ac226e77aed7fa0d3af0e85efaf721dd0f7e1703307a703fa3f5df803d4b34d35d641a4dc7c594006e285e44e7016fb963ffe7a5af1e7bb03649cb983132917b142baa50fb45b8bbcc6a3bf860fb669d86262ecafdfb6151a03dcf8be433f6f3140ba294e13c78baa869b70ebf53b5665dc0af407d111b87fde038f61eddde7c18e54c27deea077463181fc47c571d2fcd0a3a3c880a576f5dcaa034791d02f3d4ed75e8c1322183b29a62125ba1ae9b7b95a81632c0368b454158503076e3c62327fc9058e9af74bc1503790e7f5b23487c9b243bbc76cf2185dc21e0219ffff0386070c501d2dedfd5854e6b427ebd511b9ec12864f64ad25f5724bac6070a7a103005aef95f8f141a39d5a05154e82c0fddcae72797c4f2b3d538a444e6d86edaa030bcd7829abca6ca5289405b6be80fb7528e08fa8a499119869499f3fc466ea04035650135745bcd57372655370d488d77a0656a86c0527a839dca211dce4c7157e032705742db4d13cb35652dd66f6824dd9e5571040af9f1f93df3d0a4a6642b51b03a8dad6fd934641950fcd0ba0a94794ee6018e4e7be54a39694c761e765589348030214f4c71b42e0fd99a2c5a0530ef0283ac350b6601e9ad3a1eb33757971c34803a8d69241ee74452bba003dcd989c8d8d4a17c7ff876cfcc980636b1f8b8e30fd038979a102cc529bf29218092a80e58205585d7c61f3e4c76095516589e5cfd81d038ed46910ce68c811cfd436c73d73e42bb83751a732f9ec608119772b78e311a803ab19ce157de5de4ea44218001dcc7edd121a4b61a2f01154bbe5ef646fcd9ec403ae323f944161990d516b671c169b34454c56c0dc2895baf6b8843d6d6e5b5abe03cecc35068fde6bca3cb34be29161ac3dfcac8cf6c528d92017792da172e94a7e030d6bd1e0b89ffc54168f8cea45193aa51279799a2ac9ff58917fdda65e71ff3803b2091aba4f213da33a192510c2d17984526962bb7ad1560b8dab9e3cd5eb1a87030c35f6d742e4ac57da4e8698f5672e14809faf4c4fb9afae45b146cbc41dce80030afd0d577712361808587052750e5ab6e1bcb666c02b5d9c2d6fcf566b6ce02c0308a98f938a979d6532460fca9bedabd238df73a3abde35f5d6fc67055d8efd1e039c67fe8d25b4b72281cef0dfb313edb0627b653cd7d1700059e09d118f5877bc0341b53daa2cdec4abbe38794b75882c81df0e2bdb4c5aae9d2aa86db9e1822523032cd8f70d4281067697a228979f4055c5aae3bc5d42b61192cd88352ead70bf6903bb13dd1706c920f0d825dbb556b595d3c6bf1bf06c1b455b8522cd1c81ec36b803a3f11fa7542504a039690a679d4051556f3996ac796a0953180c2b110c7205d4030b28257e7aad2fe8ac5a0dd60bfaff0466b4a33d635d7d312aafab1205e90c5a03c11239c2c578f6366d46479fd0aaf7e0933bb97898d6f278bd378df9f58b6f74033fa79b6f6e5d3296eccddd86063f7dc84503f1761aa618448e5c4303c244568c03c256e8ad5ec9824e212ebbaaaf50e3793df293f71b97881eb947294e1b85becf03ec4d1da50900fcbdd12bb5dc83fa1dd4c8c623a12695587ac68540a72b3e9c0803b3c6cfd25f6d37c228028a9ec88dc3f9ecb98f7a776202355fcfcaf46e0f8679039ff2ab8d80bd7110f4f88109deec8d5fb382c8dc4aeb48a3659173bd670249cd0327e3795bc5d6c5c106bd17fb530630f081282c18727149ffbeeb67037b6499f903b0dd521afa67087841f751fd06a91792fc2e7fa10a8b25c0434a11fd6c95c77f031aa2715fc3749ff71e27d28322d4e9ebe587dfb722332a2d2fc48d654a34ae3a05581e035c606158f0440c668481a4c457cff85703e1e117cb3d6c3f07c19855400c11479f2c230a85174905581e03aa9705b369190b5183f2b073797e29cc07766928dff9c307e9ec9994300401035713eea1764d8f2464447ba59fba61e15f1a5c4fc8cd610f8fdb3af4505a1a5a0141080300ead19e58881221cf02e877c91f01944607fa694ec5d1b2b5d1ef280c9acde103c07be3267e504d3a12fee2d6d54cee3a2fd10362112fd1e965df3b27e1e320ce05581e036bcc20594481b8d81021bef54a7c6be4b7ddd6913563d0127fe99d6ab00c0f4701af438415655f05581e03248da3b8720a2784a17616c093aba16d03ef0ee6535979bab7e32c4cd00848047e4d4c9d366cef05581e034a1b12a03d5a86cb2528dcc8f7fe3dbef88565541101273545612d5a400c1901d448511d91f23b36f55e0308a77a72a67a37a2f70a706ddb1fafbe055a13a156d9967ad677da6dd1aeaf3a05581e03122e75bb2c083f1d2d69d81675788d63efeaa7e70484159369c5f76ed004010219dd5a03a2396a9ed6f1c26f33f287eff0b756e1c4b994aafd76e682c3b2a3591c382ad003ee08a098b615eb35befb53490f2e05a48eea3d0b79878be9e07fe72f119c7a03035fa10b9debfc6b215c5d850b2637b12bf01758c6f468f6de3efb6b5235518198036354dd74770a90b0d36cbbb832b80408fb6a1a3a95aed95705a71c4004ee0c5d0391f07bde9b9b713dec0892f777b4171ab5d79550fcaa3f727ce371a579fbfa0803d9be28a4b11068c97e32cb61b4689e9b53027c142b734b29e86c44f1a717f798039014a6aa6ef1f464bfcfcd7e5e69d23d6fdb70a7c59b82b427994389859a137d034b7136d8b9196da29a931889357bca83dc5024a19980b3a823609e027a7d28cd032bf1c60f958a3208e625e38b99c425ef423d2d7e6fd0ad28ae077290d064590503af42f0784bc62c055ba67eeb07d6e2803bdabdacdbdb587fc41d024053b299c203081ec485bf3270f85c3f65fb49ec4a39420946da7614eb7c2109341b49b2df0303850504463e8d8be67556790cf187db8dc166db818c2f622b532f4402eeafc13f039995ac4e35f0c738509c2e3a662cf588bf3532cb3b4c3b57b2252f2bcbb8133c0219ffff03a0dd053e237402631c1143d2b091ffe2cf8976c51ecfd5f3a4ee9ffb1fd492090307b915372335d8d439892834230eab0a74e5c2827bb6eb9a79cbd84b3e126369039a105c4238c5da6415135af7116574a5b00d0e2867b3275ceef2805350da77a303aa054982670793e2e90bd8e74dc603327b9c0f10b8f203385cb270105cf983ae03238821def48781961b4813b105941038ecee6859a6ff51e71adb08e529d4124b0321a15b14aa1d0922ca24e3e859d45b187674666180ccf4bf3f23278f6ffabea803815cf9d7ee19e93844c2d8bc2ab65392e26328a99c052bf932e423006bbb78fb03366d5a9588612a223872ec3a857dfdcd6eef3931c5a1df39671940984a8c402403ca3ab1de8fcc41d12a6f1164d49e77969d0862d7f81924fe6aaf920c0b3a384d0219ffff03c0c27676d2bdf8a48899317c9bf382ab7e493c966646ddac1ab7713c3ac146950353c5e32a2498ca30da94de8c49896e0f01156ee9b1058165a41f2caf07faf450035b3dfa50f1cb4ff47add88abbda3f85342cb0eed97d094ff357a562a0c479a69039a8417bb2e45e35071b139b3545afed10937c9a7078a6708fa30b9c8e2238426032543d287d0df869ff2ebd4a0b7e3d1a777bc9b2c297d072f6b428325c1757dfc03223030562cbbf3dcd031da9a069ee99aacac77615f929eb6921174149680b27503bed7f41065fb7c01a4e94ad082f3d1258f36540b42e577950c6d1f9992cbb7d60219ffff038783f41b3afdf0834e6a42d4c4ee38346b12998e6b243ac58d1fca79eaa149b903eda5fb657335a57ed988a60795f3216d92542d298d68de9d5f66a011532e7d0e03e903bd445461327baa0859bd0adabdd1f2dda510fd398dec055ae0b41757fdf903a5f5a931db46cc393e56f3469dd46c6543554eaad090d400478dc19a3d3dbb1703dc3a394c7d9680788c8d952bc714676fdd073f1bbfd42957decb5147a35dfea80219ffff03e74b1af12d32d6625b6e9af4f108418707cef62d83099fccbbb77a12fbd553d10219ffff03117181bdd1994c779539c2ccf1d27bb08dd7508d23b4915ee591310e0070a25d03abee9fa26290e923ce0bc1dafa074085b5eae1d5bb8effca8ff89de5ae704945039c84c4f87921f21e03bacbcfeb05c06b99de3e380a9c5ec7d8c8496048419215030cb0e65e7d1e87bd15a2a912fbd6771cea78c87c087fe73a89912fad93803727036df5a4f057289010386d89611b6ca8c655a8c8f78edf787727f867059ee2c39f03af4d4fc041a2b67e93e67f442db79ec8bee16442cb66ea516f8f82466fa49e79037fa0f837c9938e7ff15a5306e65560961f1ac260ac91680af1e4f0ee1911c2a4037740d45f62bfaf1c66da3d3a07222f3f622c0d2e7a925307ed728ecdd91aedc203727b28ea80c0ada3ab2c35d8a40ea053fbac57ef7a6a8a6709ebc2def29b78f6036891cf8a17095faaeee8445712dbd95e1759044a1a30748ca820bc33478807090307c2247f223e3931da22f0611b887e3ac718c1043564e6af6839069253ccd4dc03dc7cc1c5ce71194bd006b743b09df24f69ca172a082e7a45e51973f6b72ef68703db4fa5455b0496bdea4b760e7fcfbec45a997dcdd6130e93f9fa80000573713103b6a0772933ec89fc42f219ca5a43c929a582ddf2afde8555ce60169533959097039c86eed3ec1498d68b170b938357713d93da8b9b472d49f746603980b657824403ff8d64d37a029eeb37a76a4306b670a63819ee6917678b83569baf2be3faa09d03dc194815f6d22cf0ba77425bddf4259fffb92c5801e90249fb833ffdf00bae5103bd92dbe09bd257b339440f07a0ff827546e6402ca152a9fc38b2f871a8652e7d033bd217dea5a41592a72c511bac28c0ec5846214e05dd0d18330af19aba067b4d038aa6239758091c9df09d3afa7af3adfdb3c39d85e99fe19bc680f10b474c33720318f9563305fb9ed27279b34d068f8a9bcf557895cccd3c8a61a8b3077fec99740325117ad1f37de8a2f85959655b16eed70d9b58735e22beeb113f192030258c90039c2a3950f0065d88a122be2a64186b3598fdf5927995c1185be74765a3c434b10315ce924bd41bec9366e0e121f751cc6986f3954d3146b667c6e9375d54c95e0d03d00ef616504882a2e0afa8d87b056cc18f658a799d3a62481b22a66a6f5d246903579be1a5816ff5940887ac3cc29986ccb9f46712b6b7d3894c137b8a4d80405e03e06f96fc5f9617497abe22fc13fc3f561521fb8c70be3100cf6a44b47e54f6d8031110c4a2cc01357891418fd9548aa4a1ec5ec1a46b26569cc7a2f8f6949e6039031e3f4c3898c6c6a72f51ba78478928b33799f08a33f4632e03b5ec16ebaf017a03c3ad48b1fd0bda8c5d6680bb2921da7fde7fa49b8ca2dbb556bfd50173ddaa410335f0ef42cd0a70dcdffe95b132101256a5c5f23c8d2118fcf8c6d07cb191327603040ea67430a4d66e71add4552c4582eca4ea957ec08e628cde7869041ab9b6ce0359b871c2c8f973525863f18c2f6f18a44eb2f8840fd6a1bb919905b4f01e421d03be833adcfeeb21b1c9649a72411a8f3433f3a15c6c4d9d1a0117cae337ba000603c56db9c25ca86dd976de4d2ac3ca14db6b8281bc197cbb57d8e487330fc885060354bbade28616422c37385e189a28fba5409bee41180fa08089d58145e9f4d7c20302b5f2df1cd36888b2cfa5ff5c19a2c210f0d1472073efc437fefe9493c2c9cc0313581fa11ad687397d4912c381a6dc0ea10ea7cdc13c29e29a0a1a0dce53bc9103dcffdc07401d5fadd7506f43499428f144476c02796d82fa09344dce86d660fe0332dafcd2dc2d255c7ca2c926ac6fbd0e91a7e9ed1e0822e8b285058d97a013e003264c9e0ff37d87df9a6155c0e1551697e3d3674db6cfb3ef341df887bc6d75a903696bdd89d8801a173361a0c73a96198e7c43ff939323719910dd788f2693597303f1d900423b5b8f5c83c62d8b7c5c8ac90337d4ca82a44e36151a42a391dd0a31033b89c27479ecffb8887c2b739e31b8723a3001b740bb2ffe7d26379e90be9a0404595a8860806040526004361061021e5760003560e01c80639871efa411610123578063d1b260d4116100ab578063f3dced3c1161006f578063f3dced3c1461061f578063f3e144b61461063f578063f6932b0d1461065f578063f851a4401461067f578063fa461e331461069f57600080fd5b8063d1b260d4146105a4578063d8837daf146105b7578063e0af3616146105ca578063e99bfa95146105ec578063f2fde38b146105ff57600080fd5b8063b07482d2116100f2578063b07482d214610528578063b3ab15fb1461053b578063b80c2f091461055b578063ca68d8f61461056e578063d13a35e61461058457600080fd5b80639871efa4146104ac5780639989d481146104bf5780639a09b285146104d25780639a307391146104f257600080fd5b80633ba7cadf116101a657806373679b6b1161017557806373679b6b146103f3578063779e45fc1461041b5780638575654f1461043b578063869c6b4b1461044e5780638da5cb5b1461048e57600080fd5b80633ba7cadf1461035257806341f4a9591461037e5780636aa476451461039e578063715018a6146103de57600080fd5b80630d5f0e3b116101ed5780630d5f0e3b146102e157806312a0ddc7146102f45780631afca626146103075780631bb943fd1461031d578063355b97181461033257600080fd5b806301617fab1461027557806303b87e5f1461028857806308298b5a146102ae5780630c00e013146102c157600080fd5b366102705732330361026e5760405162461bcd60e51b81526020600482015260146024820152731155120819195c1bdcda5d081c995a9958dd195960621b60448201526064015b60405180910390fd5b005b600080fd5b61026e610283366004614c00565b6106bf565b61029b610296366004614caa565b610a5c565b6040519081526020015b60405180910390f35b61029b6102bc366004614d76565b610b6d565b3480156102cd57600080fd5b5061026e6102dc366004614de8565b610c30565b61029b6102ef366004614e29565b610e5a565b61029b610302366004614e9c565b610eb4565b34801561031357600080fd5b5061029b60215481565b34801561032957600080fd5b5061026e610f01565b34801561033e57600080fd5b5061026e61034d366004614ef3565b610f35565b34801561035e57600080fd5b5061029b61036d366004614ef3565b602080526000908152604090205481565b34801561038a57600080fd5b5061026e610399366004614f0c565b610f49565b3480156103aa57600080fd5b506103c67370cbb871e8f30fc8ce23609e9e0ea87b6b222f5881565b6040516001600160a01b0390911681526020016102a5565b3480156103ea57600080fd5b5061026e611006565b3480156103ff57600080fd5b506103c6735703b683c7f928b721ca95da988d73a3299d475781565b34801561042757600080fd5b5061026e610436366004614f5d565b61101a565b61029b610449366004614e29565b6110d1565b34801561045a57600080fd5b5061047e610469366004614f92565b60046020526000908152604090205460ff1681565b60405190151581526020016102a5565b34801561049a57600080fd5b506000546001600160a01b03166103c6565b61029b6104ba366004614e29565b6111dd565b61029b6104cd366004614e9c565b611241565b3480156104de57600080fd5b5061026e6104ed366004614f92565b61132e565b3480156104fe57600080fd5b506103c661050d366004614f92565b601f602052600090815260409020546001600160a01b031681565b61029b610536366004615050565b6113d6565b34801561054757600080fd5b5061026e610556366004614f92565b6114e5565b61029b61056936600461507f565b611547565b34801561057a57600080fd5b5061029b61012c81565b34801561059057600080fd5b506003546103c6906001600160a01b031681565b61029b6105b236600461507f565b611611565b61029b6105c5366004614e29565b611784565b3480156105d657600080fd5b506103c66000805160206159f383398151915281565b61029b6105fa366004615135565b61187a565b34801561060b57600080fd5b5061026e61061a366004614f92565b611a9a565b34801561062b57600080fd5b506002546103c6906001600160a01b031681565b34801561064b57600080fd5b5061029b61065a366004615236565b611b10565b34801561066b57600080fd5b5061026e61067a366004614ef3565b611b5a565b34801561068b57600080fd5b506018546103c6906001600160a01b031681565b3480156106ab57600080fd5b5061026e6106ba3660046152c0565b611b6b565b600160ff1b81166001600160801b038216806107125760405162461bcd60e51b81526020600482015260126024820152710616d6f756e74206d757374206265203e20360741b6044820152606401610265565b81156108aa5760405163052f523360e11b81526000805160206159f383398151915260048201523360248201523060448201526001600160801b03821660648201527370cbb871e8f30fc8ce23609e9e0ea87b6b222f5890630a5ea46690608401600060405180830381600087803b15801561078d57600080fd5b505af11580156107a1573d6000803e3d6000fd5b5050604051632e1a7d4d60e01b81526001600160801b03841660048201526000805160206159f38339815191529250632e1a7d4d9150602401600060405180830381600087803b1580156107f457600080fd5b505af1158015610808573d6000803e3d6000fd5b50506040516000925033915047908381818185875af1925050503d806000811461084e576040519150601f19603f3d011682016040523d82523d6000602084013e610853565b606091505b50509050806108a45760405162461bcd60e51b815260206004820152601c60248201527f7472616e73666572206e617469766520746f6b656e206661696c6564000000006044820152606401610265565b50610988565b806001600160801b031634146108fb5760405162461bcd60e51b81526020600482015260166024820152751d985b1d59481b9bdd08195c5d585b08185b5bdd5b9d60521b6044820152606401610265565b6000805160206159f38339815191526001600160a01b031663d0e30db0826001600160801b03166040518263ffffffff1660e01b81526004016000604051808303818588803b15801561094d57600080fd5b505af1158015610961573d6000803e3d6000fd5b50505050506109886000805160206159f383398151915233836001600160801b0316611d53565b604051848152600080516020615a338339815191529060200160405180910390a1600080516020615a13833981519152826109d75773eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6109e7565b6000805160206159f38339815191525b83610a00576000805160206159f3833981519152610a16565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee5b604080516001600160a01b03938416815292909116602083015233908201526001600160801b03831660608201819052608082015260a00160405180910390a150505050565b6000876080013542811015610a835760405162461bcd60e51b815260040161026590615312565b610a8b611d83565b6040518b8152600080516020615a338339815191529060200160405180910390a16001600160a01b038a16610af05760405162461bcd60e51b815260206004820152600b60248201526a6e6f74206164647228302960a81b6044820152606401610265565b89610b44610b03368c90038c018c61533a565b8a8a808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152508c92508b91508a9050893388611ddc565b9250610b5560408b01358b3561216a565b50610b5f60018055565b509998505050505050505050565b60405160a087901c8152600090600080516020615a338339815191529060200160405180910390a16001600160a01b038416610bd95760405162461bcd60e51b815260206004820152600b60248201526a6e6f74206164647228302960a81b6044820152606401610265565b610bf16001600160a01b03881687878686338a612390565b9050610c26866001600160a01b03891615610c0c578861216a565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee61216a565b9695505050505050565b806000816001600160401b03811115610c4b57610c4b614faf565b604051908082528060200260200182016040528015610c74578160200160208202803683370190505b50905060005b82811015610e0e57601f6000868684818110610c9857610c986153c0565b9050602002810190610caa91906153d6565b610cbb906040810190602001614f92565b6001600160a01b039081168252602082019290925260400160002054163314801590610d2d5750848482818110610cf457610cf46153c0565b9050602002810190610d0691906153d6565b610d17906040810190602001614f92565b6001600160a01b0316336001600160a01b031614155b15610d4b5760405163203b1cdd60e21b815260040160405180910390fd5b6000610d81610d7c878785818110610d6557610d656153c0565b9050602002810190610d7791906153d6565b612b73565b612baf565b90506020600082815260200190815260200160002054838381518110610da957610da96153c0565b602002602001018181525050600160ff1b838381518110610dcc57610dcc6153c0565b602002602001015103610df2576040516311b18c4b60e11b815260040160405180910390fd5b60009081526020805260409020600160ff1b9055600101610c7a565b50336001600160a01b03167f2d978b051248279fa3127485450ca814a26b9b0938be9f714eda15eaec5ca881858584604051610e4c939291906154a0565b60405180910390a250505050565b60405160a086901c8152600090600080516020615a338339815191529060200160405180910390a16000610e9b336001600160a01b03891688888888612be2565b9092509050610eaa868261216a565b5095945050505050565b6000600080516020615a3383398151915284604051610ed591815260200190565b60405180910390a1610ef73380610ef1368790038701876155c4565b8561307b565b90505b9392505050565b610f096133f2565b6040517f0b3b40bc6027444e59029877c53be6734ca3724242753ffe5aa1a6066635211f90600090a132ff5b610f3d6133f2565b610f468161344c565b50565b610f516133f2565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeed196001600160a01b03841601610ff657600080836001600160a01b03168360405160006040518083038185875af1925050503d8060008114610fc4576040519150601f19603f3d011682016040523d82523d6000602084013e610fc9565b606091505b5091509150818190610fee5760405162461bcd60e51b81526004016102659190615604565b505050505050565b611001838383611d53565b505050565b61100e6133f2565b61101860006134e4565b565b6018546001600160a01b031633148061103d57506000546001600160a01b031633145b61106e5760405162461bcd60e51b81526020600482015260026024820152616e6160f01b6044820152606401610265565b6001600160a01b038216600081815260046020908152604091829020805460ff19168515159081179091558251938452908301527fede97de789011d2becb9fba8441864c3b17f4a20f7ebf0a77e93f30adc30e207910160405180910390a15050565b3360009081526004602052604081205460ff1615156001146111055760405162461bcd60e51b815260040161026590615637565b60405160a087901c8152600080516020615a338339815191529060200160405180910390a1600080336001600160a01b031663534015b36040518163ffffffff1660e01b81526004016040805180830381865afa15801561116a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061118e919061565e565b90925090506001600160a01b0381166111b95760405162461bcd60e51b815260040161026590615698565b6111d16001600160a01b038916888888888787612390565b98975050505050505050565b60405160a086901c8152600090600080516020615a338339815191529060200160405180910390a161121d6001600160a01b038716868686863333612390565b9050611238856001600160a01b03881615610c0c578761216a565b95945050505050565b3360009081526004602052604081205460ff1615156001146112755760405162461bcd60e51b815260040161026590615637565b61127d611d83565b604051848152600080516020615a338339815191529060200160405180910390a1600080336001600160a01b031663534015b36040518163ffffffff1660e01b81526004016040805180830381865afa1580156112de573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611302919061565e565b9092509050611321828261131b368990038901896155c4565b8761307b565b92505050610efa60018055565b6018546001600160a01b031633148061135157506000546001600160a01b031633145b6113825760405162461bcd60e51b81526020600482015260026024820152616e6160f01b6044820152606401610265565b601880546001600160a01b0319166001600160a01b0383169081179091556040519081527f7ce7ec0b50378fb6c0186ffb5f48325f6593fcb4ca4386f21861af3129188f5c9060200160405180910390a150565b60006113e0611d83565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6114056060840160408501614f92565b6001600160a01b0316036114525760405162461bcd60e51b815260206004820152601460248201527324b73b30b634b21039b7bab931b2903a37b5b2b760611b6044820152606401610265565b6114626060830160408401614f92565b6040516370a0823160e01b81523060048201526001600160a01b0391909116906370a0823190602401602060405180830381865afa1580156114a8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114cc91906156c0565b83526114da3085858561307b565b9050610efa60018055565b336000818152601f602090815260409182902080546001600160a01b0319166001600160a01b03861690811790915591519182527fd58299b712891143e76310d5e664c4203c940a67db37cf856bdaa3c5c76a802c910160405180910390a250565b600087608001354281101561156e5760405162461bcd60e51b815260040161026590615312565b611576611d83565b6040518a8152600080516020615a338339815191529060200160405180910390a16115ea6115a9368b90038b018b61533a565b8989808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152508b92508a9150899050883380611ddc565b91506115fb60408a01358a3561216a565b61160460018055565b5098975050505050505050565b60008760800135428110156116385760405162461bcd60e51b815260040161026590615312565b611640611d83565b3360009081526004602052604090205460ff1615156001146116745760405162461bcd60e51b815260040161026590615637565b6040518a8152600080516020615a338339815191529060200160405180910390a1600080336001600160a01b031663534015b36040518163ffffffff1660e01b81526004016040805180830381865afa1580156116d5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116f9919061565e565b90925090506001600160a01b0381166117245760405162461bcd60e51b815260040161026590615698565b611777611736368d90038d018d61533a565b8b8b808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152508d92508c91508b90508a8888611ddc565b9350505061160460018055565b3360009081526004602052604081205460ff1615156001146117b85760405162461bcd60e51b815260040161026590615637565b60405160a087901c8152600080516020615a338339815191529060200160405180910390a1600080336001600160a01b031663534015b36040518163ffffffff1660e01b81526004016040805180830381865afa15801561181d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611841919061565e565b90925090506001600160a01b03811661186c5760405162461bcd60e51b815260040161026590615698565b611604828289898989612be2565b60008860800135428110156118a15760405162461bcd60e51b815260040161026590615312565b6118a9611d83565b89356001600160a01b031673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeed19810161190f5760405162461bcd60e51b815260206004820152601460248201527324b73b30b634b21039b7bab931b2903a37b5b2b760611b6044820152606401610265565b6040516370a0823160e01b81523060048201526000906001600160a01b038316906370a0823190602401602060405180830381865afa158015611956573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061197a91906156c0565b905060006040518060a001604052808e6000013581526020018e60200160208101906119a69190614f92565b6001600160a01b031681526020018381526020018e6060013581526020018e60800135815250905060008c8c90506001600160401b038111156119eb576119eb614faf565b604051908082528060200260200182016040528015611a14578160200160208202803683370190505b50905060005b8c811015611a7a578e60400135848f8f84818110611a3a57611a3a6153c0565b90506020020135611a4b91906156ef565b611a559190615706565b828281518110611a6757611a676153c0565b6020908102919091010152600101611a1a565b50611a8b82828d8d8d8d308e611ddc565b95505050505061160460018055565b611aa26133f2565b6001600160a01b038116611b075760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610265565b610f46816134e4565b60405160a089901c8152600090600080516020615a338339815191529060200160405180910390a1611b43888484613534565b610b5f336001600160a01b038b1689898989612be2565b611b626133f2565b610f4681610f3d565b611baf565b3d6000803e3d6000fd5b80611b8757611b87611b70565b600160005114601f3d11163d151780611bab57633c9fd93960e21b60005260046000fd5b5050565b604051601581017306ff0b40e9091053eee51fa1d482ce5d852f523360611b825260206000600484335afa611be657611be6611b70565b6020806004808501335afa611bfd57611bfd611b70565b60206040600460088501335afa611c1657611c16611b70565b6000806000881360018114611c32576020519250879150611c3b565b60005192508891505b507fff1f98431c8ad98523631ae4a59f267346ea31f98400000000000000000000008452606060002083527fe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b5460208401526001600160a01b0360558520169250338314611cb357635960139160e11b60005260046000fd5b60843592507306ff0b40e9091053eee51fa1d482ce5d852f523360611b845230831460018114611d2557826014860152836034860152336054860152816074860152611d206020600060846010890160007370cbb871e8f30fc8ce23609e9e0ea87b6b222f585af1611b7a565b611d48565b336010860152816030860152611d48602060006044600c89016000885af1611b7a565b505050505050505050565b611d668363a9059cbb60e01b848461369a565b6110015760405163fb7f507960e01b815260040160405180910390fd5b600260015403611dd55760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610265565b6002600155565b60408801516000908990611e3d5760405162461bcd60e51b815260206004820152602260248201527f526f7574653a2066726f6d546f6b656e416d6f756e74206d757374206265203e604482015261020360f41b6064820152608401610265565b805160208201516001600160a01b0391821691611e5b9116856136ed565b9250611e6681613778565b15611ed5576000805160206159f38339815191526001600160a01b031663d0e30db083604001516040518263ffffffff1660e01b81526004016000604051808303818588803b158015611eb857600080fd5b505af1158015611ecc573d6000803e3d6000fd5b50505050503094505b6000805b8b51811015611f11578b8181518110611ef457611ef46153c0565b602002602001015182611f079190615728565b9150600101611ed9565b508260400151811115611f845760405162461bcd60e51b815260206004820152603560248201527f526f7574653a206e756d626572206f6620626174636865732073686f756c64206044820152741899480f0f48199c9bdb551bdad95b905b5bdd5b9d605a1b6064820152608401610265565b508515611fd35760405162461bcd60e51b815260206004820152601b60248201527f74686520706172616d65746572206973206465707265636174656400000000006044820152606401610265565b895188146120155760405162461bcd60e51b815260206004820152600f60248201526e0d8cadccee8d040dad2e6dac2e8c6d608b1b6044820152606401610265565b60005b888110156120865761207e868661203b86602001516001600160a01b0316613778565b8e858151811061204d5761204d6153c0565b60200260200101518e8e87818110612067576120676153c0565b9050602002810190612079919061573b565b61379a565b600101612018565b50612095826020015185613954565b602082015183906120af906001600160a01b0316866136ed565b6120b99190615784565b925081606001518310156121085760405162461bcd60e51b8152602060048201526016602482015275135a5b881c995d1d5c9b881b9bdd081c995858da195960521b6044820152606401610265565b60208083015160408085015181516001600160a01b038087168252909316938301939093523290820152606081019190915260808101849052600080516020615a138339815191529060a00160405180910390a1505098975050505050505050565b601f193601356561aefa81eaab60d11b6001600160d01b0319821601611001576001600160a01b03811665ffffffffffff60a083901c1661012c8111156121f35760405162461bcd60e51b815260206004820152601b60248201527f6572726f7220636f6d6d697373696f6e2072617465206c696d697400000000006044820152606401610265565b600061220182612710615784565b61220b83886156ef565b6122159190615706565b9050612229856001600160a01b0316613778565b156122d7576000836001600160a01b03168260405160006040518083038185875af1925050503d806000811461227b576040519150601f19603f3d011682016040523d82523d6000602084013e612280565b606091505b50509050806122d15760405162461bcd60e51b815260206004820152601b60248201527f636f6d6d697373696f6e2077697468206574686572206572726f7200000000006044820152606401610265565b50612347565b60405163052f523360e11b81527370cbb871e8f30fc8ce23609e9e0ea87b6b222f5890630a5ea46690612314908890339088908790600401615797565b600060405180830381600087803b15801561232e57600080fd5b505af1158015612342573d6000803e3d6000fd5b505050505b604080518281526001600160a01b03851660208201527fffc60ee157a42f4d8edbd1897e6581a96d9ed04e44fb2ab53a47ce1eb8f2775b910160405180910390a1505050505050565b60006125dc565b62461bcd60e51b600052600160e51b6020528060405250806000fd5b828152600060206000600484600401865afa6123e8576123e86054700419d95d081d1bdad95b8819985a5b195960621b612397565b505060005192915050565b6770a082310dfe168160c01b815260008060206000600485600401875afa61243657612436605672049d1bdad95b8c0818d85b1b0819985a5b195960521b612397565b600051915083836004015260206000602485855afa6124775761247760597c1562616c616e63654f662063616c6c206661696c656400000000000000612397565b60005190509250929050565b6770a08231d21220a760c01b815260008060206000600485600401875afa61243657612436605672049d1bdad95b8c4818d85b1b0819985a5b195960521b612397565b668b0367c240bc6b60c21b8152600060406000600484600401875afa61250e5761250e60587c1472657365727665732063616c6c206661696c65640000000000000000612397565b60005160205186801561253a5789156125325761252b8786612483565b8390039650505b909190612551565b88156125515761254a87866123f3565b8490039650505b50668b0367c240bc6b60c21b8452898502633b9aca00929092028201910204905084801561258b5781600484015260006024840152612599565b600060048401528160248401525b50886044830152608060648301526000608483015260008060a4846000885af16111d1576111d1605470041cddd85c0818d85b1b0819985a5b195960621b612397565b84602085028101858061260357612603604e6b0b656d70747920706f6f6c7360901b612397565b5060405182358b15600181146126ad5734156126395761263960557111696e76616c6964206d73672e76616c756560581b612397565b63052f523360e11b83528c60048401528760248401526001600160a01b03821660448401528b606484015260008060848560007370cbb871e8f30fc8ce23609e9e0ea87b6b222f585af16126a8576126a86056720498db185a5b481d1bdad95b8819985a5b195960521b612397565b61277f565b60018c3410036126d7576126d760557111696e76616c6964206d73672e76616c756560581b612397565b670a9059cbbd0e30db60c41b835260008060048086018f6000805160206159f38339815191525af1612724576127246056720499195c1bdcda5d081155120819985a5b195960521b612397565b6001600160a01b03821683600401528b83602401526020600060448560006000805160206159f38339815191525af161277f5761277f60587c147472616e736665722057455448206661696c65640000000000000000612397565b508a9450602084015b838110156127de5780356127d26001600160a01b03821663ffffffff60a01b851660a01c600160fd1b8616600160fc1b8716600160ff1b88166001600160a01b0389168d8b6124c6565b96509150602001612788565b5060009350600160fe1b811680156128c2576000945061282b3063ffffffff60a01b841660a01c600160fd1b8516600160fc1b8616600160ff1b87166001600160a01b0388168c8a6124c6565b9550672e1a7d4da9059cbb60c01b83528560048401526020600060248560006000805160206159f38339815191525af16128875761288760577c13776974686472617720455448206661696c6564000000000000000000612397565b600080600080898b5af16128bd576128bd60577c137472616e7366657220455448206661696c6564000000000000000000612397565b612b05565b60009350600160ff1b8216801561297457600160fc1b83161561296f576770a082310dfe168160c01b8452602060006004866004016001600160a01b0387165afa61292857612928605672049d1bdad95b8c0818d85b1b0819985a5b195960521b612397565b600051955087846004015260206000602486895afa6129695761296960567c15746f6b656e302062616c616e6365206661696c656400000000000000612397565b60005194505b612a10565b600160fd1b831615612a10576770a08231d21220a760c01b8452602060006004866004016001600160a01b0387165afa6129c9576129c9605672049d1bdad95b8c4818d85b1b0819985a5b195960521b612397565b600051955087846004015260206000602486895afa612a0a57612a0a60597c15746f6b656e312062616c616e6365206661696c656400000000000000612397565b60005194505b50612a488763ffffffff60a01b841660a01c600160fd1b8516600160fc1b8616600160ff1b87166001600160a01b0388168c8a6124c6565b95508460001060018114612ab157600160ff1b83168015612a8957612a826770a082310dfe168160c01b6001600160a01b038616876123b3565b9650612aab565b612aa86770a08231d21220a760c01b6001600160a01b038616876123b3565b96505b50612b03565b6770a082310dfe168160c01b845287846004015260206000602486895afa612afb57612afb60587c146765742062616c616e63654f66206661696c65640000000000000000612397565b846000510396505b505b505088841015612b3757612b37605a7c164d696e2072657475726e206e6f742072656163686564000000000000612397565b8a8152826020820152326040820152896060820152836080820152600080516020615a1383398151915260a082a1505050979650505050505050565b60006040517f5d068ce469dcf41137bcb6c3e1894e076ad915392f28fda19ba41601d33c32a68152610120836020830137610140902092915050565b6000612bdc612bbc613b71565b8360405161190160f01b8152600281019290925260228201526042902090565b92915050565b600080612f7c565b6000846001600160ff1b031015612c0c57630b3f79fd60e41b60005260046000fd5b604051600160ff1b8516156001600160a01b0386168160018114612c9957630251596160e31b8452600484018681526000602082015289604082015273fffd8963efd1fc6a506488495d951d5263988d25606082015260a06080820152602060a08201528760c08201525060008060e4866000865af180612c8d5760206000fd5b5060206000803e612cf5565b630251596160e31b845260048401868152600160208201528960408201526401000276a4606082015260a06080820152602060a08201528760c08201525060008060e4866000865af180612ced5760206000fd5b5060208060003e5b505050506000519050600160ff1b811015612d1b576322323ba760e21b60005260046000fd5b19600101949350505050565b600181341003612d4257631841b4e160e01b60005260046000fd5b600080600080846000805160206159f38339815191525af180611bab57611bab60577c1357455448206465706f736974206661696c6564000000000000000000612397565b604051672e1a7d4da9059cbb60c01b815282600482015260008060248360006000805160206159f38339815191525af1905080612de657612de660587c1477697468647261772077657468206661696c65640000000000000000612397565b60008060008086865af190508061100157611001605571045cd95b9908195d1a195c8819985a5b1959605a1b612397565b60006040517306ff0b40e9091053eee51fa1d482ce5d852f523360611b8152600080600483865afa905080612e6657612e666055710459d95d081d1bdad95b8c0819985a5b1959605a1b612397565b5060206000803e505060005190565b60006040517306ff0b40e9091053eee51fa1d482ce5d852f523360611b81526000806004808401865afa905080612e6657612e666054710459d95d081d1bdad95b8c4819985a5b1959605a1b612397565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee8085612f12578235600160ff1b81161560018114612f0357612efc82612e75565b9350612f0f565b612f0c82612e17565b93505b50505b86612f48578335600160ff1b81161560018114612f3957612f3282612e17565b9250612f45565b612f4282612e75565b92505b50505b60405182600052816020523260405285608052600080516020615a1383398151915260a06000a16040525095945050505050565b83601f19602085028201018480612f9e576333f3e07b60e11b60005260046000fd5b50600034118015612fb657612fb289612d27565b309a505b88606052825b82811015612fde57612fd18a82358e30612bea565b309c509950602001612fbc565b506000600160fd1b833516118060018114612ffe57801561301c5761302c565b61300b8b85358f30612bea565b9650613017878d612d87565b61302c565b6130298b85358f8f612bea565b96505b508886101561305d5761305d605a7c164d696e2072657475726e206e6f742072656163686564000000000000612397565b61306a8183888688612ec6565b945050505050965096945050505050565b8151600090810361309f576040516387741f3360e01b815260040160405180910390fd5b826060015180156130b1575082513414155b80156130e357506000805160206159f38339815191526130d76060840160408501614f92565b6001600160a01b031614155b15613101576040516387741f3360e01b815260040160405180910390fd5b8260800151801561313857506000805160206159f383398151915261312c6080840160608501614f92565b6001600160a01b031614155b156131565760405163591c75ef60e01b815260040160405180910390fd5b428360400151101561317b57604051632b32713d60e01b815260040160405180910390fd5b8260800151613205576131946080830160608401614f92565b6040516370a0823160e01b81526001600160a01b03868116600483015291909116906370a0823190602401602060405180830381865afa1580156131dc573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061320091906156c0565b613211565b836001600160a01b0316315b9050600061322f846000015187878688606001518960800151613c64565b905080156132535760405163f70b432d60e01b815260048101829052602401610265565b83608001516132e8578161326d6080850160608601614f92565b6040516370a0823160e01b81526001600160a01b03888116600483015291909116906370a0823190602401602060405180830381865afa1580156132b5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906132d991906156c0565b6132e39190615784565b6132fc565b6132fc826001600160a01b03871631615784565b915083602001518210156133235760405163a7c6745960e01b815260040160405180910390fd5b6040805184358152610180850135602082018190529181018390527f5018f79d04d45a1d0ef7df4f8a02c44c1b0b59cd11983e44f1e9dfaf071db2199060600160405180910390a1600080516020615a1383398151915261338a6060860160408701614f92565b61339a6080870160608801614f92565b6133aa6040880160208901614f92565b8851604080516001600160a01b0395861681529385166020850152919093169082015260608101919091526080810185905260a00160405180910390a150505b949350505050565b6000546001600160a01b031633146110185760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610265565b60f081901c6001600160a01b03821661271082111561347e57604051634bd934b560e01b815260040160405180910390fd5b6001600160a01b0381166134a557604051633480121760e21b815260040160405180910390fd5b602183905560405183815233907f04e0c6a722afc105ccf81d8792757f388d3dad9bc137b2e269cfd6ee99faba999060200160405180910390a2505050565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b8015611001576000606060e08390036135d5576040516001600160a01b0386169061356e9063d505accf60e01b90879087906020016157c1565b60408051601f1981840301815290829052613588916157e5565b6000604051808303816000865af19150503d80600081146135c5576040519150601f19603f3d011682016040523d82523d6000602084013e6135ca565b606091505b509092509050613644565b610100839003613606576040516001600160a01b0386169061356e906323f2ebc360e21b90879087906020016157c1565b60405162461bcd60e51b81526020600482015260136024820152720aee4dedcce40e0cae4dad2e840d8cadccee8d606b1b6044820152606401610265565b816136935761367a816040518060400160405280600f81526020016e02832b936b4ba103330b4b632b21d1608d1b815250614102565b60405162461bcd60e51b81526004016102659190615604565b5050505050565b60006040518481528360048201528260248201526020600060448360008a5af191505080156133ea573d80156136dc57600160005114601f3d111691506136e4565b6000863b1191505b50949350505050565b60006136f883613778565b1561370e57506001600160a01b03811631612bdc565b6040516370a0823160e01b81526001600160a01b0383811660048301528416906370a0823190602401602060405180830381865afa158015613754573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610efa91906156c0565b6001600160a01b031673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee1490565b60006137d4838360008181106137b2576137b26153c0565b90506020028101906137c491906157f7565b608001356001600160a01b031690565b905060008083815b8181101561394757801561381a576137ff8787838181106137b2576137b26153c0565b94506138146001600160a01b038616306136ed565b9750309a505b30613826600184615784565b82148015613832575089155b1561383e57508961390b565b613849600184615784565b821080156138945750878761385f846001615728565b81811061386e5761386e6153c0565b905060200281019061388091906157f7565b61388e90602081019061573b565b90506001145b156139065787876138a6846001615728565b8181106138b5576138b56153c0565b90506020028101906138c791906157f7565b6138d590602081019061573b565b60008181106138e6576138e66153c0565b90506020020160208101906138fb9190614f92565b90506001945061390b565b600094505b61393b8c828b8b8b87818110613923576139236153c0565b905060200281019061393591906157f7565b88614265565b508392506001016137dc565b5050505050505050505050565b613966826001600160a01b0316613778565b15613af3576040516370a0823160e01b81523060048201526000906000805160206159f3833981519152906370a0823190602401602060405180830381865afa1580156139b7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906139db91906156c0565b90508015613a4357604051632e1a7d4d60e01b8152600481018290526000805160206159f383398151915290632e1a7d4d90602401600060405180830381600087803b158015613a2a57600080fd5b505af1158015613a3e573d6000803e3d6000fd5b505050505b478015613aed576000836001600160a01b03168260405160006040518083038185875af1925050503d8060008114613a97576040519150601f19603f3d011682016040523d82523d6000602084013e613a9c565b606091505b50509050806136935760405162461bcd60e51b815260206004820152601c60248201527f7472616e73666572206e617469766520746f6b656e206661696c6564000000006044820152606401610265565b50505050565b6040516370a0823160e01b81523060048201526000906001600160a01b038416906370a0823190602401602060405180830381865afa158015613b3a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613b5e91906156c0565b9050801561100157611001838383611d53565b600030733b3ae790df4f312e745d270119c6052904fb6790148015613b965750600146145b15613bc057507f568575351b1514757989de9c786c66097e2ae26e38a5fd1b885ac2dd02b8b0bc90565b50604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f6020808301919091527f9569cad29571f225e7f2c73ecd677d78be383da74efd13f4af2bade05dc1a8de828401527fe6bbd6277e1bf288eed5e8d1780f9a50b239e86b153736bceebccf4ea79d90b360608301524660808301523060a0808401919091528351808403909101815260c0909201909252805191012090565b6000428460e001351015613c7a57506002610c26565b60006080850135613c8f60a08701358a6156ef565b613c999190615706565b90506102208501356000613ccd8a83613cb860608b0160408c01614f92565b613cc860808c0160608d01614f92565b6145a1565b905080831115613d1657604080518281526020810183905291935083917facd4baa7803154e33bc54ca36afe61420bf31d5f1bf3587746c146d2f3a76e50910160405180910390a15b6000613d24610d7c89612b73565b9050613d8281613d3a60408b0160208c01614f92565b613d486101208c018c61580d565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506147cf92505050565b94508415613d935750505050610c26565b613da284828a60a0013561480c565b94508415613db35750505050610c26565b8515613efb577370cbb871e8f30fc8ce23609e9e0ea87b6b222f58630a5ea466613de360808b0160608c01614f92565b613df360408c0160208d01614f92565b735703b683c7f928b721ca95da988d73a3299d4757886040518563ffffffff1660e01b8152600401613e289493929190615797565b600060405180830381600087803b158015613e4257600080fd5b505af1158015613e56573d6000803e3d6000fd5b5050604051632e1a7d4d60e01b815260048101879052735703b683c7f928b721ca95da988d73a3299d47579250632e1a7d4d9150602401600060405180830381600087803b158015613ea757600080fd5b505af1158015613ebb573d6000803e3d6000fd5b50506040516001600160a01b038c16925086156108fc02915086906000818181858888f19350505050158015613ef5573d6000803e3d6000fd5b50613f89565b7370cbb871e8f30fc8ce23609e9e0ea87b6b222f58630a5ea466613f2560808b0160608c01614f92565b613f3560408c0160208d01614f92565b8c886040518563ffffffff1660e01b8152600401613f569493929190615797565b600060405180830381600087803b158015613f7057600080fd5b505af1158015613f84573d6000803e3d6000fd5b505050505b8615614022576000805160206159f38339815191526001600160a01b031663d0e30db08c6040518263ffffffff1660e01b81526004016000604051808303818588803b158015613fd857600080fd5b505af1158015613fec573d6000803e3d6000fd5b505050505061401d8860200160208101906140079190614f92565b6000805160206159f3833981519152908d611d53565b6140f4565b306001600160a01b038b16036140665761401d61404560408a0160208b01614f92565b8c61405660608c0160408d01614f92565b6001600160a01b03169190611d53565b7370cbb871e8f30fc8ce23609e9e0ea87b6b222f58630a5ea46661409060608b0160408c01614f92565b8c6140a160408d0160208e01614f92565b8f6040518563ffffffff1660e01b81526004016140c19493929190615797565b600060405180830381600087803b1580156140db57600080fd5b505af11580156140ef573d6000803e3d6000fd5b505050505b505050509695505050505050565b606060048351106142335760208301516001600160e01b0319811662461bcd60e51b14801561413357506044845110155b156141d357602484810151808601820180519192909190614155908490615728565b61415f9190615728565b865110156141a75760405162461bcd60e51b815260206004820152601560248201527424b73b30b634b2103932bb32b93a103932b0b9b7b760591b6044820152606401610265565b84816040516020016141ba929190615853565b6040516020818303038152906040529350505050612bdc565b6001600160e01b03198116634e487b7160e01b1480156141f4575083516024145b156142315760248401518361420882614899565b6040516020016142199291906158a2565b60405160208183030381529060405292505050612bdc565b505b8161423d846148c1565b60405160200161424e9291906158d7565b604051602081830303815290604052905092915050565b60808201356001600160a01b0316600080614280858061573b565b9050905060005b81811015611d4857600061429e604088018861573b565b838181106142ae576142ae6153c0565b60200291909101359150506001600160a01b038116600160ff1b821661ffff60a084901c1661271081111561431b5760405162461bcd60e51b8152602060048201526013602482015272776569676874206f7574206f662072616e676560681b6044820152606401610265565b6143258188615728565b9650614332600187615784565b8503614399576127108711156143995760405162461bcd60e51b815260206004820152602660248201527f746f74616c5765696768742063616e206e6f7420657863656564203130303030604482015265081b1a5b5a5d60d21b6064820152608401610265565b8861440857600081612710146143c5576127106143b6838e6156ef565b6143c09190615706565b6143c7565b8b5b90506144068e6143da60208e018e61573b565b898181106143ea576143ea6153c0565b90506020020160208101906143ff9190614f92565b8b84614aa8565b505b81156144d2576144188a8061573b565b86818110614428576144286153c0565b905060200201602081019061443d9190614f92565b6001600160a01b0316636f7929f28d8561445a60608f018f61573b565b8a81811061446a5761446a6153c0565b905060200281019061447c919061580d565b6040518563ffffffff1660e01b815260040161449b9493929190615928565b600060405180830381600087803b1580156144b557600080fd5b505af11580156144c9573d6000803e3d6000fd5b50505050614592565b6144dc8a8061573b565b868181106144ec576144ec6153c0565b90506020020160208101906145019190614f92565b6001600160a01b03166330e6ae318d8561451e60608f018f61573b565b8a81811061452e5761452e6153c0565b9050602002810190614540919061580d565b6040518563ffffffff1660e01b815260040161455f9493929190615928565b600060405180830381600087803b15801561457957600080fd5b505af115801561458d573d6000803e3d6000fd5b505050505b84600101945050505050614287565b600060f084901c6001600160a01b038516806146c35760405163e6a4390560e01b81526001600160a01b03808716600483015285166024820152735c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f9063e6a4390590604401602060405180830381865afa158015614617573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061463b9190615955565b90506000816001600160a01b0316630dfe16816040518163ffffffff1660e01b8152600401602060405180830381865afa15801561467d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906146a19190615955565b9050846001600160a01b0316816001600160a01b0316036146c157600192505b505b600080826001600160a01b0316630902f1ac6040518163ffffffff1660e01b8152600401606060405180830381865afa158015614704573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906147289190615989565b5091509150816001600160701b03166000148061474c57506001600160701b038116155b1561475f576000199450505050506133ea565b8360010361479757806001600160701b0316826001600160701b03168a61478691906156ef565b6147909190615706565b94506147c3565b816001600160701b0316816001600160701b03168a6147b691906156ef565b6147c09190615706565b94505b50505050949350505050565b60608101516080820151600091906147f1856147ec888585614b3d565b614bac565b61480057600192505050610efa565b60009695505050505050565b6000828152602080526040812054600160ff1b81016148305760035b915050610efa565b8015801561483e5750600083115b156148465750815b80851115614855576004614828565b8085036148755760008481526020805260409020600160ff1b905561488e565b61487f8582615784565b60008581526020805260409020555b600095945050505050565b6060612bdc826040516020016148b191815260200190565b6040516020818303038152906040525b80516060906f181899199a1a9b1b9c1cb0b131b232b360811b906000906148e99060026156ef565b6148f4906002615728565b6001600160401b0381111561490b5761490b614faf565b6040519080825280601f01601f191660200182016040528015614935576020820181803683370190505b509050600360fc1b81600081518110614950576149506153c0565b60200101906001600160f81b031916908160001a905350600f60fb1b8160018151811061497f5761497f6153c0565b60200101906001600160f81b031916908160001a90535060005b8451811015614aa0578260048683815181106149b7576149b76153c0565b01602001516001600160f81b031916901c60f81c601081106149db576149db6153c0565b1a60f81b826149eb8360026156ef565b6149f6906002615728565b81518110614a0657614a066153c0565b60200101906001600160f81b031916908160001a90535082858281518110614a3057614a306153c0565b60209101015160f81c600f1660108110614a4c57614a4c6153c0565b1a60f81b82614a5c8360026156ef565b614a67906003615728565b81518110614a7757614a776153c0565b60200101906001600160f81b031916908160001a90535080614a98816159d9565b915050614999565b509392505050565b306001600160a01b03851603614ac857614ac3828483611d53565b613aed565b60405163052f523360e11b81527370cbb871e8f30fc8ce23609e9e0ea87b6b222f5890630a5ea46690614b05908590889088908790600401615797565b600060405180830381600087803b158015614b1f57600080fd5b505af1158015614b33573d6000803e3d6000fd5b5050505050505050565b60006001600160ff1b0382167f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a1811015614aa0576040518581528360ff1c601b016020820152846040820152816060820152600080526020600060808360015afa505060005195945050505050565b6000826001600160a01b0316826001600160a01b03161480614bea57506001600160a01b038084166000908152601f60205260409020548382169116145b15614bf757506001612bdc565b50600092915050565b60008060408385031215614c1357600080fd5b50508035926020909101359150565b6001600160a01b0381168114610f4657600080fd5b8035614c4281614c22565b919050565b600060a08284031215614c5957600080fd5b50919050565b60008083601f840112614c7157600080fd5b5081356001600160401b03811115614c8857600080fd5b6020830191508360208260051b8501011115614ca357600080fd5b9250929050565b60008060008060008060008060006101408a8c031215614cc957600080fd5b8935985060208a0135614cdb81614c22565b9750614cea8b60408c01614c47565b965060e08a01356001600160401b0380821115614d0657600080fd5b614d128d838e01614c5f565b90985096506101008c0135915080821115614d2c57600080fd5b614d388d838e01614c5f565b90965094506101208c0135915080821115614d5257600080fd5b50614d5f8c828d01614c5f565b915080935050809150509295985092959850929598565b60008060008060008060a08789031215614d8f57600080fd5b8635955060208701359450604087013593506060870135614daf81614c22565b925060808701356001600160401b03811115614dca57600080fd5b614dd689828a01614c5f565b979a9699509497509295939492505050565b60008060208385031215614dfb57600080fd5b82356001600160401b03811115614e1157600080fd5b614e1d85828601614c5f565b90969095509350505050565b600080600080600060808688031215614e4157600080fd5b85359450602086013593506040860135925060608601356001600160401b03811115614e6c57600080fd5b614e7888828901614c5f565b969995985093965092949392505050565b60006101408284031215614c5957600080fd5b600080600060e08486031215614eb157600080fd5b83359250614ec28560208601614c47565b915060c08401356001600160401b03811115614edd57600080fd5b614ee986828701614e89565b9150509250925092565b600060208284031215614f0557600080fd5b5035919050565b600080600060608486031215614f2157600080fd5b8335614f2c81614c22565b92506020840135614f3c81614c22565b929592945050506040919091013590565b80358015158114614c4257600080fd5b60008060408385031215614f7057600080fd5b8235614f7b81614c22565b9150614f8960208401614f4d565b90509250929050565b600060208284031215614fa457600080fd5b8135610efa81614c22565b634e487b7160e01b600052604160045260246000fd5b600060a08284031215614fd757600080fd5b60405160a081018181106001600160401b038211171561500757634e487b7160e01b600052604160045260246000fd5b806040525080915082358152602083013560208201526040830135604082015261503360608401614f4d565b606082015261504460808401614f4d565b60808201525092915050565b600080600060e0848603121561506557600080fd5b833561507081614c22565b9250614ec28560208601614fc5565b600080600080600080600080610120898b03121561509c57600080fd5b883597506150ad8a60208b01614c47565b965060c08901356001600160401b03808211156150c957600080fd5b6150d58c838d01614c5f565b909850965060e08b01359150808211156150ee57600080fd5b6150fa8c838d01614c5f565b90965094506101008b013591508082111561511457600080fd5b506151218b828c01614c5f565b999c989b5096995094979396929594505050565b600080600080600080600080610120898b03121561515257600080fd5b61515c8a8a614c47565b975060a08901356001600160401b038082111561517857600080fd5b6151848c838d01614c5f565b909950975060c08b013591508082111561519d57600080fd5b6151a98c838d01614c5f565b909750955060e08b01359150808211156151c257600080fd5b506151cf8b828c01614c5f565b9094509250506101008901356151e481614c22565b809150509295985092959890939650565b60008083601f84011261520757600080fd5b5081356001600160401b0381111561521e57600080fd5b602083019150836020828501011115614ca357600080fd5b60008060008060008060008060c0898b03121561525257600080fd5b88359750602089013561526481614c22565b9650604089013595506060890135945060808901356001600160401b038082111561528e57600080fd5b61529a8c838d01614c5f565b909650945060a08b01359150808211156152b357600080fd5b506151218b828c016151f5565b600080600080606085870312156152d657600080fd5b843593506020850135925060408501356001600160401b038111156152fa57600080fd5b615306878288016151f5565b95989497509550505050565b6020808252600e908201526d149bdd5d194e88195e1c1a5c995960921b604082015260600190565b600060a0828403121561534c57600080fd5b60405160a081018181106001600160401b038211171561537c57634e487b7160e01b600052604160045260246000fd5b60405282358152602083013561539181614c22565b806020830152506040830135604082015260608301356060820152608083013560808201528091505092915050565b634e487b7160e01b600052603260045260246000fd5b6000823561013e198336030181126153ed57600080fd5b9190910192915050565b6000808335601e1984360301811261540e57600080fd5b83016020810192503590506001600160401b0381111561542d57600080fd5b803603821315614ca357600080fd5b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b600081518084526020808501945080840160005b8381101561549557815187529582019590820190600101615479565b509495945050505050565b60408082528181018490526000906060808401600587901b850182018885805b8a8110156155af57888403605f190185528235368d900361013e190181126154e6578283fd5b8c018035855261014060206154fc818401614c37565b6001600160a01b031681880152615514838b01614c37565b6001600160a01b03168a88015261552c838a01614c37565b6001600160a01b0316898801526080838101359088015260a0808401359088015260c0808401359088015260e0808401359088015261010061556f818501614f4d565b151590880152610120615584848201856153f7565b945083828a0152615598848a01868361543c565b9983019998505050949094019350506001016154c0565b50505085810360208701526147c08188615465565b600060a082840312156155d657600080fd5b610efa8383614fc5565b60005b838110156155fb5781810151838201526020016155e3565b50506000910152565b60208152600082518060208401526156238160408501602087016155e0565b601f01601f19169190910160400192915050565b6020808252600d908201526c6f6e6c79207072696f7269747960981b604082015260600190565b6000806040838503121561567157600080fd5b825161567c81614c22565b602084015190925061568d81614c22565b809150509250929050565b6020808252600e908201526d6e6f74206164647265737328302960901b604082015260600190565b6000602082840312156156d257600080fd5b5051919050565b634e487b7160e01b600052601160045260246000fd5b8082028115828204841417612bdc57612bdc6156d9565b60008261572357634e487b7160e01b600052601260045260246000fd5b500490565b80820180821115612bdc57612bdc6156d9565b6000808335601e1984360301811261575257600080fd5b8301803591506001600160401b0382111561576c57600080fd5b6020019150600581901b3603821315614ca357600080fd5b81810381811115612bdc57612bdc6156d9565b6001600160a01b039485168152928416602084015292166040820152606081019190915260800190565b6001600160e01b031984168152818360048301376000910160040190815292915050565b600082516153ed8184602087016155e0565b60008235609e198336030181126153ed57600080fd5b6000808335601e1984360301811261582457600080fd5b8301803591506001600160401b0382111561583e57600080fd5b602001915036819003821315614ca357600080fd5b600083516158658184602088016155e0565b6508ae4e4dee4560d31b90830190815283516158888160068401602088016155e0565b602960f81b60069290910191820152600701949350505050565b600083516158b48184602088016155e0565b650a0c2dcd2c6560d31b90830190815283516158888160068401602088016155e0565b600083516158e98184602088016155e0565b670aadcd6dcdeeedc560c31b908301908152835161590e8160088401602088016155e0565b602960f81b60089290910191820152600901949350505050565b6001600160a01b03858116825284166020820152606060408201819052600090610c26908301848661543c565b60006020828403121561596757600080fd5b8151610efa81614c22565b80516001600160701b0381168114614c4257600080fd5b60008060006060848603121561599e57600080fd5b6159a784615972565b92506159b560208501615972565b9150604084015163ffffffff811681146159ce57600080fd5b809150509250925092565b6000600182016159eb576159eb6156d9565b506001019056fe000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc21bb43f2da90e35f7b0cf38521ca95a49e68eb42fac49924930a5bd73cdf7576c7724394874fdd8ad13292ec739b441f85c6559f10dc4141b8d4c0fa4cbf55bdba264697066735822122086de79ead0fb270f5ba155e53602dfc8ed82c71fdd9d09db0688152e77f6d3ff64736f6c634300081100330058210390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563054d01b673cfc6441b0ab383327afaf3ad1019455170058210310e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6041010219080405581d0222a6136796a4543993032dac350fd03a38b21b9171733dc96a647ab90701195a8805581d02213db77e19bf46a10eac2cc934eb93da061b24922305d34507e19ee704010219040105581e03bba256d6d66ad6a711d3b9d82772be67a8ae407e1ef7a71ac166da7fe00c0147024d62d7fa523b03c29ba103680f2a5969c810900a54d65ba114eed252f3ae79dfc831b53274327b039e570bb9c762292ffd1b2f83ea25d6e68fe1bbb49af4b1150d02da3b75be4a5c03ccbc414f180ccbae836c4938ca3a85fbd0d4e397e809418d8f7cc83090b1293e0219e70e03602be9dd1fac2a83b31fba97e30e5b2a5a50938e05d7481aca2b31bc07b7892203709c2e28c97713ea6148c8e927c4722871ffd908dac25a47d178dafa57d663a3039b5ba9e59aa720b27edbc05b9fa3e64efbdb55af554492526bcda64782150e65030d51868fa67dee90cf565fa6c40af4d7158076f082e1bedf7e945875a6ee141903266159f6836de0ad1682089250b2bbc8e74dc1cf6cd35b400b4b1c9c62e76e5703cbc34d9bf51c51134a309f40826de0df7032c45eb8b284b0834c98b5f1d5bd450219ffff0392d8e422ecb7684fc0a26c8435b32b7ac8ddbbaf7fe98e791574fc0a903de0a203d6f67c08eb0c6a50267b76d5462de5a980dd1f3e2f57d19626b502a2ce0c2ef10219ffff03232c4dbea54b335fb249e78d8e339ee85b5b27e73593ebb863e5651b0e7bb755032ca38a9db335d0c2c4ed2548cd13f6c82ab82a3091244015576d92565b9f7cf203aa476c3c11097a9f3b1f966d4de9099ba3f12116d1c3ccd06de020aa1c2bae110329c2bdb46590464736286542b8ebc13d6f0bfa783e014cc0e5ea0f4d87790a4c03a3f3ec44330da199cc9aee219147025a467019432243870c18c5ee3a0a9da9a50391c7829d9771305fbfb77ece2bf188feaeffe9f6f198c7a4517f4636034e19190350e1a621487b30bb20f5e7bd90109c9157056e364528f3d1b16a315ed83ad6b20383dc8af67a6a9fb6f4c8c203391c79253cad71f068f42fde63ac37c162e5014c038081a43121e0d7b60a3152555f57f301c2c5f57656bace9338c310a2e86c78ae03574da74d6a833d7768a0699f1f2da04ef8d8719b3625097d1b78feab812d2f6a03f547d3c32ba3662ae6e98ba4c148ef9796f6086256b59d4536d2e3f3182249cd0352fccf30626acff42e3942ff6c08910f20d6e0c332bf16a59455a3c6f2bba4f40219ffff03257f83834752baa02f3f0cb8a3c127ce5990bb64efe47f5876edfaec654fd5ce036ec5c147d9e3ea880d313758aeaefd7c14bc5cf3a0735744eb03c0b79829338f03b0878c3343b0ea28fabda80e52b8a60185ae44167f2413d43d60def3d092da7f0356b562989d209ee78cef362c80528dd149be50b28f83915d23ce8df6d49a5ebe0219ffff0349ce97e9c2b02020f71de457b4fd73bf80676d90249dde7c38fa347917973cd203154fbe70ddf0efa59c72a9c305741f33914e799eb71984f2590c8d0c9b40625a03cd3c0bbd7b429a7fd430c125504797013a14d24156388a797958bb66eddfa55a039e6aed648d2d6c2ac8bd1eae86119a674e5186f7e5046042d5eecf015f42addc03518c12343b8ce2f24450df0350faee263c805d516ed72cbf2b02d0ac48619312036e729dd98f05ed4b1fa191fb7b6c402fc70538e1868b08d47a5b042d8ec9ddb303cfe7605319cb8c5f88ef238690a169e2edf5b10a4c6727b1324b6b2d3c411eca038838c5e639b37254066a4ce06c6459fba5c21b4020330439b95aef88948cd6250379628250c9072cd159158f8e51d42319612854bdea7608694adb730e09d52fa203ae4a8761d78258107512f30a2038c37858c50c59f07f74a749c8f5a48f100a8303c6b21bad4fb2594c31f5db1d16b8f056089fd1a43bafb6c32018580c2c5f691e039a42a31278925aaab12da31f2fe2e0bfe38fd576e3c3cd12bc48b8d756baadcf031fdfba3891b08eee159846009c73163245e8e6f732ff819e16f612beb3ac7166035ce9ed1903b425c597ba87be016cc317017822098755e8cb8ab07d79b039270803727cae139dce2cdcf0e2d49f929e06ccbfad5ece5241525246e471f015c60a8503f7d2c336ddebd273185925765dab08951b9fd55635b55a8a5c2f75315e0fcdcc032ce120e4ce4c254f358fe3ad34db1b6c566870d8a1dd0cca3a72faa2813c230803b88cd32743aeb99106a87a77fc3b9a821015233292573cde2a4659c02368610803fc92c7bfc1671932a11b81e7c44c059986429415e7f9fcc249dcebb8bc59fa2203d6698ad8ba52a3106b99fb545526633e05ffc5c406f25c040c8b37b3386281f303629c5990bf3764aeb1768b8a1f8b71d1e4720c81f00ae4dcfecfb7b4bff1fd81033b453516a9ba5391419658b076b9b1092f4819ececbff53cec68060254c08b68036adc78301061192388d0ba67258acb37867d27e1c9a664ee93117ebd90e8e09603d5d9983329836384c7deaf218ac2f73894ee0a81a4eb63952cd14425827d7120033da8de781a274bd61312c7586c666e2d1c889276b29deb1dc7969ef35de2365103f0863f63f355d774c34cbb4a245511f79fac3f32ee99a2aa71b2ce440d7016f6038d8a5628e350b5118482dc6b2d351ec67b0a76266094520b7d750ca2eeff938003f0bd82a1cd66dbe912fc4a096ab0c83777201a4d6b9ace21dbce76afccb7ad2505581e03742b224a6edcb0a9f129ed15434b0b7b5408f082172deed7dc98e207b0040405581e03e4ca577fa2ceec4a0e2749e1067e15e86f5663f9e98c8145b75299eb200c05470162271a39f47805581e03b19f14fc0fc0ea8e5f25219a175153b2f664f557172f1784203f51e0f00412030a4c1f9f9eef03f672308acf0a75b362600c8ab4c7fc4658adec29bc3e161a1103d25ede967d060a280ad631b35ff60a7fc955602ac67a8349dfe7bb97e2ad09be03303931f8ef9df9270c30e0ac139957f2641beba9374ab0f098cd92ffad96d5cc039b742f21ee655c278b19615fdd86646d780cb6ee8868d9f74ec47eefdfdfc99205581e039069a7bd827b1274fe127505b670752269dad387fa9dd70f294014367007011bffffffffffffffff05581e0387fa54e0b582e59e4f9e1b6732c83d747ddb4c8034997c058c92da15c00c01470bbbaa940efbbd05581e03634ec721d2f5bbc50cff2667067a75ea2c52ce0b48947f42ce740e19b00847b1a2bc2ec5000005581e0350aa6615051b1f0e68f9dff8f78b938b489f07ffcc35acf0883fd4ad600c0146f87a35f5cdc0031659de68e6a279da28599e113109376d848bf72a54b56d945750294b8c97e773030657e508cc6d404d8cc89513d451fab06b3288013a5753906560d9cbed22571f05581e037ea481148d8073f5fd85415b3199575687070ac2add9de08b0550ba57007011bffffffffffffffff05581d0234b476ab74923dfbb6d9f870185bc95b906637066cbe90ef926995fd0c014711dd178485ea4405581d028e66375e8296cf37782b3f6253b0d09f95d8e941dddb38d66cf4a54a0c1825480abaf0a0561af54c0219480002195fab036214cc2422b5238d998607365f6ba91c81041b1b1594798bbe2c40b88e6065d3038161b27953d99ac839e6dec507d3a54643fc5fdd7d55bb14f66b53ce2be7279503a683dd096527ead19dd5097f3f5f171b3f3e5ae41c38900d2d641ccf0ca5ef3d03d73f1917f1afdc3d6b8c7be40146d3c6b9202e953eb1a7b965c03c8a49a2477403217e20e54f67c98f1b3685e7135ad86cadde7182b87f23c3db1a26445f5fcfeb0300b5e71df20a3fad30060a835062aa03cb56d6a8f37d05020345a548ca34e42103be2841aab895ca6de6f1a8027195a3470d8ca776ab1ea3b2049ac3451a7f4a0d0219ffff03f619dfca387897d0cc58cf42ebff0919a83dbb7792e77f004f0f69a549b3f228034edef06c66a69b4d26433a008c58f6815086323fed87e6d970271d7cbbbc1ce903c36b426ed13b2175a2718139841b8f93d50e4a59f04bc4cc5b83d745bd9aa50403a29d8a0054b4ba9c9dcaf39040bb5e0b75ba65679cbde316722c1719b404db5c03b0822101606180f9b736f72a8b509eb72e6d03de220dd4d8a9ee04c91dff9ea703f110526b7cbff2ab1b18e41d13d77d8340444851bc0e3881dc82cb3542b0ee91036ad4e077c3790da5135c678eac503a4ba998fbdfb3c868bccf5d8d38af5e4a9f03ee9d74c2977e644a27d084e82379a24210e5f25bbd899676a9516b5c805663d603f431f9e9c1b51abfa3ae4297e108efb5a2659a471276403c92bf39ac3f0c877f0219ffff0376345ae39eead2478f982585513e5e1d3ec046667a3ecb7125f021af070634280380ade4c28985b1a4d643f0ad055e38875d78857f0c37d7286010fd9c71b94b1803f93fbb74647c68b924d3646a382287da851a215f2b23384859fbe69ae00d8e53038b629f5765d9bde88284fe11a5d2093a999c3e50121d2a6a14d0c7c639874db20359565e3c218af56fa85b5386ba9d2d82d0bb4612a9be37c4d4dff6ec2eafbfd103f9154e2b7fe7ca97a80ced229c533c2a5557c99a25ff5eaf93603b9558444164031180b35c7bd750a709897a1652b8b3245be6ab2cc56fabc5d8219c24b391f8320361d9250b4872c1050da3945662e41a90a4af1d332b434b89164e005945470c57039882f0851f2f693e63987b84049a8b0feb09fb13cfe9d0548270d8ccb0dc839003986e9c704eac18a88b1a61dd5b2d69a6c1bef7bfedf9a4afb0470471a648329d038e116b8bf6289bce1d0fdc2e153f4257bda89f163e52b39cf11e499b5a2fa9e30346fcf93affeb9a54246f56918c7ec7830f668e1c02d8895be615399f4f1de5c903cf1f9544a9fd15022b6e6fa9c19ce328b05d04c094fcf478ee7c69ad12a8507403c2068113db16d9add34b850a02da9f2f1825b5c0ffe522fdaa4aae533aa761490219ffff03d6e440812ff7521a1cdc4db57d5daa75f7474f7a30d6d523d06bf2716d914013031ffc65b58502f0f29d80e56074e90e194dc3f3466269fa4e85dcd92e4926bb9903da7ed97ce37b93329ecf410f40bc4c42f125be5b51a3bd4e74649854ff1be5310371b29916e622ab02a09862e73312899b7436a21d1b187ef305262cd36951285103db26966e425cceecede9e760a41fe08e2aa19924fe20dbdd8b2c0649a1f2dd75036951be3a029c12811c8cf4a5c435d0ca0cb751fbe98c1ad35d31fff00c41a68303361af053e1aac4677ae269ce65b9762b28a84eb85eeb089ac3bd93c2a089df3803f2b2c8fe553497f6caf0ee03cd1b8e20063c87119bee941f51c724ad59eec2f803921f49412261702f3e24801557774fc1c0de3226e51a2da3b90b56db171a7c140314339afa49561622f3a222ca2060b173d46ca618e0f1ef1f7638b56445c453d703832d72c0852c2b424c737865182109ef00e4e4adc8f4e80789ebef7bf2a9eda403d55279bce2eef5a1ff56c08b8cf79b34d129c2f2b654a48b46de529c46ee3c950219ffff0219ffff0323e9399d30b73eefed7059d98eb2a883d525ab0f400a0b7fedc801117b00cbdb03a8423c0aa3924b34931b35f5153e0f26c2f63549484b78075badc5133173c103039068d569aa9f5b035b9d960999fee29f6174dedddfd9ea528b7f02f545921ee9035dd93f54e96e3319a9b679afedcfb96171cc269301334688fd068f6eb44766970321786df8391fbc0105cae1b1bea6e8d57d7631e87ab87ceacf6cf8ad8818c24c0313465819dea42d0d17ffcf44850c6196412ce9aa17226e149baecf43d0dc86c103b10ac732b3621366730ee74840659b5ec6c067b4543e51fd5d02e9fd4239804503f1d891b68ce222bf8ece9d28aedf2b86412bd5afede74d74d61c62d0f79603c4030b0fed4ecf9edd189f63fb3c58bcf9eaa818b14853e4b41e4a14306eafae9c7b033ee47e9f4db8e86a475bf0e50febfbd9686f58cdcb677f7ec2bfc55f8f80205503eaa17d33a872a4a0262c6227e9b96f1c219c8fb834531b5a9e0f43a7f26aea14032c28915b8bc9af66c7ac778b85ec27d69bd4ee1e52d98f7dec2bac3bb79a6bf503da64b91fd87b3d7a3747ea0bfc4022236d20fba7da1c5f0837a6158d75927c22037c6578681cf86c872ca1eb809b50139d91f642c59eb4b2628297b3cf3c724fa003b18f54015abcefb3ce1f273d5adefcc1482bf9722841544f2f600c7b0b23e1bc03af00583ecf31d24a169f26061d68808fbfb4cd16f170b3444933f0ba58a7cf2a0353806ce8bb7f52271afbe0b1b7a8020655b4239bbe37f7f92e189ef1fdd19ddc03044f14c3e292b6afbab7343c4f4b0c6df56e0a17e5fac6132e7f068e0df93bf303cd822db56b3ccc0172e6eb0b4a8746026937afaff23afb59585fbe61f3df04da0381b633ab987db860eb2400ada1d7ee5ac92d39e37819ed4f50a426df4791d8510303a057b36a5455eeffa44c26dcfaaa4e999db79c12b0db9d20831bcad7c9cd38039852b8665d2a1e54578448c498eacccd15209b3ad8502c5160ae6655a4e4dd3a03f979a240acd281a7d1b3fa8c270ca3e196b307d6ef5d7340e73d6594e9ca79c30365e5952232f1399cd5f18a2a5863994ad529a0781b96eb41910c554ad021b666030fe28cc9031498cfe367d9e4a5337390ddc9815242bcfcad7494e5101443de010391d46d87a15aa9288ca0299d65048b2a752523a5146cc1f10195dced776620b803625b2c5a8f0e05e2123897ece5a951f8a5fe4e8afa822e935d265982cb09958a03855a93f294bd1325d5bf38f68a9bfb9a651f2749cc54b3fdd9bb58e0095d9936032b13b5d4b69480325b9d0358b71d027767816ca0b64354a271b1778bf43f806303fcc90e4ae3d374e2b9311e2ea551c2f610d26d1f3897694d16e81aeff5301edb037f8c9057fc832a2298bd992b22b7f5ef238b46d521751b81de5fb3105dbd58250355f1996e75df98cfb8ab9c4505a49944be23d45c3e4f9e2621285f3debbe96cf032f9a959730ce118a9ca97f8f98ce9d1b5f125dd13a332d3166911a48906f0f7803248cf22050293cd74a31cd77bcbc615fc48780e5127e4db90f561c2fe5ade88c036f5c0728eda9f97cbbc16a6115586eaed85dbcbed48390c4f2c607b1946133e803f930756eab0a581728fcaf851943828e16798fc52107aa6407e16ee43509d2d0034e31bbf7855bacb924329ec5e58c715559ba6aa408702a73ac850a2e9410c7d205581e0312036d9faa424ceab1ca58eb6d82293145300d480bd295e609f9d40000040105581e030d144d1690a0587d192755c76f7226e0c103d0d777191cdb9d68233c200c02477cdd42821dc0d105581e03537c5035b8dc527f66b8c82f050460805b210bf9637652831df5f70f700c01468a4f3539580005581e03565b4df2c5957476d2006aac2d1c5c41e343b98ef339abc02c4098fcd0040a0399c99f1a6d65a9097e0f8ca61683878ed26099e347359f5cdc600bd194dbe908039188efad72c6747c1e7f1e19036c430f6c4291fece213788dcd97857028c1b5e05581e039c512f91e21f13ebca1e24419617e8054943a56245eb3ee3ba050d533007011bffffffffffffffff05581e03058602b1552e19ad96578f935466a24f25fc14b8709982b7e8cc1e09700402031b460c826a854d61dca82f718e088b8b4c4082ffeb93752d7691bc62c51dc0280605581e03f69d4cd590481a229ffea933ae23c87ffd52e58dab86e024a8beb852d007011bffffffffffffffff03ce33220d5c7f0d09d75ceff76c05863c5e7d6e801c70dfe7d5d45d4c44e806540306b487d15c028b6df56c3ebb9b7086965eba3a240857a647faece2ff13269f2b05581e03fa7069cd405b5b8c7800fff1718aa9a388c02157288dfba2776a26ab1007011bffffffffffffffff03224ac66d1d1839d1b253d6323595fe5dedb6dc1cee7e965d8d5a6e0ecc207fcf045904cd6080604052600436106100225760003560e01c8063972fdd261461013e57610029565b3661002957005b6000610075600080368080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929392505063ffffffff610174169050565b90506000610082826101c0565b905073ffffffffffffffffffffffffffffffffffffffff81166100b0576100b06100ab83610219565b6102c4565b600060608273ffffffffffffffffffffffffffffffffffffffff166000366040516100dc9291906103ee565b600060405180830381855af49150503d8060008114610117576040519150601f19603f3d011682016040523d82523d6000602084013e61011c565b606091505b50915091508161012f5761012f816102c4565b610138816102cc565b50505050005b34801561014a57600080fd5b5061015e6101593660046103a7565b6101c0565b60405161016b9190610427565b60405180910390f35b60008160040183511015610195576101956100ab6003855185600401610302565b5001602001517fffffffff000000000000000000000000000000000000000000000000000000001690565b60006101ca6102d4565b7fffffffff0000000000000000000000000000000000000000000000000000000092909216600090815260209290925250604090205473ffffffffffffffffffffffffffffffffffffffff1690565b6060604051610227906103fe565b6040518091039020826040516024016102409190610448565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909316929092179091529050919050565b805160208201fd5b805160208201f35b6000806102e160006102e7565b92915050565b600060808260048111156102f757fe5b600101901b92915050565b6060632800659560e01b84848460405160240161032193929190610475565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009093169290921790915290509392505050565b6000602082840312156103b8578081fd5b81357fffffffff00000000000000000000000000000000000000000000000000000000811681146103e7578182fd5b9392505050565b6000828483379101908152919050565b7f4e6f74496d706c656d656e7465644572726f72286279746573342900000000008152601b0190565b73ffffffffffffffffffffffffffffffffffffffff91909116815260200190565b7fffffffff0000000000000000000000000000000000000000000000000000000091909116815260200190565b606081016008851061048357fe5b93815260208101929092526040909101529056fea2646970667358221220266f45dcc0f640b86b617d19d2affc31f4b5547fd56bc2761d8eddc74675491564736f6c6343000608003303b0ce7aa66408c3e7c3cb5ad2b76775e06bec75a1b6d5386510071db70eb0437a036a3f3d9ba908faf011efee911b9495d4403d775e868f6e4b4b5348c7d5c0d06e03fea5236bcdfdfc14af4f01ca11cc002f5651d11990cde1c39f2587c0dfa06f4703e7d7d230b79241501de03646b5a613ea9c978ad5840e616ccb707a3950c4de0d03764aad7aca29b0b2b32170434f282b68bb95f1f16d44c395bf689604cef9360f033fade95b5922dcfca63297218058b9c75281c0b11e471fb585b9cf5364810914034e581e3ae3e2a0586ae6aef7ea845857b7dd6847d7275063b1a9c7aac271cd2003fb9a67888c547e81c8337bac148c4a1271d5648f5e168e281a3ee0e8ccca2a21033e4751e3a41b77ddfa5a1d15fd330853739815e3ebd264af468b8c628121236203fe1a116115b375e9c9f8b2afe20a07290d8946cf6366b22a7c159304c2fdbead0340a140437e792074fc9e1cb7e999def3022b9573c545c106d7198c74dacc6998034d3593c3d462a09128e25203f283583e0325860b6cbdf6a51ad8dc956ad833dc0377ce61b0981afdc148ecd4e2d070bee088cfbae1dd2af355e003e92b7cb9ef4e037710344a6afba5117bca6533ce620550625bc0796ea7190fb6860f59746aa41503d34eb08b660b2bd524ce8d3ab784790d90ab0a918335098becb59fb3ff9f025e00581f03ef48a3fb52a11df1c03ea5c7051fb734ebebb68fcdd31deca84afb9a658044651d86930370dba42a7fe66539f010324b2f313a19edf4cd799d08ef9b6ebf4a41bdbd948700581f037bf2db81923b8f5b02dd0daf5927e9d9080e51874aa01594f6f09c31181044650f01270323ca7cf3546296a9ab2571400d830b382f7c1b60b409b70817f04d0f9896786500581f0311ae6b56fd6e9b2f627611bb223f25a2b60883eab900f7d0086ef7b9a410478000000000000000581f034db5135beadada975c08ba9087c860849f9385f41ef363afceccdafb77804904d9cc0dcc16ae42d00394fa14a6e5c3e25d76597089442f9a56955b9a4a0099041eb5e9a82821147fc600581f03e9e3f2883729911d62e713a21e677a2e3735a59e24490a5eb3f7a73229804464d8b8bf00581f033a817c3e25e3a65a760c8a4281bc1be71818c9b025564ef05408cc3d7ca0450129bbcfa503469612a97156b3c3a9e00fb050f1f2a9598f8abfb1bb1098c5863f5c9f6c5dac00581f032e3768645bad16ed4f9c612df5f874db229ec7803c44b347d6791306cd904915af1d78b58c40000000581f0370f9d7c131062107c4f9a83b02773a52294fd54e2b7eac591579cb9adbe04803da0e412568e000037e8f9280a145199c0b08889b13de9ff893850de6667ed0fe4401c3c5f7ee8ab900581f0306208e5be2d375302728d7c1b7b0b07da01ef1c8c7bae4ded935b76fe770540e992c001e375785846eeb9cd69411b53f30f24b0219febf0309501aa967736a5c0b37887b0ca835b7d9be92538669ae6caf195dace5a893a9033d6086c99427fe19e85de8782c3723f97b7d5f6f5712a3055f5dc2d2bcd7c1e8039be4ec0b15f32f121f6b347e0730ab0ab1aadba74e246f9450ca9daf24499d3803ebdc2a749844963f78344065362320f763efa87cbfcff0c5a5ffa647bd7647f30315092680895a68050316717f2fa31af0feb15b385321a6df73ffc25e166edf7203ef587a81463cc6ad7befa70bae1a566ac5833f5282cb71b8e5a0d5fcd4189c5603a53956cafec2d4c319c7a30c5a55b30f7e0013c7d39cbf3a85808575d2720f3503da52aed619e5934bc3ea0c6ca4fcbfef2f2af2830355c348da5059fad4024eda035326791fb2c6a73c6423dc5960dfb8e168c8f6fef2b8c805c44ae912ac98f5cc03fa236c4645da7d2747ce04befa6986d171dde3afa9e3e9d470da986218907b240219ffff03935b913becd96efcdb38b965666c2f507106373f5b2fbb4d8f7d477957f6b8da0337d0f8bd289ac087fa9f78ba720886b8217879fcbc60223f151ead6203f4585d036940e73578730a5078b3c98db6c9a6b316f5596ad7f9d398b3ef458b234f2acf03a2fedab75a7d89211c801a559c98044113e493eb11bc60dd286e5ddf5a6eab2203bff6bf01af5b424ca85f58424fa607212a10c82455f79079f3f0dfd1074fa3de03e2a67fc965767aee64d5496d389c3bc5f6398e609cec95bca7078ef9b8958c3903b9aaaf64b71b370a82b79174d57bb0aa4c8076a9c5619e23c25933f06fbdeee803fb04c5b408521c5c5e2ca0376b7d6974058bde92eb13f60c0676a263f553b87c0338fb7e5e12ded506211f8c670e4c1b52476051ba1a9a33a03bc9de8ab921c53f03b08ce4a6d966ab2f78fa16791aaad71cfbac083ac853fa572fcb227a001f8aab0219ffff036691c7380cd6b4c48eb70c9a20f73dc82e8a116f5368173145f6984b870b9f3403639ccab01467513cf5ac3bd0e7568b6869d4be6516fa97b47c244f802e914332037bb429e3b830c49c9ec71284eb611ca557d16cfdf2f0b8a1f435842163c268790308c62167dac8e1d6a35d4fce8b31a2860f32f40ef94ced1a48d6b9d171ee415e03250301ae6a8055b15b5513e3222a78a64c3adf5b45273c96f7adf0a3ed6eb24103968c38445fe34521242de5c0beaa6226780a4a06c4ec681af4f541580a969310031ac86c0f0d51abe16d33b74bdf374247c70dbb30eaf329f7e1652a6eaa5adbcb03ef4f5bbdd303df6a8ae454912eca5b8bf0cd48a8164861c0a46934cb2c21976d03a686bd137bcf4fdd06b47b9855d2dd7563dab07c154e35fa5648d1b621bcc4cb03f90b13a07201d96a03850a1d9b0d0e261cdba9a8689d6c0b6cc7c54cdab68fc103ed976c5386acbe7ff4ce6b7fbf98f35718c698aebab41bf2ecae23e7d05c45dd0219ffff0382ffe01ff71d2f482d053c3b4a04328e398f13ac820250a050529c079de34cf40325e587640dcbe55ee70a48e35d923b19890b7f770f5869474032981159fefa600375cd4b16bf931ef16a9eeb86b1cf0c187ec70d4d6df990d234d62dde7497aba603746e02d9144955ff6031c708770c4ca0ac5a227345154a47c083aac1fe4df26a0314d111b847141a125fe83ee3c2c4683a6ce355fad8a6b736b9a0139910b6b20203ca4b784c21ec8ea78d95c07e063165aab1827c385003575330458c930835ffc8032fee87bb5bef0335bd9673be6a30c090c7809ddff527b63bf52b13bd7df762bf034bf125a75ce551e89859c492021d8bb0fc21acf3a0e71658cb63707e92cc48aa03e0b4af1c012469191b6e4527ed435ff166dcf0a18188a797a0a573cc6adf55a3036d4b9d07fe6e1faeba3f963553cc5f3edfce90b8f1d44671a3077c88c3fe47be030078ab74644281326df8fbfb042134616ba46b2bd415eea8050dffbed288eda203302cd5879977d39371dbaf9b63c1134d4ed847e98bd3253b509cd4254d0500ae03838de2ae1baf946091f7178d4dd5c229c299e3fb4a518716394a5e00b7e225b9033e91f0a7357fa3b16ce59e13851da289f6f957a1aac19092cc2dea3e9ce282bc0343ef56c10b89611752452c9d0e6eb47e22259faed6dfe85f0f80d0fa80be7ae6034174cca3fb7056b58a09531db1e2eaf8078cef843dc1d906e2e119386114f08f0383e6c2c18f3788b74b7ce38a011127b0205015db2d8f4932b55a91c0245e082e0307038aad54071664dcf1905d50ec04e0f39c249e6063ae950c8332afdcd35616037d75f916da953425db4f36a311c9fe999cd29474de591edcd692876287d59bb20392666def05af80478a07277c32eb9b0528c0ee847e189fd65b3f068a4921faee0359a6b7facee3e4fb70ddbccc1ce142d6171fe55ae4cbf204419869eb964c7d84031a62c74fd653731a52f9e927033b5379d36877033d98fcc9d52d927ab3c717db0355b3559bd8a26f1d5c51e17894b3883c2590dbff4b4530a6511309b9a11486fe03633b4fe4206fd81e661cf112844e48856de446033e8144b23368abe9a7946aa2032529c989a41cbbeceba4847c601bc84d6ca0cab9910fcfd684c88ec21bd125ed039463ce90a8f70cbdabc4e3ec1a4d38a3f209aec2d3b6525e79fcccf40b93f31a035d3dab3bbdb4a5884db37c747cdf27d15110a542d50db8933e73883600eca22203c995099d916a943b6e190ca082d50ddf6aac3494af7a1e454d5732a6f9f79b0203eb88687f58295ac3ca65828c179424443dc8737398a283e3bf251af101f699f00388fc8f3ed60085dd651eebac041347788bd91e8d694184d1dc3797dd7145be4d03c04cf7fe8a0382d34a5ed517e8567177c7597dc3dc129c33e7a32a6afb78977f03ccbef0ff94b8039c1ef95f5a779c52c65545b98cd5aea65e082d702a368647a903ceeee71c9636aed7ea0eae407563d4ac6a0aae332442fb2353edf0d4f9519fbf03d1dbb275ba6cb7847f0b2623b1435a35eac5b75d28f00a58a7c42dff523a7acc03121bdf8dfcc2d5ba70cf575785f5f72cb8324fd06919510c2ae257b377f1d0ed03e151c21d3d092120380d3353dee89e9e2c93e29b781b554c74363de9e7c656e6038f67d49ee74e465e309e3bfd6da15543b1f2aee598077604d9ec8af38cc8fc0b03e94d98f37b2095a469af358578365f0a56b997bb7303dd0e42cba247f59ffa6703d396137cd4d892ce8dac00d33e02c6cca3e65eb5f573b710cb4b54b0d1558db303ec9cf0df3dfe42a5de0a4357709ffed25311ed0e010281ce1bbfb8403c5e3f3f00581f0354be5236b9fac774814b48ede6d10fdeeba4bf92e0505d2af4541d57f7f0482447c78effe6720000581f0371681d4b4dc0b85ad0d65eb3f72ca54ee9cd025fb17670ea469877cf9540488ac7230489e8000003081cd9eaad2e17f42b9093bfbee07362be69a76a0e9074ffb6664f51a5935f8300581f03585f6099e8d1fd8b63b2f60c29aee197ab900e201f27ca4cf063bd99bd904464310c2f0371a230fcaa184372107e8c14b23b8b0c2afa378c33c1b072a00a90d88eed7cb0033bf5e5f8f27c256c19e6697ee09f9907b709816eb2112cc087570e5936f1802c00581f032ed27c54b87de968e370534783582e249679e93fa7ad68b3f79be12ed3e045694fb3456d00581e028618dae0befd3457f6aaba9e53df1861ec7833ae7ef9409e2468a303d3540e992c001e375785846eeb9cd69411b53f30f24b00581e02fcccb0219c51184bae6d7605cbced62226204abce5fd1e67807f7efcc848c39a3a66a988664c021908400347840a15429732620a2f50feed7b6a75c929594cd0a9a4336fd7526867cd311f02197cee034b15000bfb3753651013aad17ecf138ce71112c9ad8dacd3d69155edde5d225803f9a574498ed90510981724e50853a5fc8a27a81847abfdfad66db1e1899d4e0b03e7d6daf5644fab0c80b70d1b168699e391884ac64cd03c918b9bc95bbd15eb8d03abd9c5830e69719bfdf089af61be70241a4cac54a4587bd2955896185c59fc7703253aeb97303f761c6d5e6baa695bc7766d65f457604ee6a69ea0f6595a1e05070352a7bfc93ce4ddff689b6cf7a84f06a6612bfcadaa04dcb80d22807c28d2b46503934f7bc4fc38980c98259b6afd7263bc8ff3de945fe148069ffbcf83ccfc0762039b3f9cf6bab7ff6af7c282f7fb39e8f81473a8a8dc5ad86ff9bf794ce085bf870219ffff03b2e89a7983e51892267dc0706b4d8bc50a7427b3405f202b01058fd4d6a71a3e032d8b1450309f2dd2ead1d92d5ad3dff017023cd948ba9e1013b943d4994bd908035a1976fe882aa4438566a528be1e4c5f11a0e2a73efcc9623d1bea6a317e2dfe0219ffff03acee623287562deb59489922493adbb5917b5fbc6027bc84bb55f43df7f0eabb03e6ace9270deb37b6338a2a011dc674b37b27c4736a3906b5c943cc9f5be15e870302b795b684a11dfe1eb4a375e4241e1d990580a0f8397113f177543a60251f1f03feda25294423f4db79e8ce8e274770649eb2cf89186aae275c29d0f93da76f870219ffff0355ff479beb067a17a188b8e1e95c680cba012f02e60a063fdb994e62345f63a203cca27c0a3508a98f0b3174b6de7faf401a8e76b7cb5cee9e9d9769dd3fe7104803b123fbefd07f62885e9b6355251e12a3480f77591c29db11e8c846babbf1a10a03a219dc14973ac784cbd20251fa31e2ecf432d905ce4c6b0860b9f5ff9290e3be037063292cbc30c2367c78c56f27ff163569fcbb0acb3c803f573cd988f61dea510219ffff05581e0376206f0ba778b536f8b5c5571b9cf6ee4279b8082d1545b9c5dc0b90700f044804d2f05f318bb64c1904cd0219f23e030356fb2ec062e1cac458b921fbccbb3f4d3e48c2378b52e2e7ad0bb69b841d93039db634b3587d75d5bcedd67164e02fb80e249876ee8ccb7d819da510daa8800d03dbbd8a3f5b6df75b02b6cfa04c5f3244530dfc6108c90878f96cf44d6b3d133303fb497226c81384cb04f0cec78c93195a3cda400a7fefb13d310fbcfee70e2989036ed959e1743ae41cc34259baae5586176885e7eedf1452848f99b8f3b2fb52e903f33c1db47d18c2ab2e22b62c88f06c74359cf0a56ca85f0ed7103ba68be169da035900fb625ae3519ac78bd5a805103fe7b33e5537e0fb38d1683425cd4343a1a603584688d03e2cfa0fb39b152f4f085d1db6c95a8ecf981c3750e1127330aa0996035e46e1a0c95d09e927c89cbf8f2489a2c6dcb777b20101704498ff1b0f70fe6f03aedb26cca64ee7b46ec7072a40ecd4947cc1d0bd92e66d90132c00a3dc5f6280036a0e3bbbf243120a51a4e87edc44dce0840fb7b6c272229677afe82b6f407b8b03409d731e3a813ab450dbb4cd3850c51edd88b2e4eb46028f46b597b4aec15a0b0388cbd79e3594bbdef0a0b565d3b67c7f3e6bddf6f55c325d1f409e3ae535b1020219ffff0353ac907a52ca6c934573ee1a5179fb1cb6220924f8949d3717dda710b231eddd032050a42120acda39c75a857eae5651522f6ede8b78ddebc63fadd2c32ac06ca9036eb058f6f920d9f4874b30ad114b3be2de4dfc24913f2fc5c04cf8942fc8335c03e997ed41248d3d0de834819f6ebf42fc5bfa3dbeb9ed9675ea3fa6d0366e4cf40219ffff036ad4a1b0a179bfde73a46b859902c41f8c3506f2a6ec6c9155649b0cde9afb0603a564bd6f99d5026abd352578955062d995caa6e17d2e47b5e901bb685b9c03f303d66d49257915128eea087bda816dad21b6a61e4cc02ccb41c94f4833319fb38b03c5920674fcd25a5f40660771ed3af1e3ae4a28e72cee0349c066b746abd0495d0333e02285b0a8892e1293f692f145153fd14403540a655428cdc960f9c45913f1038567acaef04ab488fc6b5b3c50758b3b4670b11541b31cc1899807d3ac203fe203476e59c93870ce4c4d81602fb7439cedcedd1ab457ca62a20a86b18ca0bb12740219ffff0219ffff039e11e0b2e60bfa240fbc4ce5ea38a02668cd86f2d95d59f355a9225f52b99fd403fc563fb961a4fa575a897d9f4782cea4aa3bbaa736c014846a0bb11a4c5fe7e303132b0f35cc84603c40392e2fc0cc386c9dd21e9b717edfa1e7233d07c3dd4789037e0555ef45379f0651b5487f7c8ec7339ddd94265bc4674e2165045383d2e84d03e4bb075ab8fdc0ded8002d558addeddfe14edaece1f6d9c0ca715724af1dbd0c030a8950f99b532a8e26ad2601e12d70cbbf37f09579a05e361ab7417432537bb603d41651365e44d8fa6051a9179bb9c72785d20b7cfe477d492f941119906c5a0e036aa2f522e1d31c6db622aa08cccc87d69268ed80f745cfb15e8ffee818f17a9c03167168664087740699af7559845849ac5d2c37e46993b1cfb89030abd5c5678e03ead67f4b130256083a1b83fdd04133ad9587aefc5bfeabba82279eeac7e267c40337f2b0d48efffa5ee07805b36ae00f303d7d733b689d94b57ebf5a549731289f03fe25d52c1c69a9921a8fb733268eed66ee4c117e6456906d450b8bf45e6a1e420366dcbaaaf02e05ddce1c2bfff2013b6a93766c8f21102573d938981dd158d8ee03360d70a8c2f3d19eb886827a94e14d2e0fb61f923c2ad48070c06658b615565403e2b6989696eca188b2c8d6864ad190ba03e031aa0533cfacd4b18cc78901f98803993741b583d063de0b616b9c920d773762cd0687a6522cd375665e06a29aa2a9037e72d0b2925079fee15d1123a14d11d701e0fd67b8c50cbef908b48c2959f2db039eb03468ad91edee4e9045d619225f0e1c5c0f4728c08c7f27baa3b545fc292703c86848378e6702f77fbff2c72f86ce7afd0d7fe1b36aa537374815fdc91bf9d103a54f7e603147f631c9fda221af8c6dccb7e779426106355aae344f823241f99e0387e56545f25e0108be617fc86753a719d67c22c067dbd73005aae352ca8807c0030bcc49a91a110ba428956d1d628fa767f5a2b651d8e7249ec5fe339e3181ab0c036d790bf3be4f64e13ff88dda3134975b7980278851590d32457f7b89fcb23668035b27d8065caf2542fafaacd95073cbdf2e6214d322efd71033c6342ba61f4bfa0380508373e59f16e54d9ecd555e14bb44bb73609243e83971aabe5991c494416303c76bca48687403bca79381d53238adcac50af92fe15112cf6844ecf70e95d575037ffeb11d9cbda1b66d2af1aab1afa8bc5e5ddc0044148ee0b44421bc4ba4bd4e03e26989cf74bd4873d24eced20cce94e0308e045694eeceb26de3d4043016d49503b9b2b2c21021fb7e0b634ab25e2e3b83275c74d2384c642920d3110d21ff094c03782e6d908f1b076c71019bdd818bf8b1bc122e2a110e3943e9b03bb3aaadc480039b546ac1aee65f52f3d153127b17e99960b5a23394478abaf78fa154d0fe4aba03ddfe845d47c377e9cfa9e2187c698f76a802161bb3f42cfbf8ed0febb4a6f956035774f1fc435893c448d5fe018c80ae6996377929bacbc50b1d23fc33e12c235e03d8b3e513eaa136ab7feadb9a153995b0f421a0a364a0f416feb6402f5856217203a812aa0a90d8dbe124172185f9c305a0f9fe5b067001cbbac74d62a194e93dbd030466bb6434638e3601c4840f4b7373ced2a22b6bade1a68706ace14a4afd1f2d0380b155e133453b70b4ab6d42a9c094f56ff4ab9040b2204b7bbeb8e69a78adb5039afc1e6b6080903add242c438e61ab2c7894c20ca514c6c482e17e8042c4a05003844fea85154023e42effbb9fa5305924efd745b47a5447bbbe88785b92f847840386645c304aff70b12c503395ea59f87ccab97a025073028e6ba658790ae87aa603d08b1ef6a8a1592d093bd336d381f626a2adc8e88c49e72a87a05fc4191185bc03f02fda2c46476f7c34bad85b77afcb7309c0c33445ce9acf1ed3f2bfde892bb403e300efdf76b150c1ed4fb63faff11276dd99de69de5c346909a4449d8f4326f703ec52bcb262b565d0dcf6fe93f3e4a8301056fa69690c9f40a4c89f2cde2bcf710375e776360f21abbee70d76d3428d96feeaf626f99fce98b5a55f3dd32120e43c032dff75f85c7eabfd7160762a43e6dfa465e31f1b2f9edef25e775e472a5c2a5f033025352304dd923ba987ee6116c11a63e6a0f183bf0fe058f4f9b241f425cc1103cd15b9a0da894f89fe6724f75d2c9bd8dadcfe907c06182982af2ba68e1d809003f99366ec7c32fa666d98664d415069ba3e7885eb942619859ba4609c72e774a10388097f0f375a04b44f762080f0e88e346e6b031772500247286cdbd63e1b4fc603d81597f66d4bfa4898bce568ff07b7b8fbfb766e2d8e4b0d33464d79c779e6410357b167d35071fa4302eb90e112aeaedf2831affcaf58bf9e70f1847da8e1e9ab045908b360806040523661001357610011610017565b005b6100115b61001f6101b7565b6001600160a01b0316336001600160a01b0316141561016f5760606001600160e01b031960003516631b2ce7f360e11b8114156100655761005e6101ea565b9150610167565b6001600160e01b0319811663278f794360e11b14156100865761005e610241565b6001600160e01b031981166308f2839760e41b14156100a75761005e610287565b6001600160e01b031981166303e1469160e61b14156100c85761005e6102b8565b6001600160e01b03198116635c60da1b60e01b14156100e95761005e6102f8565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b815160208301f35b61017761030c565b565b606061019e83836040518060600160405280602781526020016108576027913961031c565b9392505050565b90565b6001600160a01b03163b151590565b60007fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60606101f4610394565b600061020336600481846106a2565b81019061021091906106e8565b905061022d8160405180602001604052806000815250600061039f565b505060408051602081019091526000815290565b606060008061025336600481846106a2565b8101906102609190610719565b915091506102708282600161039f565b604051806020016040528060008152509250505090565b6060610291610394565b60006102a036600481846106a2565b8101906102ad91906106e8565b905061022d816103cb565b60606102c2610394565b60006102cc6101b7565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b6060610302610394565b60006102cc610422565b610177610317610422565b610431565b6060600080856001600160a01b0316856040516103399190610807565b600060405180830381855af49150503d8060008114610374576040519150601f19603f3d011682016040523d82523d6000602084013e610379565b606091505b509150915061038a86838387610455565b9695505050505050565b341561017757600080fd5b6103a8836104d3565b6000825111806103b55750805b156103c6576103c48383610179565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6103f46101b7565b604080516001600160a01b03928316815291841660208301520160405180910390a161041f81610513565b50565b600061042c6105bc565b905090565b3660008037600080366000845af43d6000803e808015610450573d6000f35b3d6000fd5b606083156104c15782516104ba576001600160a01b0385163b6104ba5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161015e565b50816104cb565b6104cb83836105e4565b949350505050565b6104dc8161060e565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b6001600160a01b0381166105785760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b606482015260840161015e565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b60007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc6101db565b8151156105f45781518083602001fd5b8060405162461bcd60e51b815260040161015e9190610823565b6001600160a01b0381163b61067b5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161015e565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61059b565b600080858511156106b257600080fd5b838611156106bf57600080fd5b5050820193919092039150565b80356001600160a01b03811681146106e357600080fd5b919050565b6000602082840312156106fa57600080fd5b61019e826106cc565b634e487b7160e01b600052604160045260246000fd5b6000806040838503121561072c57600080fd5b610735836106cc565b9150602083013567ffffffffffffffff8082111561075257600080fd5b818501915085601f83011261076657600080fd5b81358181111561077857610778610703565b604051601f8201601f19908116603f011681019083821181831017156107a0576107a0610703565b816040528281528860208487010111156107b957600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b60005b838110156107f65781810151838201526020016107de565b838111156103c45750506000910152565b600082516108198184602087016107db565b9190910192915050565b60208152600082518060208401526108428160408501602087016107db565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a264697066735822122012bb4f564f73959a03513dc74fc3c6e40e8386e6f02c16b78d6db00ce0aa16af64736f6c634300080900330058210390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56305437aab97476ba8dc785476611006fd5dda4eed66b005821032df0bdf5a5f92d8037cf11e50f13d8017aefc99d20a73c826416df79570d4810546e38d4999fdb6fac24973e508cde9397e369c5af005821035b20eef8615de99c108b05f0dbda081c91897128caa336d75dffb97c4132b4d054bcd3a47e4d0000cf170e25d1bd3d53f7c08be0a60058210310e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6049011d6c8fba323ca0250058210328a5566b8a884201ab44e2d991177ce8b88325e02e52cbc3da6e67b3ecf29c604101021948a405581d02beea506e63d9311b0eb23c6ed86e7ddc2248d7bbe9d3d02bc17219010f0149011d6c8fba323ca0261908b305581d022f5dce6d993107338134f4048a0ef5d2430bef1a1a61f0a9ab5c5fa60c01450572bfaa000219a0000334160b2b3414a1b448458100361c64bdccf5936cc939e8b2d592200ed19105df03da3cc90ef62d3ad9e255dff552fa7525c31d06eb64d5df5045e56a7bcd3538fe05581e037013a88a4a324f330a934552003c0ea9bf3bbae161c26003b4f849c0800c024701383b84a9a17005581e038a68ca0de222757ad180dfe2910996104dbdf45e510d8b7f8ce0c729700c01470cc8aa441de58003562d59a51820d47f520c975e0b2bcffac644a509749a3161f481f57b6e826d210605581e0334748c9b864c1a7503fc03dd1016d10f8275d447ebcd45d257d242560007011bffffffffffffffff05581e0349a7d5dc763a3e1a8fff885943cd752edfd2814de2560f113870c7f4400c03480c2d8c1449f7162e02191ce403f1f0fc1c77adf0e1cb5abf0749989e5eb140c12723125ae612d53b5e650713530219ffff03ee5a53f74f94eed211b1550315db61e61ec4bbcbf88d691d9ba75d007b0415540219ffff0326d411259dd7a816b3e4416eccc5f9f3f8797f60e9181cb62c60f59668bb20890323172268bd2a09f78c3552b2e5469c6b05c29a7d95b51d92028cc3bbaab691340219ffff03872087c58f60307b4963d61f7279828d9659dd62ba57be08be4367c53f688e5f0342bd50705846793c09d7e345dbc01747561e7615b92d9b5568466d332d9cb72d036746d7523176057df9ab50e4124f7f615d7d18f3773c220e5a3269d2b3a9528003e9c64ed05da18c3ebb4debb8492489dcf5a681431a2901138f51a6bf6346994b034ebd4a376a2edb82cc2ddad312403e5665f5d2af52367cb74512252b2605f2dd0219ffff03a6839ce079f60fe5a0bd5ee110ebdc668f09e876aed102b15594c44975775c5503e086e477c41fadfe88bf76baf2d04aad8f1ee57ac9e0b5ded35062eeed59fa150307a6af432a815dfc18260a96302efda221a50d09284c6bc818d5b164da9919bb0382257a760af0178efb22ff1983af08c955bf92062ef7dec5d694d64b657a4824034f06e32cbc687a439d9a12eff4a36118fd188bfc7901f79c7f7b98f0287e603b0317f36a9ac825f2fc8a2645631bb1beb8516c0f0e7cfbaa06010b5c7a151eb1ed03d48abe54fa6f199c420e4917ab4c765d23eb852e22d547fd55c37f01a03546410398aa75631e6b1fc7e191c7aa3619ad11b8267b977d2cf4e894f3a69d109c9cb403a185050c2dc07f8446343fd835edba8ff120b78c513555065d08a8c396089efa03c4a450ff4fe0b60bc2f915baac2d659226e8a53c97994a59fcbb80a0084d4651032d17c5617f0ad9fc4ec576c97fa75b32b239ccc37b2d8b4cd22a90704b2590280312e5da494b0bddf2f9118e2b35474df3c516dcbbf861c95f8500c817afa1b4b9039530abcde320a8d9b39fb7d51e409f4304d8060a4899be848378f735aa90b58303fb5d50a431f418aaa71dd484241df04246e654f0e67748321b19ae6c78ec327303f543828ef2845f2c8cf51ae72a1dd5fc2faf6263bcf28ddae421b63805feac21034ead60a6ac6264a2d238cddee2e5f1e90cb3e2ba7689d3a75b50ce80830e079203678a94c99d74e1ae18662084e14b8893f74db93efd64fc26ff0e966ebcde173403e192c69a516ba6c70068c90d97ed4c2033e55a3a2586c8adb19df29afa2277d503a51b07521882854afa78ee6e331b5d517644356fe8b7234873c1bd8a3b87d64603b69d810acfa81e692d1be7318ecce80bd30cf020b36925fbc2e7de498d64cd2e03eb75b2d973cf7d6f68e3e94ea6a1da16136ee04fd939beeb25970523fee36129036c8b0f5ae48859f90edff010e930899f9b9c69e2831edb176f9606f1a5ee7b7f03153c9b0963e63277b37ad32e7292dd70fe80d17355d5072f7c3743583570c86403108c18059693942247bd69adb0df426f8d4da285a2a65ae8aafe230335f49a1503c18819da1e1030a0a57e3e6a8e1c7ab1ea6d1dd9e5a84ab1800695f8041485c4037071502c6556c483a799c2a51ce6ec3844e25bf7eea6546cba9529806b4b19ab0373cdcc78853cf441d75a71aac0cd2c192cdb7b60969e74f21dc3b6c93ab32da0038cf2110a8ad3c555e4f0a8f10d16cbf380090ace829f5b86783f7210c7f13b2303c6788ad3233c4c8f782a2b38f8853c6f6162cef4f67e5e7f2dbbad9add5cb74805581e03399bd41150c10abeaf754ac321869d467391ef5decd21b75fff4219e700c0147012e2dd507d14003ff0957529f4b0b7086551af1d2f543907f305a6618b6d43e042f333e0ce4648e05581e035cd68627a90d1ca3dc18b99862721b2278fa98386e6d523036ea949ac00848046ad03e9b97ae0003ea8e8bd08804ae9b32373abc45eb59d48a256f042146a09f2538a2d3034eecb601410a039d09a5a782766c68adf3c29606454c5274e07e05c4f40bf0c376258caac4537f039fc58d92eb043d53f3c41dc5169b576520a8aca309d69101b339122a14be7c0d0368d9ef9c32c6355d5ce1cb3803f4f20debc1aacb6619685de20bfe9763730eda03907c071fd61daba3703da934f69afbbd97123740e1068cd951252cf29a291651035b83bdbcc56b2e630f2807bbadd2b0c21619108066b92a58de081261089e9ce503e2a8b47e80f9bfd9d571a0cabe7497fbfba8e0ca15a24d21f33b9ab0c56b993105581e03916c2ed2b6d5339fb36af92813895bba1355bbeab9ee10542daea391c007011bffffffffffffffff05581e03206138f42a11cde77dd412a77b978ad37f6b4f570aec86e0a1c790a2b0040103117189cf78bcaf818810f233e4ae673fcd70f45794330df8e3bb714cc316772c0219dd7903f54c9cbb3080ff1e2bcaef7ab712448d665da3956026df1d171dfe8bfffe0b0503ac2cd5329fbd1e6fd22680a56158b22fc584751834b48f69bdc0836599c0aded0366979e47a7fe99007395a35155f9603a06296f7e6aa6951a2220751dee62515203dc8883551a971349f5fd8cb22867c6743eee07a1f77d57b0f9d907bf1b879ede039fd8eeacf6465b4d38ca65a9379dbe44b2d293f00cc09a33d8e1283f7637af5303210b5c315bd98a298830bae4b2e60431d30619b36227a1183655980059acc58f038cf582082fb608aabf9d2bca983532883fd7e3eee95529fbee0d1a758eb2c4ec0219ffff0342bf1e11d8795edf35957bbd2d61a7277b438beb12af11648267c35ba3ff374703f5bb67db6cda6a25812358b5f708e4dca91f7f8462eb0be9601ffcae8948177203e93fd382cc1bf408ed0706845b918c5720cb36c7c7941123e2b7bdb3d758f89b03f9400af720ef1840945af5b36d0e4f3af5766ee8d0f4aaf8fecbd418fa7eec39033fd6a57415a27e5b7c4bf731daf7286537ba5639a9567d88c478c0b137cd7262030456ab8efb69183cbdb8c63c71cd129614e9daadf2e7b9b8a0c4101e8e9c453003e018d814d055c7330e3ab9b3359d7db7734449adf4c178d26028e75a0c1ac45603fd167e2b0bdf5f0c30d3529fb3e926c1d267e84fc9685901f51a90ab331466bb03b1242b71b64b3bcddd4d3c1786d54af1b260ac4f7c2066f6fcdef568365ac7ef0357e5b239bcc5ad5717ffd615c344856b3d6c9dd74f704e1e7856c0e0585bfdf303cde81b38ca1a223ec508f1f226792e3f12ac056747bf684a5d603e01cfb2d2380219ffff0364defbb9a6fbc924bd567cf3387a6e25d055042845772fa3ab529b3a9a5828000301f3e8092a01718f3cd47c56de74d8b98559038dea01eedda183185be80d86d003aab320ecf5d16d33bca1122f35f9e17316adb65c2baf57353709bd78c03a25d103270c40a28a22bec1ad8f14c07a11f95a91a16458c14557c276413c96afe8497603bdeb8754d7f1164e87019989ed477d7bb1395164675e64500e47837dd1b31747031f49eb284cc882e095eb09dbf642eb60badbf48a2f372ef44fb15153cdf521210387fa81eb2dda109118258ac34d94ace4af4b2dabfb2d41ae4b6a35ed8071da09030d2f75caaaf51b3959469327ffe70edceab2d58e08207a099eece96e1f869c2c03842ab4cfaa843db0bca6aad8d175f3f2eef317e140898c42b1a1c9fd550991b80384623c79273883912ad178f5da4bf9f91dd07b4290b9352de96e0bc13a2bae4c03a57b5dc0dfb8e51d48353a5d2087a71218013b0ffc8c6e3e3edef4601154ee7e032ed3744231ce450bc20761a6a8130d754e72fc14184506042f02b4f78bcf00bd03077a347e4b1dccc359665872fcc3ad60cf7e3f23084cdea50e603cf38fc257c803503811ba352666fcecc55fdd0d3ccc2b75d7e4bb8e1d4b9bd7d772627800fc930219ffff0219ffff03108ffd2a8772789b9ad3335cdc989a073af4088847ecd9f1514520655118ea17032ec3966d3cdefccd3e2ee6e41d833259f813b7ef7475232c6597eae911de2f6e03aae95c40c66c1d5a14f791e81cd7a17534fd3b14c0b570a7bda842fb0e93b30d0363ee859bbd97767a30a408024f7d5a0686599706654a20b0d17a182c943bcecf0343af6e03f204c12ff28e52fa8f46d4a35a3fac06a19a9cfd6beec73080b62c8c03990fa8491a1839f504db72a498614761da84a9b28298a81be1a040445f117644037ceba9a733de1c94bdb8c0d50df46a228e927b52bc96d136c6a88c103a345d210337f9558f1cfe7f448830c8480ec60c9930002ed48b30ccd9e1ef4c5d8caa2aff03083af68cff161b4a6e1ffbe01e0b05d8eb71bb08413e8c06df21ae387642755f0307e13e7578300b73dcaadb5416304ab2219a78b4cfa3d45ac71fa0050a88ce410398769ee425eb19b8f0f9e72f8803cf909f9f3f6af05803274fb51f0691a89477034d116c0489c6a7830a6e71739878323d8fe0faa2bdc6d5f7b26e830cea8d7fe403cc9eb9d3881d2a179b91f6fc872a850802f828800678bf13fcac07a4db7589820314e53be3d411ae0f3f9ab05bee4ffe840f1642ea1a0b1b565bda279fde68351903b1754f87f0a900d340be549a6b15a0f30d55ee636a33e46943a708770a99d68303be0a1d3868303e0e13c4e3659a46c1b5c9390f34478ada9522f12a5aca74ae5e03a790fe17be277293a34810538b9673d4fbe3d03a0b035dc498fb87edf5837221035d8423169fb402a507758ba4d194af340fece08129070bccdaf0567e012712d103ac5779f00db1f3779385ed52fddcdb0742f5596922e73917a14b39851f20ddfd03311db8afba9fd90bd253ff2e853ee56485612c83bb4e6e86fe29a8116427a94103682bf118d6c7588275e2bf659f49f9386a9cfdfdb62c8f1974c6b83ddcbda7cc032a005fd0432915d283dc5acf30d7d4ca36354c94c959aa5d87869f4758dfc3d8035b0cf0b4fc18610cc5234a6742cd07a94fea25d89e41220758f6b59d2257483e0329343f36760cbb0951e444492e04d89771c8905af0c8705f5949fe5157a4e38a03a7282ad3171cd6a8b00a6fe5cfacd2fad9db9d351ba4f0b846d26482e1b8f9fa03da4be3855117f77715adc181a7f4a881cf1ed8293ae0dddefe40f8a5d12f886f03f47b99538fc4e2d7d6fbb8b555e79f69dea2562a70608ec8a76ad665845e4feb03a825017dc238633febb30917c45993c6063432aac186794af783bbb39f398d0303f8280644186a3b896527ba7062dde863bcbf66dee141c4d8c7d3ade009d461860315f2adb176e17ebfd73c507d85e6c1bff295f733e4a4618fbf47a15b73148a650360dd50876647af16ff77e424f71c5b94a330919fcaf2932e738a9e2c4efa4a5805581e032af143968bf17c4f670b868c298a55e7c5c16991bdb658e3eed3cd4c9007011bffffffffffffffff031804e5cd0675cdeb28d9517d51606517bdd557b39a1b48aeec456ecdbec352fa03645c169e00b675096fd330b389d954736e724467b40f8111b68cbe1d8ca8ff7a03edeacad95a6c7b2ebd8060e27a659b4b3f4b863f34db539070207ca072783b4205581e03a011bcf3da4861819cd38306db473ce00a0f7ab83b5f017d398e28afc00401035fa8c6f35c8fa363a6451cf6d5134e313c0454d707dcb36c9e01e8a10578c468045933cb608060405260043610610138575f3560e01c8063715018a6116100aa57806395d89b411161006e57806395d89b41146103eb578063a9059cbb14610415578063bf474bed14610451578063dd62ed3e1461047b578063ec1f3f63146104b7578063fb201b1d146104df5761013f565b8063715018a61461031b5780637d1db4a5146103315780638da5cb5b1461035b5780638f9a55c014610385578063909bd599146103af5761013f565b8063313ce567116100fc578063313ce5671461023957806332712140146102635780633395f22b1461028b57806335949eab146102a157806351bc3c85146102c957806370a08231146102df5761013f565b806306fdde0314610143578063095ea7b31461016d5780630faee56f146101a957806318160ddd146101d357806323b872dd146101fd5761013f565b3661013f57005b5f80fd5b34801561014e575f80fd5b506101576104f5565b6040516101649190612377565b60405180910390f35b348015610178575f80fd5b50610193600480360381019061018e9190612435565b610532565b6040516101a0919061248d565b60405180910390f35b3480156101b4575f80fd5b506101bd61054f565b6040516101ca91906124b5565b60405180910390f35b3480156101de575f80fd5b506101e7610555565b6040516101f491906124b5565b60405180910390f35b348015610208575f80fd5b50610223600480360381019061021e91906124ce565b610579565b604051610230919061248d565b60405180910390f35b348015610244575f80fd5b5061024d61064d565b60405161025a9190612539565b60405180910390f35b34801561026e575f80fd5b5061028960048036038101906102849190612692565b610655565b005b348015610296575f80fd5b5061029f610774565b005b3480156102ac575f80fd5b506102c760048036038101906102c29190612692565b6108a6565b005b3480156102d4575f80fd5b506102dd6109c4565b005b3480156102ea575f80fd5b50610305600480360381019061030091906126d9565b610a5b565b60405161031291906124b5565b60405180910390f35b348015610326575f80fd5b5061032f610aa1565b005b34801561033c575f80fd5b50610345610bef565b60405161035291906124b5565b60405180910390f35b348015610366575f80fd5b5061036f610bf5565b60405161037c9190612713565b60405180910390f35b348015610390575f80fd5b50610399610c1c565b6040516103a691906124b5565b60405180910390f35b3480156103ba575f80fd5b506103d560048036038101906103d091906126d9565b610c22565b6040516103e2919061248d565b60405180910390f35b3480156103f6575f80fd5b506103ff610c74565b60405161040c9190612377565b60405180910390f35b348015610420575f80fd5b5061043b60048036038101906104369190612435565b610cb1565b604051610448919061248d565b60405180910390f35b34801561045c575f80fd5b50610465610cce565b60405161047291906124b5565b60405180910390f35b348015610486575f80fd5b506104a1600480360381019061049c919061272c565b610cd4565b6040516104ae91906124b5565b60405180910390f35b3480156104c2575f80fd5b506104dd60048036038101906104d8919061276a565b610d56565b005b3480156104ea575f80fd5b506104f3610de2565b005b60606040518060400160405280600881526020017f536f726120496e75000000000000000000000000000000000000000000000000815250905090565b5f61054561053e611305565b848461130c565b6001905092915050565b60115481565b5f6009600a61056491906128f1565b6461f313f880610574919061293b565b905090565b5f6105858484846114cf565b61064284610591611305565b61063d8560405180606001604052806028815260200161336e6028913960025f8b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6105f4611305565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054611dd99092919063ffffffff16565b61130c565b600190509392505050565b5f6009905090565b61065d611305565b73ffffffffffffffffffffffffffffffffffffffff165f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146106e9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106e0906129c6565b60405180910390fd5b5f5b815181101561077057600160045f84848151811061070c5761070b6129e4565b5b602002602001015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff02191690831515021790555080806001019150506106eb565b5050565b61077c611305565b73ffffffffffffffffffffffffffffffffffffffff165f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610808576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107ff906129c6565b60405180910390fd5b6009600a61081691906128f1565b6461f313f880610826919061293b565b600e819055506009600a61083a91906128f1565b6461f313f88061084a919061293b565b600f819055507f947f344d56e1e8c70dc492fb94c4ddddd490c016aab685f5e7e47b2e85cb44cf6009600a61087f91906128f1565b6461f313f88061088f919061293b565b60405161089c91906124b5565b60405180910390a1565b6108ae611305565b73ffffffffffffffffffffffffffffffffffffffff165f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461093a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610931906129c6565b60405180910390fd5b5f5b81518110156109c0575f60045f84848151811061095c5761095b6129e4565b5b602002602001015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff021916908315150217905550808060010191505061093c565b5050565b60055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16610a04611305565b73ffffffffffffffffffffffffffffffffffffffff1614610a23575f80fd5b5f610a2d30610a5b565b90505f811115610a4157610a4081611e3b565b5b5f4790505f811115610a5757610a56816120a6565b5b5050565b5f60015f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b610aa9611305565b73ffffffffffffffffffffffffffffffffffffffff165f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610b35576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b2c906129c6565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff165f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35f805f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b600e5481565b5f805f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b600f5481565b5f60045f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff169050919050565b60606040518060400160405280600481526020017f53494e5500000000000000000000000000000000000000000000000000000000815250905090565b5f610cc4610cbd611305565b84846114cf565b6001905092915050565b60105481565b5f60025f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b60055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16610d96611305565b73ffffffffffffffffffffffffffffffffffffffff1614610db5575f80fd5b6008548111158015610dc957506009548111155b610dd1575f80fd5b806008819055508060098190555050565b610dea611305565b73ffffffffffffffffffffffffffffffffffffffff165f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610e76576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e6d906129c6565b60405180910390fd5b601360149054906101000a900460ff1615610ec6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ebd90612a5b565b60405180910390fd5b737a250d5630b4cf539739df2c5dacb4c659f2488d60125f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550610f633060125f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff166009600a610f4e91906128f1565b6461f313f880610f5e919061293b565b61130c565b60125f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c45a01556040518163ffffffff1660e01b8152600401602060405180830381865afa158015610fcd573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ff19190612a8d565b73ffffffffffffffffffffffffffffffffffffffff1663c9c653963060125f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663ad5c46486040518163ffffffff1660e01b8152600401602060405180830381865afa158015611077573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061109b9190612a8d565b6040518363ffffffff1660e01b81526004016110b8929190612ab8565b6020604051808303815f875af11580156110d4573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110f89190612a8d565b60135f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060125f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f305d719473061117f30610a5b565b5f80611189610bf5565b426040518863ffffffff1660e01b81526004016111ab96959493929190612b21565b60606040518083038185885af11580156111c7573d5f803e3d5ffd5b50505050506040513d601f19601f820116820180604052508101906111ec9190612b94565b50505060135f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663095ea7b360125f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6040518363ffffffff1660e01b815260040161128c929190612be4565b6020604051808303815f875af11580156112a8573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112cc9190612c35565b506001601360166101000a81548160ff0219169083151502179055506001601360146101000a81548160ff021916908315150217905550565b5f33905090565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361137a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161137190612cd0565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036113e8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016113df90612d5e565b60405180910390fd5b8060025f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040516114c291906124b5565b60405180910390a3505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361153d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161153490612dec565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036115ab576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016115a290612e7a565b60405180910390fd5b5f81116115ed576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016115e490612f08565b60405180910390fd5b5f6115f6610bf5565b73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16141580156116645750611634610bf5565b73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614155b15611b295760045f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff16158015611707575060045f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff16155b61170f575f80fd5b61174b606461173d600a54600d541161172a5760065461172e565b6008545b8561210e90919063ffffffff16565b61218590919063ffffffff16565b905060135f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161480156117f6575060125f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614155b8015611849575060035f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff16155b1561190357600e54821115611893576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161188a90612f70565b60405180910390fd5b600f54826118a085610a5b565b6118aa9190612f8e565b11156118eb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016118e29061300b565b60405180910390fd5b600d5f8154809291906118fd90613029565b91905055505b60135f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614801561198b57503073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1614155b156119cf576119cc60646119be600b54600d54116119ab576007546119af565b6009545b8561210e90919063ffffffff16565b61218590919063ffffffff16565b90505b5f6119d930610a5b565b9050601360159054906101000a900460ff16158015611a44575060135f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16145b8015611a5c5750601360169054906101000a900460ff165b8015611a69575060105481115b8015611a785750600c54600d54115b15611b2757601554431115611a8f575f6014819055505b600360145410611ad4576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611acb906130ba565b60405180910390fd5b611af1611aec84611ae7846011546121ce565b6121ce565b611e3b565b5f4790505f811115611b0757611b06476120a6565b5b60145f815480929190611b1990613029565b919050555043601581905550505b505b5f811115611c2857611b818160015f3073ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20546121e690919063ffffffff16565b60015f3073ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20819055503073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051611c1f91906124b5565b60405180910390a35b611c788260015f8773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205461224390919063ffffffff16565b60015f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550611d1b611ccf828461224390919063ffffffff16565b60015f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20546121e690919063ffffffff16565b60015f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef611dbe848661224390919063ffffffff16565b604051611dcb91906124b5565b60405180910390a350505050565b5f838311158290611e20576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611e179190612377565b60405180910390fd5b505f8385611e2e91906130d8565b9050809150509392505050565b6001601360156101000a81548160ff0219169083151502179055505f600267ffffffffffffffff811115611e7257611e71612556565b5b604051908082528060200260200182016040528015611ea05781602001602082028036833780820191505090505b50905030815f81518110611eb757611eb66129e4565b5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff168152505060125f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663ad5c46486040518163ffffffff1660e01b8152600401602060405180830381865afa158015611f5b573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611f7f9190612a8d565b81600181518110611f9357611f926129e4565b5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050611ff93060125f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff168461130c565b60125f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663791ac947835f8430426040518663ffffffff1660e01b815260040161205b9594939291906131c2565b5f604051808303815f87803b158015612072575f80fd5b505af1158015612084573d5f803e3d5ffd5b50505050505f601360156101000a81548160ff02191690831515021790555050565b60055f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f1935050505015801561210a573d5f803e3d5ffd5b5050565b5f80830361211e575f905061217f565b5f828461212b919061293b565b905082848261213a9190613247565b1461217a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612171906132e7565b60405180910390fd5b809150505b92915050565b5f6121c683836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f00000000000081525061228c565b905092915050565b5f8183116121dc57826121de565b815b905092915050565b5f8082846121f49190612f8e565b905083811015612239576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016122309061334f565b60405180910390fd5b8091505092915050565b5f61228483836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250611dd9565b905092915050565b5f80831182906122d2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016122c99190612377565b60405180910390fd5b505f83856122e09190613247565b9050809150509392505050565b5f81519050919050565b5f82825260208201905092915050565b5f5b83811015612324578082015181840152602081019050612309565b5f8484015250505050565b5f601f19601f8301169050919050565b5f612349826122ed565b61235381856122f7565b9350612363818560208601612307565b61236c8161232f565b840191505092915050565b5f6020820190508181035f83015261238f818461233f565b905092915050565b5f604051905090565b5f80fd5b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6123d1826123a8565b9050919050565b6123e1816123c7565b81146123eb575f80fd5b50565b5f813590506123fc816123d8565b92915050565b5f819050919050565b61241481612402565b811461241e575f80fd5b50565b5f8135905061242f8161240b565b92915050565b5f806040838503121561244b5761244a6123a0565b5b5f612458858286016123ee565b925050602061246985828601612421565b9150509250929050565b5f8115159050919050565b61248781612473565b82525050565b5f6020820190506124a05f83018461247e565b92915050565b6124af81612402565b82525050565b5f6020820190506124c85f8301846124a6565b92915050565b5f805f606084860312156124e5576124e46123a0565b5b5f6124f2868287016123ee565b9350506020612503868287016123ee565b925050604061251486828701612421565b9150509250925092565b5f60ff82169050919050565b6125338161251e565b82525050565b5f60208201905061254c5f83018461252a565b92915050565b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b61258c8261232f565b810181811067ffffffffffffffff821117156125ab576125aa612556565b5b80604052505050565b5f6125bd612397565b90506125c98282612583565b919050565b5f67ffffffffffffffff8211156125e8576125e7612556565b5b602082029050602081019050919050565b5f80fd5b5f61260f61260a846125ce565b6125b4565b90508083825260208201905060208402830185811115612632576126316125f9565b5b835b8181101561265b578061264788826123ee565b845260208401935050602081019050612634565b5050509392505050565b5f82601f83011261267957612678612552565b5b81356126898482602086016125fd565b91505092915050565b5f602082840312156126a7576126a66123a0565b5b5f82013567ffffffffffffffff8111156126c4576126c36123a4565b5b6126d084828501612665565b91505092915050565b5f602082840312156126ee576126ed6123a0565b5b5f6126fb848285016123ee565b91505092915050565b61270d816123c7565b82525050565b5f6020820190506127265f830184612704565b92915050565b5f8060408385031215612742576127416123a0565b5b5f61274f858286016123ee565b9250506020612760858286016123ee565b9150509250929050565b5f6020828403121561277f5761277e6123a0565b5b5f61278c84828501612421565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f8160011c9050919050565b5f808291508390505b6001851115612817578086048111156127f3576127f2612795565b5b60018516156128025780820291505b8081029050612810856127c2565b94506127d7565b94509492505050565b5f8261282f57600190506128ea565b8161283c575f90506128ea565b8160018114612852576002811461285c5761288b565b60019150506128ea565b60ff84111561286e5761286d612795565b5b8360020a91508482111561288557612884612795565b5b506128ea565b5060208310610133831016604e8410600b84101617156128c05782820a9050838111156128bb576128ba612795565b5b6128ea565b6128cd84848460016127ce565b925090508184048111156128e4576128e3612795565b5b81810290505b9392505050565b5f6128fb82612402565b91506129068361251e565b92506129337fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8484612820565b905092915050565b5f61294582612402565b915061295083612402565b925082820261295e81612402565b9150828204841483151761297557612974612795565b5b5092915050565b7f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65725f82015250565b5f6129b06020836122f7565b91506129bb8261297c565b602082019050919050565b5f6020820190508181035f8301526129dd816129a4565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f74726164696e6720697320616c7265616479206f70656e0000000000000000005f82015250565b5f612a456017836122f7565b9150612a5082612a11565b602082019050919050565b5f6020820190508181035f830152612a7281612a39565b9050919050565b5f81519050612a87816123d8565b92915050565b5f60208284031215612aa257612aa16123a0565b5b5f612aaf84828501612a79565b91505092915050565b5f604082019050612acb5f830185612704565b612ad86020830184612704565b9392505050565b5f819050919050565b5f819050919050565b5f612b0b612b06612b0184612adf565b612ae8565b612402565b9050919050565b612b1b81612af1565b82525050565b5f60c082019050612b345f830189612704565b612b4160208301886124a6565b612b4e6040830187612b12565b612b5b6060830186612b12565b612b686080830185612704565b612b7560a08301846124a6565b979650505050505050565b5f81519050612b8e8161240b565b92915050565b5f805f60608486031215612bab57612baa6123a0565b5b5f612bb886828701612b80565b9350506020612bc986828701612b80565b9250506040612bda86828701612b80565b9150509250925092565b5f604082019050612bf75f830185612704565b612c0460208301846124a6565b9392505050565b612c1481612473565b8114612c1e575f80fd5b50565b5f81519050612c2f81612c0b565b92915050565b5f60208284031215612c4a57612c496123a0565b5b5f612c5784828501612c21565b91505092915050565b7f45524332303a20617070726f76652066726f6d20746865207a65726f206164645f8201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b5f612cba6024836122f7565b9150612cc582612c60565b604082019050919050565b5f6020820190508181035f830152612ce781612cae565b9050919050565b7f45524332303a20617070726f766520746f20746865207a65726f2061646472655f8201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b5f612d486022836122f7565b9150612d5382612cee565b604082019050919050565b5f6020820190508181035f830152612d7581612d3c565b9050919050565b7f45524332303a207472616e736665722066726f6d20746865207a65726f2061645f8201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b5f612dd66025836122f7565b9150612de182612d7c565b604082019050919050565b5f6020820190508181035f830152612e0381612dca565b9050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f20616464725f8201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b5f612e646023836122f7565b9150612e6f82612e0a565b604082019050919050565b5f6020820190508181035f830152612e9181612e58565b9050919050565b7f5472616e7366657220616d6f756e74206d7573742062652067726561746572205f8201527f7468616e207a65726f0000000000000000000000000000000000000000000000602082015250565b5f612ef26029836122f7565b9150612efd82612e98565b604082019050919050565b5f6020820190508181035f830152612f1f81612ee6565b9050919050565b7f4578636565647320746865205f6d61785478416d6f756e742e000000000000005f82015250565b5f612f5a6019836122f7565b9150612f6582612f26565b602082019050919050565b5f6020820190508181035f830152612f8781612f4e565b9050919050565b5f612f9882612402565b9150612fa383612402565b9250828201905080821115612fbb57612fba612795565b5b92915050565b7f4578636565647320746865206d617857616c6c657453697a652e0000000000005f82015250565b5f612ff5601a836122f7565b915061300082612fc1565b602082019050919050565b5f6020820190508181035f83015261302281612fe9565b9050919050565b5f61303382612402565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361306557613064612795565b5b600182019050919050565b7f4f6e6c7920332073656c6c732070657220626c6f636b210000000000000000005f82015250565b5f6130a46017836122f7565b91506130af82613070565b602082019050919050565b5f6020820190508181035f8301526130d181613098565b9050919050565b5f6130e282612402565b91506130ed83612402565b925082820390508181111561310557613104612795565b5b92915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b61313d816123c7565b82525050565b5f61314e8383613134565b60208301905092915050565b5f602082019050919050565b5f6131708261310b565b61317a8185613115565b935061318583613125565b805f5b838110156131b557815161319c8882613143565b97506131a78361315a565b925050600181019050613188565b5085935050505092915050565b5f60a0820190506131d55f8301886124a6565b6131e26020830187612b12565b81810360408301526131f48186613166565b90506132036060830185612704565b61321060808301846124a6565b9695505050505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f61325182612402565b915061325c83612402565b92508261326c5761326b61321a565b5b828204905092915050565b7f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f5f8201527f7700000000000000000000000000000000000000000000000000000000000000602082015250565b5f6132d16021836122f7565b91506132dc82613277565b604082019050919050565b5f6020820190508181035f8301526132fe816132c5565b9050919050565b7f536166654d6174683a206164646974696f6e206f766572666c6f7700000000005f82015250565b5f613339601b836122f7565b915061334482613305565b602082019050919050565b5f6020820190508181035f8301526133668161332d565b905091905056fe45524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e6365a2646970667358221220faf31d25f5bba3321231e05d0088926b459202ad60fd4c4f7681424d8b8b286064736f6c63430008170033005820035b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db904114005820039d0e332f80d1cff51a8a4d8865620006c19ed620818900cbffcee63890d910480cd157e3580065c40219108000582002ffb05fa7fb6a86937d01a672ff01821f7da270655c75053d497d06c32e5346480c7d713b49da000003255f32ed2a6bde98002da6e3cc0e58b70a5c3aff988ad5ddec23964bead0189600582002bcbd2ca84c64596a5f4a56e37d84862dbe3f7b4f8e487005029c09dbeb1e9a48519b8da754b697c80398d66d4877a1fb465b7a78297794c425caf898b8e7350b4ba0127bea651f55a5005820023c38f9d0b8def738a80423f5265f1ada8d30b34be15c19bc3a3fd1df57d55a42029703314b929e13059b976b44aa45bd1151392160875b85ace22641d630d3164429bc005820025f4b1b6e2ee5136983d50c8431bae54e98f73b1caf7d3e17cd5798afda01635820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00582002ec4e4fbbcb6da7b80a630e152dca58edc7dc693b288741bb9c8ae9765a20ae5820ffffffffffffffffffffffffffffffffffffffffffffffffaa9621c7818547ff03323eb01f87ce6292295fb80c6386ed7dba5a24992ddb40d9f650a8c5b91f786e00582002738f9317652af9a2f8d98a36d9b74af5d5efb3005bd748616a3951790c1be1481c0e9fabf28e665200582002a83e01ee189236ea420c06c1e5ad90ee0241afac784f6f77e49865ae28d7ef4807588688d615179a0219afbe00582002444e3cbf73033e1b3611d22ec5d580e428dace4b6282de44d4e8f3155962cd4802e368fc01683cf9005820026311e91d717d058f799f715c63bbb3bc375e51d8f7465b4b47ed7e710a1af148183f863632009b8c005820026847dc741a1b0cd08d278845f9d819d87b734759afb55fe2de5cb82a9ae672483a4965bf58a400000393fea7c248e1632707119833e743d7b4c08d7e8e83edc0d635aef8ed23ec0dbc0325c25567ee1cf657c32e1672aa17e8da341a31e8eb483e8ffafaad1683bd875202193a400323c8c4e0df9d31c4cfcd74b19e920aef8a6be3f58d833ba46ae1fce01c10da790058200233f2dabe70191bf91fee4f9192ccb4504339a8d7408b1f65a4903f2b916a115820ffffffffffffffffffffffffffffffffffffffffffffffffeb9013c7aaf39062037ffe4e689610cb88f86a56212b1816ca45df612faef8d7976586254db00649ff03cda10e5391e9264228eb2f0bf034f1ca9f26c78abf2cee8fbda3a8926dbe0b78038a2670b1260d74eceb27bc089fab9138528a3cd6f144f557d44ce7b6a9d02a9303a874ccc003f5fda475569abf6e38cec1a397e3481c89767abd60752450da87670058200200adfa2d10f44031f716086e8a88b410cbcddc45a2423c0b1dad37c1f3b1a85820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff033fa7a7af3e3e272ca70a6c5bd1899dcf9ae5d1629e59b68a7e7ca4b5ab14ac0201410d00582002a8cd92dfedbb1d839e6d78cf6ecb71f14d547134c79c2ee1efce9a4eb1fa624802be735d5101ab6c005820025afb5375241c8723c9b801292630c2ba18cfae58963b95963507b1c2642db9481d044d641b3ff24602190fdd032f98a28fea9c256f312526db825f1e1def4919d6c7838f156cbb1f2d0b250ffb035ed1b9a0abaca55b8185639f555671606960c317025b1d03df6efde927741b8003732861e026d5c368f4f63aec23b05e6b935c100c04105bda9f7cab75346a245000582002ea540327ac56e8c788686a6063618ec78e3c1635660cfe53397209b174133c4817e50e2ae28be1f5005820021877b57dda2f007e1abc736d6f26bebe6633bd8d41d03cc915856d2336eaf45820fffffffffffffffffffffffffffffffffffffffffffffffff48ed2e258780baf00582002e2f9063983567252a650cc97a252926147af57153df0b458bbc49d82b949285820ffffffffffffffffffffffffffffffffffffffffffffffffba2662f0eeb654ff00582002d2e02aa0f5af8c37b4ddaeb0e96f6e7e2e7317db11479ebaf39e8a7ceb5b294864a151da1f0decde0345a5a859c0ce381a99a5c83df826c5e1b0480f7fa3b081275c6b15d9a7152baa035236fe8186bcdfa1066327db9addd460af663f697163d0f382fe12d1e9e1f21900582002de8ffda797e3de9c05e8fc57b3bf0ec28a930d40b0d285d93c06501cf6a09057010001df675ac7eb6a7053e6988565f57dc3da177661ef03818b287d07bfe7ad6b072e3416af9f7e5717f5d00548b66293da464f6d9c1a0d037352aa8975319123fba4fe7c7f8b6249d3595fef4d7962b0aef9887769ad7de50058200296098a2446992373a77be9e143670314335ac28d9a1f87c775d7af694a61f9485ee43b728c07df89033e320bde6d10d065f028067fec9b1f739fe03bf81b6b09158cf371d331e8d3b9014106030530e71f99b92abc4c27d827fff142800b5b0a63696a0b1c78438449f07027260219d67f00582002dc61613003c2437d393fda5248af1d077f6825b1a38e61033509f051002e605820ffffffffffffffffffffffffffffffffffffffffffffffff8ba0eb582bc2595803bb48d01868b678d50046f4e696f719a436968e1d491c85f4fe62c359e13edebd03eecd73caf6493e4be7c603e4e1080383ac00d3dca01db6cfea259509573099d8005820028452c6f95a7f9525d32f33fb2253d40897cb9cdebd16a9b4545e99261edb3d5820ffffffffffffffffffffffffffffffffffffffffffffffff8b984f00ed0e6c98005820024b38c10e058d274c6f94ea67affa92614db48fcddb8170c5c4031cd8090813480e95a1f178897cf500582002eb66a969808d62b1afb823795ef60ba0bc917f35815ccff6bc8b36715bcfa05820ffffffffffffffffffffffffffffffffffffffffffffffffe92c7ab1dba5df5b038ef189089c02ce0fecf07f5c7a057d6b0cc76cac47d5e03874a5da5033063d5700582002a34a87882315cf2fda3dda6cbc2c1add4016ca5c8683d0f6f71d79fc3e66195820ffffffffffffffffffffffffffffffffffffffffffffffff9112c4cde760a01700582002150f4ac141e5ec5504f6b4a2d8401fa1aba4d7e8f68a4f65a83dde9a58161b4806bdf88e5f71b78a005820028d876d584f4a8242779a0975855998e30ee37efb2ae467e24cd62427ecb0e3485b3339c8692f3ab9037b387dce82f7e3b3e7b4b6278f4068f3df3554e5ba3580af6ae2641aeeae7f6d03a5047a3fd6acc6d8505bb01d8738e839b8e6631ea2448ec35ab1043824b83e860219ddf6005820022d974797b3b46e4d943da9dc92b06cb2934a6e91fbbf97a119cb77b4a24fc94847d15e72bcc28a8f00582002585ff0d8ce7cadeb77acfb034dcd8dd3d7689b2aa97599ba171c777da37ec15820ffffffffffffffffffffffffffffffffffffffffffffffffd4e053a6dadbcef903969c3fceb182b762ea8ba65a0f993844fff06bc6fe4bf7ccaacb0c925be2bddd00582002565112902a4a5a91fde59db5a2e801b4c4e8cf0168b26eb61ff2e36ddc7308480b3b9bbd30ddab9f0363f80b925ed26d77c6bb2d5519b19ecc3ed20c468c3bd58b978e944048b7981a03cb1ed5e25447d7e02fb23549f51223ddd7160b5762875ed2f10d4ba4d501e33600582002d69c03cf5551cc8b0f393cf43bc578c22eacc469dc716b10f867636dfa3ebc485569de387e7ab80003a7416d4f5229ce571c29639c4f74b49b7048cea0edf9073cc2cc9793779c7e31031b2806e68a9e13dbe98d6823e61ac80e368b9c5e9eabeea806ff1a0d5537fcbf01410e00582003108e10bcb7c27dddfc02ed9d693a074039d026cf4ea4240b40f7d581ac80204916ce3f1e16bf15000000582003232da615b5c033fb8d0072d39388965f67cfd9d0dffb0a0d3ba04e20168eb0481b5723013f013d3a0058200328a345034a2aa0ec6b4a56d2b1342ee46f38caf2955f4a47fa965abcf9e2604807588688d615179a021902220385a752ad25004391b2e3475e9818c51e06c1a358e3956514a4066fb56709450500582002ae75c1fa39d39eeb533514396d24cc991109978850263abf852c80bd46c60d481761df2327e5224c0219fbd60058200258223ec9b1cd060e6e88ab61ffda14222c82576a2ca0fa217f4daa57c732345820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00582002ce2b00e67e51b88f13a6faf65dd6bc62d933dd14c707e6edfe4e50b89b59715820ffffffffffffffffffffffffffffffffffffffffffffffffeae8d50aa59cd3e0005820029fe2a24cce1cd41ed05437c7cbb96a7b0f02c9ce00289d521ddf59df9f60ac5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03ec97e91d18ced98c4f01522a83906b723bd4bb6d7ecefdc2cadf056f1ef9863800582002496e4d7297e0f840d838f0719c8eb46740450f6270da99726357c7a43a983c5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0058200274a36c3cc810528bb60d34384c32d51c09471d004373bfd0026e8e123497925820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0314ed59207e71e8cbd5b0d38dd79fd663b25607297280ccf7d34b7fb7929a5c3800582002ab67acdc83a12e0782c15bc4edda80c36929b792dfc9f0fb145cdfd10857685820ffffffffffffffffffffffffffffffffffffffffffffffffaa9621c7818547ff005820025f4baa35c70a3bf96cb82c8401f63c407096104600027e70f86be6ab32aad5483c527bd8a08019a70058200263acd56fcb2f339fb3f7dedb16a10f2aff189c738a182fbc77cee7ab5608a8481e581d85cc30c3fc03e31416e8abf81feb5b727d7831fa6e9b25289a671a142053739b7835283551440219f66e038fb6b20c4f0d9e12c5d002661372e3b5056893bb5d49ccc36d760b85865c63e001410400581f02d5bf52c97786f6d49f6f5af64808f5c05d84076b253a3de803687a314a0d481def1d7d83b212ba00581f02a5c4a82f2f74ffabf6e51c5edc80dce67d50982c0c165e3df2feab8b12e35820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0219100200582003ba592b733732b5db407258ebb5f6a11046235e770a179ba85aea1fc8ce4e905820ffffffffffffffffffffffffffffffffffffffffffffffffaa9621c7818547ff0058200393956e6942405ee084e8f6c832ab5f163ceebc85c317e8948788711f0542b05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0219880400582002327f18f1218f10e5564604e6d6600edb492beb431010fa198bd26022b6dc145820fffffffffffffffffffffffffffffffffffffffffffffffffb7b8c0e13e5518e032eedc573beedb59e9330c854fa29681a521acbba2288950aa204f0839c4412b9005820028a8b5e3d47b1975e30e71e509c5f9fd93fe502bc38ae124317652ae0d182885820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005820026cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c6884118005820021d605fb637aa8c7717aacaacfc49ecdf5769d052093fd56e4aceb4ee7d0c0e5820ffffffffffffffffffffffffffffffffffffffffffffffffaa9621c7818547ff005820029e766b2c698518b98289a405279a4de84dd6049c5fd4f813357bc1c8d1685e5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03d1c19c44f8e052c279743d3fd7a358f7d39ba32d90a75c475eadd159c9e5cdfb005820027bc50f9564b90b48c4a65061f356ccb7748969d2f2c7aadc9d031f3623b51d5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff021954df0311b8fe0315e564bb30976c75a52bf2f67198847f07de5384a1c6d877862a6f6b03bcbf5bb710b84a303bfaf20a232a23452e0ece6c6b9a0d5952ace144304d302d037df575970351f0066f502bb0d6caf8b56ed74385c5fe0a3615f8928d79f38a0103b646890cbbc31e7d49846d22d5688238637dbd95707d83bf4ad7c6ec6a39944a005820022f7a27493622f61e3365374a4b634aff849db10c60fd5427d6aac4a047cbc8480d8f7d2d8c069aa0005820020f4b0cddebca23d2ed385dc7e32cfe59a1c0e4e1c97fb80d935e0fe34712d65820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581f02172cdd55898a22955a9a0bf9d6a3758d8cd8106521105c3f611f0f4839dd4817b084268bda1da600581f024a454dc3493923482f07822329ed19e8244eff582cc204f8554c3620c3fd4916ce3f1e16bf1500000219082000582003a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec34440547a250d5630b4cf539739df2c5dacb4c659f2488d02190180034abb82ee5c29edffd45e070956f328bf02b4216fc5c37694dd75e4f711ff53820377995c8950a268b647f0ca6953df0e080c9f78d6fb51ace8028d8dc8dfd234f10058200218b5cbc40af393a10dbe47c36bae4843a3ef36c8129535c1c7096168e6be895820ffffffffffffffffffffffffffffffffffffffffffffffffa92a820890902aa70219be1e0339e20ef5060e7fd3dd24308efc8e6e35150efdbcea62054f41a213c021f45faf03f842b6ac46dcfa019f4a3e561ad924d1b2d26087f6026da5251b82ce67ef529d034ce8525f58614a21f7ae593d6fba34483581cd0f66aa21f66c783fbba535715a005820026eb630cb900483fb16f5cab455ea908fda88ac470ae123e060320544e2101a486d558200aeb3acdf0058200273550101a06d06d5938f124853d15593bdf0736ad246da599d0e98e6c6188e4803cf5b53b3533b24005820031ed3604650b2aeb65f74a72d2bab8f72eeedaac2dde3d554b57f413b8aab404801fb17a92d5c32c9005820033a7de1166cb53a7600f7f9f52a3a49a00c5d436df6773c6d20bb1679d33f605820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00582003a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a80411402183803b91326e6a2c9fa047c8bd798ec24236687ee90f1cc5d50ba9c7f9e69798324ab00582002005d929aa278bdcdcd39219e13c566064d0561a8b8a8d582267f88589cca36480f6d31dd3d2352980379bafa2283069cd505dd6ea8267925bdec7f80721e379528c51b2bd5d65f840c034ec13858d2fff1f311b6d74083da8a223c8b0e917a7171d3cd0c29743d10297e030a9397b9c4e5e7d21529d108fa5271073f7d62a476f0cc1b2507438167d4de42005820026d7b5282bd9a3661ae061feed1dbda4e52ab073b1f9285be6e155d9c38d4ec410102196eef00582002d3e948e6a5b56dcc9b4a7e103ac01e6b059cf285482de8fcf963c25efa2f7e48013cb4ef34e4e36603beefc61f2bacba252b4d25fc4c4edc3e6014175d31d78bc8d528e4519b2a974d00582002d16192d0a6798e6197663e1f89ada19483a35605ee10b968f08fb595d373925820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005820036990105719101dabeb77144f2a3385c8033acd3af97e9423a695e81ad1eb5041cc00582003710088d32e4994e46679566ebfa9500c14ae78be6b520a091f2e6cfe143850480b172ab904f4cd5502192800005820029786cc93ad8f4f91813264000043a054e66d1caf2b2a86e8fd975c8cabfea249013234647c72fce12a031db3ef4b0c75d2229bf08141ace300d5c7ff3908ff9d36cd6a0d59e352c913f100582002a547f0890839bdcd4fc4b7c095db256dd585339efed8e975a8d8e139c44eb4481018d652aafb29a100582002834e2cd19284199db710501916e3b1b362ab824580275d16eba52b782f536a5820ffffffffffffffffffffffffffffffffffffffffffffffffc52a055048a39e9300582002768a983e211af9d796a60f857a3644c23d0ce4d297bc698ac8ffb79c12492e45010000000000582002005169355126d939da883c623da688a0969e2222ea9815f3710ed05dec2289482154bf545a4a471803524e3202caab6f56fa6441c66caae9c37955b753c46d43907c5ed605af8a15f00219fed103a519b11272c1bff29c32df6014f39228cfefc8a073b9b1cf5fa4a4a78006715c005820027f48010d796c29b5aad4fc5c152f344d68cff517aaf3a0996cf7904d4751b3482608543101313c2200582002aa4d51f69eb291c140b6f9648f4b61562e4e404d95962cfc5765453118372a5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0058200241aee9229b07ed4a400d1a4eed9976238604d36658acb418ba0d9cb2b27e8e48778f0792e84ce2ee00582002af5c5ddaee0bf4a15c3d1893e01ca63c840269303f9e66f7094e75c6a352b45820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005820024142445b2301e4ea5611d5eff4ef981c2a07acb2f2187a32467d78967ad327480852de72071daaf600582003880657d58a62b40e3c027ce8416fbc66be9c9bd6bd42147e86f83511295a405820fffffffffffffffffffffffffffffffffffffffffffffffef9c83cc5b48dd83e00582003560cffe9526d74d63aad97dfe4aef6b7ce300b64c7455023396b3491c352c0490215a2f25a7370a09802192200005820033ea59b58fdba855a6d190cf5caec5f93aaf864767b0eb6a809c2c132afa4c0480f581fa2423bb638005820036e570e2f67c3027e9a44bb16d8755d39e52de9fa7f65d34e01103a2d169e405820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0219400402194537005820025a586a4be78b8487e653b2ce51fbf634a768207dbb99eae5de4249f1b9d0365820ffffffffffffffffffffffffffffffffffffffffffffffffda71d1de03e7b7da035d1555ae00077dd648143826ad3a913a9df60796dabffaee1bd8ed3beb46b3c70399e7a91a6ea447ac45fff256751ea1dfc09fb45007ed19ebcc282abb48cb935800582003d5e7dfe2872d00c84c6ded1428c95a5f1705f3fbb70c1b69b3bf25b9ca66e05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00582003be6f2cd22d3f34de208228f5d6491b41f0804c063d65af6d91b80283c98a30410102190801005820027cee98442ecc82d8b2fc9984bbe567211a7c044a168e7e956ee5d8eca2ba3f481ae98547ada32e3a0058200215e13cd9a0736f20dff93fc118b1c5b5b8c250776e1e58e0132b32350e09505820ffffffffffffffffffffffffffffffffffffffffffffffffaa9621c7818547ff0304e7710c47796243b8124fdfc4a9d5789acdaa9f5e0acdb1520683c00d9e249600582002d5365ea1cf3c3d39d64a115840ccd8cf9b55a3900746c637789b2bda5b2b155820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00582002b85d58da4ed176bec88101a805a50d36dcf3039a157fd91fb47c9b269e36e55820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff032e9f907bc5708238e7e8f8042e7b4281eb5b1f7126fcaae1ea5e46b7416d176e005820022319175196c6c661f7dc69acd2dae315270660e5b82d974d3927861a0f935a4802c68af0bb140000037a7b8f7d64d739352c13ebfb7ac406872c213eb7b4114c8835ccc9d607f13521031c3354ad087b5ec1b99fc19cbe4bdaf3d8fec168e6347368763f3dc963b9d1cb0219b9ff0219ffff05581d036a101de4a9179825a19e13659e6a8011fc585d9a332b0a91c55d8bd007011933cb035b83bdbcc56b2e630f2807bbadd2b0c21619108066b92a58de081261089e9ce503d269c60893f40cff1e32d7b1ee8d3c5c1e5964a71924b5ea49b1c5fd7f23a52905581d031789bf6cb26eed1e8b3ea56221825a9a063fee0853cede07567c520007011bffffffffffffffff0218a005581d02e7824bdaf0cd934e9b787c487e492997289412ddf18c36543bc19a370c04467e679e70b9d802190180030ffba86baaf43f22a3c9e6955d19b5d1941aadd2526da81285b43a5b06039bae05581e039b386b408462161eca34e768554d18247a6de3f9dfa2357f127ef09ae0040102199d5d03579eb1a0c5fda29de507f6ccadb25a1d9e40feeed1155e6ee160345f4bb36dce034c0f88f6a61f35409e3bfd6386c0f231ff1326ebf7ad956106c1d4b3a57eb704032cba1eae703deaa8a59d070015c15615f90a5035bc46bb83c149733de25359cc0377c271c132870a0c67aa96e8c294f2452b80ac9422c50f1ad883c6c88b478fdb032f7cca978d052db1905e4412a490f6df4a1415bae9f7ac62f8ed67abb51620970392daefbadd96e0f7bf14ae8dd638700dc283e6d07b90d8ab72271a8ecc87587f0318370b4bcce959f2b92e13554182edbd205fc43a085090b8bc76520f10f157e70392f9ce86473b8dd6ce65badbbba5ef9dd1b3bbbbec130fcc0cf116e34076e50603b91eceeebc4bcb6c63ce5c33299db60b7e4ec1746d0f1fb2319485a779788e68030588bbb02468329de2c189793cadb6bff1c1473d61180d3421171361a0b179d0033eb9fb02ce8cb36b9730882518128fe21f35822d6d27ff4cb1432d01d30d83c603016829d72741b5c4e696abf5e97b3eb0ef26f4d43e5a0abedf42f03aba85a7d103787876d959fc11caed534a7ff6ffaad32bd9cf10482ade3ec346e9ecc0f688b10327cb206460a3f405532a4f9a10364271fa30c6fe828e323821d39b521be6b67d0219ffff0391a12cd7409df898675c4418e51a7c6ee68824b9ffb530902009a1ae9fb2584a03075dce743e152e052f1d455abd9b05c24a4ae1eb09bc014a601e074a7e4ad8e90360abda94d0df1ec0acaac7a9634066c7d0e0f1a1e5e665013ffcf9c73aa16dcf032a026ae2ae6a04edb8f9eda104ed855bf7b9f147cec70c74b85ef40d1ce084d603f5c2a0e4033a23e388662d5443ab193dd15a49bdd2c6270094124e26ee2ba245031108860c6578591c8ab169774409a78daf2a3d3255c457c048e40234f337d6a603d7fd24453059eca52a9ee481b65b572f9eb62bca2cb14d11c67e49e8cff27185037371e732b040f195f6f43304dbbe5cce11365f46e7e305a1c5fb61d75247f12003f4c979446bd2c4df50c9a6648c0266ddacb5ea09424c794358d8d5dd6206a75e03f512eb08a40b6707e3c92265da1e517ff76245bdac0064752384b59f8b8c10f20219ffff038d84ffac5ced83925b7d25d4435341583fd92996e11c96c4b670459acae91c2903cbb03de33fb9c93b91bcc3e06ceab607a217e3d812a4967be3702c00efac481403bf512ee7a715bebf95014bddd6df800747dab07221266e72a06608cdaf8b604803276d5fff5722b14e381d8f7a7e1d69666c6fbea6d9b1606df0d73970be43f6cd03927062299b451ac2cde2a21636fceb63fce372945d608049da9366ad6a1a0b27034493bae9040f371425960a86181ee9459fb3ac44e438f745c709bb32b4365fc80219ffff0306b9d9597bb88c2a7e54a170820830e89fd66b952b83e8945de6619ee77d4f1d03436622163e4bd309b7eccd939228a3adf0e67d288873277b9e78e4b541e569bf03d9c575e36329371287799908185b427cdef2dc4012d6230ef61c9807e388e249037e8ce31eb7f2b3d015aff9f4db69e1417d3fd190d182e57431d61a035a87371103c5ff1b14dcf4f3ac7f604d78316bf1bb7adb0fbd55fdcde06c3dfdbf6d1ea7b10350d9e07c72f3f1f779ab0bea062811101f0ef247f8ec5b3e501cc5d497597d0f0302ae5747f5d2cf20f71b19466b11f9935c3abfb87915c5b8aa17b838cd036b270219ffff03ce22e0e8bd02fe2f895584073998da27559fcdb454695c1a7261f18c2bb7ee5d0393dcf20e65a10053f4be33b6ac52cb83fe7663137f14161417e900fd84be10fe0307ff7b9935fb7ec7d573d602ad01c7333bbaec414be46cd4c57f7b148ca043c40344f8e6e40359686ba47e772410682d3866fd074b66df440980351ca2881ef3830219ffff03d985500aeb04d748b77fdf3529ed53f5b96e5d62d8695e1cabd35e713b030ab20374ed45e771a90b9c346408e68a46b520365e5d5fa3e31b8e2397c8bb0e1a2bb903f1812756ceb667f0768e9bfd77e1dcdd1b70b51a7ac877fb9e3bf585b6320e8e0359df74959093d8bcbdae18a658ec4768b976093eced86a45eaa2938ec145664a031fcbf0e0b2c9801c6b95fbae34c93a4fba8916bc98d3b40c20ad9bc3f988f1ef03a791d6eb706b7a03ee89d4aec801ba1abe78ba0184bdf26a7393e963af02e3d903f1e04725d8666dec005379be2a598668daa4631deb2b61c66f60b31c0d666ec203832044ae9da4a60789aff925e579ea62c18d19add19fe9a75de943207ef836d103d87e272d291952c6a3aba1001b3e3bc351787b7f5c1f40075d5fba2a5e89a30903d9c1b14bf49390a6d9438255d7d40afb609662bdbc6b97dfa25dbb75ac8af860033efc65323efccdb204dcf0f1d4e071d9724cef9e754a3adb4dd94b333199ad8f031a9ca299e6ff942567b671f96b002aacbcb721831fb3ed19eb240ff286670d9c03fa968972e05d55fa5e86742095610f838c988e1aa33bee115912cd48cc05370a03e4c834dda4865d4352847e41eacabe7bc026c4da4cef1b80d5a6a2ceff60c11903f43bfd4e34de0e209000f6f2a1590bf0426f0bbd0e8ce227159a3aa616f1253603f68366266b81e531ae24279788bea5e3d55934dd98109295909015ccb3816bd403e6f7389981151a19524ea613e6a519a8d34052aaef594d09a25c69b7fc1ad676031ede4af18d0341de71353ffd896eaf9ce710f7656d434231e9ba88616a85050e0393d2e4bc8e2b87bc799706ba945c393a0ee6c842a20a34467d322f42334e10ac03eaecc2733ee91b9b4892774eb8819c27a3649de6eaf8ac96ef4145dbb2e95c89036c6af24ea6bc200566430d73604a32f32d7625e80758598a570069b765a7d15103778f23d77ab44cc721554dc9e941c59b4ff786a5cbb7cb6825e25e5ef06d65e303c43c0d36232b69c73fa67104219ca36496a38deaf51eaae544a7e112c847a36c03b99e6909083df3ec81d9a19d28e50069659b6b6f7a53a017df25f7fd5edab2a303c31216239384871581825c62351a89a895010848fd7b69838737408e533f74d4039fd1fb03269d90e32c78a6f32f292110656c63c62cfe97fec04ed386e2c4c4a6038ed1638ccd76ab0d2b7a278eef4dafc660bafa14915c9e0e8d82be4126616e9903ad5961095c525ad009365123e53882e75e7b6076cb6a4291cf3112fb010f82620305c14c02a73b21663459df1965e083c0e2b5c82cc6bbace9c5dc828dd0f438a30325560e7762f28c0b68b0ab23e7b4e0d04a3ffcc19ea363dfe49060afdae3573003e5d0902d134148bf46e0822eaaa001b26652ebad372cdb396c30ac560a4eafc603aa06cd35e038f80a907e86a29daeeb605aa805fdfeb2892ed2ecc30c5699d76c03d4bbaee488e57a11254fa86776740ba3f70e8b548861da474a1eceee71710227034005d5a0259649602499f395e7c8d68776d223f6dcbae17abf5b1b8e037cfcb4032123bb34a10b37ed16159b0756bc1dbc07e9e8da2863d31b78c355dde2d3123103f0bc15ae09b08ddb6e2cf6ade1f7c1944bd252b7f97ee96bfe23e907d1d7a71b03fa32fac57fcee6bccd2cb9876caa66647e085fa7d0afa02160c0f72c63a67a0103cdba035265b99fbc55bb8d9dd98d21bc0bd5ca643abdccc24b94001962fa312503c6932c4a78ce64337acc9de5887ba1a18d1203c7a5ad40f650b662b3b52e3a4f03fee5b9354f7afa3b7926c49e77d204b4c687d1de0a6ed5cbc60d01c24b2a773403a1c1f0e0e3b9fc91e4ccaab0529836713baa4e2262c24de893185f24d1b3b4ab031ba5342dc7ef049089d5472d2709cad094a2aa69077cfe6ec32eb8d5be0552620357a94bebab69c48d97717ef7f9c091c8c4fff761812fb43dd9dcc72574634e7303d20f80191e58a26e4b72dbde7d20b494c7c955967cef2fa17d412983023c127003fb635ddcc5bd902288b57c6d190ff1e0befec811d0f452dfa0ef329edefeb0030362f10d279ce64a2967891eac21fdb25bc32459e999e5c3a8234c0dfbc437d2600305bba7e2adb1af2607a51fcbeae91204d8713c2e3b7913fe5e3a32d33149185a033d4b58695c4ef858e16b907987c43ab32d3d4b01611fc12b2cbc8c69aae34406036b09308b2f5ff7329af362444d57e70a44b9998e69d1e0c2de12f15690a941a603a2eafbb8df8f6a5789e9a39f131cfdc75d65d0426d1fdaef88aaab5e1ad513eb034d7d7549f2c4654e772c170f01fc80f34a60463d7f5993c57069f690f09d859703cc92cc79decc6471f4b751bc0ca0ecdd3c2b12c28aafc100405ddb1a589fc66c03e4d66c6365e4d8425e5f4b90985ebec8bd4e9ece873e9b9d5ac10ce479a49d7f03034ee1eb117d05caf0d3874025311bf768f13f9b2ef7ef91b2c216b94d9464e003b991cfe7777d20c44e924b20c3b95d48944e27014ade9ea4ecdbf11213d08dfc039f7f98c70db192293bda936097b12697b00a30955e4669264da63e728a4fd4b80325ce7fcb4d0f24bad6c80d3bdcd1aa759bb064f6b90d74dbc4c9fee7d68ab43103cb942b3ba64b0debd84c6d35917bf94fc685703fd82639ac3fa7f5a3bae2d5e70308a93def30c398f4e67bfc028293514a33225618616a0776ea0bd11a7d77678c03567309f3694e20d08eb905bf2cc396cfede8cc06aa11c765add2013e62eaa55603f88c60914869cad3501f881b02bf278caa7188d19c94a43e3df52eb026e1323503b34002a9c8962b973709ce8518d1195888bd047d9d1f10f61217be7a7834db670318f218c6f0601ab4a3b5f123233d3c9828646813554c9f31ed2c21fb04a4bfc805581d026da1572e969498d2aeea7890749e291e3428439d62b97a9112bcd2f70c18404783ef33385aeac5031af4fe3189f1941a1e227b6d38354818cf82e57978a7bf9981911f0ef35fd5d505581d02979b35fb846ebe67a2ee86ffa3b044acc5629bc837f590c6801592470c0645012a05f2000219082403278fdcfb64cff6b6a8edb8dbe115ce42d7f1ad26d01bf944d929bd00fd5cdadb05581e03471313e3dd8816e34201436699edc90918142f363b4f358abd93a7e2400c05472c84cf74b75230038302ac6d84b493efc5789f7626f30a186f1970b65ace5f5bb5dc82d0045bd21c05581e0376327eed36c4af62b611494701a64ecb75b54417609bca443d25583c700402031a573896010463741f44a9e53c7dc1d07b66dbdcfb756b68d716e54e968263230335d419e98b5308eea4be2e9af37c934f7db1c679b57498ec0372f3a5e9782bfc0302591673bd9f1a2df27d4e5c0fa507178045d7a767661eac189e0fcd332068a70398a00fe4a9347a33bbada3aa60eee6ce58f11dac052cbde4008ade269ac7cdba05581e03830c219d3451d96b97bdf300a3b88072ffba2545956ef8cc07f6a1a56007011bffffffffffffffff02198bb20219ffff036cae310dc347d0f1b2c9f71d9d3455e85783411a10d73ea0a1657943983bc4130219ffff03e62918726fe53651863b398b7d280a948e18f797df2df91d81c52f5bd9c2bb650219ffff030a328b4bcebc9e6af48f4c8dd7d6c82a255b432c0457eb625b2341d99eb428da03e61e596012413bbc10e2364c6383a9a373be32f5344ffe4c49c9d8e7f0133ccd03ee90bd82b2f384a933f1841c528e6ce163ebfa1cd1dc2e68a0362ead3703836003ec9db5e28ec1b4cbdeab06db0c7f1e3318c35661f5aebcbcb72955bded7c5244033e82e48cace172f9ede7867ee2a3e335c0501d72ff644bc3132f79e08b72db4903416a5c6720b6a27fa1ee6153b1c4693bf9a7011a1f672f6a50bc119e5d5d28e303c74aaaed70f29903d95047f75565ebdc86284bc1006158d469f8f00cddabca9303783b0a8d0cc00c837f2646f608d77a0126f6e40886e34fdb09a8ba43cee7f4d00219ffff0350d9488c8b2eef2bc676c7203c6ee1cdf00eb12d4b8b3ca97f372f52397b452803513b3cbcf8e5dcfde4f0b1a986f1952de9b0732a5197fac4f7befd5ec9a47d360219ffff03d3427ebdbc516db4f8ce553f34cbc557e7cd57611179e29ddc53b1dcf8ad69c703345cf3eccb164b180e73bb2246cd67b4360856618c81557cc40f9653c0b582bb0381ea7f05444ea211c97bd82797dbe729f0e304e4022965acdf887f695752e66303eccd85d47406a08d88690764ddb96460a922ae92e877c2e744fb634707a9967c035386a932fbd7fa2a93f3135daf702fdca185f266a6942aae08d7fe1de2d99f9603e345f5a4c4c6a631628c369e20d8398ce73392638cf8082360b460ebd532b8d703a2f1f51124caacea9a6c470d360ef751e0f4e85fbe4ad714e6520967cb1c7cc2039707f03c56818da080eb3faaa395f9b4137f6923db4a996cce2bab36b56117e9034f73cee25a48791596a34b406fce65158bf9b6b6f174b15fb9845fcb1122a29e03c835dfb8b6526e52bbd52570d0b4b7c9d4d762d49f764e709d29ce2aeb0122d303a4c849171da0cac4b5ddd2fee18ad25f5f827db7713896c90c2f237c497431410399f82e838bb52daf79aa310078e31adb5ee59b51ce2bba2c37967d4c90af88c103b0c912339bb39f60578e70f72700682c9aa101d5263e914eaef126e8c96f610203a0425eeac624b2862168e6b0eeef819bf535191e9a9ec19e88978d0d8c28fcbf034e0d8b09a41be87bb0eb3d0d0710929ab7d2fd19bc1dadaa4a1c4177b6e09ae6035f8779eba8ecb7358928d25b9cd855ccd8df417a0a5075654e189a6db129c0f8031d4628e211da367227a23ed36650e830d55d8f2745bca6ad9d34debc6f3ef1d503720640896ab527d6b6ba2b2cf776044eb2f66baf107c968aa9a9457dd6ed05ba03569812b1252887a1c6c10dc083a79a5199d3d8e90069e18c8da94b05c2f9ac13036e2d0bf8dc6e674c0591c720eb9f2f38f77fa3a356bc0df6c8616dbe91eafb87032f954665c63fa1f176e9761e51070de59bf20347f5f5cf30665d3921ea50e45503a2732e7ea3395ce910157e40e3c7cfc53b745458ce4c84ff0920d614fc3ff00a03ec781d7a2f1f305c5b21e32fdd5016e42aa4b361855cf803416f5aae057e975f03909646b313b078a40086a6414a4c3cd9b4594d3544d160df9572f8c580c8f70c03755ceac27a6c81ee7b10f41cfe33ffd31b6bc34cba389f6df23e5569fbb90c1403ec5e98e14dfbc90eb46fd51562e6b148ac5ded713c0a94f730e5787d54f252950326a8b434661dd7f751a0106d730678ce3805fa572295336eb8ae3c0f952eeb050317b4c10d2ba669947a31a3deb5c328775561fa60515495d3b12f04788fb483a703faf9d803ab56773756298c25904854f89a5ba23851798b8536f50ecedd04587303ec3e90542f1b2073ac50ad0a0416650c44ed8a597291670fbcb9242ed21c6d99033411ffbdb84e6d4e59f2a7df103f50408914066c73f3f6f53632c27635eca204035e1482193dcc977cf58e7108ce2b32587dc42ea8ec379fe7172ec7f74da5442403622483e9dfe82dfc1d4a81adb651ec6deaa031dba8ad452643fd6ddc4b369fdf03776d40cebe5359b85f62f9170889a8aa9a516ef5342b8d6323f0420b40483d0003900ee424e0ffba5f1f83ef94b59592b63fc7393635d57de49b81a3c64d5896440384545919f1d8aa7dd6f919584d3c8cb85ff96b43ce464d71ce1d998340a226fa05581e039e91f3985aa9356f17b1269ffbea7276bc875c1f87632a88b1df07ee300c0147277af8187b7cfc05581e03df83fcf7887910a3fa90b9b14c8650855296c438aca012151c87b173f0040105581e03f1b426288cac2eb41e81d7068c9b47936f232505ab58f39977656f365008480320db8cffada80005581e0315bae696893ace6979b9463b8af411e9e7ee73c6e6a773ccaa74ba26d00c18224758e3312d5514d803702d223c6e51ac63ff8ad1620c65e768a79f24c12c9e82a52b20776e7ca189f205581e03c7bd9bef4670c155a21164dbbf8528317624038383c62aaadf8687efe00c014609184e72a000031271f1a3c827e8250ce21f4c5501d4b57e4c30e3a914445a92447b9aed8a04a705581d025a3c668361a652c79f070961fac3e9dd7eed911a6d6eca2b37de71a704020459462660a0604081815260049081361015610022575b505050361561002057600080fd5b005b600092833560e01c90816301ffc9a71461093d57508063150b7a02146108af57806324856bc3146107e85780633593564c146106b1578063709a1cc21461044f578063bc197c811461038a578063f23a6e61146102f95763fa461e330361001257346102f55760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102f557813590602435926044359067ffffffffffffffff918281116102f1576100db9036908301610a97565b919092878613908115806102e7575b6102bf5783850186868203126102bb5785359182116102bb5761010e9186016136d0565b5060208401359373ffffffffffffffffffffffffffffffffffffffff938486168096036102bb5761013e9161415a565b959097602b89106102935786359260178460601c98019561016d62ffffff883560601c9660481c16868b614365565b3391160361026b571561026157508186105b15610197575050505061019493503391613ac2565b80f35b9395945091929091906042871061021b5750505083601711610217577f8000000000000000000000000000000000000000000000000000000000000000821015610217577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe961021194019161020c33916141b5565b6141e2565b50505080f35b8480fd5b91969550929391508454841161023957506101949394503391613ac2565b8590517f739dbe52000000000000000000000000000000000000000000000000000000008152fd5b965085821061017f565b8483517f32b13d91000000000000000000000000000000000000000000000000000000008152fd5b8382517f3b99b53d000000000000000000000000000000000000000000000000000000008152fd5b8980fd5b8286517f316cf0eb000000000000000000000000000000000000000000000000000000008152fd5b50888813156100ea565b8680fd5b8280fd5b5082346103875760a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261038757610332610a2b565b5061033b610a53565b506084359067ffffffffffffffff8211610387575060209261035f91369101610a97565b5050517ff23a6e61000000000000000000000000000000000000000000000000000000008152f35b80fd5b5082346103875760a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610387576103c3610a2b565b506103cc610a53565b5067ffffffffffffffff9060443582811161044b576103ee9036908601610ac5565b505060643582811161044b576104079036908601610ac5565b5050608435918211610387575060209261042391369101610a97565b5050517fbc197c81000000000000000000000000000000000000000000000000000000008152f35b5080fd5b50346102f557602090817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126106ad5783833567ffffffffffffffff811161044b576104a1829136908701610a97565b90818551928392833781018381520390827f0000000000000000000000000554f068365ed43dcc98dcd7fd7a8208a5638c725af16104dd613675565b50156106855780517f70a082310000000000000000000000000000000000000000000000000000000081523084820152907f000000000000000000000000f4d2888d29d722226fafa5d9b24f9164c092421e73ffffffffffffffffffffffffffffffffffffffff168383602481845afa92831561067b578693610646575b5081517fa9059cbb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ea37093ce161f090e443f304e1bf3a8f14d7bb40169581019586526020860184905294849186918290899082906040015b03925af193841561063c577f1e8f03f716bc104bf7d728131967a0c771e85ab54d09c1e2d6ed9e0bc4e2a16c9461060f575b5051908152a180f35b61062e90843d8611610635575b61062681836135fa565b81019061388d565b5038610606565b503d61061c565b81513d87823e3d90fd5b9092508381813d8311610674575b61065e81836135fa565b810103126106705751916105d461055b565b8580fd5b503d610654565b82513d88823e3d90fd5b9050517f7d529919000000000000000000000000000000000000000000000000000000008152fd5b8380fd5b5060607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102f55767ffffffffffffffff8235818111610217576106fb9036908501610a97565b91602435908111610670576107139036908601610ac5565b92909160443542116107c0573330146107b1576001958654958773ffffffffffffffffffffffffffffffffffffffff88160361078b5750509185949391610782937fffffffffffffffffffffffff00000000000000000000000000000000000000009586339116178755610b54565b81541617905580f35b517f6f5ffb7e000000000000000000000000000000000000000000000000000000008152fd5b90919293506101949450610b54565b8585517f5bf6f916000000000000000000000000000000000000000000000000000000008152fd5b50807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102f55767ffffffffffffffff8235818111610217576108319036908501610a97565b91602435908111610670576108499036908601610ac5565b9290913330146107b1576001958654958773ffffffffffffffffffffffffffffffffffffffff88160361078b5750509185949391610782937fffffffffffffffffffffffff00000000000000000000000000000000000000009586339116178755610b54565b5082346103875760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610387576108e8610a2b565b506108f1610a53565b506064359067ffffffffffffffff8211610387575060209261091591369101610a97565b5050517f150b7a02000000000000000000000000000000000000000000000000000000008152f35b849084346102f55760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102f557357fffffffff0000000000000000000000000000000000000000000000000000000081168091036102f557602092507f4e2312e0000000000000000000000000000000000000000000000000000000008114908115610a01575b81156109d7575b5015158152f35b7f01ffc9a700000000000000000000000000000000000000000000000000000000915014836109d0565b7f150b7a0200000000000000000000000000000000000000000000000000000000811491506109c9565b6004359073ffffffffffffffffffffffffffffffffffffffff82168203610a4e57565b600080fd5b6024359073ffffffffffffffffffffffffffffffffffffffff82168203610a4e57565b359073ffffffffffffffffffffffffffffffffffffffff82168203610a4e57565b9181601f84011215610a4e5782359167ffffffffffffffff8311610a4e5760208381860195010111610a4e57565b9181601f84011215610a4e5782359167ffffffffffffffff8311610a4e576020808501948460051b010111610a4e57565b919082519283825260005b848110610b405750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8460006020809697860101520116010190565b602081830181015184830182015201610b01565b9192909260805282810361350d5791906000905b828210610b755750505050565b8382959394951015611b4c5760059282841b60805101357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe19182608051360301821215610a4e578160805101359767ffffffffffffffff8911610a4e576020836080510101988036038a13610a4e57606097603f90818989013560f81c166001976020821060001461317157506010808210156127b4575060088082101561187e57508061109157505050610c2a908a614198565b92909860a08560805101013560001461108757610c6173ffffffffffffffffffffffffffffffffffffffff600154169b5b35613854565b9960408660805101013585829d927f80000000000000000000000000000000000000000000000000000000000000008314610fcf575b50959c95505b7f8000000000000000000000000000000000000000000000000000000000000000811015610a4e5760428610610fc85730915b86602b11610a4e578d91601783013560601c9083359462ffffff8660601c96610d1573ffffffffffffffffffffffffffffffffffffffff92839260481c16868a614365565b169084881015610fac57806401000276a4965b602b60405199604060208c01528160608c015260808b0137600060ab8a015216604088015260a0875260c087019587871067ffffffffffffffff881117610f7d576040948288958688527f128acb080000000000000000000000000000000000000000000000000000000087521660c48a0152868a1060e48a01526101048901521661012487015260a06101448701528160007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4088610deb610164820182610af6565b0301925af1928315610f71576000928394610f2f575b5050610e159310600014610f2857506141b5565b9a60428510610e5657309085601711610a4e5760177fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe991019501949b610c9d565b50985098606091969597949392509160805101013511610efe575b1580610ed1575b610e8a57506001019291929092610b68565b90610ecd60409283519384937f2c4029e9000000000000000000000000000000000000000000000000000000008552600485015260248401526044830190610af6565b0390fd5b507f8000000000000000000000000000000000000000000000000000000000000000828501351615610e78565b60046040517f39d35496000000000000000000000000000000000000000000000000000000008152fd5b90506141b5565b91929093506040843d604011610f69575b81610f4d604093866135fa565b8101031261038757505160e092909201519190610e1538610e01565b3d9150610f40565b6040513d6000823e3d90fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b8073fffd8963efd1fc6a506488495d951d5263988d2596610d28565b8b91610cd0565b60149192501061105d576020602491604051928380927f70a082310000000000000000000000000000000000000000000000000000000082523060048301523560601c5afa908115610f715760009161102b575b503880610c97565b906020823d602011611055575b81611045602093836135fa565b8101031261038757505138611023565b3d9150611038565b60046040517f3b99b53d000000000000000000000000000000000000000000000000000000008152fd5b610c61309b610c5b565b6001819d969d9b989794959a999b146000146111b7575050506040926110bf84836080510101359382614198565b608051840160a00135156111ab5760606110f273ffffffffffffffffffffffffffffffffffffffff600154169435613854565b946080510101356000557f8000000000000000000000000000000000000000000000000000000000000000851015610a4e576111319361020c866141b5565b9091901561119c5750611143906141b5565b0361117357507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6000555b610e71565b600490517fd4e0248e000000000000000000000000000000000000000000000000000000008152fd5b6111a691506141b5565b611143565b60606110f23094610c5b565b9194929391600281036112065750505061116e925073ffffffffffffffffffffffffffffffffffffffff600154166111ff604060608560805101013594608051010135613854565b91356139d0565b9193916003810361157857505060805181018084019390604090850312610a4e57823567ffffffffffffffff8111610a4e5782608051010192606084860312610a4e57604051946060860186811067ffffffffffffffff821117610f7d57604052602085013567ffffffffffffffff8111610a4e57850160208201809882011215610a4e5760208101359061129a826136a5565b926112a860405194856135fa565b8284526040602085019360071b830101918a8311610a4e57604001925b828410611513575050505085526112de60408501610a76565b956020860196875260606040870195013585526040846080510101359067ffffffffffffffff8211610a4e57602061131f92611325966080510101016136d0565b5061417b565b909173ffffffffffffffffffffffffffffffffffffffff600154169473ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba3163b15610a4e5794929391906040519586947f2a2d80d100000000000000000000000000000000000000000000000000000000865260048601526060602486015260c48501935193606060648701528451809152602060e487019501906000905b80821061149a575050509461143e9285949273ffffffffffffffffffffffffffffffffffffffff600098511660848701525160a48601527ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc858403016044860152613537565b03818373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba3165af18015610f715761148b575b50610e71565b61149490613576565b38611485565b9197965091929394602060806001928a5173ffffffffffffffffffffffffffffffffffffffff815116825273ffffffffffffffffffffffffffffffffffffffff848201511684830152606065ffffffffffff918260408201511660408501520151166060820152019801920188969795949392916113d8565b608060208584030112610a4e5760206080916040516115318161358a565b61153a87610a76565b8152611547838801610a76565b83820152611557604088016136bd565b6040820152611568606088016136bd565b60608201528152019301926112c5565b600495509193508482036116e757505090916040606061159e8286608051010135613854565b608051909501013573ffffffffffffffffffffffffffffffffffffffff908116933516806116145750479283106115ee575050806115de575b5050610e71565b6115e7916144d1565b38806115d7565b517f6a12f104000000000000000000000000000000000000000000000000000000008152fd5b9391908051937f70a082310000000000000000000000000000000000000000000000000000000085523083860152602085602481895afa9485156116dc576000956116a8575b50841061168257505081611671575b505050610e71565b61167a9261453f565b388080611669565b517f675cae38000000000000000000000000000000000000000000000000000000008152fd5b90946020823d6020116116d4575b816116c3602093836135fa565b81010312610387575051933861165a565b3d91506116b6565b82513d6000823e3d90fd5b8103611714575061116e925061170d604060608460805101013593608051010135613854565b90356138a5565b9091906006810361184e57506080510160608101359060409061173990820135613854565b9282158015611843575b61181b573573ffffffffffffffffffffffffffffffffffffffff16938461177f57505061116e92506117786127109147613984565b04906144d1565b8151907f70a082310000000000000000000000000000000000000000000000000000000082523090820152602081602481885afa91821561181157506000916117dd575b506117d661116e94939261271092613984565b049161453f565b906020823d602011611809575b816117f7602093836135fa565b810103126103875750516117d66117c3565b3d91506117ea565b513d6000823e3d90fd5b8482517fdeaa01e6000000000000000000000000000000000000000000000000000000008152fd5b506127108311611743565b83602491604051917fd76a1e9e000000000000000000000000000000000000000000000000000000008352820152fd5b819d969d9b989794959a999b93929314600014611b85575050506040916118ad83836080510101359185614198565b92909460a082608051010135600014611b7b576118e373ffffffffffffffffffffffffffffffffffffffff600154169135613854565b908615611b4c576118f385613a94565b8760011015611b4c5761191561195d9161190f60208901613a94565b90613c34565b907f96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f613b2c565b938481611b32575b5050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff860193868511611b03576119b9946119be73ffffffffffffffffffffffffffffffffffffffff9687928a85613a84565b613a94565b16948651947f70a082310000000000000000000000000000000000000000000000000000000091828752841693600499858b89015260249460208987818d5afa988915611af857600099611ac3575b509160209695949391611a1f93613cad565b8751968793849283528a8301525afa928315611ab857600093611a83575b50906060611a519260805101013592613ab5565b10611a5d575050610e71565b517f849eaf98000000000000000000000000000000000000000000000000000000008152fd5b90926020823d602011611ab0575b81611a9e602093836135fa565b81010312610387575051916060611a3d565b3d9150611a91565b84513d6000823e3d90fd5b90986020823d602011611af0575b81611ade602093836135fa565b81010312610387575051976020611a0d565b3d9150611ad1565b8b513d6000823e3d90fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b611b4492611b3f88613a94565b613ac2565b388084611965565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6118e33091610c5b565b919492939160098103611f66575050611b9e9082614198565b608051840160a0013515611f5c57611bcf73ffffffffffffffffffffffffffffffffffffffff600154169335613854565b92611bd9836136a5565b95611be760405197886135fa565b83875283901b820160208701368211610a4e5783905b828210611f44575050506000946002875110611f1a576040816080510101359680517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101908111611b035790815b611ca757505060805101606001358611611c7d578215611b4c5761116e9585611c7892611b3f85613a94565b613cad565b60046040517f8ab0bc16000000000000000000000000000000000000000000000000000000008152fd5b90977fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff89019750888811611b035773ffffffffffffffffffffffffffffffffffffffff611cf7611d6d9984613a70565b5116611d2373ffffffffffffffffffffffffffffffffffffffff611d1b8c86613a70565b511682613c34565b819a917f96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f613b2c565b90604051907f0902f1ac00000000000000000000000000000000000000000000000000000000825260608260048173ffffffffffffffffffffffffffffffffffffffff87165afa9a8b15610f7157600092839c611ed1575b5073ffffffffffffffffffffffffffffffffffffffff1603611eb7576dffffffffffffffffffffffffffff8091169916905b9880158015611eaf575b611e855782611e0f91613984565b916103e892838102938185041490151715611b0357611e2d91613ab5565b6103e590818102918183041490151715611b0357611e4a91613997565b60018101809111611b0357978015611b03577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff019081611c4c565b60046040517f7b9c8916000000000000000000000000000000000000000000000000000000008152fd5b508115611e01565b6dffffffffffffffffffffffffffff998a16991690611df7565b611f0a919c5073ffffffffffffffffffffffffffffffffffffffff935060603d8111611f13575b611f0281836135fa565b810190613c77565b509b9092611dc5565b503d611ef8565b60046040517f20db8267000000000000000000000000000000000000000000000000000000008152fd5b60208091611f5184610a76565b815201910190611bfd565b611bcf3093610c5b565b92945091600a81036120cc5750608051830160e08101358101946020808701359450909291611f9991908703018461414d565b1161105d5773ffffffffffffffffffffffffffffffffffffffff93847f000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba31692856001541691843b15610a4e5760409587875198899687967f2b67b570000000000000000000000000000000000000000000000000000000008852600488015261202190610a76565b166024860152808883608051010161203890610a76565b16604486015265ffffffffffff808360805101606001612057906136bd565b166064870152826080510160800161206e906136bd565b166084860152816080510160a00161208590610a76565b1660a48501526080510160c0013560c484015261010060e48401526120b1916101048401918701613537565b03815a6000948591f1908115611811575061148b5750610e71565b600b8103612296575050506120eb604080926080510101359235613854565b91807f80000000000000000000000000000000000000000000000000000000000000008103612266575050475b8061212557505050610e71565b73ffffffffffffffffffffffffffffffffffffffff90817f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc216803b15610a4e578351927fd0e30db0000000000000000000000000000000000000000000000000000000008452600493600081868187875af1801561225b5761224c575b5030908616036121b4575b5050611669565b6122139460006020948651978895869485937fa9059cbb00000000000000000000000000000000000000000000000000000000855284016020909392919373ffffffffffffffffffffffffffffffffffffffff60408201951681520152565b03925af1908115611811575061222d575b808080806121ad565b6122459060203d6020116106355761062681836135fa565b5038612224565b61225590613576565b386121a2565b86513d6000823e3d90fd5b47101561211857600482517f6a12f104000000000000000000000000000000000000000000000000000000008152fd5b600c810361242657505050906122ac9035613854565b9073ffffffffffffffffffffffffffffffffffffffff807f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc21660408051937f70a08231000000000000000000000000000000000000000000000000000000008552600430818701526024916020878481885afa968715611ab8576000976123f2575b506080510183013586106123cb578561234e575b50505050505050610e71565b833b15610a4e57600091869183855196879485937f2e1a7d4d0000000000000000000000000000000000000000000000000000000085528401525af190811561181157506123bc575b5030908316036123ac575b8080808080612342565b6123b5916144d1565b38806123a2565b6123c590613576565b38612397565b82517f6a12f104000000000000000000000000000000000000000000000000000000008152fd5b90966020823d60201161241e575b8161240d602093836135fa565b81010312610387575051958361232e565b3d9150612400565b600d8103612681575082608051010191602083019360208260805101850312610a4e573567ffffffffffffffff8111610a4e57849160805101019182011215610a4e57602081013590612478826136a5565b93604093612488855196876135fa565b838652602086019285849560071b820101928311610a4e578501925b82841061261f575050505073ffffffffffffffffffffffffffffffffffffffff90816001541684519060005b8281106125b357505050817f000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba31691823b15610a4e5783517f0d58b1db000000000000000000000000000000000000000000000000000000008152602060048201529451602486018190528592604484019290916000915b81831061256f57505050509181600081819503925af1908115611811575061148b5750610e71565b91938395506080602091846060600195975182815116845282868201511686850152828d820151168d85015201511660608201520195019301909187949392612547565b81856125bf838a613a70565b515116036125f6577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114611b03576001016124d0565b600486517fe7002877000000000000000000000000000000000000000000000000000000008152fd5b608060208584030112610a4e576020608091875161263c8161358a565b61264587610a76565b8152612652838801610a76565b83820152612661898801610a76565b8982015261267160608801610a76565b60608201528152019301926124a4565b9294505050600e810361278357506040918251907f70a0823100000000000000000000000000000000000000000000000000000000825260208260248173ffffffffffffffffffffffffffffffffffffffff806004983516888301528886608051010135165afa918215611ab85760009261274e575b5060805101606001351180159290612710575050610e71565b517fa3281672000000000000000000000000000000000000000000000000000000006020820152908152909150612746816135c2565b9038806115d7565b90916020823d60201161277b575b81612769602093836135fa565b810103126103875750519060606126f7565b3d915061275c565b602490604051907fd76a1e9e0000000000000000000000000000000000000000000000000000000082526004820152fd5b9150915060189b95939897999692949b808310600014612d435750810361282a5750505060009250906127e883928261417b565b81604051928392833781018481520391357f00000000000000000000000000000000000000adc04c56bf30ac9d3c0aaf14dc5af1612824613675565b90610e71565b6011810361288157505050600092509061284583928261417b565b81604051928392833781018481520391357f0000000000000000000000000000000000e655fae4d56241588680f86e3b23775af1612824613675565b601281036128d857505050600092509061289c83928261417b565b81604051928392833781018481520391357f000000000000000000000000941a6d105802cccaa06de58a13a6f49ebdcd481c5af1612824613675565b919392509060138103612a3e575050909150357f000000000000000000000000b47e3cd837ddf8e4c57f05d70ab865de6e193bbb916040600080825160208101907f8264fe9800000000000000000000000000000000000000000000000000000000825260248781830152815261294e816135de565b5190606086608051010135885af192612965613675565b948415612a04578273ffffffffffffffffffffffffffffffffffffffff612993921694608051010135613854565b90833b15610a4e5782517f8b72a2ec00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9290921660048301526024820152916000908390604490829084905af1908115611811575061148b5750610e71565b505091925050517fae9bdf0000000000000000000000000000000000000000000000000000000000602082015260048152612824816135c2565b60158103612b4f57505090604091828051917f6352211e0000000000000000000000000000000000000000000000000000000083526020836024816004976060816080510101358983015273ffffffffffffffffffffffffffffffffffffffff968791608051010135165afa928315612b4457600093612b05575b5081903516911614918215612acf575050610e71565b517f7dbe7e89000000000000000000000000000000000000000000000000000000006020820152908152909150612746816135c2565b6020939193813d602011612b3c575b81612b21602093836135fa565b8101031261044b575190828216820361038757509181612ab9565b3d9150612b14565b85513d6000823e3d90fd5b60168103612c765750506040918251907efdd58e00000000000000000000000000000000000000000000000000000000825260208280612bc160049660608660805101013590358884016020909392919373ffffffffffffffffffffffffffffffffffffffff60408201951681520152565b038173ffffffffffffffffffffffffffffffffffffffff8886608051010135165afa918215611ab857600092612c41575b5060809081510101351191821592612c0b575050610e71565b517f483a6929000000000000000000000000000000000000000000000000000000006020820152908152909150612746816135c2565b90916020823d602011612c6e575b81612c5c602093836135fa565b81010312610387575051906080612bf2565b3d9150612c4f565b909290601714612c87575050610e71565b60409073ffffffffffffffffffffffffffffffffffffffff612caf8383608051010135613854565b93351692833b15610a4e5782517f42842e0e00000000000000000000000000000000000000000000000000000000815260805130600483015273ffffffffffffffffffffffffffffffffffffffff909216602482015291016060013560448201529160009083908183816064810103925af19081156118115750612d34575b806115d7565b612d3d90613576565b38612d2e565b9396938214159050612d7e5750505061282492507f00000000000000000000000074312363e45dcaba76c59ec49a7aa8a65a67eed391613717565b60198103612dd5575050506000925090612d9983928261417b565b81604051928392833781018481520391357f0000000000000000000000002b2e8cda09bba9660dca5cb6233787738ad683295af1612824613675565b601a8103612e2c575050506000925090612df083928261417b565b81604051928392833781018481520391357f000000000000000000000000a42f6cada809bcf417deefbdd69c5c5a909249c05af1612824613675565b601b8103612f53575050506000612e4481928461417b565b9390604094818651928392833781018481520391357f00000000000000000000000074312363e45dcaba76c59ec49a7aa8a65a67eed35af1918291612e87613675565b92612e95575b505090610e71565b73ffffffffffffffffffffffffffffffffffffffff608083815101013516612ec4606084608051010135613854565b90825190612ed1826135a6565b60008252803b15610a4e57612f2d94600080948651978895869485937ff242432a00000000000000000000000000000000000000000000000000000000855260a060c0836080510101359260805101013590306004870161380f565b03925af19081156118115750612f44575b80612e8d565b612f4d90613576565b38612f3e565b91949091601c8103612f8e5750505061282492507f000000000000000000000000cda72070e455bb31c7690a170224ce43623d0b6f91613717565b9193929091601d81036131175750506060816080510101359060409173ffffffffffffffffffffffffffffffffffffffff612fcf8484608051010135613854565b9435168351947efdd58e0000000000000000000000000000000000000000000000000000000086526004936020878061302e87308a84016020909392919373ffffffffffffffffffffffffffffffffffffffff60408201951681520152565b0381865afa96871561225b576000976130e2575b50608090815101013586106130ba57845161305c816135a6565b60008152823b15610a4e576000946130a486928851998a97889687957ff242432a0000000000000000000000000000000000000000000000000000000087523090870161380f565b03925af1908115611811575061148b5750610e71565b8385517f675cae38000000000000000000000000000000000000000000000000000000008152fd5b90966020823d60201161310f575b816130fd602093836135fa565b81010312610387575051956080613042565b3d91506130f0565b929450925050601e810361278357508161313560009392849361417b565b81604051928392833781018481520391357f00000000000000000000000020f780a973856b93f63670377900c1d2a50a77c45af1612824613675565b9499989a92506020819d9792969d989498146000146131da575050505050508061319e600093849361417b565b81604051928392833781018481520391357f00000000000000000000000000000000000001ad428e4906ae43d8f9852d0dd65af1612824613675565b602190808203613351575050505090916131ff6131f7868661415a565b96909561417b565b929061324160409788519760208901997f24856bc3000000000000000000000000000000000000000000000000000000008b5260248a01526064890191613537565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc878203016044880152818152602082818301951b82010195856000915b8483106132d357505050505050505091816132c5600094938594037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081018352826135fa565b519082305af1612824613675565b90919293949596977fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe085820301885288358284360301811215610a4e578301906020823592019167ffffffffffffffff8111610a4e578036038313610a4e5761334160209283928b95613537565b9a0198019695949301919061327f565b929750929593509350602281146000146127835750604080936080510101359060009060028310156134e1575050808491156000146134895750506000907f0000000000000000000000001e0049783f008a0085193e00003d00cd54003c71925b6020838251937f095ea7b3000000000000000000000000000000000000000000000000000000008552600496878601526024947fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff868201526044968792355af13d15601f3d1187600051141617161561342e5750505050610e71565b91600e7f415050524f56455f4641494c45440000000000000000000000000000000000009260206064969551957f08c379a0000000000000000000000000000000000000000000000000000000008752860152840152820152fd5b036134b8576000907f0000000000000000000000002b2e8cda09bba9660dca5cb6233787738ad68329926133b2565b600482517f5461585f000000000000000000000000000000000000000000000000000000008152fd5b602492507f4e487b71000000000000000000000000000000000000000000000000000000008252600452fd5b60046040517fff633a38000000000000000000000000000000000000000000000000000000008152fd5b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0938186528686013760008582860101520116010190565b67ffffffffffffffff8111610f7d57604052565b6080810190811067ffffffffffffffff821117610f7d57604052565b6020810190811067ffffffffffffffff821117610f7d57604052565b6040810190811067ffffffffffffffff821117610f7d57604052565b6060810190811067ffffffffffffffff821117610f7d57604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117610f7d57604052565b67ffffffffffffffff8111610f7d57601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b3d156136a0573d906136868261363b565b9161369460405193846135fa565b82523d6000602084013e565b606090565b67ffffffffffffffff8111610f7d5760051b60200190565b359065ffffffffffff82168203610a4e57565b81601f82011215610a4e578035906136e78261363b565b926136f560405194856135fa565b82845260208383010111610a4e57816000926020809301838601378301015290565b919290613724908361417b565b90938460405195869384378201906000958693838580955203918635905af19261374c613675565b9284613756575050565b73ffffffffffffffffffffffffffffffffffffffff60608201351661377e6040830135613854565b91813b156106ad576040517f42842e0e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff93909316602484015260800135604483015290919081908390606490829084905af190811561380357506137f85750565b61380190613576565b565b604051903d90823e3d90fd5b919261385195949160a09473ffffffffffffffffffffffffffffffffffffffff8092168552166020840152604083015260608201528160808201520190610af6565b90565b73ffffffffffffffffffffffffffffffffffffffff908082166001810361387e5750506001541690565b90915060020361385157503090565b90816020910312610a4e57518015158103610a4e5790565b9092919073ffffffffffffffffffffffffffffffffffffffff16806138cf575061380191926144d1565b7f80000000000000000000000000000000000000000000000000000000000000008214613902575b92613801929361453f565b9050604051927f70a08231000000000000000000000000000000000000000000000000000000008452306004850152602084602481855afa938415610f7157600094613951575b5092906138f7565b6020813d821161397c575b81613969602093836135fa565b8101031261021757519350613801613949565b3d915061395c565b81810292918115918404141715611b0357565b81156139a1570490565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b919273ffffffffffffffffffffffffffffffffffffffff91827f000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba31693843b15610a4e5760009484869281608496816040519b8c9a8b997f36c78516000000000000000000000000000000000000000000000000000000008b521660048a01521660248801521660448601521660648401525af18015610f71576137f85750565b8051821015611b4c5760209160051b010190565b9190811015611b4c5760051b0190565b3573ffffffffffffffffffffffffffffffffffffffff81168103610a4e5790565b91908203918211611b0357565b92919073ffffffffffffffffffffffffffffffffffffffff8082163003613aee575050613801926138a5565b8084959411613b02576138019416926139d0565b60046040517fc4bd89a9000000000000000000000000000000000000000000000000000000008152fd5b9173ffffffffffffffffffffffffffffffffffffffff93613c2d916040519060208201927fffffffffffffffffffffffffffffffffffffffff000000000000000000000000809260601b16845260601b16603482015260288152613b8f816135de565b519020613c01604051938492602084019687917fffffffffffffffffffffffffffffffffffffffff000000000000000000000000605594927fff00000000000000000000000000000000000000000000000000000000000000855260601b166001840152601583015260358201520190565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081018352826135fa565b5190201690565b73ffffffffffffffffffffffffffffffffffffffff8281169082161015613c585791565b9091565b51906dffffffffffffffffffffffffffff82168203610a4e57565b90816060910312610a4e57613c8b81613c5c565b916040613c9a60208401613c5c565b92015163ffffffff81168103610a4e5790565b9260028210614123578115611b4c57613cc584613a94565b9160019481861015611b4c5791613ce360209461190f868601613a94565b50926000935b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84018510613d1c575050505050505050565b613d2a6119b9868685613a84565b92613d3b6119b98a88018786613a84565b936040908151957f0902f1ac00000000000000000000000000000000000000000000000000000000875273ffffffffffffffffffffffffffffffffffffffff80941694606092600493808a86818b5afa998a1561225b57908d9594939291600091829c6140fd575b50508780916dffffffffffffffffffffffffffff8091169c16921692168214998a6000146140f7575b8651958680947f70a082310000000000000000000000000000000000000000000000000000000082528b8883015260249889915afa9283156140ec578e6000946140bb575b5050808303918115938480156140b3575b61408b57826103e5808602958604149114171561405e57613e439083613984565b926103e880830292830414171561403157613e689291613e629161414d565b90613997565b971561402957600097905b898b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe820181101561401d579161190f6119b9613eb9936002613f039c9601908d613a84565b8198917f96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f613b2c565b965b988551918d83019367ffffffffffffffff9484811086821117613ff057885260008452813b15610a4e5760008a93613f8382968b519c8d97889687957f022c0d9f0000000000000000000000000000000000000000000000000000000087528d8701528d860152166044840152608060648401526084830190610af6565b03925af18015611ab857908d969594939291613fa8575b505050505094019391613ce9565b909192938095965011613fc45750505287903880808080613f9a565b6041907f4e487b7100000000000000000000000000000000000000000000000000000000600052526000fd5b876041887f4e487b7100000000000000000000000000000000000000000000000000000000600052526000fd5b5050508b956000613f05565b600090613e73565b856011867f4e487b7100000000000000000000000000000000000000000000000000000000600052526000fd5b866011877f4e487b7100000000000000000000000000000000000000000000000000000000600052526000fd5b8689517f7b9c8916000000000000000000000000000000000000000000000000000000008152fd5b508115613e22565b8181959293953d83116140e5575b6140d381836135fa565b8101031261038757505191388e613e11565b503d6140c9565b87513d6000823e3d90fd5b90613dcc565b899c50899250908161411a92903d10611f1357611f0281836135fa565b509b9091613da3565b60046040517fae52ad0c000000000000000000000000000000000000000000000000000000008152fd5b91908201809211611b0357565b91823583019161417460208435958186019503018561414d565b1161105d57565b91602083013583019161417460208435958186019503018561414d565b91606083013583019161417460208435958186019503018561414d565b7f80000000000000000000000000000000000000000000000000000000000000008114611b035760000390565b939193602b841061105d578462ffffff6000614267946142ee6142999935988960601c9a8b9a61423b601789013560601c9d8e109c73ffffffffffffffffffffffffffffffffffffffff9e8f998a9460481c1691614365565b16968b861461434a576401000276a49a5b60409d8e9b8c93845196879560208701526060860191613537565b91168b830152037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081018352826135fa565b848851998a98899788967f128acb080000000000000000000000000000000000000000000000000000000088521660048701528c6024870152604486015216606484015260a0608484015260a4830190610af6565b03925af190811561433f576000938492614309575b50509192565b9080949250813d8311614338575b61432181836135fa565b810103126103875750602082519201513880614303565b503d614317565b83513d6000823e3d90fd5b73fffd8963efd1fc6a506488495d951d5263988d259a61424c565b73ffffffffffffffffffffffffffffffffffffffff92838316848316116144c9575b62ffffff90846040519481602087019516855216604085015216606083015260608252608082019082821067ffffffffffffffff831117610f7d577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80613c2d9183604052845190209361449c60a08201957fe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54907f0000000000000000000000001f98431c8ad98523631ae4a59f267346ea31f98488917fffffffffffffffffffffffffffffffffffffffff000000000000000000000000605594927fff00000000000000000000000000000000000000000000000000000000000000855260601b166001840152601583015260358201520190565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff608101845201826135fa565b909190614387565b600080809381935af1156144e157565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4554485f5452414e534645525f4641494c4544000000000000000000000000006044820152fd5b60009182604492602095604051937fa9059cbb000000000000000000000000000000000000000000000000000000008552600485015260248401525af13d15601f3d116001600051141617161561459257565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f5452414e534645525f4641494c454400000000000000000000000000000000006044820152fdfea2646970667358221220b2d6a39827110492aaa15cba3556e23894a51f2f635dc99ae66d21764ad4d90b64736f6c634300081100330058210390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56305820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0058210310e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6041010219080405581d02367b74293bb052cf32dbf499b5c69c98fda9beb40172c7513283aa71070119462603eab71320eeef085932d28d44e0baf57b58344eb1e00ad28043fed2773af1d7eb0605581d02fa23563862f322c7e205623469c0b78f515b247fc070b0a8a7a361b207011bffffffffffffffff02192210030c975042158b302d08b687b5ad55407adea0f6c0c8e2ae9027af5f2b66a90e2503db731c607521cbd787386a1fe342cdf1bac891251c300135c9c48e15f96572ae021979ab036f2ee834e1f27ab29334718c1891c1fbf7b4905b5c3c8413a6455bc362071e0703a6e69c4e7f6c01cdbd2a57007b66d03fcc4d915e823bc0128e46df6d003bf27103886f7645e367e56ba68b766987c56dfef14001d37ea052544e5bc80fa127574203d5e6f2c9fedc019daec57e9f9de99606ad6622acd7e5752e59cce941342141550368fd523bc7a98534cd05165476e5f4aff3d49d9353645c45a3718810935c63b703a2b47f7b9bb8970017b462680a3019b86a20a3053c28d35b35d8055c6e9ec09f0219ffff038d82a113feec5be1befcd1a92c6b71e128f238629f74c895b40f1b0d31970eb703e32cc14941b0c0e26fd4cd6a1ff7904594c84aee9ebc8ea8971e9e4ebb4e11630365a9fd54eea42809ad5d130b0257ffc59ecea4d36525c5f059f0ba74e12d97da03281a34dfcf4c9272ce28f683e1214c28e3bc7320ec32034b66c88950440b6d29037acfa0a74627f84ecc785940d286ea2f47d8719391be009f1ace0ba56de41b970311cdc47e8172f6af544d8b529571110c099bc95182cb9fbefdbfa94a345b453703f2b6daba9341b5868251a1ca9c40c9331d0458fd2860406a2d3b2c2429368f0103607f0de0931910ba9ecf0e19204ef4454c8f293039132d816a94fd013c53224503de0dd51065e16214b69968bc663ea47bd1b58f4a23def0cdb9ce7826e6b5798b03208d267634ee7ef8b6dd028468268e4642393faeea95298b745a6d3793a910240219ffff0338c61070304c5a56b382ff5fe8279b579ba528f8450ef0972671f54aaf8a1807033df2a9b7c7835a37cae5bcacb6bd5614801c723c65b8c038b6895bb8721005d30345f5378f333db1e4dbc4ff7bb03fb174652735acc417491426ec1d5b4b7c962103f624a84b5da7da6f6d297359ff86b3f5b7aa514c3c544d10b7cdda48c3b6538c03d2ea0d5257ab5b2e9d684cf536fa24090bdbf8144370ff8296130dd05fef871f03649f0628bacb781a5e38fe229e48f6333d3fcccb12e8232e0ef40894f79bd01403265f0d31dffe3ddaceb1a6264373b2071694a42cdc8deac6f08e7167e3b53b5c0348755492522f9d4186ee1d2ef54e05eb48ff50167faaa3cd8bdbbdcd0f1dfb270388fcd11bfa6af7102fc26c75109a9bfb91b18b098abdf9941b4fd719c4b38b51032b8c63effd1c55715b1030ff3b0e540120c7468ab949e045e2b56b75a851f8ad03f1f869a4f7853a567e16451e77c8fcd41eb35442dca17ffa169117e376b12f52035315b68e12efbfd7e425d1303ac50276124043bdec90967756d20c135a4439030219ffff034fe4674fca460c6b0d3775f93ae770fd2f8f6adbf3df8a38cbd20f6858dae16603d5f6e825b679423f3bc83a3858566924aca2c9fe0fea7a738e50c7473705430f03d8f3699affd4c37f3873e6cb56c520da8768f9b299beb3d24a6870e3ef840cf90219ffff03a856879ba50e428f706e8ea8ab23bdfcf6e533526774c801832f406c496c8bf5035060819b722c0510205215121ae18a0c91ff232cb1bcfb88cf5b126021f47f0103f192f4200aad1a68680a0ee8a45f52d5d9499b04c4ca9a9468ee3e6d05fb06a4031dd5eed083075ab6aab72f5bc31a42da9e452af16b44126c68c121599b5d7d5d034b4c77225b00a5628f40c7b1d2edde5a0e1e73ee390eaf46102e93ef8b5c6ec803f4cdd6911f721cf8a8349705e06795e1537271c6e71ce482458062864481d36a03224c9b283bca44bd906cbb6f8aa336e7a8832c19b90234aca912ac95e3b23b2403738163b6eff66aaa37335f5c07a84f61fad13f1dbe24a9e3305b4f9847612e16035366ef61de8dd9508e66112b8a8b9107be9b4713d881a04d20e9b24b4f8555a30367bf6f3570230084e9c845f3e358a0a970cba8ad34aae28c1b39d795c49abb9d034879cf3d253b210eaf97a54bfb2882218cd549e779dd0d460106eaff45f0e2ae03f344efafc63dfff5359260d7f7bbcab1fc6c9323363f11b02462ad8b02487a7e03698d776c9cdd56361bade4cd6f0367c0b52268918652074bb0f1671277be82ad037b49101ecf8284f85a08144c80e24b6387eee2b2b282d59822cf8371b48a26280316035ab46792ea0c73b6c679cb03babde3ef39281aa0432db7112e9a803b947503eafd1132f34423710d00cdb047c30f5dc4c63684c6b4aa48a8c0094e6b2ad4a503397aecf779627d1b5ba31cddaeb3a41d45ba3a8851546c42fc8b848da29e968f03a31a78dca744bb051e20db4fcd34e9bba63d3706702f20d33ad199cd4ab4158403a908dc082006334a16b811a096a809f25c9d7b210a0fe3f8d7bd6cdcc36e7f77032915c27a44a0c56ab5b5afdb8d8abe8f346ef4025707debb97ab0ebb7848384103fc2222896f3316227969e6b783146dae8b67e920fb4ecca348f07fb708be8d3603177bccb80a7c5a54167ce15e7e4dac105176699cb48417cdb1f44b8c3a11164f05581d020562f180317169b17c114dceb8f530662cbf5cb45104976be1c5799c040105581d02cf6f47949b59006dcee0bad9757d925c1b03370423e7b3d302a33b0e0c0247051bbc32954c0005581d0207d087d0e06a061d8cf2eff483c4c045de877251fd50ec7b4ce9adf1084801ab91dfed466c000219182203a690a9b22e0e9d4081a8239747d3e85652d6142224268c40bb04bf4e613b0e4f05581e03f795d314522a92fe3cddad5ccb6effe61e7c8f0e1712b46dac64cc1dc00c014705217d32a74c4805581e03cb5f1adcd30ce464ef14796b2dd04127771e378a1fd231dd39e1298dc0040205581e03d81578c817561388355e2ad7b32ca851dfc59f8c6c00ed57a27d33b2500c0647019f6c1af7231805581e03c67474d53313f21b8ca161dd5b4e8363a97fe4c39547284fe47c396aa00c03470389b413e1aec003d3183fbfdab637c08db5ae2b8a3a28bc294ad04da22eb904ad8adb7d93b7aede035401c2db8b68c2e025117584dae009174f816ed4097edfa39027af3985a6b86e03f47c023b26d46373454f84175ac598ed5487c76eed04930adc60c25f0d7f258205581e0311a2f42e657e9333f22ad102102245aa3da2808880764b8b220367a8300c01466346fa684d000219d6e30361ac68c7460b43f1179d400ece1a1c2347efd5bd0d9f82a89858826b616c121a03440ccd7dd0fbc59b9b31d45edaa2ca42e226a8534328acc77ddbf0685902f6b10345883bdaa16f74f7608c7e7d23b01bd2eb3175864cc1216f1c1b17e223cce3cb03b5a1452b917a08cc1fb519a022df0763cc3cada88fb73de62685c43a5776171203b0856d38cc1efff710a845dfd301eb555c3a5d363b25be0c7296b3c7436f212c0395581839c4f631602aa9815d6c6f5bde978402810d2a81379c062219c446dcc80353ac20cc0541b52d042bcac44795ec73e83b99454312bc9dabb1805ffcc69d0b03aace27d352a94b435805f2e867d3ad5a313873bbbb7daa09f3e8448bd65c3e7c038fdc73be650f209da66653d3dd754ff0a5992dc0bca44af1900cc2e4270bc3bb031a596835d22e9dfe81946f42c32c99e4847c7ea1240e9f618495fc553373634d034208930241b5a8096c2095002ae9f3376ffe7bf5622681fa02ae4d2ee243468103a09b17a06953faf770cdf4004f487d6ca38dd959bdb95de1d4efb989c074442703546736042c4a11904245877b4ead062251fce46bab096d5e70a455ffa788cd9d0219ffff03ac9a2d51a10f528167c4801018f26235bebf1dd784e39eddf8edea5bf30d55f503465555c4dc84934066ddb671d203404f6a6518ad795f2e1f00cd97cbaba5aa4d03ecf80f1437417b984cde4b6191bf8705fe9743c9694bf7e8a0e39f118a2a639b03732291a91c072c786847787dc5a1b6ed7d782a4d402ab15d4f3dcfe85c47459503b04eb23bbb084ab2d08e808940529bda17651367e69915eba2e7f78a71d6a12b03d6e5fd7b0571ff163315cc7f3eed475a9ef4cd06d7e51964cbf00868c212e91703a491332be56b92bf50fdee51b32061690b6ffd4540fb9edb6c8d751c1a93c959037f6a1004b4c0e87449ae7e676984de52682d4dd5e4a82f6560d54b458fe97e4203fde2bb17312eef02c226e9e307b1e75a568953146b1e50175c962d52762b9e7003519c02bb0ccd6e4e7a983a322aea9c5ac97410629bd1140a8bc0f34c640a4a1103150963371f7a441d90eb104c6819e3052f886f4e41363f36880ea99983ff7d2003fe94cd9319fb1dde5cafbc421fa08111caee08185b518bc8f41efd021dd65930033fd2f7d42851ebdd5eeb3f785b883cb8c6a716d1d14acfa1da3e4dad33cba3960219ffff037a3b2292b97d951935219e48e0af413fc0b2a080ec12ecb11500fa30693f371c034d898ec8e9221364417fa29aacfdbfacf664733803a271be359d515f1c75e39e03da84dc259556887bf48381f635e12a92b100ce69da5eeeba6dd685cc45e7a9f7036b0f68c757e23d6ef24b887411b95b3a6061862203d12f6650c081cd6b7a96e7032815d7f992d035862ec2b2b1711b9100d5a4bada4e683644b52cf0eb808fe803032ee816bdf1bf4916da95b818192a58ae97ab144037fc0773ab2f7844d8034e6a03b5814acffb81775b79b223c8a41d7927e351e5836a2162f5d7ae6db7e5646b93034fd3d1ff915c4f3ec0903f7233c4e0eea80f7f3dbc57627438f11ac2580994390219ffff036ae455a8f98f602ad3df3bb4b8f35140ed320d28981d268d11e4bdecf761f691030f365cab02f9fbbab84a2d2abc964a5d04815fbf27caa1f574c20ff88dfe3d34033f5ffd77dfa969a03878e24bcdd6aae9804df7dd805355087f754cf34ca27351039e1674c0fe03b797fb0d0ad7c1a0e47e9813b457884b61087b07394399bc21b80379e4ddae0109844cc53761f22bc5b53c3910bd6105cf368eae6d358cdf3311210219ffff03cd98bf9f6f35337d1dc7521ba37683c5a9c7db2f97c15ebd327bd1ecc94f73480399358e17a1bce7878169d6caa3743f1d3ab4becf862ddbef324ad5fcec1ffee20387f568ac40805eee9bafb18fd2b86755ddc504ff7d7a8e623da11e7db03bdf53033ab9ea79b00ca0d33fcc0e64dd87b8722ea5be97610aea3007b451e751c3087c0349d44405343b3471e6bf6c72a37de5605908e5de5aefeeaa449a759d4e0227d503f1f346e98202b9b6197ae71711be78b64951b18c4629c1f91c10e7f18f4f8a87034ec68f38a295d5464c83f1d4a64558e7abd9cb6f649c27888fc513859fb17cc90341b4a1403e3a2cdca6627e540ba32ee84397f3764dc4a8f67441fdb35032f249032a70224aebc05d5bff03c5a4163781d136fa81cbc9fab25c18c2609578e2039303e1c63c898087885a3d179684fd728e1daea6112639de2b454e3c953d4db4c6e20307a213c9ef6e55d90bdad7c1a9604a16879cf7e5fd3cc67cd7f2a75bac8971fc03241f8c19007140a630e5edd4da2b6b2b9c89da8a7d9fae6f2ba8a28f4d2d632d034af651f1e0cc55a176ced1698a8fce42331445baf3095b5f247b21f48dd7c22e0302153fede80508f9ad33413847336a160617d67cbacd452f2807bcb5e2c54a59033133288ed6d559d82a7f2f68cdfa086c5138894e4d58fe0070242d73d208969e031f71467888afa462854d1c40e49f6f81ad28a85256a6d3ec58a97d60d70262290327a9c52f24d0bd1d5b735faf7c322e32eac9d9906aa4657bdb8e80e6f4637ec00303117d5f66c9040f7dff9e742bc1ab5a86b8f46d88e0fc43a794a2a0897e0a7b03dd77d7a3707ba04957fbfeb34b23d447212261a7c4bb8f250c3ba3bcf297730e0327f86466975873c5c29cadc05699588b004c248585542aa28a2d2cdb85ca48b103166d0292a047a2b742102af5a626b605e56b1a8a92ad222086e373f8d45576ca05581e03a43e3903f66aaefd901fb67613eaa643955317e0bb01c1a792b4c00630040105581e03a341e158089440752a302ccd30e9dfcb39f0b62a9b6a500de87183dc40040203106a972eed001c08010e86dc4053257b14ffcfd34afc7474f5c204c56f33fc7903510b686d4bb215b8da138b060ef7aca04959308b7200eae2e7d7183bdd91775305581e0309822469b484d0d834e850f6213228ba12127fe695d2fcc5c7e0969f70040305581e03a5fb559e6713f246852c3b9b24d8521e4e21007486f220197f2e181fc00c0246e1069bcda90005581e038ece7292ca4c5fbc902a74d5ff3cf4c515e0b2a8d3a824073a46c39fa0040205581e0377a4c85cd5ba94f9ddb4c65ba76da98bbd1a329f35fc18b1599dea2a100c0b47106502b615b5e605581e030baed5bca79d091f4ad010f631622b1cd06ccd3e691a2c3565e70ffb40040205581e03b17f6401573465e8dfc06e5acaaab0ac800896b72e8ed1330ce6667db00c0446940d56198a0003a7e48f0d73eaf7c47528d6ab59ffae7b4aec13c5eb888b7359f014521767c84803f4894f5d93a9fad81ddc3c915bba04e8b24b153184b4a38622d4e353288b537905581d02be5be7319110e052ef96ea1f34e3470214805f16c64ec3d744461a1b07011bffffffffffffffff05581d0359795cbe09a5b3d4cf6416da453015c5a2cd9d77646c7ed8bfe0c56004010459567e608060405234801561001057600080fd5b50600436106101ae5760003560e01c806370cf754a116100ee578063c45a015511610097578063ddca3f4311610071578063ddca3f4314610800578063f305839914610820578063f30dba9314610828578063f637731d146108aa576101ae565b8063c45a0155146107d1578063d0c93a7c146107d9578063d21220a7146107f8576101ae565b8063883bdbfd116100c8578063883bdbfd14610633578063a34123a71461073c578063a38807f214610776576101ae565b806370cf754a146105c65780638206a4d1146105ce57806385b66729146105f6576101ae565b80633850c7bd1161015b578063490e6cbc11610135578063490e6cbc146104705780634f1eb3d8146104fc578063514ea4bf1461054d5780635339c296146105a6576101ae565b80633850c7bd1461035b5780633c8a7d8d146103b45780634614131914610456576101ae565b80631ad8b03b1161018c5780631ad8b03b146102aa578063252c09d7146102e157806332148f6714610338576101ae565b80630dfe1681146101b3578063128acb08146101d75780631a68650214610286575b600080fd5b6101bb6108d0565b604080516001600160a01b039092168252519081900360200190f35b61026d600480360360a08110156101ed57600080fd5b6001600160a01b0382358116926020810135151592604082013592606083013516919081019060a08101608082013564010000000081111561022e57600080fd5b82018360208201111561024057600080fd5b8035906020019184600183028401116401000000008311171561026257600080fd5b5090925090506108f4565b6040805192835260208301919091528051918290030190f35b61028e6114ad565b604080516001600160801b039092168252519081900360200190f35b6102b26114bc565b60405180836001600160801b03168152602001826001600160801b031681526020019250505060405180910390f35b6102fe600480360360208110156102f757600080fd5b50356114d6565b6040805163ffffffff909516855260069390930b60208501526001600160a01b039091168383015215156060830152519081900360800190f35b6103596004803603602081101561034e57600080fd5b503561ffff1661151c565b005b610363611616565b604080516001600160a01b03909816885260029690960b602088015261ffff9485168787015292841660608701529216608085015260ff90911660a0840152151560c0830152519081900360e00190f35b61026d600480360360a08110156103ca57600080fd5b6001600160a01b03823516916020810135600290810b92604083013590910b916001600160801b036060820135169181019060a08101608082013564010000000081111561041757600080fd5b82018360208201111561042957600080fd5b8035906020019184600183028401116401000000008311171561044b57600080fd5b509092509050611666565b61045e611922565b60408051918252519081900360200190f35b6103596004803603608081101561048657600080fd5b6001600160a01b0382351691602081013591604082013591908101906080810160608201356401000000008111156104bd57600080fd5b8201836020820111156104cf57600080fd5b803590602001918460018302840111640100000000831117156104f157600080fd5b509092509050611928565b6102b2600480360360a081101561051257600080fd5b506001600160a01b03813516906020810135600290810b91604081013590910b906001600160801b0360608201358116916080013516611d83565b61056a6004803603602081101561056357600080fd5b5035611f9d565b604080516001600160801b0396871681526020810195909552848101939093529084166060840152909216608082015290519081900360a00190f35b61045e600480360360208110156105bc57600080fd5b503560010b611fda565b61028e611fec565b610359600480360360408110156105e457600080fd5b5060ff81358116916020013516612010565b6102b26004803603606081101561060c57600080fd5b506001600160a01b03813516906001600160801b036020820135811691604001351661220f565b6106a36004803603602081101561064957600080fd5b81019060208101813564010000000081111561066457600080fd5b82018360208201111561067657600080fd5b8035906020019184602083028401116401000000008311171561069857600080fd5b5090925090506124dc565b604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b838110156106e75781810151838201526020016106cf565b50505050905001838103825284818151815260200191508051906020019060200280838360005b8381101561072657818101518382015260200161070e565b5050505090500194505050505060405180910390f35b61026d6004803603606081101561075257600080fd5b508035600290810b91602081013590910b90604001356001600160801b0316612569565b6107a06004803603604081101561078c57600080fd5b508035600290810b9160200135900b6126e0565b6040805160069490940b84526001600160a01b03909216602084015263ffffffff1682820152519081900360600190f35b6101bb6128d7565b6107e16128fb565b6040805160029290920b8252519081900360200190f35b6101bb61291f565b610808612943565b6040805162ffffff9092168252519081900360200190f35b61045e612967565b6108486004803603602081101561083e57600080fd5b503560020b61296d565b604080516001600160801b039099168952600f9790970b602089015287870195909552606087019390935260069190910b60808601526001600160a01b031660a085015263ffffffff1660c0840152151560e083015251908190036101000190f35b610359600480360360208110156108c057600080fd5b50356001600160a01b03166129db565b7f000000000000000000000000aa6e8127831c9de45ae56bb1b0d4d4da6e5665bd81565b6000806108ff612bf0565b85610936576040805162461bcd60e51b8152602060048201526002602482015261415360f01b604482015290519081900360640190fd5b6040805160e0810182526000546001600160a01b0381168252600160a01b8104600290810b810b900b602083015261ffff600160b81b8204811693830193909352600160c81b810483166060830152600160d81b8104909216608082015260ff600160e81b8304811660a0830152600160f01b909204909116151560c082018190526109ef576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b87610a3a5780600001516001600160a01b0316866001600160a01b0316118015610a35575073fffd8963efd1fc6a506488495d951d5263988d266001600160a01b038716105b610a6c565b80600001516001600160a01b0316866001600160a01b0316108015610a6c57506401000276a36001600160a01b038716115b610aa3576040805162461bcd60e51b815260206004820152600360248201526214d41360ea1b604482015290519081900360640190fd5b6000805460ff60f01b191681556040805160c08101909152808a610ad25760048460a0015160ff16901c610ae5565b60108460a0015160ff1681610ae357fe5b065b60ff1681526004546001600160801b03166020820152604001610b06612c27565b63ffffffff168152602001600060060b815260200160006001600160a01b031681526020016000151581525090506000808913905060006040518060e001604052808b81526020016000815260200185600001516001600160a01b03168152602001856020015160020b81526020018c610b8257600254610b86565b6001545b815260200160006001600160801b0316815260200184602001516001600160801b031681525090505b805115801590610bd55750886001600160a01b031681604001516001600160a01b031614155b15610f9f57610be261560e565b60408201516001600160a01b031681526060820151610c25906006907f000000000000000000000000000000000000000000000000000000000000003c8f612c2b565b15156040830152600290810b810b60208301819052620d89e719910b1215610c5657620d89e7196020820152610c75565b6020810151620d89e860029190910b1315610c7557620d89e860208201525b610c828160200151612d6d565b6001600160a01b031660608201526040820151610d13908d610cbc578b6001600160a01b031683606001516001600160a01b031611610cd6565b8b6001600160a01b031683606001516001600160a01b0316105b610ce4578260600151610ce6565b8b5b60c085015185517f0000000000000000000000000000000000000000000000000000000000000bb861309f565b60c085015260a084015260808301526001600160a01b031660408301528215610d7557610d498160c00151826080015101613291565b825103825260a0810151610d6b90610d6090613291565b6020840151906132a7565b6020830152610db0565b610d828160a00151613291565b825101825260c08101516080820151610daa91610d9f9101613291565b6020840151906132c3565b60208301525b835160ff1615610df6576000846000015160ff168260c0015181610dd057fe5b60c0840180519290910491829003905260a0840180519091016001600160801b03169052505b60c08201516001600160801b031615610e3557610e298160c00151600160801b8460c001516001600160801b03166132d9565b60808301805190910190525b80606001516001600160a01b031682604001516001600160a01b03161415610f5e57806040015115610f35578360a00151610ebf57610e9d846040015160008760200151886040015188602001518a606001516008613389909695949392919063ffffffff16565b6001600160a01b03166080860152600690810b900b6060850152600160a08501525b6000610f0b82602001518e610ed657600154610edc565b84608001515b8f610eeb578560800151610eef565b6002545b608089015160608a015160408b0151600595949392919061351c565b90508c15610f17576000035b610f258360c00151826135ef565b6001600160801b031660c0840152505b8b610f44578060200151610f4d565b60018160200151035b600290810b900b6060830152610f99565b80600001516001600160a01b031682604001516001600160a01b031614610f9957610f8c82604001516136a5565b600290810b900b60608301525b50610baf565b836020015160020b816060015160020b1461107a57600080610fed86604001518660400151886020015188602001518a606001518b6080015160086139d1909695949392919063ffffffff16565b604085015160608601516000805461ffff60c81b1916600160c81b61ffff958616021761ffff60b81b1916600160b81b95909416949094029290921762ffffff60a01b1916600160a01b62ffffff60029490940b93909316929092029190911773ffffffffffffffffffffffffffffffffffffffff19166001600160a01b03909116179055506110ac9050565b60408101516000805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b039092169190911790555b8060c001516001600160801b031683602001516001600160801b0316146110f25760c0810151600480546001600160801b0319166001600160801b039092169190911790555b8a1561114257608081015160015560a08101516001600160801b03161561113d5760a0810151600380546001600160801b031981166001600160801b03918216909301169190911790555b611188565b608081015160025560a08101516001600160801b0316156111885760a0810151600380546001600160801b03808216600160801b92839004821690940116029190911790555b8115158b1515146111a157602081015181518b036111ae565b80600001518a0381602001515b90965094508a156112e75760008512156111f0576111f07f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28d87600003613b86565b60006111fa613cd4565b9050336001600160a01b031663fa461e3388888c8c6040518563ffffffff1660e01b815260040180858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b15801561127e57600080fd5b505af1158015611292573d6000803e3d6000fd5b5050505061129e613cd4565b6112a88289613e0d565b11156112e1576040805162461bcd60e51b815260206004820152600360248201526249494160e81b604482015290519081900360640190fd5b50611411565b600086121561131e5761131e7f000000000000000000000000aa6e8127831c9de45ae56bb1b0d4d4da6e5665bd8d88600003613b86565b6000611328613e1d565b9050336001600160a01b031663fa461e3388888c8c6040518563ffffffff1660e01b815260040180858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b1580156113ac57600080fd5b505af11580156113c0573d6000803e3d6000fd5b505050506113cc613e1d565b6113d68288613e0d565b111561140f576040805162461bcd60e51b815260206004820152600360248201526249494160e81b604482015290519081900360640190fd5b505b60408082015160c083015160608085015184518b8152602081018b90526001600160a01b03948516818701526001600160801b039093169183019190915260020b60808201529151908e169133917fc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca679181900360a00190a350506000805460ff60f01b1916600160f01b17905550919890975095505050505050565b6004546001600160801b031681565b6003546001600160801b0380821691600160801b90041682565b60088161ffff81106114e757600080fd5b015463ffffffff81169150640100000000810460060b90600160581b81046001600160a01b031690600160f81b900460ff1684565b600054600160f01b900460ff16611560576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b19169055611575612bf0565b60008054600160d81b900461ffff169061159160088385613eb5565b6000805461ffff808416600160d81b810261ffff60d81b19909316929092179092559192508316146115fe576040805161ffff80851682528316602082015281517fac49e518f90a358f652e4400164f05a5d8f7e35e7747279bc3a93dbf584e125a929181900390910190a15b50506000805460ff60f01b1916600160f01b17905550565b6000546001600160a01b03811690600160a01b810460020b9061ffff600160b81b8204811691600160c81b8104821691600160d81b8204169060ff600160e81b8204811691600160f01b90041687565b600080548190600160f01b900460ff166116ad576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b191690556001600160801b0385166116cd57600080fd5b60008061171b60405180608001604052808c6001600160a01b031681526020018b60020b81526020018a60020b81526020016117118a6001600160801b0316613f58565b600f0b9052613f69565b9250925050819350809250600080600086111561173d5761173a613cd4565b91505b841561174e5761174b613e1d565b90505b336001600160a01b031663d348799787878b8b6040518563ffffffff1660e01b815260040180858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b1580156117d057600080fd5b505af11580156117e4573d6000803e3d6000fd5b50505050600086111561183b576117f9613cd4565b6118038388613e0d565b111561183b576040805162461bcd60e51b815260206004820152600260248201526104d360f41b604482015290519081900360640190fd5b841561188b57611849613e1d565b6118538287613e0d565b111561188b576040805162461bcd60e51b81526020600482015260026024820152614d3160f01b604482015290519081900360640190fd5b8960020b8b60020b8d6001600160a01b03167f7a53080ba414158be7ec69b987b5fb7d07dee101fe85488f0853ae16239d0bde338d8b8b60405180856001600160a01b03168152602001846001600160801b0316815260200183815260200182815260200194505050505060405180910390a450506000805460ff60f01b1916600160f01b17905550919890975095505050505050565b60025481565b600054600160f01b900460ff1661196c576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b19169055611981612bf0565b6004546001600160801b0316806119c3576040805162461bcd60e51b81526020600482015260016024820152601360fa1b604482015290519081900360640190fd5b60006119f8867f0000000000000000000000000000000000000000000000000000000000000bb862ffffff16620f42406141a9565b90506000611a2f867f0000000000000000000000000000000000000000000000000000000000000bb862ffffff16620f42406141a9565b90506000611a3b613cd4565b90506000611a47613e1d565b90508815611a7a57611a7a7f000000000000000000000000aa6e8127831c9de45ae56bb1b0d4d4da6e5665bd8b8b613b86565b8715611aab57611aab7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28b8a613b86565b336001600160a01b031663e9cbafb085858a8a6040518563ffffffff1660e01b815260040180858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b158015611b2d57600080fd5b505af1158015611b41573d6000803e3d6000fd5b505050506000611b4f613cd4565b90506000611b5b613e1d565b905081611b688588613e0d565b1115611ba0576040805162461bcd60e51b8152602060048201526002602482015261046360f41b604482015290519081900360640190fd5b80611bab8487613e0d565b1115611be3576040805162461bcd60e51b8152602060048201526002602482015261463160f01b604482015290519081900360640190fd5b8382038382038115611c725760008054600160e81b9004600f16908115611c16578160ff168481611c1057fe5b04611c19565b60005b90506001600160801b03811615611c4c57600380546001600160801b038082168401166001600160801b03199091161790555b611c66818503600160801b8d6001600160801b03166132d9565b60018054909101905550505b8015611cfd5760008054600160e81b900460041c600f16908115611ca2578160ff168381611c9c57fe5b04611ca5565b60005b90506001600160801b03811615611cd757600380546001600160801b03600160801b8083048216850182160291161790555b611cf1818403600160801b8d6001600160801b03166132d9565b60028054909101905550505b8d6001600160a01b0316336001600160a01b03167fbdbdb71d7860376ba52b25a5028beea23581364a40522f6bcfb86bb1f2dca6338f8f86866040518085815260200184815260200183815260200182815260200194505050505060405180910390a350506000805460ff60f01b1916600160f01b179055505050505050505050505050565b600080548190600160f01b900460ff16611dca576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b19168155611de460073389896141e3565b60038101549091506001600160801b0390811690861611611e055784611e14565b60038101546001600160801b03165b60038201549093506001600160801b03600160801b909104811690851611611e3c5783611e52565b6003810154600160801b90046001600160801b03165b91506001600160801b03831615611eb7576003810180546001600160801b031981166001600160801b03918216869003821617909155611eb7907f000000000000000000000000aa6e8127831c9de45ae56bb1b0d4d4da6e5665bd908a908616613b86565b6001600160801b03821615611f1d576003810180546001600160801b03600160801b808304821686900382160291811691909117909155611f1d907f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2908a908516613b86565b604080516001600160a01b038a1681526001600160801b0380861660208301528416818301529051600288810b92908a900b9133917f70935338e69775456a85ddef226c395fb668b63fa0115f5f20610b388e6ca9c0919081900360600190a4506000805460ff60f01b1916600160f01b17905590969095509350505050565b60076020526000908152604090208054600182015460028301546003909301546001600160801b0392831693919281811691600160801b90041685565b60066020526000908152604090205481565b7f0000000000000000000000000000000000023746e6a58dcb13d4af821b93f06281565b600054600160f01b900460ff16612054576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b1916905560408051638da5cb5b60e01b815290516001600160a01b037f0000000000000000000000001f98431c8ad98523631ae4a59f267346ea31f9841691638da5cb5b916004808301926020929190829003018186803b1580156120c157600080fd5b505afa1580156120d5573d6000803e3d6000fd5b505050506040513d60208110156120eb57600080fd5b50516001600160a01b0316331461210157600080fd5b60ff82161580612124575060048260ff16101580156121245750600a8260ff1611155b801561214e575060ff8116158061214e575060048160ff161015801561214e5750600a8160ff1611155b61215757600080fd5b60008054610ff0600484901b16840160ff908116600160e81b9081027fffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff841617909355919004167f973d8d92bb299f4af6ce49b52a8adb85ae46b9f214c4c4fc06ac77401237b1336010826040805160ff9390920683168252600f600486901c16602083015286831682820152918516606082015290519081900360800190a150506000805460ff60f01b1916600160f01b17905550565b600080548190600160f01b900460ff16612256576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b1916905560408051638da5cb5b60e01b815290516001600160a01b037f0000000000000000000000001f98431c8ad98523631ae4a59f267346ea31f9841691638da5cb5b916004808301926020929190829003018186803b1580156122c357600080fd5b505afa1580156122d7573d6000803e3d6000fd5b505050506040513d60208110156122ed57600080fd5b50516001600160a01b0316331461230357600080fd5b6003546001600160801b039081169085161161231f578361232c565b6003546001600160801b03165b6003549092506001600160801b03600160801b9091048116908416116123525782612366565b600354600160801b90046001600160801b03165b90506001600160801b038216156123e7576003546001600160801b038381169116141561239557600019909101905b600380546001600160801b031981166001600160801b039182168590038216179091556123e7907f000000000000000000000000aa6e8127831c9de45ae56bb1b0d4d4da6e5665bd9087908516613b86565b6001600160801b0381161561246d576003546001600160801b03828116600160801b90920416141561241857600019015b600380546001600160801b03600160801b80830482168590038216029181169190911790915561246d907f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc29087908416613b86565b604080516001600160801b0380851682528316602082015281516001600160a01b0388169233927f596b573906218d3411850b26a6b437d6c4522fdb43d2d2386263f86d50b8b151929081900390910190a36000805460ff60f01b1916600160f01b1790559094909350915050565b6060806124e7612bf0565b61255e6124f2612c27565b858580806020026020016040519081016040528093929190818152602001838360200280828437600092018290525054600454600896959450600160a01b820460020b935061ffff600160b81b8304811693506001600160801b0390911691600160c81b900416614247565b915091509250929050565b600080548190600160f01b900460ff166125b0576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b1916815560408051608081018252338152600288810b602083015287900b918101919091528190819061260990606081016125fc6001600160801b038a16613f58565b600003600f0b9052613f69565b925092509250816000039450806000039350600085118061262a5750600084115b15612669576003830180546001600160801b038082168089018216600160801b93849004831689019092169092029091176001600160801b0319161790555b604080516001600160801b0388168152602081018790528082018690529051600289810b92908b900b9133917f0c396cd989a39f4459b5fa1aed6a9a8dcdbc45908acfd67e028cd568da98982c919081900360600190a450506000805460ff60f01b1916600160f01b179055509094909350915050565b60008060006126ed612bf0565b6126f785856143a1565b600285810b810b60009081526005602052604080822087840b90930b825281206003830154600681900b9367010000000000000082046001600160a01b0316928492600160d81b810463ffffffff169284929091600160f81b900460ff168061275f57600080fd5b6003820154600681900b985067010000000000000081046001600160a01b03169650600160d81b810463ffffffff169450600160f81b900460ff16806127a457600080fd5b50506040805160e0810182526000546001600160a01b0381168252600160a01b8104600290810b810b810b6020840181905261ffff600160b81b8404811695850195909552600160c81b830485166060850152600160d81b8304909416608084015260ff600160e81b8304811660a0850152600160f01b909204909116151560c08301529093508e810b91900b1215905061284d575093909403965090039350900390506128d0565b8a60020b816020015160020b12156128c1576000612869612c27565b602083015160408401516004546060860151939450600093849361289f936008938893879392916001600160801b031690613389565b9a9003989098039b5050949096039290920396509091030392506128d0915050565b50949093039650039350900390505b9250925092565b7f0000000000000000000000001f98431c8ad98523631ae4a59f267346ea31f98481565b7f000000000000000000000000000000000000000000000000000000000000003c81565b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc281565b7f0000000000000000000000000000000000000000000000000000000000000bb881565b60015481565b60056020526000908152604090208054600182015460028301546003909301546001600160801b03831693600160801b909304600f0b9290600681900b9067010000000000000081046001600160a01b031690600160d81b810463ffffffff1690600160f81b900460ff1688565b6000546001600160a01b031615612a1e576040805162461bcd60e51b8152602060048201526002602482015261414960f01b604482015290519081900360640190fd5b6000612a29826136a5565b9050600080612a41612a39612c27565b60089061446a565b6040805160e0810182526001600160a01b038816808252600288810b6020808501829052600085870181905261ffff898116606088018190529089166080880181905260a08801839052600160c0909801979097528154600160f01b73ffffffffffffffffffffffffffffffffffffffff19909116871762ffffff60a01b1916600160a01b62ffffff9787900b9790971696909602959095177fffffffffff00000000ffffffffffffffffffffffffffffffffffffffffffffff16600160c81b9091021761ffff60d81b1916600160d81b909602959095177fff0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1692909217909355835191825281019190915281519395509193507f98636036cb66a9c19a37435efc1e90142190214e8abeb821bdba3f2990dd4c9592918290030190a150505050565b60008082600281900b620d89e71981612b9957fe5b05029050600083600281900b620d89e881612bb057fe5b0502905060008460020b83830360020b81612bc757fe5b0560010190508062ffffff166001600160801b03801681612be457fe5b0493505050505b919050565b306001600160a01b037f000000000000000000000000151ccb92bc1ed5c6d0f9adb5cec4763ceb66ac7f1614612c2557600080fd5b565b4290565b60008060008460020b8660020b81612c3f57fe5b05905060008660020b128015612c6657508460020b8660020b81612c5f57fe5b0760020b15155b15612c7057600019015b8315612ce557600080612c82836144b6565b600182810b810b600090815260208d9052604090205460ff83169190911b80016000190190811680151597509294509092509085612cc757888360ff16860302612cda565b88612cd1826144c8565b840360ff168603025b965050505050612d63565b600080612cf4836001016144b6565b91509150600060018260ff166001901b031990506000818b60008660010b60010b8152602001908152602001600020541690508060001415955085612d4657888360ff0360ff16866001010102612d5c565b8883612d5183614568565b0360ff168660010101025b9650505050505b5094509492505050565b60008060008360020b12612d84578260020b612d8c565b8260020b6000035b9050620d89e8811115612dca576040805162461bcd60e51b81526020600482015260016024820152601560fa1b604482015290519081900360640190fd5b600060018216612dde57600160801b612df0565b6ffffcb933bd6fad37aa2d162d1a5940015b70ffffffffffffffffffffffffffffffffff1690506002821615612e24576ffff97272373d413259a46990580e213a0260801c5b6004821615612e43576ffff2e50f5f656932ef12357cf3c7fdcc0260801c5b6008821615612e62576fffe5caca7e10e4e61c3624eaa0941cd00260801c5b6010821615612e81576fffcb9843d60f6159c9db58835c9266440260801c5b6020821615612ea0576fff973b41fa98c081472e6896dfb254c00260801c5b6040821615612ebf576fff2ea16466c96a3843ec78b326b528610260801c5b6080821615612ede576ffe5dee046a99a2a811c461f1969c30530260801c5b610100821615612efe576ffcbe86c7900a88aedcffc83b479aa3a40260801c5b610200821615612f1e576ff987a7253ac413176f2b074cf7815e540260801c5b610400821615612f3e576ff3392b0822b70005940c7a398e4b70f30260801c5b610800821615612f5e576fe7159475a2c29b7443b29c7fa6e889d90260801c5b611000821615612f7e576fd097f3bdfd2022b8845ad8f792aa58250260801c5b612000821615612f9e576fa9f746462d870fdf8a65dc1f90e061e50260801c5b614000821615612fbe576f70d869a156d2a1b890bb3df62baf32f70260801c5b618000821615612fde576f31be135f97d08fd981231505542fcfa60260801c5b62010000821615612fff576f09aa508b5b7a84e1c677de54f3e99bc90260801c5b6202000082161561301f576e5d6af8dedb81196699c329225ee6040260801c5b6204000082161561303e576d2216e584f5fa1ea926041bedfe980260801c5b6208000082161561305b576b048a170391f7dc42444e8fa20260801c5b60008460020b131561307657806000198161307257fe5b0490505b64010000000081061561308a57600161308d565b60005b60ff16602082901c0192505050919050565b60008080806001600160a01b03808916908a1610158187128015906131245760006130d88989620f42400362ffffff16620f42406132d9565b9050826130f1576130ec8c8c8c6001614652565b6130fe565b6130fe8b8d8c60016146cd565b955085811061310f578a965061311e565b61311b8c8b838661478a565b96505b5061316e565b8161313b576131368b8b8b60006146cd565b613148565b6131488a8c8b6000614652565b935083886000031061315c5789955061316e565b61316b8b8a8a600003856147d6565b95505b6001600160a01b038a81169087161482156131d15780801561318d5750815b6131a35761319e878d8c60016146cd565b6131a5565b855b95508080156131b2575081155b6131c8576131c3878d8c6000614652565b6131ca565b845b945061321b565b8080156131db5750815b6131f1576131ec8c888c6001614652565b6131f3565b855b9550808015613200575081155b613216576132118c888c60006146cd565b613218565b845b94505b8115801561322b57508860000385115b15613237578860000394505b81801561325657508a6001600160a01b0316876001600160a01b031614155b15613265578589039350613282565b61327f868962ffffff168a620f42400362ffffff166141a9565b93505b50505095509550955095915050565b6000600160ff1b82106132a357600080fd5b5090565b808203828113156000831215146132bd57600080fd5b92915050565b818101828112156000831215146132bd57600080fd5b600080806000198587098686029250828110908390030390508061330f576000841161330457600080fd5b508290049050613382565b80841161331b57600080fd5b6000848688096000868103871696879004966002600389028118808a02820302808a02820302808a02820302808a02820302808a02820302808a02909103029181900381900460010186841190950394909402919094039290920491909117919091029150505b9392505050565b60008063ffffffff8716613430576000898661ffff1661ffff81106133aa57fe5b60408051608081018252919092015463ffffffff8082168084526401000000008304600690810b810b900b6020850152600160581b83046001600160a01b031694840194909452600160f81b90910460ff16151560608301529092508a161461341c57613419818a8988614822565b90505b806020015181604001519250925050613510565b8688036000806134458c8c858c8c8c8c6148d2565b91509150816000015163ffffffff168363ffffffff161415613477578160200151826040015194509450505050613510565b805163ffffffff8481169116141561349f578060200151816040015194509450505050613510565b8151815160208085015190840151918390039286039163ffffffff80841692908516910360060b816134cd57fe5b05028460200151018263ffffffff168263ffffffff1686604001518660400151036001600160a01b031602816134ff57fe5b048560400151019650965050505050505b97509795505050505050565b600295860b860b60009081526020979097526040909620600181018054909503909455938301805490920390915560038201805463ffffffff600160d81b6001600160a01b036701000000000000008085048216909603169094027fffffffffff0000000000000000000000000000000000000000ffffffffffffff90921691909117600681810b90960390950b66ffffffffffffff1666ffffffffffffff199095169490941782810485169095039093160263ffffffff60d81b1990931692909217905554600160801b9004600f0b90565b60008082600f0b121561365457826001600160801b03168260000384039150816001600160801b03161061364f576040805162461bcd60e51b81526020600482015260026024820152614c5360f01b604482015290519081900360640190fd5b6132bd565b826001600160801b03168284019150816001600160801b031610156132bd576040805162461bcd60e51b81526020600482015260026024820152614c4160f01b604482015290519081900360640190fd5b60006401000276a36001600160a01b038316108015906136e1575073fffd8963efd1fc6a506488495d951d5263988d266001600160a01b038316105b613716576040805162461bcd60e51b81526020600482015260016024820152602960f91b604482015290519081900360640190fd5b77ffffffffffffffffffffffffffffffffffffffff00000000602083901b166001600160801b03811160071b81811c67ffffffffffffffff811160061b90811c63ffffffff811160051b90811c61ffff811160041b90811c60ff8111600390811b91821c600f811160021b90811c918211600190811b92831c979088119617909417909217179091171717608081106137b757607f810383901c91506137c1565b80607f0383901b91505b908002607f81811c60ff83811c9190911c800280831c81831c1c800280841c81841c1c800280851c81851c1c800280861c81861c1c800280871c81871c1c800280881c81881c1c800280891c81891c1c8002808a1c818a1c1c8002808b1c818b1c1c8002808c1c818c1c1c8002808d1c818d1c1c8002808e1c9c81901c9c909c1c80029c8d901c9e9d607f198f0160401b60c09190911c678000000000000000161760c19b909b1c674000000000000000169a909a1760c29990991c672000000000000000169890981760c39790971c671000000000000000169690961760c49590951c670800000000000000169490941760c59390931c670400000000000000169290921760c69190911c670200000000000000161760c79190911c670100000000000000161760c89190911c6680000000000000161760c99190911c6640000000000000161760ca9190911c6620000000000000161760cb9190911c6610000000000000161760cc9190911c6608000000000000161760cd9190911c66040000000000001617693627a301d71055774c8581026f028f6481ab7f045a5af012a19d003aa9198101608090811d906fdb2df09e81959a81455e260799a0632f8301901d600281810b9083900b146139c257886001600160a01b03166139a682612d6d565b6001600160a01b031611156139bb57816139bd565b805b6139c4565b815b9998505050505050505050565b6000806000898961ffff1661ffff81106139e757fe5b60408051608081018252919092015463ffffffff8082168084526401000000008304600690810b810b900b6020850152600160581b83046001600160a01b031694840194909452600160f81b90910460ff161515606083015290925089161415613a575788859250925050613510565b8461ffff168461ffff16118015613a7857506001850361ffff168961ffff16145b15613a8557839150613a89565b8491505b8161ffff168960010161ffff1681613a9d57fe5b069250613aac81898989614822565b8a8461ffff1661ffff8110613abd57fe5b825191018054602084015160408501516060909501511515600160f81b027effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6001600160a01b03909616600160581b027fff0000000000000000000000000000000000000000ffffffffffffffffffffff60069390930b66ffffffffffffff16640100000000026affffffffffffff000000001963ffffffff90971663ffffffff199095169490941795909516929092171692909217929092161790555097509795505050505050565b604080516001600160a01b038481166024830152604480830185905283518084039091018152606490920183526020820180516001600160e01b031663a9059cbb60e01b1781529251825160009485949389169392918291908083835b60208310613c025780518252601f199092019160209182019101613be3565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114613c64576040519150601f19603f3d011682016040523d82523d6000602084013e613c69565b606091505b5091509150818015613c97575080511580613c975750808060200190516020811015613c9457600080fd5b50515b613ccd576040805162461bcd60e51b81526020600482015260026024820152612a2360f11b604482015290519081900360640190fd5b5050505050565b604080513060248083019190915282518083039091018152604490910182526020810180516001600160e01b03166370a0823160e01b17815291518151600093849384936001600160a01b037f000000000000000000000000aa6e8127831c9de45ae56bb1b0d4d4da6e5665bd1693919290918291908083835b60208310613d6d5780518252601f199092019160209182019101613d4e565b6001836020036101000a038019825116818451168082178552505050505050905001915050600060405180830381855afa9150503d8060008114613dcd576040519150601f19603f3d011682016040523d82523d6000602084013e613dd2565b606091505b5091509150818015613de657506020815110155b613def57600080fd5b808060200190516020811015613e0457600080fd5b50519250505090565b808201828110156132bd57600080fd5b604080513060248083019190915282518083039091018152604490910182526020810180516001600160e01b03166370a0823160e01b17815291518151600093849384936001600160a01b037f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc216939192909182919080838360208310613d6d5780518252601f199092019160209182019101613d4e565b6000808361ffff1611613ef3576040805162461bcd60e51b81526020600482015260016024820152604960f81b604482015290519081900360640190fd5b8261ffff168261ffff1611613f09575081613382565b825b8261ffff168161ffff161015613f4f576001858261ffff1661ffff8110613f2e57fe5b01805463ffffffff191663ffffffff92909216919091179055600101613f0b565b50909392505050565b80600f81900b8114612beb57600080fd5b6000806000613f76612bf0565b613f88846020015185604001516143a1565b6040805160e0810182526000546001600160a01b0381168252600160a01b8104600290810b810b900b602080840182905261ffff600160b81b8404811685870152600160c81b84048116606080870191909152600160d81b8504909116608086015260ff600160e81b8504811660a0870152600160f01b909404909316151560c08501528851908901519489015192890151939461402c9491939092909190614acf565b93508460600151600f0b6000146141a157846020015160020b816020015160020b12156140815761407a6140638660200151612d6d565b6140708760400151612d6d565b8760600151614c84565b92506141a1565b846040015160020b816020015160020b12156141775760045460408201516001600160801b03909116906140d3906140b7612c27565b60208501516060860151608087015160089493929187916139d1565b6000805461ffff60c81b1916600160c81b61ffff938416021761ffff60b81b1916600160b81b939092169290920217905581516040870151614123919061411990612d6d565b8860600151614c84565b93506141416141358760200151612d6d565b83516060890151614cc8565b92506141518187606001516135ef565b600480546001600160801b0319166001600160801b0392909216919091179055506141a1565b61419e6141878660200151612d6d565b6141948760400151612d6d565b8760600151614cc8565b91505b509193909250565b60006141b68484846132d9565b9050600082806141c257fe5b84860911156133825760001981106141d957600080fd5b6001019392505050565b6040805160609490941b6bffffffffffffffffffffffff1916602080860191909152600293840b60e890811b60348701529290930b90911b60378401528051808403601a018152603a90930181528251928201929092206000908152929052902090565b60608060008361ffff1611614287576040805162461bcd60e51b81526020600482015260016024820152604960f81b604482015290519081900360640190fd5b865167ffffffffffffffff8111801561429f57600080fd5b506040519080825280602002602001820160405280156142c9578160200160208202803683370190505b509150865167ffffffffffffffff811180156142e457600080fd5b5060405190808252806020026020018201604052801561430e578160200160208202803683370190505b50905060005b87518110156143945761433f8a8a8a848151811061432e57fe5b60200260200101518a8a8a8a613389565b84838151811061434b57fe5b6020026020010184848151811061435e57fe5b60200260200101826001600160a01b03166001600160a01b03168152508260060b60060b81525050508080600101915050614314565b5097509795505050505050565b8060020b8260020b126143e1576040805162461bcd60e51b8152602060048201526003602482015262544c5560e81b604482015290519081900360640190fd5b620d89e719600283900b1215614424576040805162461bcd60e51b8152602060048201526003602482015262544c4d60e81b604482015290519081900360640190fd5b620d89e8600282900b1315614466576040805162461bcd60e51b815260206004820152600360248201526254554d60e81b604482015290519081900360640190fd5b5050565b6040805160808101825263ffffffff9283168082526000602083018190529282019290925260016060909101819052835463ffffffff1916909117909116600160f81b17909155908190565b60020b600881901d9161010090910790565b60008082116144d657600080fd5b600160801b82106144e957608091821c91015b68010000000000000000821061450157604091821c91015b640100000000821061451557602091821c91015b62010000821061452757601091821c91015b610100821061453857600891821c91015b6010821061454857600491821c91015b6004821061455857600291821c91015b60028210612beb57600101919050565b600080821161457657600080fd5b5060ff6001600160801b0382161561459157607f1901614599565b608082901c91505b67ffffffffffffffff8216156145b257603f19016145ba565b604082901c91505b63ffffffff8216156145cf57601f19016145d7565b602082901c91505b61ffff8216156145ea57600f19016145f2565b601082901c91505b60ff821615614604576007190161460c565b600882901c91505b600f82161561461e5760031901614626565b600482901c91505b60038216156146385760011901614640565b600282901c91505b6001821615612beb5760001901919050565b6000836001600160a01b0316856001600160a01b03161115614672579293925b8161469f5761469a836001600160801b03168686036001600160a01b0316600160601b6132d9565b6146c2565b6146c2836001600160801b03168686036001600160a01b0316600160601b6141a9565b90505b949350505050565b6000836001600160a01b0316856001600160a01b031611156146ed579293925b7bffffffffffffffffffffffffffffffff000000000000000000000000606084901b166001600160a01b03868603811690871661472957600080fd5b8361475957866001600160a01b031661474c8383896001600160a01b03166132d9565b8161475357fe5b0461477f565b61477f6147708383896001600160a01b03166141a9565b886001600160a01b0316614cf7565b979650505050505050565b600080856001600160a01b0316116147a157600080fd5b6000846001600160801b0316116147b757600080fd5b816147c95761469a8585856001614d02565b6146c28585856001614de3565b600080856001600160a01b0316116147ed57600080fd5b6000846001600160801b03161161480357600080fd5b816148155761469a8585856000614de3565b6146c28585856000614d02565b61482a61564a565b600085600001518503905060405180608001604052808663ffffffff1681526020018263ffffffff168660020b0288602001510160060b81526020016000856001600160801b03161161487e576001614880565b845b6001600160801b031673ffffffff00000000000000000000000000000000608085901b16816148ab57fe5b048860400151016001600160a01b0316815260200160011515815250915050949350505050565b6148da61564a565b6148e261564a565b888561ffff1661ffff81106148f357fe5b60408051608081018252919092015463ffffffff81168083526401000000008204600690810b810b900b6020840152600160581b82046001600160a01b031693830193909352600160f81b900460ff1615156060820152925061495890899089614ed8565b15614990578663ffffffff16826000015163ffffffff16141561497a57613510565b8161498783898988614822565b91509150613510565b888361ffff168660010161ffff16816149a557fe5b0661ffff1661ffff81106149b557fe5b60408051608081018252929091015463ffffffff811683526401000000008104600690810b810b900b60208401526001600160a01b03600160581b8204169183019190915260ff600160f81b90910416151560608201819052909250614a6c57604080516080810182528a5463ffffffff811682526401000000008104600690810b810b900b6020830152600160581b81046001600160a01b031692820192909252600160f81b90910460ff161515606082015291505b614a7b88836000015189614ed8565b614ab2576040805162461bcd60e51b815260206004820152600360248201526213d31160ea1b604482015290519081900360640190fd5b614abf8989898887614f9b565b9150915097509795505050505050565b6000614ade60078787876141e3565b60015460025491925090600080600f87900b15614c24576000614aff612c27565b6000805460045492935090918291614b499160089186918591600160a01b810460020b9161ffff600160b81b83048116926001600160801b0390921691600160c81b900416613389565b9092509050614b8360058d8b8d8b8b87898b60007f0000000000000000000000000000000000023746e6a58dcb13d4af821b93f06261513b565b9450614bba60058c8b8d8b8b87898b60017f0000000000000000000000000000000000023746e6a58dcb13d4af821b93f06261513b565b93508415614bee57614bee60068d7f000000000000000000000000000000000000000000000000000000000000003c615325565b8315614c2057614c2060068c7f000000000000000000000000000000000000000000000000000000000000003c615325565b5050505b600080614c3660058c8c8b8a8a61538b565b9092509050614c47878a8484615437565b600089600f0b1215614c75578315614c6457614c6460058c6155cc565b8215614c7557614c7560058b6155cc565b50505050505095945050505050565b60008082600f0b12614caa57614ca5614ca085858560016146cd565b613291565b6146c5565b614cbd614ca085858560000360006146cd565b600003949350505050565b60008082600f0b12614ce457614ca5614ca08585856001614652565b614cbd614ca08585856000036000614652565b808204910615150190565b60008115614d755760006001600160a01b03841115614d3857614d3384600160601b876001600160801b03166132d9565b614d50565b6001600160801b038516606085901b81614d4e57fe5b045b9050614d6d614d686001600160a01b03881683613e0d565b6155f8565b9150506146c5565b60006001600160a01b03841115614da357614d9e84600160601b876001600160801b03166141a9565b614dba565b614dba606085901b6001600160801b038716614cf7565b905080866001600160a01b031611614dd157600080fd5b6001600160a01b0386160390506146c5565b600082614df15750836146c5565b7bffffffffffffffffffffffffffffffff000000000000000000000000606085901b168215614e91576001600160a01b03861684810290858281614e3157fe5b041415614e6257818101828110614e6057614e5683896001600160a01b0316836141a9565b93505050506146c5565b505b614e8882614e83878a6001600160a01b03168681614e7c57fe5b0490613e0d565b614cf7565b925050506146c5565b6001600160a01b03861684810290858281614ea857fe5b04148015614eb557508082115b614ebe57600080fd5b808203614e56614d68846001600160a01b038b16846141a9565b60008363ffffffff168363ffffffff1611158015614f0257508363ffffffff168263ffffffff1611155b15614f1e578163ffffffff168363ffffffff1611159050613382565b60008463ffffffff168463ffffffff1611614f46578363ffffffff1664010000000001614f4e565b8363ffffffff165b64ffffffffff16905060008563ffffffff168463ffffffff1611614f7f578363ffffffff1664010000000001614f87565b8363ffffffff165b64ffffffffff169091111595945050505050565b614fa361564a565b614fab61564a565b60008361ffff168560010161ffff1681614fc157fe5b0661ffff169050600060018561ffff16830103905060005b506002818301048961ffff87168281614fee57fe5b0661ffff8110614ffa57fe5b60408051608081018252929091015463ffffffff811683526401000000008104600690810b810b900b60208401526001600160a01b03600160581b8204169183019190915260ff600160f81b9091041615156060820181905290955061506557806001019250614fd9565b898661ffff16826001018161507657fe5b0661ffff811061508257fe5b60408051608081018252929091015463ffffffff811683526401000000008104600690810b810b900b60208401526001600160a01b03600160581b8204169183019190915260ff600160f81b909104161515606082015285519094506000906150ed908b908b614ed8565b905080801561510657506151068a8a8760000151614ed8565b15615111575061512e565b8061512157600182039250615128565b8160010193505b50614fd9565b5050509550959350505050565b60028a810b900b600090815260208c90526040812080546001600160801b031682615166828d6135ef565b9050846001600160801b0316816001600160801b031611156151b4576040805162461bcd60e51b81526020600482015260026024820152614c4f60f01b604482015290519081900360640190fd5b6001600160801b03828116159082161581141594501561528a578c60020b8e60020b1361525a57600183018b9055600283018a90556003830180547fffffffffff0000000000000000000000000000000000000000ffffffffffffff166701000000000000006001600160a01b038c16021766ffffffffffffff191666ffffffffffffff60068b900b161763ffffffff60d81b1916600160d81b63ffffffff8a16021790555b6003830180547effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790555b82546001600160801b0319166001600160801b038216178355856152d35782546152ce906152c990600160801b9004600f90810b810b908f900b6132c3565b613f58565b6152f4565b82546152f4906152c990600160801b9004600f90810b810b908f900b6132a7565b8354600f9190910b6001600160801b03908116600160801b0291161790925550909c9b505050505050505050505050565b8060020b8260020b8161533457fe5b0760020b1561534257600080fd5b60008061535d8360020b8560020b8161535757fe5b056144b6565b600191820b820b60009081526020979097526040909620805460ff9097169190911b90951890945550505050565b600285810b80820b60009081526020899052604080822088850b850b83529082209193849391929184918291908a900b126153d1575050600182015460028301546153e4565b8360010154880391508360020154870390505b6000808b60020b8b60020b121561540657505060018301546002840154615419565b84600101548a0391508460020154890390505b92909803979097039b96909503949094039850939650505050505050565b6040805160a08101825285546001600160801b0390811682526001870154602083015260028701549282019290925260038601548083166060830152600160801b900490911660808201526000600f85900b6154d65781516001600160801b03166154ce576040805162461bcd60e51b815260206004820152600260248201526104e560f41b604482015290519081900360640190fd5b5080516154e5565b81516154e290866135ef565b90505b60006155098360200151860384600001516001600160801b0316600160801b6132d9565b9050600061552f8460400151860385600001516001600160801b0316600160801b6132d9565b905086600f0b6000146155565787546001600160801b0319166001600160801b0384161788555b60018801869055600288018590556001600160801b03821615158061558457506000816001600160801b0316115b156155c2576003880180546001600160801b031981166001600160801b039182168501821617808216600160801b9182900483168501909216021790555b5050505050505050565b600290810b810b6000908152602092909252604082208281556001810183905590810182905560030155565b806001600160a01b0381168114612beb57600080fd5b6040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c081019190915290565b6040805160808101825260008082526020820181905291810182905260608101919091529056fea164736f6c6343000706000a03b77131c1b9aee7534978db824b772cc3256a400cf8d6166a19d8a58b360c0ba003a1f78d9693bcd5a58c2feefa8b6865ebcfd841e62fb82e526e188ab7882ce4190366a309a96c0dcda4eecc20e8cdafebc753c57a5133104c8cb0638f0c19063a880355d40929974909af7204e0854244aa122333f0d83462287a6328c4df3dd513c103c5ecc63d6b5b23a69e5a2d1cc54f7858681e7659e77be3b12e5d682468aec2e803f34c132a8d62712860aaa9dd7726231931ead6b0ec8feca80502a9bc71bb518e035e7c06830da5d61486d75fb45aef711f2314109257409684641035f66b4b67d7035f364edd41fd03d6d5559fe2734b2a0605628c80aa43078f2d64b4040ef6d56803d8d159b23abefbaa99d9b6c0ee3df9f8cfc34d0e5ad8f452bc4df6411ec2d04203eddd52d56b49348455db9b1d2bc13c3b9b6270bcbf20f5b76de854b94a6459bd0396b1960dd27e222527e4cb064eb8449a70a219dbaad098c06c4914f579f4831900582003decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5630581f010000960096001cff4604000000000000000017aec52119ccd7cc88860f2603eb8bea9692a47fb99a8010b171af4618327466493ea64bdad070c36389e2a352014101005820034e426c4bedf21aa91acbc91530bfe652db9ebcab8cf58a5e826a05f75b61305820ffffffffffffffffffffffffffffffffffc2d6ed506d354d2a49a2f317cee393037f0acc77793cb6606c3aa841e62b2d7d400f12ae729395d21fa313a70b16dd710058200310d70cb8916de18e97192e4bc74ed0fd05842fb98dbd70d1b4a091684594c050385c4b772fc5848f05ab69adfa9172e8005820033dcad244d5755f1d2b08d84747e15a5e72a22f6c5405fba4cb4fe9e226ec004fa36f1822be7bf3e78d1871b94d4b140058200328644b4979a1fd54d6000361880e49ebfa78c8a19050b8a4fecca4b21068604fae1387dff059c22e476e6e406d166400582003ea50641ec7821f7a4e0615a834a425087d66b3b27e8304826904d6cc8e8fa05066ca06fd1b9e60d9d76686b334bbab0700582003ff92a342df489dd95855c374221e2582967b984deaafe340c4c23fd0d2bb504f1576e0ba37d5e368068c08f619293f02195b3503ad1aa595d32fba83caf31a4907aa9e79bb5f1b66df1108b19cad16f7aeed1708038b5f70a3a91f73a3d819bf5f0e0b7304d43c349ddd443e09951d3b22bb425ab10355252b34159a667de1da04e7a39f3caf68e03306710f4193113bb73b36f4b70d035124f83a6f3133ccb9ade52132236aa5f50ffcd179277b65176100c08046098e034a90d45640de6511ed3a39d1f6d60b8d2a500f9c9bee5ac0fb439a93cf30421c03e8b92938b9979f7657b399adb3d3340e6239af360b346cb67dd2b69d110f68a20219ffff03c25585b5075f18f1e87f9d934f7df1b6acee449e8daff4c8f1ed50378b65d369039c0edf77a4f67500d94efe580b3c0bd8ff2a8060b1a000563675366414b20feb00582003968ff42a154441da5f6c4c935ac46b8671f0e062baaa62a7545ba53bb6e4c0582001000000000000000000016fd193522bd9fd77edbffffc8811714c3765c3b7b703eb9bef866e0b242f641e8235df09d54029a7dccd085be029ebe412f466557f0500582003c121d27737c7e04e89b1c35a201228afbc590edb2b7cc7c37b1cccd911e4a05820fffffffffffffffffffffffffffffffff5e53f39e2a0a8c19db565df1f277e7a00581f0214e07e238b869c8de897078a136a012c4cfc1865f9bb2fb068d563e723b75004bae6218a382c25d79ee59665b5433b00581f0287fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace5007b776b6f7f89a9b01153e6edf9f705b00581f021ba098010bf28167e34294f0d4f5362a5ad91a479b25b0e4a29c29d913724f0b01b6bdfb95c34ae42942f98c6362021902a00058200372231f0df6d604b39b3c9e0b21f04f81d032a267d8ab634f1f11407211f8c0500e15ac082551fd581c8d4ba52739699900582003018d72b691f50a1d0388afe78d55c356d4eda980fedb81e990019a266969204f207d27080ad32f53c39787886ee954005820033f34d991ba4cc16296e0e23edc9f782e961faf5c50b7abbf97dadcc9e8bde050055a477131398231df1a40476e43ff680058200386fbcd7aa48bb25b13db7fc2c791a8bce6c3c61d08b05158c6f6af3cec9270582001000e89fe00000000000000000000010e7ef369415fe992eefffffaa9da42120058200326da45bb052096e7110481c3729a44362fcde2c35f8bf9197e13b79615771051010000000000000000000000000000000000582003a7b91630585f599ae9f4c43b7dceaa11a82e83e07ca04a9993f9061f2329c0500c72de35d21081f9d8f783ab4010c951038d26cdf0783d7dd5ae5510f6718c8e2d1e969d776a612e420f1f52911e5931cc021997f703fcacafd197a9813e8d1eda7f19c05bcf2124c474f23943c597b6e9655fbc91b803c77b5a7e14566ae2886fba2f0748ee2e9e6564d584b15264b8ec5038e2cbd8fb0301fc456aa244080b2967e61fae40ce7a93d78ccc6dc6f594c91287ffc9b8698a03912ede85665af05f8d00f694b97d0ce4128b2328710ccf276e8781be836857150343ec712582b76168cee9a4fb0222b5ddc89fd311d08a3d2c7d64e4651a166f1c035470a6bc2e3ea047739dd2f52333b6369f1957354b9c0415c980a78957c24bd4039a97b39d25426509a820615931d6947b8b5165fcd7d37a01114074d52fcdfdc403ec332af67c7442a29bf444450221b07a0274550580a6b65c4523b74fcfc4a7590351087bb1af26310c3a04979b58bd128d4b63f71d92200eb9b9381a3e72dfd48103494b5e06e26dd91d8401df94d2afbdd9e6cd6e2b7f7af6ba088b7ce0f7b577ef03778149debd725a1a38d065c900070d24635a695d3d297e2e4c84a76308630fd6035184035a15e4e3d73282f850618df84ecb05f5e46b490691a314ea8f3436f427030017e6fc16f5e96004b5ca6b6110258b81090991f3d9705f7dc28c484e9fc7ee03a5a4a351624154bd6caa487570cfd25c8e8778a12e944290748fb28fbf280666036ad37e6bd9f69adf26986c532c9058c76d23c6de34e52f13f395512bdd503ce30219ffff0319bebc0d5b9b3e2c426e12f2a9f713208ba322f9864aa2b1b5a944368dce26df03395723bc577c79b2b53b32b19ff3776faec8f0df59ae3b23ae540646f2d32d5d035228cf4114183b998c8c33bffe16de282b577d944e1d309a056e481ac650394503c7b18898724891b2ba2541b13ea2eab5ecf5a4bfa0f7c020fccac95a9f2eefdc03dae8404b93fe261341cf3c3fef6f00d0c1b99cd42d9fc181c9519ef17f09abab03928c2c7eaed2d45972bbb9183db6a6326c92657bc7ce0d31f953968dbba6713503cddff768c20048446a862f463188ef7d25cab55a4648664edaf847a1bb3ab006030d8685d32ff5fafce8532465bb27d075ebb1b5edfd4a1a50c5b533f8287509cd03dc443848fa04fbf4b9e64bdbe30ec15d818bdf4edc25ff8b7f266e8af17d5bab0307fdd008a4cf94c97b558504b2c7851945ab2fc18fe842140bb3255bc1722e7d03c0a1ab5b5f4e29a6c7159dce173684492672f1c42b62c48e154b54c281a0e866005820034d838ae5a65c8684413e5b1fcdad8ad7c086453dfdc5b5a0d562914945d9704f3cb0cfea6a5225f3d651767aac185b0368706b4b36e124522853198276c703644a5b6d3ff373848992bcd821e8cc5ef80058200305c4dff12a2714b8b53f012978be28b6502cb9cf75debc3c5d698f88d1ab905006b611cf3200b3b76acefbc1dac1b9c200582003bd3c01070c86ec2f69d5dfa20e0eb72498e15246f6406724d60f2bc474ea40503df4362df57ebaf686420ab7a547d5f400581f0280cb52633fcf143703e205ff58e289adc650372f4feb3c2394fb63a8d0145820010000000400000000002884010015000001882020000000204000000000000000581f024e74037d718043e4ee5e4948658f38671f033648d671e4c2451864da8a2d4f0bfbb1ec225804fa6ab43cec0d261b00581f02d7d41f680d5328f2591f00795632a7a766e55ff0e428199037bf0b3cd2e44f4e73c958dde80f1550924838bd128c0219042100582003c22f5d19620fce14649126c5a51907c5f8e9a6b57af75a9c331c808f0b140050039754149606326c4f79a30a795d9c7000582003d649816771542bf1f465abe97c8a9e73195a7d97dbe5df4da6eaa593600e105820ffffffffffffffffffffffffffffffffd8664657acdc1dbba5cfc5f2ade037af00582003d8ef97b7aa7e801aed1c178428c53eacb13aa0b41f6f2c8fde03fd966ee7f05820fffffffffffffffffffffffffffffffffab46c98418671b9176361d098bfa7d8005820032b9c192a4ea260a57815d3b69ba50415ab39f2a8704e6d4f689e680f559a405002969a47ffb335a5988c5f5fd7a5043903fd1e7351b9464944ccc17b65807e4009cc5e8789b731486300f903a97fb4ec6100582003b3171b18daf9ce9a31212ed4faf6f734a32d2828ac1c1afec2367f320c7be04fb630fbdfa69d21b66265954f6c0ddb0373673a9f3790f26b8091ef58c4595c7c38d4eb47eb95bf2f4aeeaf8e76ebd82602199fbd0383deb6d3a9f6f3e4d31373a9d6eb5c93872baf5e792e06bd20776d4a870037a60390121b97179f77ff96846a265459df220ecd717aa39a0a72c12b703ba8460f600340fd9d1d88d3b863d2825a7da190b82c1836c507f28b2e21d7deef9cdd060c1d036fb25ca541ff6c5a282c887fac9565b0125587257e53e3515795ba45e98ce59b0219ffff03ee1e1aa658fb8c60920df74ddce91dd1d0f8a6f3ab18c60980eefeb806433f1403b90db81803bb6373cc3b3d68e54a7949f17f02262d8e4e3faea8f45cddc404e8034d1271ea4abdc2e3dc878a5fb289ea2dae6f987cb311c2f49fa5256e588f596b038bb11596e850820838d84661005b7827ed47407593996c855e59ac4c21e6631503e49796c1f22ce92cd663cd8ccee356b7520e733a4623d099594b5f2e123dd8cd0380b692f11f202873250cfe5a415c52491ef77ee89a038120b1579409baa6dc0303fefef13c3796cff6dd46c26e9d1df070508f77a45ab261f508c3e983f9e2b045032e5c79ae34200497f4f0d8d8c458ed7f8a6d2f13d8c9d3b2160604134ac64586032191502e0303bfdf725603f434780e13f50a8981811a6cf86bda7babccd83624033a9f78d7ae4ce0b01493b7be5502918e69780c33cee283758daa371cbbad51190390cac81febaa40d8eec9c1cf00c6ebcbc0d0f4ef7710fea16b48f2877ea62058033f5c39ebe2fe74689645198d3a14c67c85a3aa53059b8191ce69e5adac1163c903276619a42502df20063e1ed86b402dc3e7e7c4e2d5f7e19a7ad1c6ece812306903aeb5125574ee3697243cd18132ac4cdb41d8016326709b33b5e693b614c9264500582003d9919653a601927c0af950d3a9cc227ab7ce19b0c5eac3ab77a84e20bd6f404fc570331596ad6cd707c29f4f8cdb0a00582003525a322cbe10b91c29bb870cd3c38e9f33d4a35d237d8695052d912f383140500251e25eb209c2294b206712f95c182a0338cf2caa64e9da7d2050ab4565346afadc69cf1a7519cf4991d83c3c733ff0a403bd69f78e580eab7da9e0d19abf6f25c25dd615f79e2bf55424256a2afe1f0853036efd46199bdaa43f827731d07165fb78c71a18e66d987e29e501bf978a353c08005820039480327b2cfe2326890bdd1d5854b4ac8eac5f88839e3a1ecfd61b8aeddd505002874069c9522c7fc0b26949db10a7a003e6f6ed277165db0683a3a625a3ca4a13dd2e45aaefb42bfe58422a9c730750df0058200318f60fb8648676c7adce56102ade3b25254bafa7641b6d45d6589fce8775304f010de13fff819b46596b99d9870df100581f0232d19b93bcabe3cce7ca0c18a052f57e5fd03b4758a09f30f5ddc4b22ec458200100000000000000000001739cf8c3361479d55f6efffc7f6fb6bce365cf606300581f02c42644cfe9df7db4f9f66ddc2a14e7afc71e2d49fb7d80dbdc1a480c61044f34f95ae32c061bb8177277bfd2b9d102190808036b519555bba3fc0346356b40268693403f6f53944d579bc8f42575000096f72602197e3b03aec9b08e2aa7c86ce83f4777a5f7702f79275fcb66dfd7dc119408136ef6308e0354fb36f6aec3c0e8edbbae05d09c1025ce5599bf7bb01982279afd041894b46f038e655744c5dba5193b4586b0df1ea37e397c130e16dc65fc1a94a87d32248ac20219ffff037c753178831286f4d1f2593639a7fe69531e9a80e938ca79f82238fbb20d56a803182a3b78787fa55b9628f7b5176117b2c0b4b92e7d0a63433f03eb7e7e69b9c103e521db573cd3ca1599e6cface24543bd7b7584a64f8316a7928223b7d6e7c094038afe0b7700a605689217bb16fa87a3ff388dd51ea160a09820e6d2debac7e92d0313be0609f8fc79cbafb51b06a816b9c3b300fe618b596173b75fc615072c497d039cb07a2241f4bb6d07a6931f5bf40098244745c64398895646a70368867a099503499338faa9e931ab9765d8016f8b405f3477bae41384a3209ce50125762bae5b03fc52c593288a96831372f05e4114546473e32e31027bf249a5ecae7699b75e02039f2811e1bcb29e7511b7e8d4d24d406929bdb03523a3fa6b8f7314f9796efc6c0346732f61e74707118439bc7be73dc1ced99c53a58eb75cf8b63182db902e442100582003671d854f4c0db92567dfdd3882cfe9e64096da1233a9bb2153f97d6ab4dd2058200100ddab56000000000000000000000a8ee562be632ec9dc7cffff97b6b53e31031923b8e47eab25d58b34001d5fe3c89eeb1b909a1203f269ed600b55e798eadc00581f02acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b4a030c7ac73fb9897ea01c00581f024c7e7c7be117d923e8ed2b7cbdb713c5c901f8ee5fafddf53235ad0437984f1d44327f21439be68f32aa9a5469c602190220039eabc2ced54e36fcb1f8e2af2b39f99cf637fd15f22a9033a5d9aaa3ee3a3d5e034f47c70305bbbb734b299ec4f3cbc1dc86606fb031fd67cbff3bc686a56db0c303e3cfe8372f479600e1fd1087198873e95527013fc200d0c32e10a6991deb5a69005820032aac4e4f7bc7734b3fe13f32f940c277e8e780d309d9804e886bba4177ae804f05c4b1b82b1468c6d6558da7f557ff03392791e65ec0483f92d8603a74fe43e5d52569f836c69936669fcc6d0123d8c703e2458d45580d37fb7413cd56300a81cb2fdbe2fe04c2fee99e62dcd13a81975d00582003d8026adb3f0b39943de67e2c6bb15e0cc26ab29f561fed47880f49cd598fd05001d75f01db845d9db3c33436f33e900f02194dee034dfbc9509900386e63d6c6f176359f3ed5ec69d9fad0d62fecea5649231f97980305ceebb04b366fe035cc8cfd435e83e2e1d50b3d83002aed3370e7dfb92a24ce03758f89d5afe001d5d4ef7925b24eb5c313e8c0e8f669659d154de3d80b662fd80377896d90e9f9ef4038447d103a052ac9f32bfb11f3879903cbe6830f850d4041033bb0e0a874eb2d2884d5eb8e368891662c4780a74039782b132b60bbc2eaec020219ffff037e31ff49fab1ada0a32b272f5bc2551a11c695cfdc847ff91c7a4640c3d39b4a03449d5341ee0eaa9ee13c297201bf0f0cd299b365815d34f64627e5947475f43103b1f9459cbda042214df27cb10a8d3bce17e36003c241193d238255187f3b830700581f024851edd98ba87f6101f2a143ba0c574549a6ecca34c76f11ead81e9397934f0c27ac9081a151b5a07341de80640800581f022d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf651021684e65c4d65b79c4d9b4fe20729441502194002036d05515213ede2ba7619a8e8e6e130e51d6d553cc3b37c620bfca77282d5d3c90367ac5ca191b7bdd4ba327abba362a68ba3269ef2af82170ceb387439149804cc00582003c536c28952452ba13d1c82058ae0d67c57fd1ae363daad093f22bc9dbe79e04f0a5470f8ce8ef6a739f6f6082523360058200327377986441823f2b71d2638e5ca5e21acfc94660b4ed39268f6d8f040cac04f6e7b1fe8167c11ee1974227850b5f500582003d6a7a196273dda54d4c0e7efd6bc2e563ea621b33e1bf588a815ae0c1beb505052b4dfba30d13116f0433118b6a5a85200582003ff6432fd55ab276aff0be3da5f0ffcd7e143c7e617f6b64eb480d1bc3409505820ffffffffffffffffffffffffffffffffffa06752c0efcd039e7413a4af12f1d0005820036152da20f01019b3d3532e27e24c30a821e69673631e7fcb0d5dd5237b78605013b46fa3b1670d901945f70bc0d60b8a03d22391a202369c77d66a9444b3fea4bdb3c21e8c4292cba5c20696f768e0681f0219637903eef4041b52956da595a856926c92e3d1d4fdf0bb45e170d40a7ba69eb0eb278b03a6b189c01e0ce4c084ed2b6623361caefbd1b67a52b1f6c0e0c49b8c6c06a23903b28183b6b8c9ac5601f416adc15f9dd94e686a41cd999f025578ed0f3014a38d032f7182ee4164d21259a0ed940f9f35b679acb6a6f55282b4b8c0cb2d4c31c3e00348838e39094baa5ae8c27a429b85c04db85982d324e2c3d644fce5d36bb1aae903a63b6c605e2952ea57f6412c6260e5112ee35e1dd5947dcd1fe9c43665124a6903867e39aaabf277ec8b4ea431a869062b228957c44f6c567733df42eabed40e91035de3aa47b08b8f3534df752000887d3347b8be24b0b36ad5c2ec330a7c2c4ed003d97692ae1e96ca372e054bf68bdaa6966b132408e90392371359962f5618954f03d4db7944569f34c4cbf452fa878d4d01d8d720ff3c4fd4fedd9594a3fd37bb29038390662f3e84ee774002a1eef784e01ad642dd453bc3eec188db6ed62ca70c790398da0cdd619255b18adc865b07a06510259f984ac869564cfa44da8028d546d603604db99953f01e36304aba22a39a6b57fdb9726bb238299fb6b0405c2e1eb9ef03566d643ec3a7d350b939afea90472e58adb93df7ca069d32c14e76d32477170f0219ffff0361109cd54f42521b78efe2d81d8971e2dd8861bc4ce70dd048106d8e6cf983f103cb44824f74f8e510fdf95e9758bd80e1b8126b9fa42ce8703efb8afbf1502cef03808f73c01285ef69807c568736f36458d0f50a4799c78f206f9624c25039719103bb5a02fa828f3cef05dbfb57f584e61028825e561f422e2192ec7df8867a20650219ffff05581d0383bc100b4e63e815deeba81810d9d6921d7ad3f4b987440c84ea6660070119567e021918000219401005581e033ac23442117a03e1a39f9fa7d9d5c798bd9634e3778a89443707e7ef000c0147018ba3a825fe580219ff4d03f7441febbb24a092f21df38d55f898f166ef669af077a8a30d29e41e890e4ec8033a7c16e9870a8b58f653300df6ba113c6d9819bc8c3c749513ff4eff8e5f24ab03f4fda840d40699253dd6595de7edd1244996a322226a076b88d4e7963d1473da035e07f4f5c5bc05db698e4f74e56281bac26b92f3fd6708505b8158616f71beb2036c9c2bb1e6ff641f627d39b37f22f340f7888dbcdea68b328f56e8ba588cdf1603a8fb00dc8b59d862a48e3271d4533c255135f2b85acbf2abc6708a2011238dea03b7cc0c36b8ee2ef58a01be43d30a241bbed070d98775bf6fe2f41ba40e5584b5030bada4de2f8b01f6dd5c0f86d3e2e4965b7b29ab5deaeaa9af4bec389ff94de40219ffff036e9eb91d179e60045aa68914edceb269f2104453ef74a1b619d73a87c2074e2703d5fa2080925b5e5e8d768ce332f293daf6409a8be483814241c111dd3c6527ba03cc9623a767deb166b946677b8d0e3656362fdedb3c48cbbe4b85a4fa0eec1e3d03ea62e05a68f5e9241fd14c92963a8ae6575ca2e8578c9b13ef6126de189e7130031265699ec46ea7aaaf69519a69791b33b6907c6c056260380950f3f8d2dcead80332796f9b5d19898ca5b0d186b2be7f23b2134d39016fcc811dc792f59980837803a5e47a0573a20acdd6bbb5d9f7f8d457a23f97e314c05f83bd75e6bda9082dbd0219ffff03d5603430dcb98671ee6a5c952978d436185d507267fad3129a195b2880ac6eab032185ec2cf26df33583528167aa03262a4bf9276f9154f2b4bcc86c713676281b03fd9884e5307ce88805443a1a19216cfe7c1ba35cd8ed1ea858493835b6ea03b503298ff43a362ac7c3e6259a06fcc6e1d0edfe5777403700e81ea26650b6c39a4d033b77fff2e9760b24dd7942ad28ffdadbe0222fa2884145e2bf3068bbac9832be03d2ee942623090127a96b2b4418a3b09632ed7fdf6ec3a6e2883521b9cd9fdb8403d13735207cdbae96489c46d0597996ca47f9000aeb5d8bad466f58bca31cffb503c4228e4a97cf576f70f37830672a52a96dc91087fed7623a16d94d185eff766b039628dc0ae1456f27a1d48a62032c954c8892af74aa0982f11b610282f7fb602103a0686cb98613212aac4955e90e79c7b7be8536787805fc9553accd0854cf3d5d03fe432b8d32820e7fae2a30ee7a1b75d54ae56a8f4da9017f322523fa3cd32d390385cdd8f515f3cd335999666ce749f8d808101186ceb8c50e52be482a3267d88f03b153752f28768a3d5388b48f11d3178b770064972e92516a7c2784a7747cfae40307049d11f19341a255b49a97c187d975f86aedf58d7ead6092dccf99ce178c1b0219ffff03e0bff19788647acd2a6040cbb54dbaf819744a70d75fa5adc18b15c49ab0b11c03d0ce3ead798d76bf6d40f1dc622b08619ff804155429504a67ca6a3aa9179905037551db937751d6d3ed912b32fb9e08c225dca562ae657cbb88ad0791523b86860341d634029bdfa0a035280bd866b0c8b27f687f489535b78f6709efa537efbbee037bd49328385d47b1fa72bde391303e620c6fd218dc52af67ed485c7d1616d068032db019fcda6b36f61c15e04eecdae1772d0c3fbf284d7610ed35d24978ffb3c8037c1724ce6e17f013153110be1e1d2efb0cb2a8ef703925be0c6b520c51fe49d203bfa6fe8cf877a3d77ac36b032e6a3ec9df2e7028dbc1c3b7799b56be19080a90035e8a9aaae3046642a6d11d70402febfe2e6ad5e02578e9e7ac4d1492eb207bfd0372f17c85ab7d240df22eaf3216f48dcdbc6d09df4da1fbfc7453262f9713918503bc4f545f62f0cd92e155451432c85b5beb2edeb8962123de730d3e5f4925782a038841d625cefa6f9e837dddcd4759d1b7d75272c602cbf8509e8317752f4c47bc0394090c854b9d78b43badadb176d8a422280b3d97ee568f406e05579bf38672da038d662e7b7cbc9bc33437a4df76a7fbdb2c002d75f9d1bde109e26af355fe5a100219ffff03eee380c496116d822a6b592f7cb2b42fb5992b81dea2237f9bc703138e88c58203e1657b4f4bca026a7deec369b9915faeee2da5466013065ffa91f59ae01fe0710219ffff034cad8bc5dc9eb3ab9b105ac920b95f8f2c8911266757ffd8afb0af462cadb0ca03298d89b92dd7b5f61845b57f18f4424a76b5d62ef1952d3a4a231e64ea805859035a6106904ec070c0c3146c3b53004fb7c374b713af3a54decb9c51fe5706351503f107d6702d60b0e0f8998e1651691644b7d8bfdfb56176bee4dde9421b0c5ec0032093dc06a4999529914271294568a22740f49c3303d43e98340b709fbc0f62420384da85690dc85bc54c7aed4d8ffc5e0e7bc340d75f5fd7b15782fc8b3cf4894a03987bae9096d0b5393f74e102998fbd4b28b90212c1c7ee48103c2e4272805be0037b3c4a36c2d89dd3c90cf7c607ea4f547fa5e8a8e9dc3f7d8dada8bd72147dd8032095e6d7383a22b226561d68123ab2c1214791dbdac52229a6869d589a3b7cf8038c7c08d0acd233e2ec64fa5b7541fc4ee0c63396f7394a74c2b20e0e882f6a52036f9315416a70fc5b967f2034b62dc4ff5013f38fdc18537591448273ea7eb38703d92e544d06ecca329b2abeec9abbd14a1e201d45053131d118f0583226e8e744033eb60b9e506cf1826759d3ca4ab574ec7af1381bd3a43896f52d75c0a440162c03a297382dc39e93a33f376a22f4e72ccf7d9b9b5039956f115f683db1f56d093f031dbfec66b5dd45ebf0a7d217a13b09af51b726be65dbfd8a073dff37edb06367031e3a89995526b5fa7c7546be2f87f24ad31d5a90d43684a938e11e8bea190e9a03bb5f90b166fe08410e6a81f70e84aa61bba1c5cffb192f6944f48f18bac7842f037f257b8f881ca3b9a068f124c5e7c779977d47758727645ac3c347c8a7e2cc0403e762ec64639707cd8c88e32620676c4afab77af0506fddb9e9338a44878cbf2b0375d94e4ffd563943ad1b30ceb70ffafe652cc3ef43751275b73fce2a8f4580e903e941e8c1cd2d1c2af9e6a830eaccb9dc852eb51a595f49f8020b1374b3d9671d0320a5d0928ffed1dfe17ce654600de0a8adde1e19c6ea8d3b3ec8eb1ad4722075038e724e924921b5e1b408d19789f7a807d957061d4a2e31deabab4dba410a54c4039145304cf541f509086d369c2591052eb6c35114466bfd95d143afa2440fcf9103f37fc8726c7ac600cea8d3c0a548f9d0fbfe5c343ccf492d38ceeb0fa64e389e032e658ed64ff2b1fd07d179daa64d89e6066ba0fdb17391508b516aa8538965c4035936c2a9466d4a2dfa96c16ee3a7412be746548dd4dea4b51e72b3abeb488f8703e674d75971128d7bc12b2e4537c4d5ec14851993eccf5636d201a8810232afc705581e0340c74e91d29617db518cc91bcc79d97dfb4f10b9eed6b9a7f2b669dee00c08468428fa8d5a7003d606c59bff9ba4c408acd4bd1a03645bf9be58cc3abc35ba732a83bff897749a05581e03b202d82e408460bdd424d4427bae5c8adfccc85babec48a68d2804c7c0040103e4b302f8d306e57a338be5edba5efe74c6741ed8cc82199c5a8daaf7d370f80305581e0356675496e893230127ecdfcdd6f47502c12da422154cff2de7a11c3cc0040105581e03cfe3126e3240c5f84d3c1a1165c83583d59454bd73336d91a50d66a0900c0847014e2b4384130005581e03d4114ff9ef2f468f2564b97c647a64cf1cfdfa60ab4017ca3688b5ce90040103be8edcfb1ed054bea7dca5450a83dbc853507260efa562f2e779e101b4c13b370307be01e7e7206fe31ecee91ad75dada65ad6ba433ae647a1b9330469f7c6677c03709eb5dda2147c4fb819a20da7a29219edf23b78b89a76895cb5bb987352df3005581e0373f77543f7825a60e6a5701c432e908745ed9b08e73b0a25150b6d34c007011bffffffffffffffff05581e0305c28b46bd8e81bdfb8a5f4d6e980885d2a4af2fe0d7824beab974b9c00c191aa348024a7947673773cc0219fe5a03d10712aa13d0f3995a9804e252a7f13f0330db2f77f1262c4de517d26bb235110390d5e6127610e23202df851e12dd51e1f173df2f449ef1502f8cb8ec1946c72c0340d95d05dbc8cadc39de4143d80043af6f2823634a897954a2a06e996d6545f103b6276e002ba6533d0da0005425db938210b67c9b41bc6f2ac584aae9de0f6cab0219ffff03f3dace1347dcda686fc36606f8325207c8f43d5c89616289ec5a8c2e1392fe5e03c4b2827e222f9393dfd2c17e277bcb5015b227af2818be5ac93677e4466086af032d21e91b97d746d069e6350da7edd46e62afe84a030761354e2f946e943863a103775749f3c799933da5cccb597248d0f06452efa42a04c7b6aa22a28a59e1284a036ed81b100db3d0c10581c4b9fa794d11403c4604996c23fcb7b70d728658ae9e037dc2ce07de091a9d43d01732ad91e070fd2de1bdaa466d7f3a8afdbcb814b7eb0300ad182b787cd5f74e1e445f1c1427bbda851e4af520d186adbeb44b77638395035d0eb5886b59f11c5a89ff217828da238ad229d49befdbf4ede49356e6311ced03c34e34c58951bffa6fc614956d1fa57566ca7ee88b6ef02f1003fc2b84a27c8c035998413c1da595d78cc993f61733a859917c89d1fc8600ad414872e4c0eda24903e3b09886001963c040552d15b1b3230536f27a2913c5485a6bbe923a0cc1342a03c06d4726e1718cc11c9641586d6a88b7215be0ee05b94feedd05615ab0ed98990219ffff03e63c9ab2ed95722b19c109f03639757b6112738689d74ebdc8ecbb2c5ce46ced03a90a8502021d0c8403d106a77550fdedf8d8970d36a483174219f2ad518024e203db86accd2441d4ab15ce47241fde2b166a2c1c769042858bdab62077e3775e450395ad46356db32a3262592a3120f35f8d87e559f0d80a59a00f5f1496d79e1f3703ea0f478c5dd0d6860fe592f1064e12254146a87d7a5b32b5f69218e40543eece03f9685a4a1c22957f996b3a47d7b0d909a5337a980ad98a27ef8b01be121ebb820219ffff03a50f36b2a3aec225746cba77b256aa39baea8688ff9710f77ad371a1850b530c03d6b914782c056946db444e70e0762eadea31e3df5599237b3e66da71dc7ccd4103d52f79911ee66b756458bab97d6c63b5f089375ace9770d7cb7573287ac1bdfa038a6cb307f82845f574b2ffb6d548b3a50d4758e935abaf163a285da011b78f2c036bb9a5488e9d3123825e8ac90119b9e208035a3867cb3d6da9b6bc00678fc98403648bc1949264e264e724f3dfbb50a216cdfe05f3e95f234a81f22f47ea6772d203546b3a0d697ec1062b81522b4d084e9ec57c711cd4ea7d34bec7de09e0f1ba3e0383cd8f34312fd8ba6f85d0b5925f3a8a4ef4c2fb1dafe401bb87c9a1bea5213b03f6e8e346c25231891bae41caca569ce3119244c2a7fde5d5c71cf8fd7f3987ef03c51ef578408ed2bda43c71d7635cd6de9b456caa8f94e58ddc710393026ff931038db24e908809e8bb921ca8369d94130bc726ddb1feb383e1bb0428f10388556d03fdfe947e0412f21e731185beb4d688c76b1b25487213a889b392fe968f674f610219ffff03a60d2927f6b9b12188134cc239c40d23f9ccc70d3a7de9e09bf38b52eef3bce303433ce07a0d1bab3d582a296f83247078f7e724d1ac4092ea5111b0fb071ca02a03fb9e2161ade905fd2a2ef6bfab89ec7840ddccec045dc40070d4ced7f53b6e67031feb13beb775981d91a3062cf8e8a4fcb82d18f051a36ad7458c8e290220efca03a0e5ec6797ecd01497f609217ec925b01d05dee564308bda87e5cf92128c9a3803f674c47dd5a6c8ac69cd7ac69ddb49ced9dc58c05fa143c2cb97edccbd0b9bd303390bdbaf591b8ef8605852a1cf699e17f0ed8d513e033aebedfde8a73ebab74003179fd7a59752c138b342dfcf72466af530561333dac80ee105c76e7cde150306036a44ffb000ae3ee4b350b8845cb039cf7f9fa7b2241348b924f88218b66ab70c0348439d5859d54cd376c1c5523610849eb269b43a5433970801c31413a30b7742033fc0ea144e814572124c28db95e360a8ac2e82cecef6be56f99ded79e04ccbb803721018a806deb51a2dd6c4e4108ec19b9bfa13018030cc4787000df532f83b2d03be9cafc91ce5c493bff4195a711c3d027c59f96fb9de36ea791652d9e0ebefbb03d92b63a22b09c70eec21638713c888e08fc3094601fa959df16f5421bb0997400360cbe274a256abecadd967d1c9157250a8a6c9d9a7b732034dc42eb59f4337160356fbcad33cbc58a3f6c1d2a8bfea491d8dce5396e7d2c28b37c03966da6564ff03b82d4ee8a3bd099298354fd419779e9d9b23437e8eb0726503d635243071300a03489189e2fc1544cafbc038b00d4756dcb85fd97e490f66fb118509c17f3709cc037727b83a3c075641e096e7d358b4d508c02d94bf69dcf0e80c850aea57e3be5c03504b9e18153c54818674884cb6605cf7c1d2f51018ab2b8ea4cce38a64056af1038562b0206dcfdb03a3c955ab6787a612a6662b336db326533840332caa4b31280316fff45014600e04c5544653c60728d9f86e6b6629c64d09eb5faa6e95447e9d03421172bde32acf8d6525a1eb119c7b9af525df582ad3f66cf970ee5b40d4c1a303877aeaeed643b247b01b8f2f8fcc7ea584c37668f85dc230b45a10530ecc1fd703f7cccf2a3bd25254e8cd67559b9b8b3a510eb8a6298f45925e981230bc82f96c03c82854e63d06042387aebf6e0525c6e827ea0567f95ba0e84fbbac7b6e3babdd03ec5bb81669a23bfeac73bc9ae261a303e4233afe3f91999b6edbf44699afeffb0393f6766a00d101d1e2ccb18b17ee5d3c9169e414d348f3e38ea4b6ad51d249c3032e019ec2455ebd81fd8accdc332db5a3dbf498d5e8ab2424547c72e4bc0f754d03f3167c7ec060c7e8e4d4209142673800a2beff378cd85d5c5ab9fa44ef23f9950377ca9d123a6f1a926b0441413ead4ecfe64670147a3793e90fc40a83a98eefbf035c6b68124718d0f0ad938e089409526f2a61a3c62bdca0f15ba23d696cb9c3eb0363b7cf7f9aaf628c5ea5b0b014eb143ab8a339d6d5a9ca66cab5c07d4e9930b003bb38a3268a4529afd2b1246dc9bc1b81db56195ea458c9de3751bdef4f06794d03c2b8aee74e06f9b7719cfb835463672be34dd8c9f51a4ab228f8dcdd704773dc03fb9ffa6e4cb446cdf0fa328bb4b998ce480c24fd9fe2507df65fe11cc7601113031ff9f90f0646746ede0dd3000ee9eb92e952a2f5a19149e0f9950a9d8268bcff03111fa6456d535e473ab5cbe31075d36156b25f00be25d5fa54ef80a46b75e6a50360794bc9db223f3c2148276e90d32e3e4a23d302022f15b0b11bd49cc049b0f803e67366d9f8df4a548372165a810704bffc86a422e8b568a398556d63147c53fa03dd1d971baa9697b8962175d0d3d5897fad93c9129dde88bf91896fce83bc39b303a4d23ce45ab615a9bfff79118650a7e82cc850858c1bd43b8c6d08766d3ed44f03194bae0a4b7d6fa060b99e6f5c97b8211109d70a87e960efdf87ead447fe09bd05581e03b66d91f8635919e84725f61e09f44d36e8cd16c45dd605d495dda732600c034747e2c05e2f053b045916166080604052600436106100965760003560e01c80636ae4b4f711610069578063803ba26d1161004e578063803ba26d1461015b5780638fd3ab801461017b578063fa461e331461019d57610096565b80636ae4b4f7146101195780636af479b21461013b57610096565b8063031b905c1461009b578063168a6432146100c65780633598d8ab146100e65780634a931ba1146100f9575b600080fd5b3480156100a757600080fd5b506100b06101bf565b6040516100bd91906115b2565b60405180910390f35b3480156100d257600080fd5b506100b06100e1366004611075565b6101e3565b6100b06100f4366004610f90565b61021d565b34801561010557600080fd5b506100b0610114366004610fe9565b6102b8565b34801561012557600080fd5b5061012e6102e6565b6040516100bd91906112dd565b34801561014757600080fd5b506100b0610156366004610fe9565b61031f565b34801561016757600080fd5b506100b061017636600461104a565b610331565b34801561018757600080fd5b50610190610476565b6040516100bd9190611268565b3480156101a957600080fd5b506101bd6101b836600461110e565b610593565b005b7f000000000000000000000000000000000000000000000001000000010000000081565b60003330146101fd576101fd6101f833610700565b6107b8565b6102138686868561020e88886107c0565b6107ed565b9695505050505050565b60007f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1663d0e30db0346040518263ffffffff1660e01b81526004016000604051808303818588803b15801561028757600080fd5b505af115801561029b573d6000803e3d6000fd5b50505050506102b08434853061020e87610a65565b949350505050565b60003330146102cd576102cd6101f833610700565b6102dd8585853061020e87610a65565b95945050505050565b6040518060400160405280601081526020017f556e69737761705633466561747572650000000000000000000000000000000081525081565b60006102dd8585853361020e87610a65565b600061034085858533306107ed565b6040517f2e1a7d4d00000000000000000000000000000000000000000000000000000000815290915073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc21690632e1a7d4d906103b59084906004016115b2565b600060405180830381600087803b1580156103cf57600080fd5b505af11580156103e3573d6000803e3d6000fd5b50505050600060606103f484610a65565b73ffffffffffffffffffffffffffffffffffffffff1683604051610417906111f2565b60006040518083038185875af1925050503d8060008114610454576040519150601f19603f3d011682016040523d82523d6000602084013e610459565b606091505b50915091508161046c5761046c816107b8565b5050949350505050565b60006104a17f3598d8ab00000000000000000000000000000000000000000000000000000000610a77565b6104ca7f803ba26d00000000000000000000000000000000000000000000000000000000610a77565b6104f37f6af479b200000000000000000000000000000000000000000000000000000000610a77565b61051c7f168a643200000000000000000000000000000000000000000000000000000000610a77565b6105457f4a931ba100000000000000000000000000000000000000000000000000000000610a77565b61056e7ffa461e3300000000000000000000000000000000000000000000000000000000610a77565b507f2c64c5ef0000000000000000000000000000000000000000000000000000000090565b6000808080608085146105db576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105d2906112f0565b60405180910390fd5b5050505060448035602481013591810135906084810135906064013573ffffffffffffffffffffffffffffffffffffffff8084169085161061061e578284610621565b83835b9094509250610631848285610b0a565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610695576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105d29061149b565b5060008713156106b0576106ab8382338a610bca565b6106f7565b60008613156106c5576106ab82823389610bca565b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105d29061134d565b50505050505050565b60607ff0ec779b0bcda6d84abf99ee2c67647d1100ebbb553a9c2d1c2ba1579592832c8260405160240161073491906111f5565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909316929092179091529050919050565b805160208201fd5b600073ffffffffffffffffffffffffffffffffffffffff8316156107e457826107e6565b815b9392505050565b60008415610a2b577f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff85111561084f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105d290611407565b60408051608080825260a082019092526060916020820181803683370190505090505b600061087d88610c09565b905060008060008060006108908d610c10565b9250925092506108a1838383610b0a565b93508073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161094506108e1878483858e610c76565b5050506000808273ffffffffffffffffffffffffffffffffffffffff1663128acb088661090e5789610910565b305b868e886109315773fffd8963efd1fc6a506488495d951d5263988d25610938565b6401000276a45b8b6040518663ffffffff1660e01b8152600401610959959493929190611216565b6040805180830381600087803b15801561097257600080fd5b505af1158015610986573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109aa91906110eb565b915091506000846109bb57826109bd565b815b600003905060008112156109fd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105d2906114f8565b965084610a0e575050505050610a29565b309850869a50610a1d8c610cae565b9b505050505050610872565b505b808411156102dd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105d290611464565b6000610a7182336107c0565b92915050565b6040517f6eb224cb0000000000000000000000000000000000000000000000000000000081523090636eb224cb90610ad59084907f0000000000000000000000000e992c001e375785846eeb9cd69411b53f30f24b90600401611295565b600060405180830381600087803b158015610aef57600080fd5b505af1158015610b03573d6000803e3d6000fd5b5050505050565b60007fff1f98431c8ad98523631ae4a59f267346ea31f98400000000000000000000007fe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54828073ffffffffffffffffffffffffffffffffffffffff80871690891610610b77578588610b7a565b87865b604051958652601586019182526035860190815262ffffff909816605580870191909152606082209091529290965250902073ffffffffffffffffffffffffffffffffffffffff16949350505050565b73ffffffffffffffffffffffffffffffffffffffff83163014610bf857610bf384848484610d1c565b610c03565b610c03848383610e12565b50505050565b51602b1090565b6000806000602b84511015610c51576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105d2906113aa565b50505060208101516034820151603790920151606091821c9360e89390931c92911c90565b6020850193909352604084019190915262ffffff16606083015273ffffffffffffffffffffffffffffffffffffffff16608090910152565b6060601782511015610cec576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105d2906113aa565b5080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe901601790910190815290565b73ffffffffffffffffffffffffffffffffffffffff8416301415610d6c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105d290611555565b6040517f23b872dd00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8416600482015273ffffffffffffffffffffffffffffffffffffffff83166024820152816044820152602081606483600073ffffffffffffffffffffffffffffffffffffffff8a165af13d600183511460208210151681151782169150816106f757806000843e8083fd5b73ffffffffffffffffffffffffffffffffffffffff8316301415610e62576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105d290611555565b6040517fa9059cbb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff83166004820152816024820152602081604483600073ffffffffffffffffffffffffffffffffffffffff89165af13d60018351146020821015168115178216915081610eec57806000843e8083fd5b505050505050565b600082601f830112610f04578081fd5b813567ffffffffffffffff80821115610f1b578283fd5b60405160207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8501168201018181108382111715610f59578485fd5b604052828152925082848301602001861015610f7457600080fd5b8260208601602083013760006020848301015250505092915050565b600080600060608486031215610fa4578283fd5b833567ffffffffffffffff811115610fba578384fd5b610fc686828701610ef4565b935050602084013591506040840135610fde816115bb565b809150509250925092565b60008060008060808587031215610ffe578081fd5b843567ffffffffffffffff811115611014578182fd5b61102087828801610ef4565b9450506020850135925060408501359150606085013561103f816115bb565b939692955090935050565b6000806000806080858703121561105f578384fd5b843567ffffffffffffffff811115611014578485fd5b600080600080600060a0868803121561108c578081fd5b853567ffffffffffffffff8111156110a2578182fd5b6110ae88828901610ef4565b955050602086013593506040860135925060608601356110cd816115bb565b915060808601356110dd816115bb565b809150509295509295909350565b600080604083850312156110fd578182fd5b505080516020909101519092909150565b60008060008060608587031215611123578384fd5b8435935060208501359250604085013567ffffffffffffffff80821115611148578384fd5b818701915087601f83011261115b578384fd5b813581811115611169578485fd5b88602082850101111561117a578485fd5b95989497505060200194505050565b60008151808452815b818110156111ae57602081850181015186830182015201611192565b818111156111bf5782602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b90565b73ffffffffffffffffffffffffffffffffffffffff91909116815260200190565b600073ffffffffffffffffffffffffffffffffffffffff8088168352861515602084015285604084015280851660608401525060a0608083015261125d60a0830184611189565b979650505050505050565b7fffffffff0000000000000000000000000000000000000000000000000000000091909116815260200190565b7fffffffff0000000000000000000000000000000000000000000000000000000092909216825273ffffffffffffffffffffffffffffffffffffffff16602082015260400190565b6000602082526107e66020830184611189565b60208082526029908201527f556e6973776170466561747572652f494e56414c49445f535741505f43414c4c60408201527f4241434b5f444154410000000000000000000000000000000000000000000000606082015260800190565b60208082526025908201527f556e69737761705633466561747572652f494e56414c49445f535741505f414d60408201527f4f554e5453000000000000000000000000000000000000000000000000000000606082015260800190565b60208082526022908201527f556e69737761705633466561747572652f4241445f504154485f454e434f444960408201527f4e47000000000000000000000000000000000000000000000000000000000000606082015260800190565b60208082526025908201527f556e69737761705633466561747572652f53454c4c5f414d4f554e545f4f564560408201527f52464c4f57000000000000000000000000000000000000000000000000000000606082015260800190565b6020808252601c908201527f556e69737761705633466561747572652f554e444552424f5547485400000000604082015260600190565b6020808252602d908201527f556e69737761705633466561747572652f494e56414c49445f535741505f434160408201527f4c4c4241434b5f43414c4c455200000000000000000000000000000000000000606082015260800190565b60208082526023908201527f556e69737761705633466561747572652f494e56414c49445f4255595f414d4f60408201527f554e540000000000000000000000000000000000000000000000000000000000606082015260800190565b60208082526024908201527f466978696e546f6b656e5370656e6465722f43414e4e4f545f494e564f4b455f60408201527f53454c4600000000000000000000000000000000000000000000000000000000606082015260800190565b90815260200190565b73ffffffffffffffffffffffffffffffffffffffff811681146115dd57600080fd5b5056fea26469706673582212204cc6b7a366a0a6e22a04d2d8d2b0410ef6d76b5c47d78862c5dda3d6644d33db64736f6c634300060c00330605581d024a44b4d2b5f6d242dda9b7b806263cef66b2f139670929bb28fc7fea0701191616031b460c826a854d61dca82f718e088b8b4c4082ffeb93752d7691bc62c51dc0280605581d0248da54ef2abcff5a6301b47cc3692cc096a0adad40101738c69779e707011bffffffffffffffff0219010805581e03a0ac152d3ac0c9c9c5005c9d692bd5ec77014cd36d9486547a8ae0e5800c044718c6b04dbcde8303201700f92a763e27560f6ea05eced4cbe1d48deaa7943c683c4b4f49cb13106f0342fbb8ca46729b921d60dd79bc5e955d37ed77c2f6b882cff6874fd18527bf8e05581e036c5bc4dd61532c76ed680ce6a453319986bfa089b6f9be89f1f79574c00c02470305d37cf544b805581e035f496c54a63e3e6452326d58130075258bda3b6f8cc4c367cb565d26e0040105581e03ad44e4916d57aa025a1cca3fef327c942a8ff7d793403bcdf72a5912000c024717a681984510000316c07421137d6fca67dbf763e0cc23c12a58498d88d94791d2a6c260c562bfa005581e039e4aea363c26f33a85ab6a01b8b36e057abd6eef33bbe3118a012cc4c00c064682e7c0c427e805581e038b4747193ef8e3a8860fc395cafe5c98a3f0131092943d5219e3431a300847038d7ea4c6800005581e03ed21ee4b22c2d91a89ee20876c15c9f2625971db8ef0d1a8976c4b2be00c01470128546a26c40005581e03d16dced3be69b2b03f5944d1abef9bda9e457c9d4deb7453efb8eafea00c03467076e2d3b0c005581e03fe59d2729a8994ae4ca677efca10032142fe70091fb49ab30acfc281e00c024672560413755003eaa13b8d3ed72aef2a071a3d3b532ea9ad4997230aeee9d8150ef68f5dd83c8f0219fffb030bceeab60ca03c8c5b7cde19730f866a9ef38c0a37643c6d39546f7af96d51d10219ffff03a6006aa40a58141a519795380e434e94825fa08b5248839b2ed7b3693a4f3dfc034bba7ae1a355704b85c51cee8cf4f4dba999cd55f3604ddee93057f36bb1dee703b7832ce24ebb6fa5fdc4ef76a4c4ca5d8142648eacfdd15746d067e1ea257f6903e0f549cc631a96803e664ca36a2f7dbcd1d0ee4e066a15510eccbd894ae6557a034b75e0fa02f80cf3eadb5d800c649fe6ad3e0a6aee768f1b25a7ca8cc7f8d0040219ffff03fceec48695c750f27287986c72e35e27bebf4e1639c4fc9f65bc2eef741a5b33036f75beba82fce098703546993bed9689151fc797b4e88611ec24e971d03e8b9f035839d648b24acf37c951e7e5910e891014ece1f24171af0f522e6a6c13bd677b0350fdcf647a78979d158556530db8f37fd7ec3225aab5f1a5d4c4e31f3d2f744c0219ffff03a5358cc2454ba4c7f154311b9ff6bb01071bf9fdf23d4108c919e33423d89f2903d40009515ae21d8a43b91420a2fa991fe93effab28a8760a9698ea98773f19180360a498da729e25da3657699c97a9ace336618417c2e912914d938db3e84f2f5003873e97878802dda957d633ea49395ae2bf9103f5fd8f8000f9182ab43a04364503a9d86a2b79a6103b84dd6ef12bf5bf3787d3b0c098a6fc761cb38beb1e1ecd0b03d238cfcd2b28a4482edba8e80bf9aa0b003a3b33497d2fcdd746123e7d59dda403c2f88297f853d6310db54fe70e152c70c3ec111a81eb851e9d0a76fa9c7ad440030ed761b51c0263802d083b432009de2ec65751012f43d5f330c22f0b96e1e55d03e2e2cd5b203a4d72486e39d17cbab7d02b5201679aa17b6021c92936552f92b603b8251ed3f6a3af683c9f0ccb5236487067c747929db09befc9b68ac86281b073039690ed0c011eac200370fa571c5c639e28d03eb89806688d11412f92d174d6d5039bf266bd1cddd39435a5a3399488d55af6d11a99c3e90e0d27608ed8e38c8774038e590efdca91dcf151999342d356c277f277f16f9f211a4acf7b6f7f6213355b0219ffff03c365ee5441e8c48180b1ef35944b8733c4b3d37ebd154346b1e6001607119b2a036dae95f064b6da0ee893baac0078e75d3e19a70ce28646dab697c976062de0ef03c3ecd3ff3a9607e48e0c822d02cf6c5cbce7cd0ea15421f6d5750c073e9ebb3e03b3ff8f58edf5ddf6d4adf5c32338eacfb112cbfc8c971cd6079adbb7a2dbaa4303451a836a4df657623c87c2104e20bbbe4abf0d83b8ea7c70debc89ca4160061b03a647f616918dd2435d88cc788973431fd204e7f1de9b1de1b17e0b67df230dc50373ca8e89bec3ec9c59c9843a49b1bc3ca2b11a9cb238b5b83e05e4cfa01edb1a0219ffff039fb9394ff3933cabb7b457c93b0e5a424a1033c8a90c88449fb73d1cf507dfe003485709238c8f1f7e476de6f96806a99b795dcf4d76d53f438fe51f66763b752603b3037c5785bac94975d795ee6423b90a25a8899216d75f82284335948d09033503752a3f07b73a10e30e742fb436c8dc00758f76cbbc2efad6b9b7d7511c0322ef034e99c4cfd4cba2fd4697bbed754f92fb035e0e838101cb394e5a0935a1336abe03e1834aaec0d499aaa5a5a64ccd8d197b61c59e78793516bc903a390425112a5503ec737642c0a734faf20b72589473b4f48d62ebd2f4c0ffb0b33babc8ba09654f0347b6f41b14a49bd35053e28d7a065640a4359b4a60fcccd4a978b1a7ebffcaba037be2065d424856b81a06ce14bb23c6420688036f474fd621bfa3824bd90e7b7b03058c4f262b30de3eb651c4337f3aef21cba06a78bc87fc21d05fa4698a7e174b03f849fd05668787bda96337f42c349b4f7a7434e767b3de7567a65628771ff2d603c9082a60da784f47342cb192621aeca062a37bfe535bc629e37fcb6b622c8ee4036f57630ac7b7a0e77d7abfcba4c33b05436e5a64084296b45a5fa9b268ee63e8033256528c21d48395c1077c77f2b117dfd2a153aac00ee1ccf05d9cc7014f66a7035b4ab4e64e2ec5d0c42b68ba35948e2156783e365d7fb5dfaf665058c2e1eda3034b2740f5a6ef8a9f22bdd8fbcd19065833e209822193d5659636808a0f3c18fd03ca4bd94dfc4959317e8ed501958b680ba2b9923515d18f17f937dba6ea8eb15a03bdcf276694f341508da369e08f3d3824a32bb02547129e22ce135128460239c4037203ee0eccf89e5860bbe12c1deba265d28ae628dc0560abdff65db321301b2e03cd172e47b3df6ea808baa836772b3559747eadf1e1380319374f03276f7b800603b0fc2f77f4db7a13edfa8f2af21623fc33c665993b0cf23f41db0511e274de9903878d771dd50c40301b9c3f0ad7a73bc5f36c33e3a34be784056ec5b0c22fae6803b82f18b8043845e73cae063e9ffeadde3ba43031e54ab38da9902795d13284b003b97d9d71abef205f5132943fbdcf635c2dd7ee80350e2d8872b0f81115bc4a2903b5403b0da7bf9724a80e0bd78d446fea2c707547725531d62703713ba91bb9c80317f71902f2f1e0ca9871b58316303ea28cda7a3ddbe96ec34f12e76247dac08d032ab28ea259958c87dc987f622506bfe202450a4770f0430d71dcc734fafa40950322591b495bef360943f9e060e8b4f4d992341f06e53bce5e2323f5b88f39a2470357f5b601c39d224acbc960e5714e6fe30c6ea7ee80d1222e861e44545a09d1c803ff4c9f5c0e59aff022295b380d934e2857a2dec6c6a4e51dfb08efc449c763710306e7c49c06259ffefe69c91139058ddfc9c7979f793f60b6c8a5f626c01452fa05581e03d9ab0290a9dd356f9096641df5ce47c9f6eec327a11a153c78dc7b87700c01465d939a103a2c03a522a77020d700e5c514ccbe69e3a790f5c6394b87eb7736ac140960181d183c031b460c826a854d61dca82f718e088b8b4c4082ffeb93752d7691bc62c51dc0280605581e03c4b63b3c2dc8d1ca50adad69438e450c26647541382697608794da38b007011bffffffffffffffff05581e0379b29cfb68efd058e64ea5e9e1a4ffce49e676bd44758c9de3cbb696a00402039fa4285c7e5dbcd8918901616b84710fd6622b769ef71e53ddc6b1ebcba75d1a031f8b67be329f6419c9282095843235301b6b3475e42bc9e3262b646aba8072060605581e0360b12e35e192fabb92b653f436a8d93a3fe4a31c6f0e0bb9b6aa660e9007011bffffffffffffffff05581e03512e5fd6aa467ba0275da97cb7898c314203ff01162ef999c2e6e85ab00c014744e45d81dea8480307e2c6b61c9af478c838e0cd185c69d3833252bafc6b482672d34ad9dc615c9205581e038b5dded3dcfdf54afa0824f0a97c21abf0f2645644c8c079a7e7d279900c174711e8334ea5ff0505581d027c3cf7663beb2da7bb9dc1c18bacdf107d0d93633f4e9071ca0f312c08470accb0eb3c140005581d026526f3239521117e32e590deb149db493bb5c665ac3aae6b493c9b20040104591045608060405234801561000f575f80fd5b5060043610610115575f3560e01c80635c975abb116100ad57806395d89b411161007d578063a9059cbb11610063578063a9059cbb14610243578063dd62ed3e14610256578063f2fde38b1461028e575f80fd5b806395d89b4114610228578063a457c2d714610230575f80fd5b80635c975abb146101c957806370a08231146101dd578063715018a6146102055780638da5cb5b1461020d575f80fd5b806323b872dd116100e857806323b872dd14610181578063313ce5671461019457806339509351146101a357806342966c68146101b6575f80fd5b806302329a291461011957806306fdde031461012e578063095ea7b31461014c57806318160ddd1461016f575b5f80fd5b61012c610127366004610e4e565b6102a1565b005b610136610347565b6040516101439190610e74565b60405180910390f35b61015f61015a366004610edb565b6103d7565b6040519015158152602001610143565b6002545b604051908152602001610143565b61015f61018f366004610f03565b6103ed565b60405160128152602001610143565b61015f6101b1366004610edb565b6104b1565b61012c6101c4366004610f3c565b6104ec565b60055461015f90600160a01b900460ff1681565b6101736101eb366004610f53565b6001600160a01b03165f9081526020819052604090205490565b61012c610622565b6005546040516001600160a01b039091168152602001610143565b6101366106e0565b61015f61023e366004610edb565b6106ef565b61015f610251366004610edb565b61079f565b610173610264366004610f6c565b6001600160a01b039182165f90815260016020908152604080832093909416825291909152205490565b61012c61029c366004610f53565b6107ab565b6005546001600160a01b0316331461030e5760405162461bcd60e51b815260206004820152602560248201527f4f776e61626c655374616b653a2063616c6c6572206973206e6f74207468652060448201526437bbb732b960d91b60648201526084015b60405180910390fd5b60058054911515600160a01b027fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff909216919091179055565b60606003805461035690610f9d565b80601f016020809104026020016040519081016040528092919081815260200182805461038290610f9d565b80156103cd5780601f106103a4576101008083540402835291602001916103cd565b820191905f5260205f20905b8154815290600101906020018083116103b057829003601f168201915b5050505050905090565b5f6103e33384846108f7565b5060015b92915050565b6001600160a01b0383165f9081526001602090815260408083203384529091528120545f19811461049b578281101561048e5760405162461bcd60e51b815260206004820152602d60248201527f45524332305374616b653a207472616e7366657220616d6f756e74206578636560448201527f65647320616c6c6f77616e6365000000000000000000000000000000000000006064820152608401610305565b61049b85338584036108f7565b6104a6858585610a50565b506001949350505050565b335f8181526001602090815260408083206001600160a01b038716845290915281205490916103e39185906104e7908690610fe9565b6108f7565b336105485760405162461bcd60e51b815260206004820152602660248201527f45524332305374616b653a206275726e2066726f6d20746865207a65726f206160448201526564647265737360d01b6064820152608401610305565b5f81116105a75760405162461bcd60e51b815260206004820152602760248201527f45524332305374616b653a206275726e20616d6f756e7420657863656564732060448201526662616c616e636560c81b6064820152608401610305565b335f908152602081905260409020548111156106155760405162461bcd60e51b815260206004820152602760248201527f45524332305374616b653a206275726e20616d6f756e7420657863656564732060448201526662616c616e636560c81b6064820152608401610305565b61061f3382610c71565b50565b6005546001600160a01b0316331461068a5760405162461bcd60e51b815260206004820152602560248201527f4f776e61626c655374616b653a2063616c6c6572206973206e6f74207468652060448201526437bbb732b960d91b6064820152608401610305565b6005546040515f916001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a36005805473ffffffffffffffffffffffffffffffffffffffff19169055565b60606004805461035690610f9d565b335f9081526001602090815260408083206001600160a01b0386168452909152812054828110156107885760405162461bcd60e51b815260206004820152602a60248201527f45524332305374616b653a2064656372656173656420616c6c6f77616e63652060448201527f62656c6f77207a65726f000000000000000000000000000000000000000000006064820152608401610305565b61079533858584036108f7565b5060019392505050565b5f6103e3338484610a50565b6005546001600160a01b031633146108135760405162461bcd60e51b815260206004820152602560248201527f4f776e61626c655374616b653a2063616c6c6572206973206e6f74207468652060448201526437bbb732b960d91b6064820152608401610305565b6001600160a01b03811661088f5760405162461bcd60e51b815260206004820152602b60248201527f4f776e61626c655374616b653a206e6577206f776e657220697320746865207a60448201527f65726f20616464726573730000000000000000000000000000000000000000006064820152608401610305565b6005546040516001600160a01b038084169216907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a36005805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0392909216919091179055565b6001600160a01b0383166109735760405162461bcd60e51b815260206004820152602960248201527f45524332305374616b653a20617070726f76652066726f6d20746865207a657260448201527f6f206164647265737300000000000000000000000000000000000000000000006064820152608401610305565b6001600160a01b0382166109ef5760405162461bcd60e51b815260206004820152602760248201527f45524332305374616b653a20617070726f766520746f20746865207a65726f2060448201527f61646472657373000000000000000000000000000000000000000000000000006064820152608401610305565b6001600160a01b038381165f8181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b6001600160a01b038316610acc5760405162461bcd60e51b815260206004820152602a60248201527f45524332305374616b653a207472616e736665722066726f6d20746865207a6560448201527f726f2061646472657373000000000000000000000000000000000000000000006064820152608401610305565b6001600160a01b038216610b485760405162461bcd60e51b815260206004820152602860248201527f45524332305374616b653a207472616e7366657220746f20746865207a65726f60448201527f20616464726573730000000000000000000000000000000000000000000000006064820152608401610305565b610b53838383610dc9565b6001600160a01b0383165f9081526020819052604090205481811015610be15760405162461bcd60e51b815260206004820152602b60248201527f45524332305374616b653a207472616e7366657220616d6f756e74206578636560448201527f6564732062616c616e63650000000000000000000000000000000000000000006064820152608401610305565b6001600160a01b038085165f90815260208190526040808220858503905591851681529081208054849290610c17908490610fe9565b92505081905550826001600160a01b0316846001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610c6391815260200190565b60405180910390a350505050565b6001600160a01b038216610cd65760405162461bcd60e51b815260206004820152602660248201527f45524332305374616b653a206275726e2066726f6d20746865207a65726f206160448201526564647265737360d01b6064820152608401610305565b610ce1825f83610dc9565b6001600160a01b0382165f9081526020819052604090205481811015610d595760405162461bcd60e51b815260206004820152602760248201527f45524332305374616b653a206275726e20616d6f756e7420657863656564732060448201526662616c616e636560c81b6064820152608401610305565b6001600160a01b0383165f908152602081905260408120838303905560028054849290610d87908490610ffc565b90915550506040518281525f906001600160a01b038516907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90602001610a43565b600554600160a01b900460ff1615610e495760405162461bcd60e51b815260206004820152602760248201527f45524332305374616b653a20746f6b656e207472616e73666572207768696c6560448201527f20706175736564000000000000000000000000000000000000000000000000006064820152608401610305565b505050565b5f60208284031215610e5e575f80fd5b81358015158114610e6d575f80fd5b9392505050565b5f602080835283518060208501525f5b81811015610ea057858101830151858201604001528201610e84565b505f604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b0381168114610ed6575f80fd5b919050565b5f8060408385031215610eec575f80fd5b610ef583610ec0565b946020939093013593505050565b5f805f60608486031215610f15575f80fd5b610f1e84610ec0565b9250610f2c60208501610ec0565b9150604084013590509250925092565b5f60208284031215610f4c575f80fd5b5035919050565b5f60208284031215610f63575f80fd5b610e6d82610ec0565b5f8060408385031215610f7d575f80fd5b610f8683610ec0565b9150610f9460208401610ec0565b90509250929050565b600181811c90821680610fb157607f821691505b602082108103610fcf57634e487b7160e01b5f52602260045260245ffd5b50919050565b634e487b7160e01b5f52601160045260245ffd5b808201808211156103e7576103e7610fd5565b818103818111156103e7576103e7610fd556fea264697066735822122036575823465540cb1277c31fc2caf8a9189293837511e06209ac0d70400a979364736f6c63430008170033030ef6d3e7960a317c69218d5d92fdce0fff31359989e6286e3df138ebd3e077ab038f7a887c84db31a58a7a0d0e2b9cae5f46976bd7c5746d61462c159ef9beb7fd037b222a6da811fdfb74c6e9b5d66dd45b4f780d1bd22acc4bafeedbf5b13c34d3005820033c3826e1f77dbef94454169dc41345d1e7c3c7a14cf121e9ffe4af30076ae04b3385228ab1b849575293e1005820038235766f40646125d712d36a80881b10f838ec254690bdda6456f286ec27f05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03790493f4e7ab113d43110a0e0560ccf96c4f7bf14b7d08bdb63cf05dd967e13e0058200392d39d2135ebb0c7f898ae40ccdfb16ecb9e2f8210381cd78d89fc4981c6904b0b5f229fdff9d6cda435d200582003bc84877ad7abfca7957dd2e39622e71aa59d4ca3d7ce0c96d84b6f6a5bdf705820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00582003f4ffdfcf6e7f69781af66d6d1182fe785ba203637a5afa4fe186f29d801eb05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581f0289439f5413a769307d83a5ab107a9288f697e5500386e7632b78552c05e35820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581f02f89e54f5451c1214fe6b1acaaf91666058154883794b6cb9e3d66aa70b314c13e5b251ffc3522bbf655de002183003dddfeb32b7f496582f6f3b991e19486e644a391ee45eb32c4bfe0fae221a51240219f39200582003c267cea428e8ac6981847912a61e91dd8a81408df2120fdbdc18ee5738db505820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0058200386ca55ac77e42f21d8d4b4f9d9f8b470857abdb53413f4f0b1ff977210e2105820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff032425b7cc50df3122b79c56bb6ed94aae8706786a3fc241b67a4121284b7a93bd005820032b1e2be0efa02a0adad205ad4bb7958ce3ba49e2b4a0b886580182c7cee3605820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00582003c8fff7b3b19212c06ba5cd02a40e8e35caad1e4f66030a939b436c26c904c04bfb822c05ed3c6632725a3800582003b5f6ad52279217dd61a2354ac2e6f185a14e8022baa685bfcedd76a8d7e0305820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00582003f2f989a56404bc1f6b91dd7f8cbedd6f8055dfc7c74e7bc988945620f3fbc05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0368c5d0368755d261f1f9a1669660ce112928907bca9c1ecd473061d723429ea9021929d303f57ed86c6a30a714f95f13227aceac26c29523901d53fc490fba21b4a7eaf3310348a3fa607473f9c44193783186048a09fec318ae1e0085695dbf6dbfa0956607039f1c7528b052bc79e1b670c3dc4bbf8b95dec056f2e2b64f12a65336f2cb0f0603622b7a43821b96e38851ca23b32eb7d6cccdda87034474edb1beadc877d0bf5f03a8a4a943b7378169804a11f35ef9a480a77ccdb3ad09463511f15347f6220ba6032d63edd8657cbad64271f9dc865c8b19a638f3b674c49ee6a42cadd59ce6c9870058200367dc8a9205ffc5917d62479cc780f8a26f9c7c4888c021204281e00af14ee04b0f8eeb71aac39ef7a9394903d6ebe4e8cb46105d5cfb523ed0411f04ae53398629e81c0cc5b40b1f3567cb9b00582003464f451c68b0bb0b14ab97bff7d5d7a06f1d5f220a6481ef7cc737ca23b8b05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00582003f02809787849eafe5457bab1af5c01c25b4e1a7c19fca2e214aac9e5c550c04d01838810f254c24ce8a92f5660005820030b6e70c090a7b20f9eb912c5084325285c918ceddc49c3c8003bf3a140d0205820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00582003c43f77d64a206b64736c351a36e75d815d084672625b8edc5c3455db40f1304b068d08401270e02ec65d470058200341effc6778304fe0c4f2037d7ba9ff35e62c3211a69a05e4035684f0ff65804b984f9e6c1e85048c6ccb7f00582003933b78e9e0c7c0824f5f4836a06a3babc51260820e89e2c2d5ac463c86b8b04c1c0ae8955648488a18f40000005820032091dee59f5a2ae4d2b59babef3f726aa87d7bc579d964cf7fdd83e562b9305820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005820032ebbf4f7ea76d48e198e701cc06ad683e577e4645d7700031024252d31ec4045829e12ccd000582003dba0d6754d0a91113bc40f5a7f987e0a8f351e2a73cb21b1ffc1fcf0e30cd05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0219ee57032fb66ec702797efe4a32d372876b70baac6b3c9f2ea2efa52153695d55dcf4f0031ae4d928f9deb5753204775c1bb5ce9f1f54130b3fd7053b8f988c762c48d69b0326046a9dd92cb8859d048845d1c5cc69d019c0502c2967b21ea1c70aa777ef6703ecb601fc06dbdeda1cbe6ba391625d3e3017871e8baf2574d33e073e4924528a032b413a401bd7298f128397fb7cfdc4a1ccc0259a5a00722a68f507e0b0ba40e20219ffff03f3ac01ed40a5ef1b236c5713eabcf0deed107c1b04be4316d8c6c317f7c220060399b78e44ce40b92bcfc97cad33bd3aa9bb345b946aaf2aa7f964fe13899c380c03714f112db8a13b4c006d26c0083aeb598d92d73a77aa69c170e7f2413a3fcae603f833089298efe7618d053343c2d0fbf59f922198ead8fff9b6257e4449d9ad0203e8f44bb7c36439a3248e69fcb9905aeade3ff10933be99726d457d278353d8e60308321fd309646536899cec0e93279b2d8270fc98e83f70a5c10570a0cea617d603070c9307fd4c9ba97b87a0781a3255b7d4d9c721f63d071102fe74bb3b57891b03f40ddec684a5ce4a98cacc9be705aaa285b2088a3d10eba6e7439ea7e2a40cd603e2311cc9eb791dedd7161aa43de19292580af5b84fae5e79c1a7df5b21b9e0cd0380cc8fb0d1ed4564eac95ac88702c6f8bb9ea79eff28104c61710f2804aeb5af035c9dcf7da7688808fef00a5d26739a140747017521cf2a54bb1620955eec3479033050d0b3c30f956d4c1716cfed5a9fb29c874ecae4d0c0ebd6db9f279095e42f00582003e16d14d7ba32f45fef8fb6b1b42f9220f988d707a8cb3d365641fc9f60a81045178592cf3900581f024b7416de84eb8c25afa4e09e512d1145462041f20fdf7b140c3317f31bbc4c0813f3978f8940984400000003513755985c6d5277f635bee6c5a0998619438ef86f3c0dc28e1e6de25a9f493201410b02191100031fe698c16eb9a8731b081b4d08e077123cd1aeda6dda380a2c33b5f5171a01e00058200365c8d2bb3d47ef7fb3bbc3864f233ab17e402cfb343fc33d3e5af0475db4d04b30b8186f032e4fe86200e800582003ba02d732d98be6245a4ca1a35faee206e3511f6ffbea5e4c15d76a635cf3004b01b0fac1fbaf00a4ff7f3400582003d582414561f5928afb9bbf3d4582c289fec27dd570c91f47b16ece7d5011d05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00582003d74e135548d668f078c208969f16b4c9eb883866d3f976457fb9cc0f769b605820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03ac257f634d58696802796cd2bfb622a0acb1f2a2cfbd24be16dfc4327d54a3a40058200342712e383da2e0e688cfa4f4b37062cd6e381403ec2c1f5c24ccb6bdfd3c404c020491f68f10b98a9255c49d0058200391fc84e89078e4bdbb2407809170816a4832bcf5452d9ddd911cbd0b1bd7704b3e09de2596099e48e9ef8d0219db5c03150061d38aa4f5a20749c2bfde4f019c69bce61b0b928634e9528b21163a74a203a15d229a04a5a0e1860a4f51a264f19747b4da7016fbfd123601d8586caf5926034aa8352e42fc7eb7212bc174ca1082c6297514dd0668a4fadc613913eb696b69036f8115901a4bf6f602c25c0d5f57784af3b7f398006168510d96f6bea2e2032703a42730bbda54654ea1a11f7fb9148a553a8f95e70dec55ca28fe76c7cbea750b0219ffff03fb252103c41d62b83191fab69bcb82bae87becd234de1d861a2614076e2acd01036b8293c901ba28f84bb56d947f97205d04c61695b28909d5b3a7a553c7c1030e03f8a825b8cf742175a61589e57087a4edcce1f63e4a2fe223fd1cfe055d2a0eb403bb9f9de9c0336da083d37bcca3d1a30a05d20095e67460deff66bea4d591c47c03b4400703486765373a53a24739da37ba2727ae831dcf4b534e37258da5a810c3030814e48f23521b267853242793f04ccad96d4249f3fafd7e6a1fe2564b94e48003855cc7e7dd7d9cc3abcfb2b35a271601d4b51b8f6af77e99325f0ca80ce1489903c8f404af366bbd3ab5a4bdd574b27e965c0cba1699a97ffcfa5611e10b16033200582003b3a6cd8a8f7ecf87fb63b468db4023b921e29231349e114bef35ad1a9e7de04b821087388b0f2a4f45ccb40332e5f77b51152f01d639274e8ad337725c05d8d9172bc68443824d301ba15933005820036ed6d6dad7b5bfc08a44a6eabc45732298129831ec3f9504e9b4a7224f28204b12557ff1cad9dea3ef77f2038e61f3a3eeeb1e2c92fcd03578e9f418c54fbca9f99fd1c996041c51c03d087700582003aa19a2901d32c1a33d3506351a441a123acda08f9e030199a7bcf3b65186304b086ebae3bfe60737aed31b005820034897436c6afd7f4ec1947e0af2fbf4196dc41cb8ecebac147c8447df785ef05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00582003b6d0aa33d163bf3fa95fba81d087acda7a6aa196e1b9618574f24fb6bfc0004bb661a9d20642ae30bca812005820032381760b7b98cdea68587542e718e86528477264468a8692c4d9a4f19a13205820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005820032daa2003f63e47af9af572cc789c1ef730fd87891a6e79fcb1505398f373505820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0219855f03dc775266b2204b63525f8890775406f26db498eac07d993651bcfccd812a96b100582003d4985449b33c75631b266ffa9ceef7c1d6839341f07760f9ac56e87c455a805820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00582003d751968282d320d36bc680872ca4cbf2c886a0d1649a34ed41efe3a457fbe05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005820032d112b751e25aa20bf916ea4754884d105003eddbde69d9cce7e829803b8f04b06b410ac2c25f8e98a3403035c2b7b4748613d9c45d8c7894b070bbe892ab516dca9d7c30275b5fc51e0e87c0058200372ce0a0a5753d2e4f0f5dd164bb0fd30c9e28b773208e9ab41624ac39c70905820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00582003db93392c1f52b40fa5f6b2e44c0c27e62cb6e8e690fa143c0aacaec70324104b433b2ff41b8a6ed012c938036140c26d6a4807eaacf96f1c8d2aa0d58ae92deec477cd4f04cf7ec507f66864021932170352c79299bcc7993440ecfd3fb9d2bb18f83c86025400e1d71bb7341a3d1229da03c3f5714f88e39734dde80babf05f489cdfb6e6c1be403015f5c336bbed9c977d03d452bacf308c49f6a589931097a4f943f46ed705b99175b13bc6290bbe9605c9036d2b226caaadc3f57c10ab36ce7494617ba8afe38e80a01fa7e4b8349eff0b0803ed08153c20c083a798976865a58fdc6da7b14aa99e4ea51f43f73decfd3c78ed03cb6ee3f7f60d97735a503afe652466c10b57f9bc6ad80130de280dcebf0584b50219ffff03ccae7dbb8bb0d5478f7b9ddcb343c821125606d0af7d02310b54e8a73db6b4140349bf1fbd1fad5ae9183edfd7985e58f8d2f0d0f791f6276e8aad65b6334466400392804b2083880ee0089c4ed1f10970bcf61a4072d0ea64f8a3fae24d0262127103d90a8f54035a230d50c0514ab74483105a9d066521e0bf68650fb494c512875303bfb16f7c29221fa5aa2ce0bf37dfa3410ecd73c6fc29ca87c28970a073a44ef8034f76801ab5a8125ad25973ef0633983604ffd9673014f7c24a4c0e31f594cc3c03449ce7c0298f291b3f58bc8bdffa1077a51ed7e0161d9fe46a6380ca1059ba430332ec03b483149a35c140f252a52ecd40b9a64682b3bc01913af998194865c32e030311aa00dc4b6bf6ba1dbc3c13e6de211373018c867ad140679e5307750b9552033f74d63eb166a2e94de1d65c5ec8192886899d25b69a3779ed0bfc4e56c6fbdf0361643984c747ad2c0c31034e97bfb00e22a383f486fb0a5471c9e1eec393543203bd9364d86feb5bb4a8548d39750ada47e7915cc8fb452609fff4936d9f07ca3703e8d4569cf7ae3ca8af3b91a841f34e741e628de8588ae1d1e685c3b69a584b31038e2f0e8c548ffb13cfb86fd86dac0b5103638f6ef2cae41c0318c7d35459d6ad037cd9d41b2d5e57a819c85fb0bcb41ef5548b85607aad3d490ed95e7c66f4c1c303c25782a20e4ab36d10e5ae56f0eef6a7ffafe752d27e9e778b8f473aa453f28601410900582003532c8bbfd8aad6024506147751490a47e89ce021541def2b754b77bd5048b05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005820033323da7e67ca5f8f7b5938f0702c852c6519772365b544d11272f24f7a0fd05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03f5eb8dce2100faeb2322346f79a805d22ef8a685ae54602a1fec3cc0a177a3ce00582003201bc5347beb9e4ba04f595f9280d633d8b8bbae002a73674606d1d71147404b0f3c2be80ddaeadbbc2b8c00582003b8c91c2aec8e1a8905e6d051ac7d8b4994cf59fd0d3450e1c7025aa16a27904b149e9eb22301646e35f7a1031e95682d575d3420a1fa40d69181b21cd61e2f850330d839f39e0b321d6cbbd000582003c088ab51b2351e6d5accdac80d1e543d823d873fb1c9331c902a425507a6a05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581f02f2c15356c8065ec2c450ebead8eec19713e26dc04a0e17df722b38458dc95820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581f02f5fbfec1a1c718eadfd87609c26520e84dcc20cc72c0ef2878b5ef4946905820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0219402003913303f30f3f6fffb2dcc9010999b7e47065f38555cd5771427fc47ebe64da6203d4e559ab9bb1bf697598b5bc144f10aa51b41c51be32a74979c4ba6411f3c43602197fc303435c64d7a04b9429d7164a11925316eea1e3ae351a539ca7c4f1d4afc947b0dd034919041ff38ba0a45b92001cc32db47779ed620d704fcddfd669abfdcbba2e87030f561563c740a9ad02ccefdad701b87ba9f835bc1b265bfd6ac0d9207855a00203aab5a5711caaf54e3de04960810af10ec9ee4e48aa275b9c0744570d512560d2032f5c527a5698757a2852065dd7477051280ad77bbcde0d1dffdca568b9bd2ae103e30e3579a6370144e77a1326173ab79f84e3b0853b4053564d0a9f767e393f9c032817352df284fa8f2f30b181026a8cbb78a8557735a4db4880b7a09ee19a994b0219ffff035d236c1229c0b554f40ddb5f3159ff3dc2400eada4f0be7048df00b6acb3c3b703331a24fded924a51894413ee458da835dfa9b5a67d5b62ba6247dc9b2cf468420219ffff05581d028805254c4e619dcc39f3e70ddddf469798cdf56599b19efa9ff63a7107011910450219206003d75f6883ac458a4528483a72c1bc3cc2bde6b9c9c343917721ff6cc80e0374a905581e038aec84ef803b663eaabfb7c899e624b5288de18cce73b9bf9354e7dc000c02470893296d0cc5e005581e03c4f64461f41c5ad0fed41932f8986a374a5e21623be81e6e721a88ef600405031b460c826a854d61dca82f718e088b8b4c4082ffeb93752d7691bc62c51dc0280605581e03b2b4a87722f12d39e5a4e881fa9b4dad9b6c2b6345ff7b30142cde8f0007011bffffffffffffffff0219ef7f03fda786475ac131cf78b7fe9101f7295a18c61081b6c926f4af8d675f08a6179903eba3fd41271ab809f03852dea6852157868ceb4bb22079e364996673f3b27a9003faee0977bc1accc22b28a3509f77fb0220e77cbd0d443ff9f3f36be8b3a3c77803611bdb168769990e6a2e4e1889adcb70c4d28a2c73b5169f3e7958dda9987dac0219ffff03409e78ba7e81f4127c6dd67e3330900b3f028563e07b8d8ee18ab2a5bc8bf40c0335cf8d9a56821e4619b141150ad85621c3f5f880353196756a98f3298b07ac0803dde9d7af24f45585910b0ac6b5aa3b4b0b37384913e3b547311505a7fb386f0d03df9d96fd79c79b085c4f97dd46b6de5c69befe9b658a84dcebedf3726378807f03de2f4d3e1e146818a74496a6b9540b048ae8c6c4261a346c115d33af370bd1c30386a44c380c3ad28d588b42910edcc81f4d40b868067ffa8dadfa7f4fe1586943038fca6019285f75bc7fc9165bb6d7ad36d1a0a7a205e1dc5156c7078a0f41e834036946a8126a40129fb78e469934cca59ee5d37b7c64a85302c3ab3a4aedbc44ae0329d6fab1012d15bb24a5555f069e02085382deb14536125554b1dd9f4086f57603a447845db4adb215bbf2656b0ad38d85e1601a969d439b1fa58aa2180b910c480389625af592c052f22279c381e86c848d7c01d140630b8b75993d8f721df9afeb0348eb29cb93146910ce540d6ff928903d15d7b8869ba11cec2b74c0f7b355593f0219ffff0342bad1fcae613a291e955552ed9539aea09d81f3e9901861288e9ce75527bb2603857c2a6021e55c3ecb4c845c5903ba32419054f06c4694fefe4664a205292771036777bd6029a018bda01027a141bde1baba29dba20238b1fde1a04e8880071d85036ef67a4f66372aac3a476de2069bbb7850829aa3a2d812fb5e13302d2946278a039d7fdb0124bb89d6beebaf31176b64a2df9c96f567ff631faa23cae07d043a0503d5ba0a7164284ce0a5963833afc5a656bbdf3124c9ba978934234158abfa3c89034134401d55ab8657b70147709d58bc4dec5d6d30f844f20cd9d502bf4a2f2c1403e96d3fb4853cae6a58228a5708bcd15bae76133d44ec8e7e6fb6fb2961582bc503d6d69db198c4a5fafd38400212389e83ebd10f5a36a35441f9aaa4dd59f89529030e57be49466b87cf3023d3c404701ec35e982c749a7cb7bf55074de1455ab33e0219ffff03da0ef15e4512471c1f0fdcb74e7d9b9a698bb99e144b71b185567dc2a7f957d603bceed531e6b5a33bc7c53c38285b9b8c317afae8271d37e1107860a0d72c515e033c598b4cecf12003c4ea9afdbb6705eed722dce2504c008621d4c050586db13903a793f09d5fb1a5e1c386498b7bda5635730b3ca0094b535f2e9f02266906cecc0219ffff03aa4068534a9975b0001257e0c2fe2ca2e53eba1be3da12d88cf10615edb9f6ae03c5ceed00603107d284521b33aa38eb3c489394980d0fc384ba548f703649a737030f8e2be71d55cf8c589850d8f25d231c197013ae2f621946e4a20bc337f87eb403a20d393a73aeb67dfc4e2ca25ce8c8be62bdec853e386d2f7525eb32d694205b03306f1c97c95420b42329ece73ccab2fd2c3479d928501940e8409f80395c6a4c03284b5c9fd1d72bf4174b773178ed79db4ed449d70eb71d2de4f563839dd821f603405aed325812fe2bc24c3e47ca14a7d319c1769b390793229bb38a93a4fb139303e08bf03238889b49310f93c4d13e22e8b73682aaaf04c526026757cddb8b8854034601556a5447c924a262f81737e9c15117285f501b1b9273779436a3703badae039a4179bbe29e2f369c6098389eea51feb2d67e690f08a640c88a6772ff72156303be22092043e37a78b4a51c357f52e884d88cc52a62370264d36ebcde8edc721c03ec26516a63332450b02094c3c4e279061f4201adc00ace0e78c00fdd90c6c0b003796fd38681a7b447bc00119afdd13000da578ded0705a89a93825990f81b7f1803385bc2865ca7f44dfc75c486ed5bf5b41d2eda511dd6aa2e7ced7d23a57afcfc0332b4d3fdae9b84344a2c77aa3b6dc861360b23a696801bc97401e8af2bb37314037fd55291ebb774154090e26401959b68c0433d8f7bdf1378a3de43c4a9ab316c032405b73a1c62569e27ab4e41d010a80ce2b61f272451626ceeec4e141d0c7f2703295516e829ebf52caba115eb6e53e40f6df8a02ef686eb79d82b5b78de8e30d40314adec11d197956d85471be82e2c095eae30857ee8c3dcef2408d94d784e7ddb03c471f07038dd301c473d5ee5d1324df6d0027830de975814b5093c1a3716e4fb037b8e0c012a5453d2b32e2d869693797d89cade5217877342621e5169c64771d903c038c55386a10991979c16e018fb732aab01a537423f7cde0d8c4b9e7005061d0351a3239183ebf3e4ab46ab024b0a4fc033566fd94b60ffbaa3a040eaffbc6ca60317605e3fdf771dc421c06a6f8eceb592313cee228a7571a32aea51db359394a603cd93b56754ae570608a7a06f588543446e1ed1d03a2bc20b09be1234ed40bae00346016c2072b8683afe9451fe3bcb303b452bbe01546de17834cd21d8fc5fc73903f97358a689353db2a7620b5591eafe8a8aad6daf644ae66789ba121237f82d99036760d5ce784641815d6fff4b295fa185ca5702b9d1c756d56201a856499d456c0389e96641270bb073130ad9481cf46984e4c0b1ce7b9f329eae0d326e4fadcd5d030a9adc3a069b99bbd83943047ecb82c024f0cc456c5b375a4949b9455b277e2a03b3ee4a8441e3a19d3cbfd49da7564ff1f65ac9b25aa709f08e412f319916b10c03a1b51e5234a04959ece7e633e22cdea46cc65f7ecec39e2b32f34110b52f672503f1d42b6d50d37380fdc5a2feb712cb3662e6753f1d44e5eb5c7b4885e129235d035d4be196170b6d6e93e586b1322483fd6aa199051f3caefe6286ad839b765d8d03dc1698344d43a078f8c7cf608bc43bece4b14bdefdab26720d9be2c8695c34e703cf6f5ead294971b21efba5ec0a3ba83dd75c95ecdc723b01f288731957b0597e0381b6000b8a2925365cc6abdfaba917d069535ce88e640c6c763e4fdcf9adf92103fc426ee6e4d549dfa3e2779ef8b6808b857685c9dc2362a48427d3d83a8d0ebd0326f0501a42f29dc54bab2a28b97e5849c4678af413783073e0210e7adc328d97035c05f7aa594ed08009309fd37bf845c887421d7190f2c70095db1e7f950ca0f603b8a67e002b1009543282a6873957e04511f15e7a04d58856993fcc3ea7df36ba0373783021d46782d27873bce506133d3c65a0fdf484c29bd5401a57171aaf5e0403fab700bb77e50fb8a1a1b5b4a7cb626d0100850a547890b7d35ec0d3c5c1ee8403bd0872177a6ad38278fe084a7981e082df764d5fb42795dc4c77a5ac91fd888d03716cba084dcc86b1a13f46592666eab24278f4ce27d0841ee2b212b07609d6c703acf7430a0b8bf62413eaab75b9b6b51913756c937a97d5cbf5c5781be31a6698035d8e999755587ba57bc208af260c630b102e1f632a4525e3421f706a7e8a446305581e03b1b7140dbb197082853651d92061cf63c303b1acd46728e8793630cd400c02470231d0daf531e50358804d9c514e43993d72e4214f818817f3bbef070f90be05cf79234cee1908be03f13c458ec229d465ac25b6d138e98144fd1c48060ad94befdafa44cee618ffe105581e03ba8b0a0e5a87d3192df4af87840e26295455ee485895a4376f8370b2d00c02470147de98c1148805581e03760ff0c45616fcb05dc840044f6d9fac63fde0df8adb8d09a898bd50d00c024618261e87300005581e0336a36573829a8662acf38b0361590bb96da31c45b3edd25668dd3bfc70040205581d02af4083cb6c89b82c319d44aeb986481cde9f97dbd7db77e4becac7100c1a00201e8e490673486ce5463a882605581d022b75f0ab8856e4488839d159d1cb5c39391c25f3592cbd2ce04473470c01480163039f4f747b0002190802021945a90219ffff0219ffff03f13562e7e0bbe59fddbc0e4da33f80fde264f8431c92a034cbfec922276f03640339f615147699613099613104e19513af8d22a82fcafea2cfe5ad679c654e50300395d544e841d9c1853813b2911a7686ed227fabb298249f35b7d059f3de53d91203eec2235a910378bce52df73ed9599c4172db21dc5976fa691e2527789871879e0333a53c8dfcfc4e3781d921ce5743823be7a8324a2686b9d940404abd18d0f5c7031259a400f9e5d1bfe3bde08805d72f6a04804516c99f12a47e5d38cab2761bb403faefc54b75086802231233d641b595f841c2d7f622834ec0167a65569e4c2cb003b74f9660ccdd006e8623b80ebf513ade363e0d53176152c4d30f54427597c1fd035a954bda16f1b702e33b8eae4c5edc0754a399abb2c1a664b14113b92621b22e03e21c401fdf3abdfc0aa9a0fac867be5b7cd5dc8b2e801992168a7216af9333b603318fc23b123eb9a14d854158f7637e2899d5ec327f4f273d5d1e404ade0ff02803aab37afa00f2af7dfddf684496253686a5c514c9d02b18a63d1ce3eef2ba2e9303effd551c2b92e8afa2e0f29a9c817954acaefa42e66798d690a4d30913e8f49a031f6381b63464f7ee5605006d0f705722e571caa52a99453a9c42c0c989e09ad50319b2cb9787b2f50184045579c313a0949b909b760631b8ce5dcc23837f3ac2640219ffff03a9a7b7ce6826fc3cde003408d4ab7045219ed26673169eb430944f8759407ea10219ffff038e79b2c9b51b32c2d9441eb8b3071559f9db9540df984f3b4ee9993ecf037fa00302d172152466b7221303140d42525d9fc7dcd8d1ac03046db376839691554a7803d7db83803b68d24466a6447a1e3664cb0116936065a6a43c63471e214fdccf8f0374d242e51bdf0b94949d65c5dbc4282ff9f03380b7c04b0e8c12367abc31f3f503ed849d5813fb6757e352cef092648d5cc178fb74aa991c605eb4dd51c6dce304039b9218ec6bf3fbf023f03fc7521f8219182a472f7c7c0a277ca83e1765bed1ae032b3d83866d66c3aad6a47fe65413c160f9838e838e213b130504a0ce6c09ef2a037ece2ce283163e9950f6f3067ca724030a1e339f746c099e46bdc4a18ce4f69003267f5b02ecb0db4fd706f691266ed53cd83d8e53bd6ef9778535b35303e6475f0361314385ae0af152451f696cc1a77308034635a3bf453eb9972f0f3b5b3843d7034f04ac2d464c3e59f9b8a586d0dc7a5eda6072147c91a13339ccd93af837cdae0219ffff0219ffff" - } - }, - "code_db": null, - "txn_info": [ - { - "traces": { - "0x49f18c6370d3235fcc7cf4fd8167b8ca9bed0af8": { - "balance": "0x26c583e6f4de1f1b", - "nonce": "0x212b" - }, - "0x151ccb92bc1ed5c6d0f9adb5cec4763ceb66ac7f": { - "storage_read": [ - "0x0000000000000000000000000000000000000000000000000000000000000002", - "0xd8a47cc0a98b326a7d41ebdba69f2440a3db7a31c7edca3bc5f01523d2fabe24", - "0x0000000000000000000000000000000000000000000000000000000000000024", - "0x0000000000000000000000000000000000000000000000000000000000000025", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000004" - ], - "storage_written": { - "0x0000000000000000000000000000000000000000000000000000000000000002": "0x7b78a71646e33e6e76cabfface36f52", - "0x0000000000000000000000000000000000000000000000000000000000000025": "0x100000000000000000001739d0492174ce5ffd126fffc7f6f9c957365cf6087", - "0x0000000000000000000000000000000000000000000000000000000000000000": "0x10000960096001dff4659000000000000000017c861793bcc2ea99728962e" - }, - "code_usage": { - "read": "0x516e38ed2cd77bcb4dfb71f1afe70b0516eeb781a30e5c770fbee39aa6eb809c" - } - }, - "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": {}, - "0xcc2687c14915fd68226ccf388842515739a739bd": { - "code_usage": { - "read": "0x1b91d26a2eb23313e6e92fcd974f099f3428fd6d6e627d87d108ead574b68cb2" - } - }, - "0xaa6e8127831c9de45ae56bb1b0d4d4da6e5665bd": { - "storage_read": [ - "0xba33e9b8db02508f048aa1db6e9c0afddb03933b0bff1a3c90d6bafefde1d9e3", - "0x584faa44e8bbe81b643316e312b8153135ecec313a641b0e31d013822f728796" - ], - "storage_written": { - "0xba33e9b8db02508f048aa1db6e9c0afddb03933b0bff1a3c90d6bafefde1d9e3": "0x13b3c8f7603c2c2842ae", - "0x584faa44e8bbe81b643316e312b8153135ecec313a641b0e31d013822f728796": "0x237d1cf3f694229042" - }, - "code_usage": { - "read": "0x2105590e28e110e2e156f35dd5f376d12258ae82da2b4edab9b511d0d8bc0619" - } - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "storage_read": [ - "0xc52f396c407e484c805c250341c5b6182c11dea9069b8555d0a5c7093be785b9", - "0xd392e8d1b6835f6ffe48e3fa40f91a25dfa9ff2d64600e2b40173fa9c1d511bf" - ], - "storage_written": { - "0xd392e8d1b6835f6ffe48e3fa40f91a25dfa9ff2d64600e2b40173fa9c1d511bf": "0xde0b6b3a7640000", - "0xc52f396c407e484c805c250341c5b6182c11dea9069b8555d0a5c7093be785b9": "0x1e4537db023b263ffc" - }, - "code_usage": { - "read": "0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23" - } - } - }, - "meta": { - "byte_code": "0x02f902490182212a80850755ff20688301d58394cc2687c14915fd68226ccf388842515739a739bd80b84700000003012597010000000000000de0b6b3a7640000151ccb92bc1ed5c6d0f9adb5cec4763ceb66ac7f000000000000004e50ca71278ece8b0000000000237d1cf3f694229041f90195f8dd94151ccb92bc1ed5c6d0f9adb5cec4763ceb66ac7ff8c6a00000000000000000000000000000000000000000000000000000000000000002a0d8a47cc0a98b326a7d41ebdba69f2440a3db7a31c7edca3bc5f01523d2fabe24a00000000000000000000000000000000000000000000000000000000000000024a00000000000000000000000000000000000000000000000000000000000000025a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000004f85994aa6e8127831c9de45ae56bb1b0d4d4da6e5665bdf842a0ba33e9b8db02508f048aa1db6e9c0afddb03933b0bff1a3c90d6bafefde1d9e3a0584faa44e8bbe81b643316e312b8153135ecec313a641b0e31d013822f728796f85994c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a0c52f396c407e484c805c250341c5b6182c11dea9069b8555d0a5c7093be785b9a0d392e8d1b6835f6ffe48e3fa40f91a25dfa9ff2d64600e2b40173fa9c1d511bf80a07c51ef3e703c82bc13da77fdbb8041933823f58c7c87a773ab38a31783148210a053e67561db17a23f5c140fb794c33993ed753d770ccc87b369e8871095166073", - "new_txn_trie_node_byte": "0x02f902490182212a80850755ff20688301d58394cc2687c14915fd68226ccf388842515739a739bd80b84700000003012597010000000000000de0b6b3a7640000151ccb92bc1ed5c6d0f9adb5cec4763ceb66ac7f000000000000004e50ca71278ece8b0000000000237d1cf3f694229041f90195f8dd94151ccb92bc1ed5c6d0f9adb5cec4763ceb66ac7ff8c6a00000000000000000000000000000000000000000000000000000000000000002a0d8a47cc0a98b326a7d41ebdba69f2440a3db7a31c7edca3bc5f01523d2fabe24a00000000000000000000000000000000000000000000000000000000000000024a00000000000000000000000000000000000000000000000000000000000000025a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000004f85994aa6e8127831c9de45ae56bb1b0d4d4da6e5665bdf842a0ba33e9b8db02508f048aa1db6e9c0afddb03933b0bff1a3c90d6bafefde1d9e3a0584faa44e8bbe81b643316e312b8153135ecec313a641b0e31d013822f728796f85994c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a0c52f396c407e484c805c250341c5b6182c11dea9069b8555d0a5c7093be785b9a0d392e8d1b6835f6ffe48e3fa40f91a25dfa9ff2d64600e2b40173fa9c1d511bf80a07c51ef3e703c82bc13da77fdbb8041933823f58c7c87a773ab38a31783148210a053e67561db17a23f5c140fb794c33993ed753d770ccc87b369e8871095166073", - "new_receipt_trie_node_byte": "0xb9036802f90364018301821bb9010000004000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000002000000080020000000000000000000000000000000000800000008000000000000000000000000000000000002000000000000200000000000000000002000000000000000000000000030000800000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000080000000000000000000000000000000200000000000008000000001000000000000010000000800000000000000f90259f89b94aa6e8127831c9de45ae56bb1b0d4d4da6e5665bdf863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000151ccb92bc1ed5c6d0f9adb5cec4763ceb66ac7fa0000000000000000000000000cc2687c14915fd68226ccf388842515739a739bda00000000000000000000000000000000000000000000000237d1cf3f694229041f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000cc2687c14915fd68226ccf388842515739a739bda0000000000000000000000000151ccb92bc1ed5c6d0f9adb5cec4763ceb66ac7fa00000000000000000000000000000000000000000000000004e50ca71278ece8bf9011c94151ccb92bc1ed5c6d0f9adb5cec4763ceb66ac7ff863a0c42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67a0000000000000000000000000cc2687c14915fd68226ccf388842515739a739bda0000000000000000000000000cc2687c14915fd68226ccf388842515739a739bdb8a0ffffffffffffffffffffffffffffffffffffffffffffffdc82e30c096bdd6fbf0000000000000000000000000000000000000000000000004e50ca71278ece8b000000000000000000000000000000000000000017c861793bcc2ea99728962e00000000000000000000000000000000000000000000030c7ac73fb9897ea01cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4659", - "gas_used": 98843 - } - }, - { - "traces": { - "0xaa6e8127831c9de45ae56bb1b0d4d4da6e5665bd": { - "storage_read": [ - "0xba33e9b8db02508f048aa1db6e9c0afddb03933b0bff1a3c90d6bafefde1d9e3", - "0x6ef6c7e8f399cb2db0fa4c873fe572abbdc53d164ebe80278a1312f736e51a4a" - ], - "storage_written": { - "0x6ef6c7e8f399cb2db0fa4c873fe572abbdc53d164ebe80278a1312f736e51a4a": "0x1847fd3a07c9358c1ba", - "0xba33e9b8db02508f048aa1db6e9c0afddb03933b0bff1a3c90d6bafefde1d9e3": "0x137d0a7e86ce5fac6073" - }, - "code_usage": { - "read": "0x2105590e28e110e2e156f35dd5f376d12258ae82da2b4edab9b511d0d8bc0619" - } - }, - "0x1ab65eca96d51ad4d85a40c99cc6455f1af824e8": { - "balance": "0x1c70678d5ffc4c910", - "nonce": "0xd6" - }, - "0x151ccb92bc1ed5c6d0f9adb5cec4763ceb66ac7f": { - "storage_read": [ - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000004", - "0x0000000000000000000000000000000000000000000000000000000000000002", - "0xd8a47cc0a98b326a7d41ebdba69f2440a3db7a31c7edca3bc5f01523d2fabe24", - "0x0000000000000000000000000000000000000000000000000000000000000025" - ], - "storage_written": { - "0x0000000000000000000000000000000000000000000000000000000000000002": "0x7b7a93446ad53016a972250fbc581c1", - "0x0000000000000000000000000000000000000000000000000000000000000000": "0x10000960096001dff46dc000000000000000017f050779bbd8f69ddf2d9bb" - }, - "code_usage": { - "read": "0x516e38ed2cd77bcb4dfb71f1afe70b0516eeb781a30e5c770fbee39aa6eb809c" - } - }, - "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { - "balance": "0x9d98cc7f7141e349" - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "0x298720c707984aa2acb40", - "storage_read": [ - "0x73d637d229721ca538a0a2e9712eeef46a1889496c0a60f402b430eba28d4a2e", - "0xc52f396c407e484c805c250341c5b6182c11dea9069b8555d0a5c7093be785b9" - ], - "storage_written": { - "0xc52f396c407e484c805c250341c5b6182c11dea9069b8555d0a5c7093be785b9": "0x1ebf54f992aa1b625c", - "0x73d637d229721ca538a0a2e9712eeef46a1889496c0a60f402b430eba28d4a2e": "0x1" - }, - "code_usage": { - "read": "0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23" - } - }, - "0xdef1c0ded9bec7f1a1670819833240f027b25eff": { - "storage_read": [ - "0xfafa9e60b7fc3a82d6372f25242b5c1bd30e3927bfa454fa7233ccc186ec39fb", - "0x4a432555e3fde060855a28d4d0b3bf599bbd04b75121e1e4d841e85fbc68da12" - ], - "code_usage": { - "read": "0xade271f13b55729be286859c01c54fcb04c2dde098fc3bc9901c35927ece786e" - } - }, - "0x0e992c001e375785846eeb9cd69411b53f30f24b": { - "code_usage": { - "read": "0x5908b6ce9a77484c886ffc5f0635b2eca3327bdc7cc0be118d346f86250745f6" - } - } - }, - "meta": { - "byte_code": "0x02f9017e0181d5830f424085083c3424c88302391094def1c0ded9bec7f1a1670819833240f027b25eff887a1d1e906ef52260b901083598d8ab0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000036a76847622771e5300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb8aa6e8127831c9de45ae56bb1b0d4d4da6e5665bd000000000000000000000000000000000000000000869584cd000000000000000000000000382ffce2287252f930e1c8dc9328dac5bf282ba1000000000000000000000000000000008357255014e1179f2b6959f49011226dc001a0d841a022ae900922b65814179dfec5760f57a4db627e00dbf851afac2bb6a4bba02862ab8cafec6edcaf55a04eb96b89f2ad43de423fc3b8481888fbcb2b00df6c", - "new_txn_trie_node_byte": "0x02f9017e0181d5830f424085083c3424c88302391094def1c0ded9bec7f1a1670819833240f027b25eff887a1d1e906ef52260b901083598d8ab0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000036a76847622771e5300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb8aa6e8127831c9de45ae56bb1b0d4d4da6e5665bd000000000000000000000000000000000000000000869584cd000000000000000000000000382ffce2287252f930e1c8dc9328dac5bf282ba1000000000000000000000000000000008357255014e1179f2b6959f49011226dc001a0d841a022ae900922b65814179dfec5760f57a4db627e00dbf851afac2bb6a4bba02862ab8cafec6edcaf55a04eb96b89f2ad43de423fc3b8481888fbcb2b00df6c", - "new_receipt_trie_node_byte": "0xb903e402f903e00183033a08b9010000004000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000002000800080020000000000000000000000000000000002800000008000000000000000000000000000000008002000000000000200000000000000000002040000000000000000000000010000800000000000000000000000000000000000000000001000010000000000000000000000000000000000000004000000000000000000000000000000000000000000000000002000000000000000000000000000080000000000000040000000000000000200000000000008000000000000000000000010000400800000000000000f902d5f87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a0e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109ca0000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25effa00000000000000000000000000000000000000000000000007a1d1e906ef52260f89b94aa6e8127831c9de45ae56bb1b0d4d4da6e5665bdf863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000151ccb92bc1ed5c6d0f9adb5cec4763ceb66ac7fa00000000000000000000000001ab65eca96d51ad4d85a40c99cc6455f1af824e8a0000000000000000000000000000000000000000000000036be78d96dcc7be23bf89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25effa0000000000000000000000000151ccb92bc1ed5c6d0f9adb5cec4763ceb66ac7fa00000000000000000000000000000000000000000000000007a1d1e906ef52260f9011c94151ccb92bc1ed5c6d0f9adb5cec4763ceb66ac7ff863a0c42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67a0000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25effa00000000000000000000000001ab65eca96d51ad4d85a40c99cc6455f1af824e8b8a0ffffffffffffffffffffffffffffffffffffffffffffffc94187269233841dc50000000000000000000000000000000000000000000000007a1d1e906ef52260000000000000000000000000000000000000000017f050779bbd8f69ddf2d9bb00000000000000000000000000000000000000000000030c7ac73fb9897ea01cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff46dc", - "gas_used": 112621 - } - }, - { - "traces": { - "0xcc2687c14915fd68226ccf388842515739a739bd": { - "code_usage": { - "read": "0x1b91d26a2eb23313e6e92fcd974f099f3428fd6d6e627d87d108ead574b68cb2" - } - }, - "0x151ccb92bc1ed5c6d0f9adb5cec4763ceb66ac7f": { - "storage_read": [ - "0x0000000000000000000000000000000000000000000000000000000000000025", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000004", - "0x0000000000000000000000000000000000000000000000000000000000000001", - "0xd8a47cc0a98b326a7d41ebdba69f2440a3db7a31c7edca3bc5f01523d2fabe24" - ], - "storage_written": { - "0x0000000000000000000000000000000000000000000000000000000000000000": "0x10000960096001dff4687000000000000000017d671dc8cef8aa73e4b270d", - "0x0000000000000000000000000000000000000000000000000000000000000001": "0x2168dd6f5ffee15918b6413c2f7d03e84" - }, - "code_usage": { - "read": "0x516e38ed2cd77bcb4dfb71f1afe70b0516eeb781a30e5c770fbee39aa6eb809c" - } - }, - "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { - "balance": "0x9e0ebbcf9956335c" - }, - "0xaa6e8127831c9de45ae56bb1b0d4d4da6e5665bd": { - "storage_read": [ - "0xba33e9b8db02508f048aa1db6e9c0afddb03933b0bff1a3c90d6bafefde1d9e3", - "0x584faa44e8bbe81b643316e312b8153135ecec313a641b0e31d013822f728796" - ], - "storage_written": { - "0x584faa44e8bbe81b643316e312b8153135ecec313a641b0e31d013822f728796": "0x1", - "0xba33e9b8db02508f048aa1db6e9c0afddb03933b0bff1a3c90d6bafefde1d9e3": "0x13a0879b7ac4f3cef0b4" - }, - "code_usage": { - "read": "0x2105590e28e110e2e156f35dd5f376d12258ae82da2b4edab9b511d0d8bc0619" - } - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "storage_read": [ - "0xc52f396c407e484c805c250341c5b6182c11dea9069b8555d0a5c7093be785b9", - "0xd392e8d1b6835f6ffe48e3fa40f91a25dfa9ff2d64600e2b40173fa9c1d511bf" - ], - "storage_written": { - "0xd392e8d1b6835f6ffe48e3fa40f91a25dfa9ff2d64600e2b40173fa9c1d511bf": "0x5cbf5f5d23391d08", - "0xc52f396c407e484c805c250341c5b6182c11dea9069b8555d0a5c7093be785b9": "0x1e707650e92e464554" - }, - "code_usage": { - "read": "0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23" - } - }, - "0x49f18c6370d3235fcc7cf4fd8167b8ca9bed0af8": { - "balance": "0x264431b497d56348", - "nonce": "0x212c" - } - }, - "meta": { - "byte_code": "0x02f902420182212b80850755ff20688301e05c94cc2687c14915fd68226ccf388842515739a739bd8775ef5028145013b85a00000004012597010000000000005cbf5f5d23391d08151ccb92bc1ed5c6d0f9adb5cec4763ceb66ac7faa6e8127831c9de45ae56bb1b0d4d4da6e5665bd0000000000237d1cf3f6942290410000000000004edea8a97bd51d08f90174f85994aa6e8127831c9de45ae56bb1b0d4d4da6e5665bdf842a0ba33e9b8db02508f048aa1db6e9c0afddb03933b0bff1a3c90d6bafefde1d9e3a0584faa44e8bbe81b643316e312b8153135ecec313a641b0e31d013822f728796f8bc94151ccb92bc1ed5c6d0f9adb5cec4763ceb66ac7ff8a5a00000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000001a0d8a47cc0a98b326a7d41ebdba69f2440a3db7a31c7edca3bc5f01523d2fabe24a00000000000000000000000000000000000000000000000000000000000000025a00000000000000000000000000000000000000000000000000000000000000000f85994c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a0c52f396c407e484c805c250341c5b6182c11dea9069b8555d0a5c7093be785b9a0d392e8d1b6835f6ffe48e3fa40f91a25dfa9ff2d64600e2b40173fa9c1d511bf01a0b105c5752acf92923d7016e2edea7053d1ce73c43191ff51fe2da8bf6b27e37ea063a3408a605e3437ac6ce296063f7a7caa8e85298b80f942ee44b2dc64c8b6dc", - "new_txn_trie_node_byte": "0x02f902420182212b80850755ff20688301e05c94cc2687c14915fd68226ccf388842515739a739bd8775ef5028145013b85a00000004012597010000000000005cbf5f5d23391d08151ccb92bc1ed5c6d0f9adb5cec4763ceb66ac7faa6e8127831c9de45ae56bb1b0d4d4da6e5665bd0000000000237d1cf3f6942290410000000000004edea8a97bd51d08f90174f85994aa6e8127831c9de45ae56bb1b0d4d4da6e5665bdf842a0ba33e9b8db02508f048aa1db6e9c0afddb03933b0bff1a3c90d6bafefde1d9e3a0584faa44e8bbe81b643316e312b8153135ecec313a641b0e31d013822f728796f8bc94151ccb92bc1ed5c6d0f9adb5cec4763ceb66ac7ff8a5a00000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000001a0d8a47cc0a98b326a7d41ebdba69f2440a3db7a31c7edca3bc5f01523d2fabe24a00000000000000000000000000000000000000000000000000000000000000025a00000000000000000000000000000000000000000000000000000000000000000f85994c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a0c52f396c407e484c805c250341c5b6182c11dea9069b8555d0a5c7093be785b9a0d392e8d1b6835f6ffe48e3fa40f91a25dfa9ff2d64600e2b40173fa9c1d511bf01a0b105c5752acf92923d7016e2edea7053d1ce73c43191ff51fe2da8bf6b27e37ea063a3408a605e3437ac6ce296063f7a7caa8e85298b80f942ee44b2dc64c8b6dc", - "new_receipt_trie_node_byte": "0xb9036802f90364018304c760b9010000004000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000002000000080020000000000000000000000000000000000800000008000000000000000000000000000000000002000000000000200000000000000000002000000000000000000000000030000800000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000080000000000000000000000000000000200000000000008000000001000000000000010000000800000000000000f90259f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000151ccb92bc1ed5c6d0f9adb5cec4763ceb66ac7fa0000000000000000000000000cc2687c14915fd68226ccf388842515739a739bda00000000000000000000000000000000000000000000000004edea8a97bd51d08f89b94aa6e8127831c9de45ae56bb1b0d4d4da6e5665bdf863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000cc2687c14915fd68226ccf388842515739a739bda0000000000000000000000000151ccb92bc1ed5c6d0f9adb5cec4763ceb66ac7fa00000000000000000000000000000000000000000000000237d1cf3f694229041f9011c94151ccb92bc1ed5c6d0f9adb5cec4763ceb66ac7ff863a0c42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67a0000000000000000000000000cc2687c14915fd68226ccf388842515739a739bda0000000000000000000000000cc2687c14915fd68226ccf388842515739a739bdb8a00000000000000000000000000000000000000000000000237d1cf3f694229041ffffffffffffffffffffffffffffffffffffffffffffffffb1215756842ae2f8000000000000000000000000000000000000000017d671dc8cef8aa73e4b270d00000000000000000000000000000000000000000000030c7ac73fb9897ea01cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4687", - "gas_used": 101720 - } - }, - { - "traces": { - "0xdbcc1787b02c39aa56f0f5365125933c08feeab0": { - "balance": "0x3137bee91a03b6b", - "nonce": "0x41" - }, - "0xf3de3c0d654fda23dad170f0f320a92172509127": { - "code_usage": { - "read": "0xe5b4a5c24225a0ec7687716ddb6728a7321fa50d7362c3a5acfe55e57b4b9564" - } - }, - "0x40aa958dd87fc8305b97f2ba922cddca374bcd7f": { - "storage_read": [ - "0x0000000000000000000000000000000000000000000000000000000000000065" - ], - "code_usage": { - "read": "0xe8711c5f0fe7f3c28078140bb97b65aa015a58c06c14bad5abffa44f00f1ddf5" - } - }, - "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { - "balance": "0x9e0ef35ce50a235c" - }, - "0x6d7497751656618fc38cfb5478994a20f7e235df": { - "storage_read": [ - "0x2eb9e60c490d317d2a6384dacbf41a70692f1a2d46266ce32c5d6c8f769d10a6", - "0x0000000000000000000000000000000000000000000000000000000000000005", - "0x72c80b5bc80df91afd56c3a951a32283559e7f7eb045d83c91efeee0d02c38fc", - "0xdb111b6bab9f3303f59a63f0eb7b11cf5482c92ef8f241614c4c2bca1d38f897" - ], - "storage_written": { - "0x72c80b5bc80df91afd56c3a951a32283559e7f7eb045d83c91efeee0d02c38fc": "0x0", - "0xdb111b6bab9f3303f59a63f0eb7b11cf5482c92ef8f241614c4c2bca1d38f897": "0x1840a21798d4d5c12f8752314" - }, - "code_usage": { - "read": "0x4123f56d86f276e684177a9b98a18f2f9d66d40208c423fe55b0e5c8f9d53abd" - } - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "0x2987209d3089f85f9af1a", - "storage_read": [ - "0xed86a1228e69cf38993e7d0567ff50154ce028b0b2307ce2b12f66fa0c6a285c", - "0xa78c3571170f7dcd4a9be5fc007f2e41d0568b2d304d741c66b27ec22dd33cfe" - ], - "storage_written": { - "0xed86a1228e69cf38993e7d0567ff50154ce028b0b2307ce2b12f66fa0c6a285c": "0x7d09f651ddc19d199", - "0xa78c3571170f7dcd4a9be5fc007f2e41d0568b2d304d741c66b27ec22dd33cfe": "0x1" - }, - "code_usage": { - "read": "0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23" - } - }, - "0xd420d5f24225702a68ddf21242d5eef816dc2e6d": { - "storage_read": [ - "0x000000000000000000000000000000000000000000000000000000000000000c", - "0x0000000000000000000000000000000000000000000000000000000000000007", - "0x0000000000000000000000000000000000000000000000000000000000000009", - "0x000000000000000000000000000000000000000000000000000000000000000a", - "0x0000000000000000000000000000000000000000000000000000000000000008", - "0x0000000000000000000000000000000000000000000000000000000000000006" - ], - "storage_written": { - "0x0000000000000000000000000000000000000000000000000000000000000008": "0x65cf6087000000000007d09f651ddc19d1990001840a21798d4d5c12f8752314", - "0x000000000000000000000000000000000000000000000000000000000000000a": "0x1f3f8b4dab18b3280d3d842c2ce83b6bf72cd4cac", - "0x0000000000000000000000000000000000000000000000000000000000000009": "0x1eef95b899058844625cad5354", - "0x000000000000000000000000000000000000000000000000000000000000000c": "0x1" - }, - "code_usage": { - "read": "0x5b83bdbcc56b2e630f2807bbadd2b0c21619108066b92a58de081261089e9ce5" - } - }, - "0x70cbb871e8f30fc8ce23609e9e0ea87b6b222f58": { - "storage_read": [ - "0x0000000000000000000000000000000000000000000000000000000000000066", - "0xfca24f26881178fbb721486fd81006a8a2f47f9f9479f3a10e978c0ededf333a" - ], - "code_usage": { - "read": "0xb6a0916f3f4f33110bd1c57652c6e21f4beb32ffcfb50124f8b51cb5cee61f04" - } - } - }, - "meta": { - "byte_code": "0x02f901310140841dcd650085084cf716c08303910094f3de3c0d654fda23dad170f0f320a9217250912780b8c49871efa40000000000000000000000006d7497751656618fc38cfb5478994a20f7e235df000000000000000000000000000000000000000000821087388b0f2a4f45ccb40000000000000000000000000000000000000000000000000273aa821f0256880000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000170000000000000003b6d0340d420d5f24225702a68ddf21242d5eef816dc2e6dc001a06863a39687e4a7ece96ca402bdd324ac2c2a7ee422887fcbd1923642eac5457ea07008839de0dd17b4e1296982b88325ed658f439a519998e67671f3b5db141d63", - "new_txn_trie_node_byte": "0x02f901310140841dcd650085084cf716c08303910094f3de3c0d654fda23dad170f0f320a9217250912780b8c49871efa40000000000000000000000006d7497751656618fc38cfb5478994a20f7e235df000000000000000000000000000000000000000000821087388b0f2a4f45ccb40000000000000000000000000000000000000000000000000273aa821f0256880000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000170000000000000003b6d0340d420d5f24225702a68ddf21242d5eef816dc2e6dc001a06863a39687e4a7ece96ca402bdd324ac2c2a7ee422887fcbd1923642eac5457ea07008839de0dd17b4e1296982b88325ed658f439a519998e67671f3b5db141d63", - "new_receipt_trie_node_byte": "0xb9057302f9056f018306a490b9010000200000000000000000000080000000100000000000000000000000000000000000000000000000020000000000000002008000080000000000000020000000000000000000000100000008800000200080000000400000000000000000000000000000000000000008000000000000000000000000040000000010000000000000000000000000000020000200000080000000000000080000004000000000000000001000000000000040080000000001000000000000000000000000000000000002000000000000000000010000000000020000009040000002000000000100200000000000022000000000000000000000000000000000000000000020f90464f85894f3de3c0d654fda23dad170f0f320a92172509127e1a07724394874fdd8ad13292ec739b441f85c6559f10dc4141b8d4c0fa4cbf55bdba00000000000000000000000000000000000000000000000000000000000000000f89b946d7497751656618fc38cfb5478994a20f7e235dff863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000dbcc1787b02c39aa56f0f5365125933c08feeab0a0000000000000000000000000d420d5f24225702a68ddf21242d5eef816dc2e6da0000000000000000000000000000000000000000000821087388b0f2a4f45ccb4f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000d420d5f24225702a68ddf21242d5eef816dc2e6da0000000000000000000000000f3de3c0d654fda23dad170f0f320a92172509127a0000000000000000000000000000000000000000000000000029d70e524311c26f87994d420d5f24225702a68ddf21242d5eef816dc2e6de1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b8400000000000000000000000000000000000000001840a21798d4d5c12f8752314000000000000000000000000000000000000000000000007d09f651ddc19d199f8fc94d420d5f24225702a68ddf21242d5eef816dc2e6df863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a0000000000000000000000000f3de3c0d654fda23dad170f0f320a92172509127a0000000000000000000000000f3de3c0d654fda23dad170f0f320a92172509127b880000000000000000000000000000000000000000000821087388b0f2a4f45ccb400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000029d70e524311c26f87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a07fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65a0000000000000000000000000f3de3c0d654fda23dad170f0f320a92172509127a0000000000000000000000000000000000000000000000000029d70e524311c26f8d994f3de3c0d654fda23dad170f0f320a92172509127e1a01bb43f2da90e35f7b0cf38521ca95a49e68eb42fac49924930a5bd73cdf7576cb8a00000000000000000000000006d7497751656618fc38cfb5478994a20f7e235df0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dbcc1787b02c39aa56f0f5365125933c08feeab0000000000000000000000000000000000000000000821087388b0f2a4f45ccb4000000000000000000000000000000000000000000000000029d70e524311c26", - "gas_used": 122160 - } - }, - { - "traces": { - "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": {}, - "0x6d7497751656618fc38cfb5478994a20f7e235df": { - "storage_read": [ - "0x0000000000000000000000000000000000000000000000000000000000000005", - "0xdb111b6bab9f3303f59a63f0eb7b11cf5482c92ef8f241614c4c2bca1d38f897", - "0xb39e9ba92c3c47c76d4f70e3bc9c3270ab78d2592718d377c8f5433a34d3470a" - ], - "storage_written": { - "0xdb111b6bab9f3303f59a63f0eb7b11cf5482c92ef8f241614c4c2bca1d38f897": "0x1829b4c8b8d4d5c12f8752314", - "0xb39e9ba92c3c47c76d4f70e3bc9c3270ab78d2592718d377c8f5433a34d3470a": "0x1554873fffc3522bbf655de0" - }, - "code_usage": { - "read": "0x4123f56d86f276e684177a9b98a18f2f9d66d40208c423fe55b0e5c8f9d53abd" - } - }, - "0x6b75d8af000000e20b7a7ddf000ba900b4009a80": { - "balance": "0x153a4d0bb8a49", - "code_usage": { - "read": "0x213c3f38a90242ff53461e679cef9a1bbbae20e63de087cdbc972d813c857711" - } - }, - "0xdc900845732a53ee8df737efa282a6bc56976e62": { - "storage_read": [ - "0x000000000000000000000000000000000000000000000000000000000000000a", - "0x000000000000000000000000000000000000000000000000000000000000000c", - "0x0000000000000000000000000000000000000000000000000000000000000008", - "0x0000000000000000000000000000000000000000000000000000000000000006", - "0x0000000000000000000000000000000000000000000000000000000000000007", - "0x0000000000000000000000000000000000000000000000000000000000000009" - ], - "storage_written": { - "0x0000000000000000000000000000000000000000000000000000000000000009": "0x2bc82346b12aee24647cdf327df00ffc9eea257ff0", - "0x0000000000000000000000000000000000000000000000000000000000000008": "0x65cf608700000000000943a14c374712a218000000000001a06b4c3d0c4e80bb", - "0x000000000000000000000000000000000000000000000000000000000000000a": "0x2ef738cbf909b92548d6879e3bb43dd8dab50249c", - "0x000000000000000000000000000000000000000000000000000000000000000c": "0x1" - }, - "code_usage": { - "read": "0x5b83bdbcc56b2e630f2807bbadd2b0c21619108066b92a58de081261089e9ce5" - } - }, - "0xd420d5f24225702a68ddf21242d5eef816dc2e6d": { - "storage_read": [ - "0x0000000000000000000000000000000000000000000000000000000000000007", - "0x000000000000000000000000000000000000000000000000000000000000000c", - "0x0000000000000000000000000000000000000000000000000000000000000008", - "0x0000000000000000000000000000000000000000000000000000000000000006" - ], - "storage_written": { - "0x000000000000000000000000000000000000000000000000000000000000000c": "0x1", - "0x0000000000000000000000000000000000000000000000000000000000000008": "0x65cf6087000000000007d80f65efdd19d1990001829b4c8b8d4d5c12f8752314" - }, - "code_usage": { - "read": "0x5b83bdbcc56b2e630f2807bbadd2b0c21619108066b92a58de081261089e9ce5" - } - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "storage_read": [ - "0x12231cd4c753cb5530a43a74c45106c24765e6f81dc8927d4f4be7e53315d5a8", - "0xed86a1228e69cf38993e7d0567ff50154ce028b0b2307ce2b12f66fa0c6a285c", - "0xc4b5945b3d588809025580057cbcadbb58fc33740a13703202de569e28ad94f8" - ], - "storage_written": { - "0xc4b5945b3d588809025580057cbcadbb58fc33740a13703202de569e28ad94f8": "0x943a14c374712a218", - "0xed86a1228e69cf38993e7d0567ff50154ce028b0b2307ce2b12f66fa0c6a285c": "0x7d80f65efdd19d199", - "0x12231cd4c753cb5530a43a74c45106c24765e6f81dc8927d4f4be7e53315d5a8": "0x1e2827b20fdf1f6807" - }, - "code_usage": { - "read": "0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23" - } - }, - "0x9e9fbde7c7a83c43913bddc8779158f1368f0413": { - "storage_read": [ - "0x6e6081c5b55abcaa3c6e2e8a592b973640109071879cd803b85d3d706ee3969c", - "0xab2e97a75db32eb3b19136ac5fcb6d7a64d182e81eb81decf514e3d877434a50", - "0x5026556a9e91dc9bba9215081252f333fe1d44bf12f6b47633a8259ffde03acb", - "0xa34640eb1c607d836ceb4af07c59befcec4cdb4943351c3a245f8712063aa3b6" - ], - "storage_written": { - "0x6e6081c5b55abcaa3c6e2e8a592b973640109071879cd803b85d3d706ee3969c": "0x1a06b4c3d0c4e80bb", - "0xab2e97a75db32eb3b19136ac5fcb6d7a64d182e81eb81decf514e3d877434a50": "0x5aeaca7bfd930dd" - }, - "code_usage": { - "read": "0xde2d3cb78918547c06e2d4f09804da99d8ee9dbfecc4c8023b7ccecfe92a829b" - } - }, - "0xae2fc483527b8ef99eb5d9b44875f005ba1fae13": { - "balance": "0x67338c630eb425445", - "nonce": "0x201e8f" - } - }, - "meta": { - "byte_code": "0x02f903be0183201e8e80850755ff206883030c3e946b75d8af000000e20b7a7ddf000ba900b4009a8085077000d201b83a0918d420d5f24225702a68ddf21242d5eef816dc2e6d016ed4ee0b1cdc900845732a53ee8df737efa282a6bc56976e62010b06ca5c05f18c6f06f90311f87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a012231cd4c753cb5530a43a74c45106c24765e6f81dc8927d4f4be7e53315d5a8a0ed86a1228e69cf38993e7d0567ff50154ce028b0b2307ce2b12f66fa0c6a285ca0c4b5945b3d588809025580057cbcadbb58fc33740a13703202de569e28ad94f8f89b94d420d5f24225702a68ddf21242d5eef816dc2e6df884a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000ca00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000006f87a946d7497751656618fc38cfb5478994a20f7e235dff863a00000000000000000000000000000000000000000000000000000000000000005a0db111b6bab9f3303f59a63f0eb7b11cf5482c92ef8f241614c4c2bca1d38f897a0b39e9ba92c3c47c76d4f70e3bc9c3270ab78d2592718d377c8f5433a34d3470af8dd94dc900845732a53ee8df737efa282a6bc56976e62f8c6a00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000aa0000000000000000000000000000000000000000000000000000000000000000cf89b949e9fbde7c7a83c43913bddc8779158f1368f0413f884a06e6081c5b55abcaa3c6e2e8a592b973640109071879cd803b85d3d706ee3969ca0ab2e97a75db32eb3b19136ac5fcb6d7a64d182e81eb81decf514e3d877434a50a05026556a9e91dc9bba9215081252f333fe1d44bf12f6b47633a8259ffde03acba0a34640eb1c607d836ceb4af07c59befcec4cdb4943351c3a245f8712063aa3b601a0b48222e32e8cc2cc6a762704fbeebd29ad70e1d3fae2f6d03f3cbbd8fb7e36e3a07fcc102dc9a7cb2fa7d608696e7b2bf8760abe7efc62bfdd59a355140f512741", - "new_txn_trie_node_byte": "0x02f903be0183201e8e80850755ff206883030c3e946b75d8af000000e20b7a7ddf000ba900b4009a8085077000d201b83a0918d420d5f24225702a68ddf21242d5eef816dc2e6d016ed4ee0b1cdc900845732a53ee8df737efa282a6bc56976e62010b06ca5c05f18c6f06f90311f87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a012231cd4c753cb5530a43a74c45106c24765e6f81dc8927d4f4be7e53315d5a8a0ed86a1228e69cf38993e7d0567ff50154ce028b0b2307ce2b12f66fa0c6a285ca0c4b5945b3d588809025580057cbcadbb58fc33740a13703202de569e28ad94f8f89b94d420d5f24225702a68ddf21242d5eef816dc2e6df884a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000ca00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000006f87a946d7497751656618fc38cfb5478994a20f7e235dff863a00000000000000000000000000000000000000000000000000000000000000005a0db111b6bab9f3303f59a63f0eb7b11cf5482c92ef8f241614c4c2bca1d38f897a0b39e9ba92c3c47c76d4f70e3bc9c3270ab78d2592718d377c8f5433a34d3470af8dd94dc900845732a53ee8df737efa282a6bc56976e62f8c6a00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000aa0000000000000000000000000000000000000000000000000000000000000000cf89b949e9fbde7c7a83c43913bddc8779158f1368f0413f884a06e6081c5b55abcaa3c6e2e8a592b973640109071879cd803b85d3d706ee3969ca0ab2e97a75db32eb3b19136ac5fcb6d7a64d182e81eb81decf514e3d877434a50a05026556a9e91dc9bba9215081252f333fe1d44bf12f6b47633a8259ffde03acba0a34640eb1c607d836ceb4af07c59befcec4cdb4943351c3a245f8712063aa3b601a0b48222e32e8cc2cc6a762704fbeebd29ad70e1d3fae2f6d03f3cbbd8fb7e36e3a07fcc102dc9a7cb2fa7d608696e7b2bf8760abe7efc62bfdd59a355140f512741", - "new_receipt_trie_node_byte": "0xb9067502f90671018308c6bcb9010000200000000000000008000080000000000000000000002000000000000000000000008000000020000000000080000002000000080000000000000020000000000000000000000000000008000000200080000000000000000040100000000000000020000000000000000000000000000000000000000080000110000000000000000000000000000020000200000000000000000000080000004000000000000000001000000000000000000000000000000000000000000000010040000000000002000000000000000000000000000000400000201000000000000000000100200000000000022000020000000000000000000000000000000000000020f90566f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80a0000000000000000000000000d420d5f24225702a68ddf21242d5eef816dc2e6da0000000000000000000000000000000000000000000000000077000d201000000f89b946d7497751656618fc38cfb5478994a20f7e235dff863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000d420d5f24225702a68ddf21242d5eef816dc2e6da00000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80a00000000000000000000000000000000000000000016ed4ee0000000000000000f87994d420d5f24225702a68ddf21242d5eef816dc2e6de1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b8400000000000000000000000000000000000000001829b4c8b8d4d5c12f8752314000000000000000000000000000000000000000000000007d80f65efdd19d199f8fc94d420d5f24225702a68ddf21242d5eef816dc2e6df863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a00000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80a00000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80b8800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000077000d2010000000000000000000000000000000000000000000000016ed4ee00000000000000000000000000000000000000000000000000000000000000000000000000000000f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80a0000000000000000000000000dc900845732a53ee8df737efa282a6bc56976e62a000000000000000000000000000000000000000000000000005f18c6f00000000f89b949e9fbde7c7a83c43913bddc8779158f1368f0413f863a0e59fdd36d0d223c0c7d996db7ad796880f45e1936cb0bb7ac102e7082e031487a0000000000000000000000000dc900845732a53ee8df737efa282a6bc56976e62a00000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80a0000000000000000000000000000000000000000000000000010b06ca00000000f87994dc900845732a53ee8df737efa282a6bc56976e62e1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b840000000000000000000000000000000000000000000000001a06b4c3d0c4e80bb00000000000000000000000000000000000000000000000943a14c374712a218f8fc94dc900845732a53ee8df737efa282a6bc56976e62f863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a00000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80a00000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80b880000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005f18c6f00000000000000000000000000000000000000000000000000000000010b06ca000000000000000000000000000000000000000000000000000000000000000000000000", - "gas_used": 139820 - } - }, - { - "traces": { - "0xd420d5f24225702a68ddf21242d5eef816dc2e6d": { - "storage_read": [ - "0x0000000000000000000000000000000000000000000000000000000000000008", - "0x000000000000000000000000000000000000000000000000000000000000000c", - "0x0000000000000000000000000000000000000000000000000000000000000006", - "0x0000000000000000000000000000000000000000000000000000000000000007" - ], - "storage_written": { - "0x0000000000000000000000000000000000000000000000000000000000000008": "0x65cf6087000000000007e214465736aa6691000180b16886732f5b2b7244d35e", - "0x000000000000000000000000000000000000000000000000000000000000000c": "0x1" - }, - "code_usage": { - "read": "0x5b83bdbcc56b2e630f2807bbadd2b0c21619108066b92a58de081261089e9ce5" - } - }, - "0xebe6ef3a90f3219764c80ad4ed14c5d55fc0f7ed": { - "balance": "0xaacdccf86fb814", - "nonce": "0x26" - }, - "0x6d7497751656618fc38cfb5478994a20f7e235df": { - "storage_read": [ - "0x1bd9b9754ce474ec09aee3e6d6cc57e37d6bee4279bea3ecb0c393a9fbd783e8", - "0x0000000000000000000000000000000000000000000000000000000000000005", - "0xdb111b6bab9f3303f59a63f0eb7b11cf5482c92ef8f241614c4c2bca1d38f897" - ], - "storage_written": { - "0x1bd9b9754ce474ec09aee3e6d6cc57e37d6bee4279bea3ecb0c393a9fbd783e8": "0x9fdd79ca9a7417fca304fb6", - "0xdb111b6bab9f3303f59a63f0eb7b11cf5482c92ef8f241614c4c2bca1d38f897": "0x180b16886732f5b2b7244d35e" - }, - "code_usage": { - "read": "0x4123f56d86f276e684177a9b98a18f2f9d66d40208c423fe55b0e5c8f9d53abd" - } - }, - "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad": { - "storage_read": [ - "0x0000000000000000000000000000000000000000000000000000000000000001" - ], - "storage_written": { - "0x0000000000000000000000000000000000000000000000000000000000000001": "0x1" - }, - "code_usage": { - "read": "0xc4f0904cd0f741bb3ab2a16013d23b4d72eec59e3cb24879f0f0ba0c3fea24d9" - } - }, - "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { - "balance": "0x9e0ef37450502d5c" - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "0x2987213d7e906df8a4412", - "storage_read": [ - "0xe03178286da22a27dd0b1f68fb538cd07bc9080172ee9a5a2238416fe86db02a", - "0xed86a1228e69cf38993e7d0567ff50154ce028b0b2307ce2b12f66fa0c6a285c" - ], - "storage_written": { - "0xed86a1228e69cf38993e7d0567ff50154ce028b0b2307ce2b12f66fa0c6a285c": "0x7e214465736aa6691", - "0xe03178286da22a27dd0b1f68fb538cd07bc9080172ee9a5a2238416fe86db02a": "0x0" - }, - "code_usage": { - "read": "0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23" - } - } - }, - "meta": { - "byte_code": "0x02f902f90125830f42408507a19bfef983025906943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad880a04e067599094f8b902843593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000065cf62bb00000000000000000000000000000000000000000000000000000000000000020b080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000a04e067599094f8000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000a04e067599094f8000000000000000000000000000000000000000001e9e3fee017a9c00d7d992000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006d7497751656618fc38cfb5478994a20f7e235dfc001a0a7d3c4fa4d22844eef8509336c17265312ca81f3d2e04fe6ac1a36de9a9553c6a01dbe233b219a43644e29c3f467487ac529fb475fcf08c4865b6f98207347999e", - "new_txn_trie_node_byte": "0x02f902f90125830f42408507a19bfef983025906943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad880a04e067599094f8b902843593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000065cf62bb00000000000000000000000000000000000000000000000000000000000000020b080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000a04e067599094f8000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000a04e067599094f8000000000000000000000000000000000000000001e9e3fee017a9c00d7d992000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006d7497751656618fc38cfb5478994a20f7e235dfc001a0a7d3c4fa4d22844eef8509336c17265312ca81f3d2e04fe6ac1a36de9a9553c6a01dbe233b219a43644e29c3f467487ac529fb475fcf08c4865b6f98207347999e", - "new_receipt_trie_node_byte": "0xb9043e02f9043a01830a4fa4b9010000200000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000002000000080000000000000020000000000000080000000000000008000000200080800000000000000000008020000000000000000000000000000000000000000000000000000000000010000000000000000000000000000020000200000000000001000000080000004000080000000000001000000000000000000000000000000000040000000000000000000000000002000000000000000000000000000000000000001000000000000000000100200000000000022000000000000000001000000000400000000000000020f9032ff87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a0e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109ca00000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fada00000000000000000000000000000000000000000000000000a04e067599094f8f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fada0000000000000000000000000d420d5f24225702a68ddf21242d5eef816dc2e6da00000000000000000000000000000000000000000000000000a04e067599094f8f89b946d7497751656618fc38cfb5478994a20f7e235dff863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000d420d5f24225702a68ddf21242d5eef816dc2e6da0000000000000000000000000ebe6ef3a90f3219764c80ad4ed14c5d55fc0f7eda0000000000000000000000000000000000000000001e9e4051a1e00e786304fb6f87994d420d5f24225702a68ddf21242d5eef816dc2e6de1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b840000000000000000000000000000000000000000180b16886732f5b2b7244d35e000000000000000000000000000000000000000000000007e214465736aa6691f8fc94d420d5f24225702a68ddf21242d5eef816dc2e6df863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a00000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fada0000000000000000000000000ebe6ef3a90f3219764c80ad4ed14c5d55fc0f7edb88000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a04e067599094f8000000000000000000000000000000000000000001e9e4051a1e00e786304fb60000000000000000000000000000000000000000000000000000000000000000", - "gas_used": 100584 - } - }, - { - "traces": { - "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad": { - "storage_read": [ - "0x0000000000000000000000000000000000000000000000000000000000000001" - ], - "storage_written": { - "0x0000000000000000000000000000000000000000000000000000000000000001": "0x1" - }, - "code_usage": { - "read": "0xc4f0904cd0f741bb3ab2a16013d23b4d72eec59e3cb24879f0f0ba0c3fea24d9" - } - }, - "0x6d7497751656618fc38cfb5478994a20f7e235df": { - "storage_read": [ - "0x7c44a045784228dd49bfe46362ff42dc299b3ecd9d8e17fe35caec0494fff806", - "0x0000000000000000000000000000000000000000000000000000000000000005", - "0xdb111b6bab9f3303f59a63f0eb7b11cf5482c92ef8f241614c4c2bca1d38f897" - ], - "storage_written": { - "0x7c44a045784228dd49bfe46362ff42dc299b3ecd9d8e17fe35caec0494fff806": "0x150722519ffe5a962caf11c", - "0xdb111b6bab9f3303f59a63f0eb7b11cf5482c92ef8f241614c4c2bca1d38f897": "0x17f60f661592f75820f79e242" - }, - "code_usage": { - "read": "0x4123f56d86f276e684177a9b98a18f2f9d66d40208c423fe55b0e5c8f9d53abd" - } - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "0x298721ac84460b33c4412", - "storage_read": [ - "0xe03178286da22a27dd0b1f68fb538cd07bc9080172ee9a5a2238416fe86db02a", - "0xed86a1228e69cf38993e7d0567ff50154ce028b0b2307ce2b12f66fa0c6a285c" - ], - "storage_written": { - "0xe03178286da22a27dd0b1f68fb538cd07bc9080172ee9a5a2238416fe86db02a": "0x0", - "0xed86a1228e69cf38993e7d0567ff50154ce028b0b2307ce2b12f66fa0c6a285c": "0x7e904a1b10a5c6691" - }, - "code_usage": { - "read": "0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23" - } - }, - "0xd420d5f24225702a68ddf21242d5eef816dc2e6d": { - "storage_read": [ - "0x000000000000000000000000000000000000000000000000000000000000000c", - "0x0000000000000000000000000000000000000000000000000000000000000006", - "0x0000000000000000000000000000000000000000000000000000000000000007", - "0x0000000000000000000000000000000000000000000000000000000000000008" - ], - "storage_written": { - "0x0000000000000000000000000000000000000000000000000000000000000008": "0x65cf6087000000000007e904a1b10a5c669100017f60f661592f75820f79e242", - "0x000000000000000000000000000000000000000000000000000000000000000c": "0x1" - }, - "code_usage": { - "read": "0x5b83bdbcc56b2e630f2807bbadd2b0c21619108066b92a58de081261089e9ce5" - } - }, - "0xe36c5bd8f0b71155a26bec6bb103797e67b28df0": { - "balance": "0x389313b0ead419f6", - "nonce": "0x1a" - }, - "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { - "balance": "0x9e0ef4912e569c84" - } - }, - "meta": { - "byte_code": "0x02f902f90119839f2e54850a83ee7e738302aa0a943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad8806f05b59d3b20000b902843593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000065cf62d300000000000000000000000000000000000000000000000000000000000000020b080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000006f05b59d3b200000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000006f05b59d3b200000000000000000000000000000000000000000000014ebc6f42105214e1f88fbc00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006d7497751656618fc38cfb5478994a20f7e235dfc080a0220d39417b91ac5956b93280fa407e6f1ac3065e02b7abb24c3ede578724c097a07f5afa6d389eb30a95511c3eb34720c858741653c7bc8f71d48efdefdc004946", - "new_txn_trie_node_byte": "0x02f902f90119839f2e54850a83ee7e738302aa0a943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad8806f05b59d3b20000b902843593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000065cf62d300000000000000000000000000000000000000000000000000000000000000020b080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000006f05b59d3b200000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000006f05b59d3b200000000000000000000000000000000000000000000014ebc6f42105214e1f88fbc00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006d7497751656618fc38cfb5478994a20f7e235dfc080a0220d39417b91ac5956b93280fa407e6f1ac3065e02b7abb24c3ede578724c097a07f5afa6d389eb30a95511c3eb34720c858741653c7bc8f71d48efdefdc004946", - "new_receipt_trie_node_byte": "0xb9043e02f9043a01830c19c6b90100002000000000000000000000800000000000000000000000000000020000000000000000000000000000000000000000020000000800000000000000200000000000000800000000000000080000002000800000000000000000000080200000000000000000000000000000000000000000000000000000000000100000000000000000000000000000200002000000000002010000000800000040000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000010000000000000000001002000000000000220000000000000000010000000004000000000000000a0f9032ff87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a0e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109ca00000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fada000000000000000000000000000000000000000000000000006f05b59d3b20000f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fada0000000000000000000000000d420d5f24225702a68ddf21242d5eef816dc2e6da000000000000000000000000000000000000000000000000006f05b59d3b20000f89b946d7497751656618fc38cfb5478994a20f7e235dff863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000d420d5f24225702a68ddf21242d5eef816dc2e6da0000000000000000000000000e36c5bd8f0b71155a26bec6bb103797e67b28df0a000000000000000000000000000000000000000000150722519ffe5a962caf11cf87994d420d5f24225702a68ddf21242d5eef816dc2e6de1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b84000000000000000000000000000000000000000017f60f661592f75820f79e242000000000000000000000000000000000000000000000007e904a1b10a5c6691f8fc94d420d5f24225702a68ddf21242d5eef816dc2e6df863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a00000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fada0000000000000000000000000e36c5bd8f0b71155a26bec6bb103797e67b28df0b880000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006f05b59d3b2000000000000000000000000000000000000000000000150722519ffe5a962caf11c0000000000000000000000000000000000000000000000000000000000000000", - "gas_used": 117282 - } - }, - { - "traces": { - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "0x2987274fce7f073464412", - "storage_read": [ - "0xfa6504e3ecae9cba67ae6b5b6e806a51a8edcbb5a1e390b371e2de4942e15e5c", - "0xe03178286da22a27dd0b1f68fb538cd07bc9080172ee9a5a2238416fe86db02a", - "0xc4b5945b3d588809025580057cbcadbb58fc33740a13703202de569e28ad94f8" - ], - "storage_written": { - "0xfa6504e3ecae9cba67ae6b5b6e806a51a8edcbb5a1e390b371e2de4942e15e5c": "0x10efca6d71a9428e0fa", - "0xc4b5945b3d588809025580057cbcadbb58fc33740a13703202de569e28ad94f8": "0x95a2e751b37152218", - "0xe03178286da22a27dd0b1f68fb538cd07bc9080172ee9a5a2238416fe86db02a": "0x0" - }, - "code_usage": { - "read": "0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23" - } - }, - "0x1df4c6e36d61416813b42fe32724ef11e363eddc": { - "storage_read": [ - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000004", - "0x0000000000000000000000000000000000000000000000000000000000000002", - "0x54cdd369e4e8a8515e52ca72ec816c2101831ad1f18bf44102ed171459c9b4f8", - "0x0000000000000000000000000000000000000000000000000000000000000008" - ], - "storage_written": { - "0x0000000000000000000000000000000000000000000000000000000000000008": "0x1000001b00000000000002040fa732129f4495d38000003b3168b2065cf6087", - "0x0000000000000000000000000000000000000000000000000000000000000002": "0x687f9ab5d70e304a0d73676075b00bd1", - "0x0000000000000000000000000000000000000000000000000000000000000000": "0x10000010001000000442a00000000000000026493740823657f8089cb74c9" - }, - "code_usage": { - "read": "0xd9dcbdd3357d2c78ff4cc419e310f02017343dd23dafea9c650d2f0a146e76e4" - } - }, - "0xdc900845732a53ee8df737efa282a6bc56976e62": { - "storage_read": [ - "0x0000000000000000000000000000000000000000000000000000000000000008", - "0x000000000000000000000000000000000000000000000000000000000000000c", - "0x0000000000000000000000000000000000000000000000000000000000000006", - "0x0000000000000000000000000000000000000000000000000000000000000007" - ], - "storage_written": { - "0x000000000000000000000000000000000000000000000000000000000000000c": "0x1", - "0x0000000000000000000000000000000000000000000000000000000000000008": "0x65cf60870000000000095a2e751b371522180000000000019c8228845f595370" - }, - "code_usage": { - "read": "0x5b83bdbcc56b2e630f2807bbadd2b0c21619108066b92a58de081261089e9ce5" - } - }, - "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad": { - "storage_read": [ - "0x0000000000000000000000000000000000000000000000000000000000000001" - ], - "storage_written": { - "0x0000000000000000000000000000000000000000000000000000000000000001": "0x1" - }, - "code_usage": { - "read": "0xc4f0904cd0f741bb3ab2a16013d23b4d72eec59e3cb24879f0f0ba0c3fea24d9" - } - }, - "0x9b76d386a5fd08908b9ebcd5997abbfd3a2d5225": { - "balance": "0x2589416a6d4e993", - "nonce": "0x655" - }, - "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { - "balance": "0x9e0ef731d7d03b74" - }, - "0x9e9fbde7c7a83c43913bddc8779158f1368f0413": { - "storage_read": [ - "0x7cbb9844f0e5b9ec22c652052569aae01ddeea1751c55120863e0f68310d1817", - "0x6e6081c5b55abcaa3c6e2e8a592b973640109071879cd803b85d3d706ee3969c", - "0x6110bdf7f338e605fe2de714e58354b9585061b394826a31bde40b788d384885", - "0x3aa2b2cb7392b06aa80dc060f242a1f6af24d5a5a85dc9c33b99fd974b80180d", - "0x3065b18c8be765242d5fce3e8976149d3d206de20dd939d9c797588978857717", - "0xbc13ce45e8a7427d0b04b31b6ecc1da81d3ef01808d0276ffa53991d07181065", - "0x1455294b9a8b6ae86c252fe9623af6c72d50bca1f4c6e530b6a1bcdf11e9e534", - "0x3b9931a3dd2045a353fff56daf77ae19462c7ce97a1e636ea8ee91f3409f006a", - "0x0000000000000000000000000000000000000000000000000000000000000003", - "0x137d347834f4c0d606fb68dc1135a7458a32b2cfc9ef669be6b41be8aa350530", - "0x5026556a9e91dc9bba9215081252f333fe1d44bf12f6b47633a8259ffde03acb", - "0x0c82ab7793b0bf162f6f8a92cd0d311fa5421543395fbbf697960792e8f73704", - "0x9c4430f5adfa288e18fd6c6beedc13d3151020bdc380fdf63bf06a45d605dbae" - ], - "storage_written": { - "0xec824f15865e4f487f296b7097b0e9faf596562c8dc2ab2672b19ce830da8646": "0x0", - "0x0c82ab7793b0bf162f6f8a92cd0d311fa5421543395fbbf697960792e8f73704": "0x9b76d386a5fd08908b9ebcd5997abbfd3a2d5225", - "0x0000000000000000000000000000000000000000000000000000000000000003": "0xd108", - "0x137d347834f4c0d606fb68dc1135a7458a32b2cfc9ef669be6b41be8aa350530": "0xf9d99261a8e0e87", - "0x638ec225f8b272946de63a54e6ab2745ac543226609f3b32cf416ac38acb5826": "0x0", - "0x6e6081c5b55abcaa3c6e2e8a592b973640109071879cd803b85d3d706ee3969c": "0x19c8228845f595370", - "0x934583f3a9ac11ca412bcb979f8a9c2bc4551a31930984b6fbbf6c3e2ae52ca0": "0xd108", - "0x1455294b9a8b6ae86c252fe9623af6c72d50bca1f4c6e530b6a1bcdf11e9e534": "0x400882ca47ace78425", - "0x3065b18c8be765242d5fce3e8976149d3d206de20dd939d9c797588978857717": "0x0", - "0x3aa2b2cb7392b06aa80dc060f242a1f6af24d5a5a85dc9c33b99fd974b80180d": "0x0", - "0x7cbb9844f0e5b9ec22c652052569aae01ddeea1751c55120863e0f68310d1817": "0x0", - "0x6110bdf7f338e605fe2de714e58354b9585061b394826a31bde40b788d384885": "0x1d", - "0x9c4430f5adfa288e18fd6c6beedc13d3151020bdc380fdf63bf06a45d605dbae": "0x1" - }, - "code_usage": { - "read": "0xde2d3cb78918547c06e2d4f09804da99d8ee9dbfecc4c8023b7ccecfe92a829b" - } - } - }, - "meta": { - "byte_code": "0x02f9043b01820654839f2e54850a83ee7e738306136a943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad885a34a38fc00a0000b903c43593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000065cf62c700000000000000000000000000000000000000000000000000000000000000030b080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000005a34a38fc00a000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000168d28e3f002800000000000000000000000000000000000000000000000000003e923ae5641b48100000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000009e9fbde7c7a83c43913bddc8779158f1368f04130000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000043a77aabd00780000000000000000000000000000000000000000000000000000ba58d0ad0652c8800000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc20027109e9fbde7c7a83c43913bddc8779158f1368f0413000000000000000000000000000000000000000000c080a0c6c29ae62a8518f90964b3b89b854e3063bcd5de18e976fd15799dd6bf7141cfa0778acfdb416cc2f46feab71098bba323f340d60892488b72cf562fcb3effecbf", - "new_txn_trie_node_byte": "0x02f9043b01820654839f2e54850a83ee7e738306136a943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad885a34a38fc00a0000b903c43593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000065cf62c700000000000000000000000000000000000000000000000000000000000000030b080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000005a34a38fc00a000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000168d28e3f002800000000000000000000000000000000000000000000000000003e923ae5641b48100000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000009e9fbde7c7a83c43913bddc8779158f1368f04130000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000043a77aabd00780000000000000000000000000000000000000000000000000000ba58d0ad0652c8800000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc20027109e9fbde7c7a83c43913bddc8779158f1368f0413000000000000000000000000000000000000000000c080a0c6c29ae62a8518f90964b3b89b854e3063bcd5de18e976fd15799dd6bf7141cfa0778acfdb416cc2f46feab71098bba323f340d60892488b72cf562fcb3effecbf", - "new_receipt_trie_node_byte": "0xb907d302f907cf0183105392b9010000200000000000000008000080000000000000000000802000000000000000000000000000000200000000000080000002000000080020000000000000000040000000080000000800000008000000200000000000000000000040108020000000000020020000004000000000000800000000000000000200000110000800000000000000000000000000040000040000000001000000080000024000000000000000000000000000840000000000200000000000000000000000010040000000000002000000000000000000000000000000400000201000000000000020000080200020000000000000020000000000001000000000400000100000000000f906c4f87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a0e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109ca00000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fada00000000000000000000000000000000000000000000000005a34a38fc00a0000f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fada0000000000000000000000000dc900845732a53ee8df737efa282a6bc56976e62a0000000000000000000000000000000000000000000000000168d28e3f0028000f89c949e9fbde7c7a83c43913bddc8779158f1368f0413f884a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000dc900845732a53ee8df737efa282a6bc56976e62a00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000d10780f89b949e9fbde7c7a83c43913bddc8779158f1368f0413f863a0e59fdd36d0d223c0c7d996db7ad796880f45e1936cb0bb7ac102e7082e031487a0000000000000000000000000dc900845732a53ee8df737efa282a6bc56976e62a00000000000000000000000009b76d386a5fd08908b9ebcd5997abbfd3a2d5225a000000000000000000000000000000000000000000000000003e923b8acf52d4bf87994dc900845732a53ee8df737efa282a6bc56976e62e1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b8400000000000000000000000000000000000000000000000019c8228845f5953700000000000000000000000000000000000000000000000095a2e751b37152218f8fc94dc900845732a53ee8df737efa282a6bc56976e62f863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a00000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fada00000000000000000000000009b76d386a5fd08908b9ebcd5997abbfd3a2d5225b8800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000168d28e3f002800000000000000000000000000000000000000000000000000003e923b8acf52d4b0000000000000000000000000000000000000000000000000000000000000000f89c949e9fbde7c7a83c43913bddc8779158f1368f0413f884a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000009b76d386a5fd08908b9ebcd5997abbfd3a2d5225a0000000000000000000000000000000000000000000000000000000000000d10880f89b949e9fbde7c7a83c43913bddc8779158f1368f0413f863a0e59fdd36d0d223c0c7d996db7ad796880f45e1936cb0bb7ac102e7082e031487a00000000000000000000000001df4c6e36d61416813b42fe32724ef11e363eddca00000000000000000000000009b76d386a5fd08908b9ebcd5997abbfd3a2d5225a00000000000000000000000000000000000000000000000000bb4756d6d98e13cf89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fada00000000000000000000000001df4c6e36d61416813b42fe32724ef11e363eddca000000000000000000000000000000000000000000000000043a77aabd0078000f9011c941df4c6e36d61416813b42fe32724ef11e363eddcf863a0c42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67a00000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fada00000000000000000000000009b76d386a5fd08908b9ebcd5997abbfd3a2d5225b8a0fffffffffffffffffffffffffffffffffffffffffffffffff44b8a9292671ec400000000000000000000000000000000000000000000000043a77aabd007800000000000000000000000000000000000000000026493740823657f8089cb74c90000000000000000000000000000000000000000000000aab081aa14a00c686a000000000000000000000000000000000000000000000000000000000000442a", - "gas_used": 276940 - } - }, - { - "traces": { - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "storage_read": [ - "0xc4b5945b3d588809025580057cbcadbb58fc33740a13703202de569e28ad94f8", - "0xed86a1228e69cf38993e7d0567ff50154ce028b0b2307ce2b12f66fa0c6a285c", - "0x12231cd4c753cb5530a43a74c45106c24765e6f81dc8927d4f4be7e53315d5a8" - ], - "storage_written": { - "0x12231cd4c753cb5530a43a74c45106c24765e6f81dc8927d4f4be7e53315d5a8": "0x1e35b19a1ee01f6807", - "0xed86a1228e69cf38993e7d0567ff50154ce028b0b2307ce2b12f66fa0c6a285c": "0x7e180001c095c6691", - "0xc4b5945b3d588809025580057cbcadbb58fc33740a13703202de569e28ad94f8": "0x954292ea137152218" - }, - "code_usage": { - "read": "0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23" - } - }, - "0xd420d5f24225702a68ddf21242d5eef816dc2e6d": { - "storage_read": [ - "0x000000000000000000000000000000000000000000000000000000000000000c", - "0x0000000000000000000000000000000000000000000000000000000000000008", - "0x0000000000000000000000000000000000000000000000000000000000000006", - "0x0000000000000000000000000000000000000000000000000000000000000007" - ], - "storage_written": { - "0x0000000000000000000000000000000000000000000000000000000000000008": "0x65cf6087000000000007e180001c095c6691000180cfcb4e592f75820f79e242", - "0x000000000000000000000000000000000000000000000000000000000000000c": "0x1" - }, - "code_usage": { - "read": "0x5b83bdbcc56b2e630f2807bbadd2b0c21619108066b92a58de081261089e9ce5" - } - }, - "0x6b75d8af000000e20b7a7ddf000ba900b4009a80": { - "balance": "0x153ac555d1f4a", - "code_usage": { - "read": "0x213c3f38a90242ff53461e679cef9a1bbbae20e63de087cdbc972d813c857711" - } - }, - "0xdc900845732a53ee8df737efa282a6bc56976e62": { - "storage_read": [ - "0x0000000000000000000000000000000000000000000000000000000000000007", - "0x000000000000000000000000000000000000000000000000000000000000000c", - "0x0000000000000000000000000000000000000000000000000000000000000008", - "0x0000000000000000000000000000000000000000000000000000000000000006" - ], - "storage_written": { - "0x000000000000000000000000000000000000000000000000000000000000000c": "0x1", - "0x0000000000000000000000000000000000000000000000000000000000000008": "0x65cf608700000000000954292ea1371522180000000000019d8d2f4d5f595370" - }, - "code_usage": { - "read": "0x5b83bdbcc56b2e630f2807bbadd2b0c21619108066b92a58de081261089e9ce5" - } - }, - "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { - "balance": "0x9e19185fe4f74274" - }, - "0x6d7497751656618fc38cfb5478994a20f7e235df": { - "storage_read": [ - "0x0000000000000000000000000000000000000000000000000000000000000005", - "0xb39e9ba92c3c47c76d4f70e3bc9c3270ab78d2592718d377c8f5433a34d3470a", - "0xdb111b6bab9f3303f59a63f0eb7b11cf5482c92ef8f241614c4c2bca1d38f897" - ], - "storage_written": { - "0xdb111b6bab9f3303f59a63f0eb7b11cf5482c92ef8f241614c4c2bca1d38f897": "0x180cfcb4e592f75820f79e242", - "0xb39e9ba92c3c47c76d4f70e3bc9c3270ab78d2592718d377c8f5433a34d3470a": "0x13e5b252ffc3522bbf655de0" - }, - "code_usage": { - "read": "0x4123f56d86f276e684177a9b98a18f2f9d66d40208c423fe55b0e5c8f9d53abd" - } - }, - "0x9e9fbde7c7a83c43913bddc8779158f1368f0413": { - "storage_read": [ - "0xa34640eb1c607d836ceb4af07c59befcec4cdb4943351c3a245f8712063aa3b6", - "0x5026556a9e91dc9bba9215081252f333fe1d44bf12f6b47633a8259ffde03acb", - "0xab2e97a75db32eb3b19136ac5fcb6d7a64d182e81eb81decf514e3d877434a50", - "0x6e6081c5b55abcaa3c6e2e8a592b973640109071879cd803b85d3d706ee3969c" - ], - "storage_written": { - "0x6e6081c5b55abcaa3c6e2e8a592b973640109071879cd803b85d3d706ee3969c": "0x19d8d2f4d5f595370", - "0xab2e97a75db32eb3b19136ac5fcb6d7a64d182e81eb81decf514e3d877434a50": "0x4a3a5debfd930dd" - }, - "code_usage": { - "read": "0xde2d3cb78918547c06e2d4f09804da99d8ee9dbfecc4c8023b7ccecfe92a829b" - } - }, - "0xae2fc483527b8ef99eb5d9b44875f005ba1fae13": { - "balance": "0x673201310c98ee9f4", - "nonce": "0x201e90" - } - }, - "meta": { - "byte_code": "0x02f903810183201e8f850c6fa6bfe8850c6fa6bfe88302d659946b75d8af000000e20b7a7ddf000ba900b4009a80850784a19501b83a7f386d7497751656618fc38cfb5478994a20f7e235df016ed4ed8b3c9e9fbde7c7a83c43913bddc8779158f1368f0413010b06c93c0605467a06f902cff87a946d7497751656618fc38cfb5478994a20f7e235dff863a00000000000000000000000000000000000000000000000000000000000000005a0b39e9ba92c3c47c76d4f70e3bc9c3270ab78d2592718d377c8f5433a34d3470aa0db111b6bab9f3303f59a63f0eb7b11cf5482c92ef8f241614c4c2bca1d38f897f89b94d420d5f24225702a68ddf21242d5eef816dc2e6df884a0000000000000000000000000000000000000000000000000000000000000000ca00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007f87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0c4b5945b3d588809025580057cbcadbb58fc33740a13703202de569e28ad94f8a0ed86a1228e69cf38993e7d0567ff50154ce028b0b2307ce2b12f66fa0c6a285ca012231cd4c753cb5530a43a74c45106c24765e6f81dc8927d4f4be7e53315d5a8f89b949e9fbde7c7a83c43913bddc8779158f1368f0413f884a06e6081c5b55abcaa3c6e2e8a592b973640109071879cd803b85d3d706ee3969ca0a34640eb1c607d836ceb4af07c59befcec4cdb4943351c3a245f8712063aa3b6a05026556a9e91dc9bba9215081252f333fe1d44bf12f6b47633a8259ffde03acba0ab2e97a75db32eb3b19136ac5fcb6d7a64d182e81eb81decf514e3d877434a50f89b94dc900845732a53ee8df737efa282a6bc56976e62f884a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000ca00000000000000000000000000000000000000000000000000000000000000008a0000000000000000000000000000000000000000000000000000000000000000680a036a7f2a293f3ce240ee26aa2454a653a442aa817ce2c34f780fc46de5254a98aa01472bb980737e6a7b83cf1d8460e0144dadcf5e9aea3c58106059f680edbd7b2", - "new_txn_trie_node_byte": "0x02f903810183201e8f850c6fa6bfe8850c6fa6bfe88302d659946b75d8af000000e20b7a7ddf000ba900b4009a80850784a19501b83a7f386d7497751656618fc38cfb5478994a20f7e235df016ed4ed8b3c9e9fbde7c7a83c43913bddc8779158f1368f0413010b06c93c0605467a06f902cff87a946d7497751656618fc38cfb5478994a20f7e235dff863a00000000000000000000000000000000000000000000000000000000000000005a0b39e9ba92c3c47c76d4f70e3bc9c3270ab78d2592718d377c8f5433a34d3470aa0db111b6bab9f3303f59a63f0eb7b11cf5482c92ef8f241614c4c2bca1d38f897f89b94d420d5f24225702a68ddf21242d5eef816dc2e6df884a0000000000000000000000000000000000000000000000000000000000000000ca00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007f87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0c4b5945b3d588809025580057cbcadbb58fc33740a13703202de569e28ad94f8a0ed86a1228e69cf38993e7d0567ff50154ce028b0b2307ce2b12f66fa0c6a285ca012231cd4c753cb5530a43a74c45106c24765e6f81dc8927d4f4be7e53315d5a8f89b949e9fbde7c7a83c43913bddc8779158f1368f0413f884a06e6081c5b55abcaa3c6e2e8a592b973640109071879cd803b85d3d706ee3969ca0a34640eb1c607d836ceb4af07c59befcec4cdb4943351c3a245f8712063aa3b6a05026556a9e91dc9bba9215081252f333fe1d44bf12f6b47633a8259ffde03acba0ab2e97a75db32eb3b19136ac5fcb6d7a64d182e81eb81decf514e3d877434a50f89b94dc900845732a53ee8df737efa282a6bc56976e62f884a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000ca00000000000000000000000000000000000000000000000000000000000000008a0000000000000000000000000000000000000000000000000000000000000000680a036a7f2a293f3ce240ee26aa2454a653a442aa817ce2c34f780fc46de5254a98aa01472bb980737e6a7b83cf1d8460e0144dadcf5e9aea3c58106059f680edbd7b2", - "new_receipt_trie_node_byte": "0xb9067502f906710183125004b9010000200000000000000008000080000000000000000000002000000000000000000000008000000020000000000080000002000000080000000000000020000000000000000000000000000008000000200080000000000000000040100000000000000020000000000000000000000000000000000000000080000110000000000000000000000000000020000200000000000000000000080000004000000000000000001000000000000000000000000000000000000000000000010040000000000002000000000000000000000000000000400000201000000000000000000100200000000000022000020000000000000000000000000000000000000020f90566f89b946d7497751656618fc38cfb5478994a20f7e235dff863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80a0000000000000000000000000d420d5f24225702a68ddf21242d5eef816dc2e6da00000000000000000000000000000000000000000016ed4ed0000000000000000f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000d420d5f24225702a68ddf21242d5eef816dc2e6da00000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80a00000000000000000000000000000000000000000000000000784a19501000000f87994d420d5f24225702a68ddf21242d5eef816dc2e6de1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b840000000000000000000000000000000000000000180cfcb4e592f75820f79e242000000000000000000000000000000000000000000000007e180001c095c6691f8fc94d420d5f24225702a68ddf21242d5eef816dc2e6df863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a00000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80a00000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80b8800000000000000000000000000000000000000000016ed4ed0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000784a19501000000f89b949e9fbde7c7a83c43913bddc8779158f1368f0413f863a0e59fdd36d0d223c0c7d996db7ad796880f45e1936cb0bb7ac102e7082e031487a00000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80a0000000000000000000000000dc900845732a53ee8df737efa282a6bc56976e62a0000000000000000000000000000000000000000000000000010b06c900000000f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000dc900845732a53ee8df737efa282a6bc56976e62a00000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80a00000000000000000000000000000000000000000000000000605467a00000000f87994dc900845732a53ee8df737efa282a6bc56976e62e1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b8400000000000000000000000000000000000000000000000019d8d2f4d5f59537000000000000000000000000000000000000000000000000954292ea137152218f8fc94dc900845732a53ee8df737efa282a6bc56976e62f863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a00000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80a00000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80b880000000000000000000000000000000000000000000000000010b06c900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000605467a00000000", - "gas_used": 130162 - } - }, - { - "traces": { - "0xee1c139ea09924a95b98b56a68efc1104f049f21": { - "storage_read": [ - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x336a15c2de424b150f2fe74517a91cf099618a9cc06476ee6969c87d83e552f7", - "0x000000000000000000000000000000000000000000000000000000000000000f", - "0x3f21a237864d150ec73673aa9343e861c8699ada868b369630fe86259d2cc7b1", - "0xea698147b9768f539806750d0817afddf757d04fa6faa24ab18cb700ddf86c0d", - "0x000000000000000000000000000000000000000000000000000000000000000a", - "0x0000000000000000000000000000000000000000000000000000000000000008", - "0x0000000000000000000000000000000000000000000000000000000000000013", - "0x000000000000000000000000000000000000000000000000000000000000000e", - "0x27c000cb4a36e527548c82ca9c4152888ae4f091d192793223fa9fa8cdfdf108", - "0x382bec4ba2f2bddade773da93a20c3dc1f1a4da957985a987e0c01c70cb2c10f", - "0x0000000000000000000000000000000000000000000000000000000000000012", - "0xc3c109542df5f0b35d6c0977e1181ed3e16d8b7c34539182429309bafc1d36d9", - "0x000000000000000000000000000000000000000000000000000000000000000d" - ], - "storage_written": { - "0x000000000000000000000000000000000000000000000000000000000000000d": "0xcd", - "0x27c000cb4a36e527548c82ca9c4152888ae4f091d192793223fa9fa8cdfdf108": "0x20fa172815c441c0c", - "0x382bec4ba2f2bddade773da93a20c3dc1f1a4da957985a987e0c01c70cb2c10f": "0x6017fd9172c848c" - }, - "code_usage": { - "read": "0xff219df1cfcda45d9a2b4e3531676c6610f25962c839f543a04177f5904ef6ef" - } - }, - "0xbcd3a47e4d0000cf170e25d1bd3d53f7c08be0a6": { - "code_usage": { - "read": "0x6ed615a0ca19cd89538543cdcb17a7e8d26147013208623b1efd12f8361aa814" - } - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "0x29872765e68ecacafbd10", - "storage_read": [ - "0xb2940356fd89a84cf9a86227596a2f59f085c679d5f9450737d0ae4daf0efcea", - "0xa1418cf079260397be8c8824a66003accc52fdb01dd81e3e13c62f524976fed6" - ], - "storage_written": { - "0xa1418cf079260397be8c8824a66003accc52fdb01dd81e3e13c62f524976fed6": "0x7a5498b1e8721b5b", - "0xb2940356fd89a84cf9a86227596a2f59f085c679d5f9450737d0ae4daf0efcea": "0x0" - }, - "code_usage": { - "read": "0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23" - } - }, - "0x3328f7f4a1d1c57c35df56bbf0c9dcafca309c49": { - "balance": "0x11d6e5436565d2728", - "storage_read": [ - "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103", - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "0x0000000000000000000000000000000000000000000000000000000000000001" - ], - "storage_written": { - "0x0000000000000000000000000000000000000000000000000000000000000001": "0x11d6e5436565d2726" - }, - "code_usage": { - "read": "0x4d9be648c5bf39973670d9f8b481d5d0b971e6a2db2deccc6b98cde21c5dd83e" - } - }, - "0xdf675ac7eb6a7053e6988565f57dc3da177661ef": { - "storage_read": [ - "0x000000000000000000000000000000000000000000000000000000000000000c", - "0x0000000000000000000000000000000000000000000000000000000000000006", - "0x0000000000000000000000000000000000000000000000000000000000000007", - "0x0000000000000000000000000000000000000000000000000000000000000009", - "0x000000000000000000000000000000000000000000000000000000000000000a", - "0x0000000000000000000000000000000000000000000000000000000000000008" - ], - "storage_written": { - "0x0000000000000000000000000000000000000000000000000000000000000009": "0x1774754155affcf0e8b24d330868aa174", - "0x000000000000000000000000000000000000000000000000000000000000000a": "0xf52066d55f7abecb88d1a8dffc1e30", - "0x0000000000000000000000000000000000000000000000000000000000000008": "0x65cf60870000000000020fa172815c441c0c0000000000007a5498b1e8721b5b", - "0x000000000000000000000000000000000000000000000000000000000000000c": "0x1" - }, - "code_usage": { - "read": "0x5b83bdbcc56b2e630f2807bbadd2b0c21619108066b92a58de081261089e9ce5" - } - }, - "0x3d427f91ebcd2943e7fff89975a9a49b27bbbb57": { - "balance": "0x1851e56df56915f2", - "nonce": "0x1d1" - }, - "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { - "balance": "0x9e2097c968f96274" - } - }, - "meta": { - "byte_code": "0x02f9023d018201d08502cb417800850a214098688307c524943328f7f4a1d1c57c35df56bbf0c9dcafca309c4988016345785d8a0000b901c40162e2d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001600000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000065cf6087000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000039a7fe8a7811c5400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000ee1c139ea09924a95b98b56a68efc1104f049f21c001a0d4991c42fd0a6bbe99e02ad76f0bec40e8eceae6dc020634fd1753f0f27672b2a056102edb5986f3753438277d85e83a5359d6e768a79281a73799135ceeb7a45b", - "new_txn_trie_node_byte": "0x02f9023d018201d08502cb417800850a214098688307c524943328f7f4a1d1c57c35df56bbf0c9dcafca309c4988016345785d8a0000b901c40162e2d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001600000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000065cf6087000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000039a7fe8a7811c5400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000ee1c139ea09924a95b98b56a68efc1104f049f21c001a0d4991c42fd0a6bbe99e02ad76f0bec40e8eceae6dc020634fd1753f0f27672b2a056102edb5986f3753438277d85e83a5359d6e768a79281a73799135ceeb7a45b", - "new_receipt_trie_node_byte": "0xb9057402f90570018314ff00b9010000200000000000000000000080000000080000000000000000000000000000000000040000000000000000020000000002000000080000200020200000000000000000000000000000000008000000200000000000000000000000008004000000000000000120000000000000000400200000400000000000000010000000000000000000000000000020000000020000000001000000080000004000000000000000000000000002001000000000082000000000080000040000000000000000000002000000000080000000000000040001000000001000000000000000000000200000000000000000000000000000080000000000400000000000000000f90465f87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a0e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109ca00000000000000000000000003328f7f4a1d1c57c35df56bbf0c9dcafca309c49a0000000000000000000000000000000000000000000000000016180fc396978fef89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000003328f7f4a1d1c57c35df56bbf0c9dcafca309c49a0000000000000000000000000df675ac7eb6a7053e6988565f57dc3da177661efa0000000000000000000000000000000000000000000000000016180fc396978fef89b94ee1c139ea09924a95b98b56a68efc1104f049f21f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000df675ac7eb6a7053e6988565f57dc3da177661efa00000000000000000000000003d427f91ebcd2943e7fff89975a9a49b27bbbb57a000000000000000000000000000000000000000000000000006017fd9172c848cf87994df675ac7eb6a7053e6988565f57dc3da177661efe1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b8400000000000000000000000000000000000000000000000007a5498b1e8721b5b0000000000000000000000000000000000000000000000020fa172815c441c0cf8fc94df675ac7eb6a7053e6988565f57dc3da177661eff863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a00000000000000000000000003328f7f4a1d1c57c35df56bbf0c9dcafca309c49a00000000000000000000000003d427f91ebcd2943e7fff89975a9a49b27bbbb57b880000000000000000000000000000000000000000000000000016180fc396978fe0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006017fd9172c848cf899943328f7f4a1d1c57c35df56bbf0c9dcafca309c49e1a072015ace03712f361249380657b3d40777dd8f8a686664cab48afd9dbbe4499fb8600000000000000000000000000000000000000000000000000001c47c242087010000000000000000000000003d427f91ebcd2943e7fff89975a9a49b27bbbb570000000000000000000000000000000000000000000000000000000000000012f899943328f7f4a1d1c57c35df56bbf0c9dcafca309c49e1a09f849d23f4955d98202378ea318f2b0c7533695d3c9fb2a3931f0f919fa8c420b860000000000000000000000000000000000000000000000000016180fc396978fe00000000000000000000000000000000000000000000000006017fd9172c848c00000000000000000000000000000000000000000000000006017fd9172c848c", - "gas_used": 175868 - } - }, - { - "traces": { - "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": { - "code_usage": { - "read": "0xa324bc7db3d091b6f1a2d526e48a9c7039e03b3cc35f7d44b15ac7a1544c11d2" - } - }, - "0xdf675ac7eb6a7053e6988565f57dc3da177661ef": { - "storage_read": [ - "0x0000000000000000000000000000000000000000000000000000000000000008", - "0x000000000000000000000000000000000000000000000000000000000000000c", - "0x0000000000000000000000000000000000000000000000000000000000000006", - "0x0000000000000000000000000000000000000000000000000000000000000007" - ], - "storage_written": { - "0x0000000000000000000000000000000000000000000000000000000000000008": "0x65cf608700000000000203f58c05f29fc1be0000000000007d1b23a2a3861b5b", - "0x000000000000000000000000000000000000000000000000000000000000000c": "0x1" - }, - "code_usage": { - "read": "0x5b83bdbcc56b2e630f2807bbadd2b0c21619108066b92a58de081261089e9ce5" - } - }, - "0xee1c139ea09924a95b98b56a68efc1104f049f21": { - "storage_read": [ - "0x0000000000000000000000000000000000000000000000000000000000000013", - "0x0000000000000000000000000000000000000000000000000000000000000012", - "0x20bbe88de1a142167932f8ddec2bb05de590675e9f971acea869b6f70144240b", - "0x336a15c2de424b150f2fe74517a91cf099618a9cc06476ee6969c87d83e552f7", - "0xae6ca7f389261fe7398f95da232e6501150488dc1b71131270c7e152bf7baea3", - "0x000000000000000000000000000000000000000000000000000000000000000e", - "0x3f21a237864d150ec73673aa9343e861c8699ada868b369630fe86259d2cc7b1", - "0x0000000000000000000000000000000000000000000000000000000000000008", - "0x5ec555b6279df85a9179991aed8c52c620ea49f11355a1d8aaf78a9d909be0ff", - "0x000000000000000000000000000000000000000000000000000000000000000a", - "0x000000000000000000000000000000000000000000000000000000000000000d", - "0x000000000000000000000000000000000000000000000000000000000000000f", - "0x27c000cb4a36e527548c82ca9c4152888ae4f091d192793223fa9fa8cdfdf108", - "0x0000000000000000000000000000000000000000000000000000000000000000" - ], - "storage_written": { - "0x5ec555b6279df85a9179991aed8c52c620ea49f11355a1d8aaf78a9d909be0ff": "0x1b04061dabe01086", - "0x27c000cb4a36e527548c82ca9c4152888ae4f091d192793223fa9fa8cdfdf108": "0x203f58c05f29fc1be", - "0x000000000000000000000000000000000000000000000000000000000000000d": "0xce" - }, - "code_usage": { - "read": "0xff219df1cfcda45d9a2b4e3531676c6610f25962c839f543a04177f5904ef6ef" - } - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "0x298727924f3dd67c3bd10", - "storage_read": [ - "0xfb19a963956c9cb662dd3ae48988c4b90766df71ea130109840abe0a1b23dba8", - "0xa1418cf079260397be8c8824a66003accc52fdb01dd81e3e13c62f524976fed6" - ], - "storage_written": { - "0xa1418cf079260397be8c8824a66003accc52fdb01dd81e3e13c62f524976fed6": "0x7d1b23a2a3861b5b", - "0xfb19a963956c9cb662dd3ae48988c4b90766df71ea130109840abe0a1b23dba8": "0x0" - }, - "code_usage": { - "read": "0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23" - } - }, - "0x85fe958bf2c9289855977c0ae99752094a159000": { - "balance": "0x4e44434923c7ed2e", - "nonce": "0x1d5" - }, - "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { - "balance": "0x9e2525dce26d3874" - } - }, - "meta": { - "byte_code": "0x02f9015c018201d485025a01c50085126f7eeb008304f014947a250d5630b4cf539739df2c5dacb4c659f2488d8802c68af0bb140000b8e4b6f9de9500000000000000000000000000000000000000000000000005f7d1e9e0a5719c000000000000000000000000000000000000000000000000000000000000008000000000000000000000000085fe958bf2c9289855977c0ae99752094a1590000000000000000000000000000000000000000000000000000000000065cf608c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000ee1c139ea09924a95b98b56a68efc1104f049f21c080a0e0d2662f068013f865bfba429de67813e80848b7129709d223a19981ce918083a010cd7f31ca9471f59bf07468989f0a5782691d316222940ae078547eba4d3cb2", - "new_txn_trie_node_byte": "0x02f9015c018201d485025a01c50085126f7eeb008304f014947a250d5630b4cf539739df2c5dacb4c659f2488d8802c68af0bb140000b8e4b6f9de9500000000000000000000000000000000000000000000000005f7d1e9e0a5719c000000000000000000000000000000000000000000000000000000000000008000000000000000000000000085fe958bf2c9289855977c0ae99752094a1590000000000000000000000000000000000000000000000000000000000065cf608c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000ee1c139ea09924a95b98b56a68efc1104f049f21c080a0e0d2662f068013f865bfba429de67813e80848b7129709d223a19981ce918083a010cd7f31ca9471f59bf07468989f0a5782691d316222940ae078547eba4d3cb2", - "new_receipt_trie_node_byte": "0xb9043e02f9043a018316eedeb9010000200000000000000000000080000000080000000000000000010000000000000000000000000000000000020000000002000000080000000020000000000000000000000000000000000008000000200000000000000000000000008000000000000000000120000000000000000000000000000000000000000010000000000000000000000000004000000000020000000001000000080000004000000000000000000000000000000000000000002000000008080000000000000000000000000002000000000080000000000000000000000000001000000000000020000000210000000000000000000000000000000008000000400000000000000000f9032ff87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a0e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109ca00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da000000000000000000000000000000000000000000000000002c68af0bb140000f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da0000000000000000000000000df675ac7eb6a7053e6988565f57dc3da177661efa000000000000000000000000000000000000000000000000002c68af0bb140000f89b94ee1c139ea09924a95b98b56a68efc1104f049f21f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000df675ac7eb6a7053e6988565f57dc3da177661efa000000000000000000000000085fe958bf2c9289855977c0ae99752094a159000a00000000000000000000000000000000000000000000000000babe67b69a45a4ef87994df675ac7eb6a7053e6988565f57dc3da177661efe1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b8400000000000000000000000000000000000000000000000007d1b23a2a3861b5b00000000000000000000000000000000000000000000000203f58c05f29fc1bef8fc94df675ac7eb6a7053e6988565f57dc3da177661eff863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da000000000000000000000000085fe958bf2c9289855977c0ae99752094a159000b88000000000000000000000000000000000000000000000000002c68af0bb140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000babe67b69a45a4e", - "gas_used": 126942 - } - }, - { - "traces": { - "0x3328f7f4a1d1c57c35df56bbf0c9dcafca309c49": { - "balance": "0x11d723ba73b28ad52", - "storage_read": [ - "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103", - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "0x0000000000000000000000000000000000000000000000000000000000000001" - ], - "storage_written": { - "0x0000000000000000000000000000000000000000000000000000000000000001": "0x11d723ba73b28ad52" - }, - "code_usage": { - "read": "0x4d9be648c5bf39973670d9f8b481d5d0b971e6a2db2deccc6b98cde21c5dd83e" - } - }, - "0xd5dbf66480844b7aab2b09030aebc6cecf33e5b2": { - "balance": "0x53b82a7f4755def", - "nonce": "0x1aa4" - }, - "0xee1c139ea09924a95b98b56a68efc1104f049f21": { - "storage_read": [ - "0x336a15c2de424b150f2fe74517a91cf099618a9cc06476ee6969c87d83e552f7", - "0x0000000000000000000000000000000000000000000000000000000000000010", - "0x0000000000000000000000000000000000000000000000000000000000000008", - "0x748ace196a619e172e23c1ba712f30ad8277be72debd9e25dd0fd081fc5ea4b2", - "0x000000000000000000000000000000000000000000000000000000000000000b", - "0xd39e7a1713b7480b7572a56a5a140d5100f41a6add76a5f985b34896bf0b07e4", - "0x000000000000000000000000000000000000000000000000000000000000000a", - "0x27c000cb4a36e527548c82ca9c4152888ae4f091d192793223fa9fa8cdfdf108", - "0x52904ea9675b2af48d62072df8d8967ad3b6e4fbbfd783a8ab5f507d2030d15b", - "0x3f21a237864d150ec73673aa9343e861c8699ada868b369630fe86259d2cc7b1", - "0x000000000000000000000000000000000000000000000000000000000000000d", - "0x0000000000000000000000000000000000000000000000000000000000000013", - "0x0000000000000000000000000000000000000000000000000000000000000009", - "0x0000000000000000000000000000000000000000000000000000000000000000" - ], - "storage_written": { - "0x27c000cb4a36e527548c82ca9c4152888ae4f091d192793223fa9fa8cdfdf108": "0x210e428dc8c697a18", - "0x52904ea9675b2af48d62072df8d8967ad3b6e4fbbfd783a8ab5f507d2030d15b": "0xfffffffffffffffffffffffffffffffffffffffffffffffff3116329663647a5", - "0xd39e7a1713b7480b7572a56a5a140d5100f41a6add76a5f985b34896bf0b07e4": "0x110080a6e9e85a60" - }, - "code_usage": { - "read": "0xff219df1cfcda45d9a2b4e3531676c6610f25962c839f543a04177f5904ef6ef" - } - }, - "0xc465cc50b7d5a29b9308968f870a4b242a8e1873": { - "code_usage": { - "read": "0x7b287ee78288945f2c3ccb923d99243bbf70d6040de54ddeec372457739a4612" - } - }, - "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { - "balance": "0x9e2ae6376db6d474" - }, - "0xdf675ac7eb6a7053e6988565f57dc3da177661ef": { - "storage_read": [ - "0x0000000000000000000000000000000000000000000000000000000000000008", - "0x000000000000000000000000000000000000000000000000000000000000000c", - "0x0000000000000000000000000000000000000000000000000000000000000006", - "0x0000000000000000000000000000000000000000000000000000000000000007" - ], - "storage_written": { - "0x0000000000000000000000000000000000000000000000000000000000000008": "0x65cf608700000000000210e428dc8c697a180000000000007a0e536fe4854876", - "0x000000000000000000000000000000000000000000000000000000000000000c": "0x1" - }, - "code_usage": { - "read": "0x5b83bdbcc56b2e630f2807bbadd2b0c21619108066b92a58de081261089e9ce5" - } - }, - "0xbcd3a47e4d0000cf170e25d1bd3d53f7c08be0a6": { - "code_usage": { - "read": "0x6ed615a0ca19cd89538543cdcb17a7e8d26147013208623b1efd12f8361aa814" - } - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "0x29872761823aaa8c2ea2b", - "storage_read": [ - "0xa1418cf079260397be8c8824a66003accc52fdb01dd81e3e13c62f524976fed6", - "0xb2940356fd89a84cf9a86227596a2f59f085c679d5f9450737d0ae4daf0efcea" - ], - "storage_written": { - "0xb2940356fd89a84cf9a86227596a2f59f085c679d5f9450737d0ae4daf0efcea": "0x0", - "0xa1418cf079260397be8c8824a66003accc52fdb01dd81e3e13c62f524976fed6": "0x7a0e536fe4854876" - }, - "code_usage": { - "read": "0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23" - } - } - }, - "meta": { - "byte_code": "0x02f9027501821aa38502540be4008509aa0b046883078b85943328f7f4a1d1c57c35df56bbf0c9dcafca309c4980b9020475713a08000000000000000000000000ee1c139ea09924a95b98b56a68efc1104f049f21000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d000000000000000000000000df675ac7eb6a7053e6988565f57dc3da177661ef0000000000000000000000000000000000000000000000000cee9cd699c9b85a0000000000000000000000000000000000000000000000000223d78c91ada825000000000000000000000000c465cc50b7d5a29b9308968f870a4b242a8e187300000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000003300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000065cf60870000000000000000000000000000000000000000000000000000000000000000c080a0642a7d57b8101cf657c1d084c70135cb64084383982ecb695bb044f39c01f904a02f935286fc624907ec3ae9b3aff154439d72fd29db15a6530adc265370053539", - "new_txn_trie_node_byte": "0x02f9027501821aa38502540be4008509aa0b046883078b85943328f7f4a1d1c57c35df56bbf0c9dcafca309c4980b9020475713a08000000000000000000000000ee1c139ea09924a95b98b56a68efc1104f049f21000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d000000000000000000000000df675ac7eb6a7053e6988565f57dc3da177661ef0000000000000000000000000000000000000000000000000cee9cd699c9b85a0000000000000000000000000000000000000000000000000223d78c91ada825000000000000000000000000c465cc50b7d5a29b9308968f870a4b242a8e187300000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000003300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000065cf60870000000000000000000000000000000000000000000000000000000000000000c080a0642a7d57b8101cf657c1d084c70135cb64084383982ecb695bb044f39c01f904a02f935286fc624907ec3ae9b3aff154439d72fd29db15a6530adc265370053539", - "new_receipt_trie_node_byte": "0xb905d002f905cc018319673db9010000200000000000000000000080000000080000000001000002000000002200000000040000000000000000020000000002000000080000200020200000600000000000000000000000000008000080200000000000400000000000000004000000000000000120000000000000000000000000000000040080000010000000000002000000000000000020000000020000000000000000080000004000000000020000000000000000001000000000082000000000080000040000000000000000000022000000000080000000000000000000000000001000000002000000000010200000000000000000000000000000080000000000000000000000000000f904c1f89b94ee1c139ea09924a95b98b56a68efc1104f049f21f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000d5dbf66480844b7aab2b09030aebc6cecf33e5b2a0000000000000000000000000df675ac7eb6a7053e6988565f57dc3da177661efa00000000000000000000000000000000000000000000000000cee9cd699c9b85af89b94ee1c139ea09924a95b98b56a68efc1104f049f21f863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a0000000000000000000000000d5dbf66480844b7aab2b09030aebc6cecf33e5b2a0000000000000000000000000c465cc50b7d5a29b9308968f870a4b242a8e1873a0fffffffffffffffffffffffffffffffffffffffffffffffff3116329663647a5f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000df675ac7eb6a7053e6988565f57dc3da177661efa00000000000000000000000003328f7f4a1d1c57c35df56bbf0c9dcafca309c49a0000000000000000000000000000000000000000000000000030cd032bf00d2e5f87994df675ac7eb6a7053e6988565f57dc3da177661efe1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b8400000000000000000000000000000000000000000000000007a0e536fe485487600000000000000000000000000000000000000000000000210e428dc8c697a18f8fc94df675ac7eb6a7053e6988565f57dc3da177661eff863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a00000000000000000000000003328f7f4a1d1c57c35df56bbf0c9dcafca309c49a00000000000000000000000003328f7f4a1d1c57c35df56bbf0c9dcafca309c49b88000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cee9cd699c9b85a000000000000000000000000000000000000000000000000030cd032bf00d2e50000000000000000000000000000000000000000000000000000000000000000f87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a07fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65a00000000000000000000000003328f7f4a1d1c57c35df56bbf0c9dcafca309c49a0000000000000000000000000000000000000000000000000030cd032bf00d2e5f899943328f7f4a1d1c57c35df56bbf0c9dcafca309c49e1a072015ace03712f361249380657b3d40777dd8f8a686664cab48afd9dbbe4499fb8600000000000000000000000000000000000000000000000000003e770e4cb862c000000000000000000000000d5dbf66480844b7aab2b09030aebc6cecf33e5b20000000000000000000000000000000000000000000000000000000000000033f858943328f7f4a1d1c57c35df56bbf0c9dcafca309c49e1a0522881958b3c4a6fc0840ad3b7fb947b881edc28c004245a62541647422ade97a0000000000000000000000000000000000000000000000000030cd032bf00d2e5", - "gas_used": 161887 - } - }, - { - "traces": { - "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { - "balance": "0x9da8f5317178807d", - "nonce": "0xa961e" - }, - "0x876528533158c07c1b87291c35f84104cd64ec01": { - "balance": "0x152e639c78b8139f35" - } - }, - "meta": { - "byte_code": "0x02f87101830a961d80850755ff2068827d0094876528533158c07c1b87291c35f84104cd64ec01877f973f93e400b780c001a0a1e7913e05eb6bb6f4bcb70abd29b6c5fd217ed8c501c7e11fa8ef4cdc0db303a0380e9b3c08be1ea016553f0aecd2702f3ebfbfe59ab702eba90d6bfba8238202", - "new_txn_trie_node_byte": "0x02f87101830a961d80850755ff2068827d0094876528533158c07c1b87291c35f84104cd64ec01877f973f93e400b780c001a0a1e7913e05eb6bb6f4bcb70abd29b6c5fd217ed8c501c7e11fa8ef4cdc0db303a0380e9b3c08be1ea016553f0aecd2702f3ebfbfe59ab702eba90d6bfba8238202", - "new_receipt_trie_node_byte": "0xb9010d02f90109018319b945b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - } - ] - }, - "other_data": { - "b_data": { - "b_meta": { - "block_beneficiary": "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5", - "block_timestamp": "0x65cf6087", - "block_number": "0x1259701", - "block_difficulty": "0x0", - "block_random": "0x45d29466a7116dd48c9826216292a8796242621f55677181b0395d70a2bcf23e", - "block_gaslimit": "0x1c9c380", - "block_chain_id": "0x1", - "block_base_fee": "0x755ff2068", - "block_gas_used": "0x19b945", - "block_bloom": [ - "0x20400000000000000800008000000018000000000180200201000200220000", - "0x48000100230020000020080000002008800080020200020200020600040", - "0x800000029000000088000802000808000004000000000401080260000", - "0x20220120004008000000002c4020000040000004028000013000080000", - "0x2000000000000004020040200060080000201000010080000024000080000", - "0x200000010000000028450400800002820010000080c00000400000100400000", - "0x2200000000008000000001000004008142000020904004000200002000", - "0x19021002000000002a0000200010000000810080100004008001000000000a0" - ] - }, - "b_hashes": { - "prev_hashes": [ - "0xcfab9f57a4374e241013c389a91566b7d0c37da2ea36c9b63baa646480d0ee77", - "0x38b80ec5e15c935edb4f4956f8224a1eaa88c93938f67a1018cd7c85eb606aab", - "0xc138d04a4d815c9d83e3cebb989239461ad3ec0f9fd298e3449f3659b323400c", - "0x58bf7320b17722b62478b50020e1fb0aa18ebbaea961d868cc2575a30f63852c", - "0xb87e56f81f0456db5c7a7a17af022bde49f20cd8193463d96b05162ccffa3157", - "0xe4d6f2bfe01b73af732882d3460e13e0ab1138396fa9007a82f042e4e7402663", - "0x621fb15b9d79724387592bc72583a72c2305a5bc862ec88ac4d88ad3c745bfbc", - "0x8b7ef7a4950c70602cc1244df146ed3c719c42659cc68891f81a26880dc916b4", - "0x394f527d6a108bcfa5cd58e5d41a749a99baa9edc4b64cdf9290e01e14803916", - "0x46832687b7e3c68faaf1e001ac3699aa050c9f17085d6a9fbab3b001e1e91486", - "0xd81a2a90046cbe42fd032342889fe343a4b5c4fd80d8f69add0c0f4b4e55bc8d", - "0x4c78e389d03ec147deee89d6762cb6ac9c125920027efe921922cf806c186d4c", - "0x3f444e94733f1f4183474b90ff76248c86d805a25c4d565167b7d7a3692d3565", - "0x4998bc4c9a48073a095afa2fc93eed3dbbaa12620460bbf0e73e7e23e560c69c", - "0x1945c4296041ab7b3024f7b263293a33777925fa09459adb80e8ba8bc3f6e2a2", - "0x9aafcd5475c1fdaad41c810a1e99ba53379146a00dbb17d679c15a38df47ae19", - "0x31667b67aff44c0d86c0b924e5e26869784c1ed7ad75e0a75b434a1caaaccb9a", - "0x2beb2ba1c187a5fe2a9d4a1412396e796dcdfb858c7b4c6ff410c35350788783", - "0xaa6d56335b227575c5bc40468216257e83b47ce73e41549de224191663a09652", - "0x25337050f0ed7224687c20b0e37ce8f29e8d63709812d9403558e655935fd4c9", - "0xa9c3447f820a08321e48ee868bc912e1081db04d1de6ac9c18d1cab00245445f", - "0x8db2a1e477e1a9e07b4e5d26443a20e0b5647fe48a575334a18a8c23ff237aac", - "0x836c2b6ec407582529d154ae54ad740616a25c9e95439b36954b0886220e234b", - "0x03a3dcba6d48a16dd9fdd1aaa07aecf2a712da17cb455ba1ca6979fef2411072", - "0x76802a7e201ef4b1522542e46a97360983dc387285b1685ee8cba6ba99e14a09", - "0x4bfd1b64c7e9e706a84f7bb077eaf4fc98dc1f38162f8407daac251b4c782ea8", - "0x4063789111da1e060b25c438ae04c7f7e68d8f10afe126b68b275314251a4ecc", - "0x5f86cc18e261aab47cb8d6dc78505e7b156e022b24d4e7e399e885dd4124d4ce", - "0xe80b83684ab3ffde0d9659caa516711ba37a58c4ac9bbdb6cf6c67d04affcf5c", - "0x8f6a91bc52615b275705fdcc7ec704832f8f5f0466589fde33f6f1e836113bed", - "0x9f681e1226c7894028550212383b4a5d3ea885dd6d1c28ba12ac773b5e3ecf51", - "0x1898b052344c3d0cb7dbacf461dad55cb6f56843e2319c25a490db0eef858908", - "0xcd3dce5835974ff8209c26f5eea2e0bd35bfe46b3f22ffad6582bfa74fe3e6a6", - "0x80300830d137dc63995cd47c4b9d593ee26c36424be1aea1f205a456fa01823f", - "0x4866b34cb7823b8888ae98f84d99a1a30e5216d824dbe850b53f800f745d18c2", - "0xa40cea299f742449a2a2abd1d8d240d2e42a16c63389b773291469b600b27a4c", - "0x44209e70324b497336058736dced16d65814d90b3fd0fbcfb68a3b81cbbc52c5", - "0x109e598cb2d8b68f154a6400ca56997ed066dd8aa7a15934a8bbcfb344452214", - "0x82cc91ec296a8aa275adc93cb60e76d8809d28eb1d0f6a979a286157cd102e1c", - "0xe24079903b98fd70b9bc36e4dd372876fc02b8ed28de4a2506c40b1351e36d82", - "0x06679c947b94629035585ada54a7705fb6d66cb05e080c4d9a666f0558a5e064", - "0xb7381aae1422385528b08aa99cf2e7cc6c9943fc7e06d7d26266ebac77f2038f", - "0x6695b8d48f45d478840310cb3c9824caf874e3bee8efa56dabd9256e547465a1", - "0xf64259c9be7c6e1510c291e782ce6713fe9e6a8c0670f51484c4b52fcdf143ef", - "0x6d9c6a66065d238588d2303c570ecfee237a12f3119e1e10d6da036ee3c6acb2", - "0xbb5b372993d468b1302baf20b0abf9d8385c4882367d1c7cfb0ffcdeb9ff50a1", - "0x5b6f4ffdcd85cfc4b81655d94be7c19c2436417387b2cb2ce45113790336db35", - "0x1f0f361a4c6f7f2601e677688df6b86d069b617faed4499869631e5adee39178", - "0x42bf25b614164ffd0da182efdad686cac48576fdbdd3f38e7dd1d3bbcc9e5224", - "0xe410e090a0d43d00db127c5639f1af1614ea1680a3e5f491842bc421a8d17521", - "0xad323e505477386f3bd81056800d740a3b10a23ac519f4ae7fa1cd1a2812ab41", - "0x9ffe1e89c81176cc1753d2080339aae9331de823e319f48b997a5a9a59d14a6c", - "0xeb354c27c6adff01efba0bb57a4695009bdde67dad829fca8646eed6c627c04f", - "0x025d06bcbaf4a54651c5b432dbdc1ab61e7e25cb3b33cbd1ad597f6361c64004", - "0xfbb795e49aa2ccc545ac5427d01b8b7f4c6f0f9c6f1636fa2d3f8175ea850e7a", - "0xcfbe0b821f3d9d860aef7f90099cf3eb3d1caf66892142988fdca7990cd2685d", - "0xd38e12f24648a60b38d401b79c034aaa4272e775e44790000de39bd45848b266", - "0x18cf65ee63fea104a2ff5fc02977f31bb686a64007b8cd16d67c431d3fc9985c", - "0xfbef834f90e5cc58a7c0c4ce0eda75881d413fb327954806f43c9831e863bcaa", - "0x20fb4ff5acf0f241bd613dadc8d3146d0b6a667bd66fa8ffc3630c89810a69a3", - "0xd4a1baae192ddc2229cf6425e0fd549bb20cf99bd1857ac14f5eb36ce4ce4882", - "0x0d49346d32b8df702a53de84eff651330b1c9a8f0b00cf3cdd9a37aea37a29b4", - "0xfff87d966513ad61904e37f3e0a822d1216a24f64a2cb0ba019b2ab55b391576", - "0x069e7e81d4ddada64c915bf27e7a7f005f0cae04f85c7c9d9e1c994b35044ab0", - "0xc574c2a0764d2b3424c82a903c6091fd3fc59043c89843001ceafc2f230e250a", - "0xcb57e5e65051817c806b651160ad4fab709fd35801f649854462a391e85113c9", - "0x49df8c35cdc69b9f79b64de400f9a9bd298237e591f561b976e8133f6f4e6e83", - "0x71ff9b73be32122ffd6a70a588cbd249ccac73ac7a72f83ec0b78d401c418c81", - "0x78c53c51467b4d0b0ef26550cf7a68b2c7943942de61f7a9912b7c0b0f54db6d", - "0x89789136684064fb05f130c8bfd122a01cb6013d25f86f25d934037f2cd669d8", - "0x3ce94acc60c5eae9566cad245b5087992d23991f9feaec46c0225f346cfa03cd", - "0x50c61cd40ed98ddde2ec48ef25ffda3c24562efb45466944292042b7646d6ca3", - "0x3be36e840168b97cf843f1282dda75ce7f9a8417e679372fa24747a834b552f6", - "0xb89f248c0659d4fd96fdd11b700772171722e21409694fe7858035b39c3fc84b", - "0xe8e71d5c1ee9094b09643fdeafa63c93a835d4783382c0888c1a93b77cfb27b9", - "0x76e794d55c8879a16255a0454b40d194af4f70b5ac8550f782054dd12fc56d4b", - "0x3c5378f5ed3da82166086ae9df32bd01ab6319bdf69bd64a78dfa2137b869b71", - "0xda149ed42d40c0ad4ba073990c0de763f8c258cfbe087c9db9a5c68ef363739b", - "0x1abdceaecc72ddc6f8058499c951913c9c683d0e68199e0d1646c9df9979bb1d", - "0x46115955203391608cc6633d875c27f2bba1bcb32a5b9a79a617d78e2f15ec6b", - "0x3b1af0c7c51cb1f08fff39c89e064d002d4573760f233d833b6d9f907cff243e", - "0xb4867bc77f1e7d54dd48be67197a8611017f53bc480573c0bd557eb2b8d1865d", - "0x79d41e27c5c3cab17658716e82fdff2bd18c39a37ff7f402b2f071e76efef237", - "0x1c47d6d1ada15f750a3fe992beada41d7698a0ae73fb3ff51d3a83c780b1bee8", - "0x6414f39c9a9aef62156b57044cacef12fb99ccde6944a53c28c9c7f59130c639", - "0xdca75dcbdc8531dfd2640c9f7979abd79a3e58c5e71f319d6aa63f1297e12637", - "0xbc82fd24e1be8bc9f7032a6bfafc482eed796460b3c76cfb482b67408db0a446", - "0x2a45e46666de9e8bc3d396917cbde99a6a0d859e64cc5e9ad8d67dfccbb06a1b", - "0xe4b24c21a7f23f5455f183b5d6b37137ae08c5bbad57851b2072601db62cb50d", - "0xd2cbfc68cb6060ceb677e4da6e21f56a6b086770b6f0d3717affdfada42ed799", - "0xa2942b6aa19abeb697ddedca8be248539c48ef596e08bcda26db0ba2bd5c95e3", - "0xa00ee5291426662b965577851d2f63639d30ba9b089431315cd51b0ea78171aa", - "0x057555a06a0f1faaeb2bc0c6e92d0a95336fd557271b720841cb99e2e0ce01cb", - "0x354d717988880d99017c7af5bcd9a98d2e24db9a9b66b802f374f074ea50651d", - "0x4a076d92f54d83551475bd3a00b6fbd0428a84da527a8d1529463c3697a148cf", - "0x7a0277376b4ff3e14916db4638440940a16cdfc37319c1696e4c6df1bd59c4b8", - "0x1e7b550fcf6a76237398d884f8b020c0f776a8e65b9b1ad1170dfaf69aebded1", - "0x44f458cfd9cbe3a376e75cf5bc43445ceec5c170e2155295350bb4c57ca73ada", - "0x3a130c2e4e95ff6f630797c2f2b2e6433e06979f965ecd4a170ec5c6d22d7748", - "0xa347b7de97da63b80adcb295a1c95f3cf836a0df9bc283c9710b799c00dd0b6b", - "0xa356c10c4e099212258c6f2fdae513032556c2a380814403900c6c0711d4b917", - "0xdd428412cbb7a32c3e7dc5c056db0827cb49fa8c83d984a998f32e15c3d73b48", - "0x40f61ff284733593a7b54896381e5364a054b9e7b1eb0d9b22e479f2468344d4", - "0x2fb5c179fdf142c2af4b2863cfc9b5a42dea7ed637a0f2c39d3af3b32c46c5e5", - "0x7d430b09e2ce51282117040927af580293f7704e9095bf4265797be7af55ee8c", - "0x7c62b0144ef255f729364ffa1b0e7f20ca4c87283618030c226fc0847855de1f", - "0x82deccd92a405b56f62d988c41d2418dd64324882eca967d8260e12cfcecb5db", - "0xbb8854151124237467e120b904244231ffb7f0342d742b0242a430c2ca587072", - "0xce988c73306b3671f018ad630800a6d70c6884ecfc84d172598581f31f5b0f48", - "0x4e5617a802f9a6ea3ec57138152a30a9791f6bc30cbb372a61e02258d5c1f5f1", - "0x8a4460e937b89c2b5ffa6e478a6f9ccc665870f313a7a06abdc82185adc1e296", - "0x429b9600e6a12bc746d21d3ee2ac76c559b5f06ab3e0f5f3b4c8f4504af965f9", - "0x6e8bd561662bf27835053957e56d9b510989efee68ac5cdb8571e9e649f10b63", - "0x4512b87f62323c6c8ade9fa56741823da2ef1f49e3fc76a6ab7a2b47026ad7f4", - "0x7315fc8e86ae2843394ebacbb40440eb66b5f3bf82d5d2a96741df5dce358038", - "0x860445348840ec438748ac4c579a33bf8f16a9922ca53bdac6aa09913d162564", - "0x0ba965c543ba155ebbbbd8bff4c5c63acf221790dd30c7be4bde078bab46dc06", - "0xe814f4ca631d612d0412b7c2c6723d351b2eb19b8563713925b033c676d87cc2", - "0xa3932c3653a210ab39a21127bb8ee97dae95c5bad9910a69bfbaed7b4ab9105b", - "0xbdff1e151c0d0ccd5c6a3be48030f5f905d06169cfe43f8dc1fc715270474344", - "0xe56765c9bc310b26996b60d030e45f7984bd641747e9520b2624f2b58a577f9e", - "0x58d51a1fda0740b191b91880f30035af7db135b54eceb7d1d064653368c39da3", - "0xe6c34027035fb35aefa0796d5714154526affa7bff1e926ccf23aaadc5a251ec", - "0x601023137408602a0d3f62c932de6f62b32372be2ff3e9508b77397e9c7b203e", - "0xbb51e1752b3a324f874ecacda5e4854778d08c976757aabe0d33f9eebce5b69f", - "0x648036ab2ef5c981d11bb45fc110d8c4effe00c6d06646e2df9f04c33b415829", - "0xf0400163aeda772b0139a4e69102589f143e9f23bda49bdc12e2609cc29c74c6", - "0x8d6227386788f4dbad5d8c43d575a627818648d258c03f0025aae5783659ae71", - "0xefa8513731cee73816c0b6168cb25153d29fe6940c1b30db971e1e53b0f59213", - "0x229da5c4b9c46829a93df8ae4bb34b6bf20c37644655277af3ec4b3ccb070351", - "0xca855b6710defa8e20942d981abb9a3a124f76b420ba48e5af32639ed14f8e1c", - "0xdb6e06723e801af3bb9432d02ca8136965dda7f37c592720c8dc7ac4a0c0d45a", - "0xea83e25432f45a0231993ef7b784d200cbb0f4d068d1ffd7c8652b9f1b3da061", - "0x9c6d1a2ac894a2e8f7cbe140d2f251fb454ca77d5b7c64c700bf02b08566032e", - "0x3a59d6738a9f712ed31740f4f211f926febc652ac65d39c768cc15ce6548ac29", - "0xe71d46072083ffc7b5ac292ae827019cb5cfaa335ad9b0717f6b4cd4c002e26b", - "0x0ab071ba6eb8197f56f5d7de6070c05774aec92fe3e38775dbe31b956c20de81", - "0x86e0d7fa597d2bc87b6e09714e2d9adcbf4ace67355414ba5b6cea23c747c0ca", - "0x70a095353bd4ecc77ae6253c93181c0c8880ed57ce5bf2ade7f75c3995ab53b7", - "0x273b6e8f768ce9478e9ca94a5c158ca1152698e89259ebcdefd9bcbb6ca1b28b", - "0xaf59d7b76771c5e8b3ad2f3965b81ed284d47ee892b69b991575f51f9024d82d", - "0x977a7259893512c087f602414f2c5498b52ac4a2283e5d9baba0e7e9d6074477", - "0x69f698ec8280e85baa7ee877652d6eb93281bc68fea8c778624c048113404e85", - "0x3a8e1bb449663fcc1afbf67899e136e3460f3a97fd87e2f2c9cf368ac605527c", - "0xa2656346c9cc287bc1f8697a4906310551f47a24da135467862eacf79f196f76", - "0xf6c099ca0d8d507610a3f549c53515ee11a9c4e55f8474131d3f247e6441d11e", - "0xdb6eb9df15e4da3163d93941524f16766a76be38d4967118c20b2a242e80e22e", - "0x6dc9cd38fb172a6a89672cb36be5fc994d16a126613b94433fe79e10945b3020", - "0xf3b8cd2494813f37bffae61f62a24c1be79e3a9473a556c53bc22ad305640d37", - "0xf1ec74d51276ceb47d5300db099b2729041244c16b1c7d5a9270815e4b2ad87b", - "0xbdb2440940a26dbe21a1e678e6ef5c133ed25f05a70055e52b961aa205dad7a6", - "0xc8e608b563a7af270682b2a5607c03a3e773792b1d59b42c9307d2de0b2287a2", - "0x204766659f1238b1084cf4be97ec53b579be09106ae5252ea6120561e7fa7e80", - "0x7b83846cb399dd5ce6ca56fc96188c5ec78b1c8175f1ca46f8f8ef562630783e", - "0xfc894212ea9bbc5972bc530b6ab7ed40aa7398e8cea35518701e21aaf5225812", - "0xb246a8d3f998bc40fc2862d6d065ac490fe0b8cbe6dc76b11ee9a7d14408864b", - "0xe9391a5600ebda987d96226f776f66d2d9a9ae6dcd61fbe40e2776cea2362165", - "0xf50492c1e56ff3fe1b499732d8e04be8dfe5c16326d31ff77ed9fbfab28ba3ea", - "0x160d294e2166b84c7b39e065b5420f75b845a3e2ca91601a47e86c492253c1a7", - "0x6fa516586c76584db45fa37f1c5c0ab7b7695d4f274ec1a794f88c408c6a9e86", - "0x38b23e425dcca799a06c70d8d4cdb15aeafb16df6b906e1459c9d2c09b00ff3b", - "0x6ccf588a93524bed61c1515129f9cbf228ead29ae4b019783af7ab043c216e72", - "0x3eb9bd1119bcea770e589c55646975e0ea7a825b7b18e27c4a397f98cb3fc072", - "0x18a6d2f8a33c0671304ed13e6b5d49098c95ad9084249d1e495bd3f5c5f8283f", - "0x4401e98be2f7bc8fb46f912d75d4918a03ddeb49c9c9f117bd92565e58a8469e", - "0x55ac69ff9f1a9bd7f88d9758ea891501844b3a932a15d8a4fdcb7ba2c1084495", - "0x3c7fa8eea9e6b19f49803ce3d3926dca64eae46a46d9a658919bae2c0d94b71d", - "0x1130c450d59983001032f00f934f838b20bb1520ff667de9bd8784b892bb6336", - "0xabdfb3d27da751c199289d132e29025923dd47efed222047b43e1584b3937da4", - "0xcfc39333669c6bb5506b2400bd0b682202a8397b9a369f597628c8c2ff62771d", - "0xa24e1607bf874ce72a9a237fc0604836287a2254b6bedc8befc20ca112f3f0b2", - "0x837ce20fe8b6184ec57972d5e9214a0f7e87822fa8e87c293645bbf095ef7a3b", - "0x25e11a9811c15fa64eb38bae5bf9c28d0135fda07185f68e668fbf1a9845970a", - "0x9bc56ef616eb63be18c96e479a4ff1b9b19c3e5874a7faf31d8a890964a507e9", - "0xdd1a5ac21a1acc161f4c73aa56ceac6dd90720cb4e293f15b6a2869d9a5d7501", - "0xd2474ed9bcc5145772ac21309b3bf461201ec85a13cc4b2503efebfe42f8aa9f", - "0xb90dde4373058caa2f5e402268b7c68486939369c062fe189b77d6ab73c96614", - "0x380ace11ada254f8c85083fde6e750ed01d2fab1409450ecf90311c16fc7154b", - "0x82ed384914a2d06ed9a3f9627b75c5329cea39b47667bc27b0c9815154b3c07c", - "0x71871257062f2de58dfe19330339c332eb336b509a3950c9f55210d702459f03", - "0x40a19295674bc11d2c5e28ee7ba584bf13cec92b709dc6d903f799871d4d5b16", - "0x76aa47f9816d3f2769edd4c605203fdbf732006664239e70ee8f46fc0bea693a", - "0xbdc813957728ae825aa3f11f0a518423cb563bd03535d8a8c017af39edd93305", - "0xea13b4df207c200a948fee3403af558b37c8ed660921268d3cd670bab7861404", - "0x98782d2a94d2eaff161ff7175580e794db2dd18ed9ed60431a3c663568ad6ce7", - "0x2f4e2925fd598937c5f268487ab6bcf07130403fdbad2a5422c9efd42bcb4413", - "0x7acf29b8b50a40e5879790dab398203f7a6d459947629624a09927d72a1c8876", - "0x2d08ef96ce1a6b2478b8ae9f442b14156b0a998f9c7e3778e0ab6f556bbcb5d6", - "0x7418de8e710dd6bb61c7559f581525b654b1950efebc62a18b71938ed2e70370", - "0x849c7c76b4a8f8d9090703f09a770fcd90458b99f099fb3f6802724f09782ae6", - "0x5fc720cfff22ef360b7ee9c1630137198a31166716a127c5cee0ebfa14e76719", - "0xd9b4dca033dbc28e1ef2a13d50fc550f18836eaef9d05bc6685fe9d531763a2f", - "0xe70dc6fe376311d6670e956d259af7d8a9c71ca90254849b0b91e2e9b5d5255c", - "0x2c5761e9caf98725a4170fd68b4784dbc9390bb720cf036e7284f6e82a5ec795", - "0x6b358b969aa8c56a722ec87e786e8dfaed7af9cdc1e289ca114c3f7211c36ef0", - "0x2d3a4b11eaf0dc658c0c19956edb0d60163f48198202784c33e992c706453ac4", - "0xda5a12aef28cbd95fb7f0f8041d26bc3cf11bb07a482426052d978f7715dacda", - "0x7d22ed949ac8b7388defd431d086dc651ef5eeb5fb7bf008b41ffeef577fc027", - "0x9b47b1800be3d0e9b008cf10e8afa67debc21dbf6f887b7c29c53713df8ecda2", - "0x7ed432ec67586b2f518e540b5f2b67d714fec68cfe3f1a569c093d9a780119b1", - "0x5440e156e97c8493bb9f4807230a808647de9ef8e14524cb7dbc0901a40184fc", - "0x986385123bdb3ed5c7226b882240ee619f30fd584a32957fc27a83aa1e06673d", - "0xa59fe31fbfa950d479a86eeff096fc455a7a699b3ff86ed1130bd8c8c1124853", - "0xea3bc775c21f3c1272e8204036fb517cc2f82441346e6ca3987a71993d907ca8", - "0x8001476e7252fabbc4fa046ae1f62634567ea538881c6a8a11b288f37ed64fcd", - "0xfd2a71ecf1b26505212d71467d2390cc80cecb0dad7888a6d0d0a6055eb9ca84", - "0xa7ea114a10801202d6a101371e5f9422136f1e173f98dea6810a9a09638872d4", - "0x5c3a8cecb7291388548f6150a16a6307b6edc4eeb69b8f01be34e8098bc7af28", - "0x1f6c0678871618e92bc65493552e70199aa342082d059fa6b0dd110a4b33c8d7", - "0x68fc40e6659f80a785e24cb875372e22f730b6c03d55448863db1eee57eb80a1", - "0x236adad26fbb619a9bb1ccc3e5c21fe705972498ff07898ba0df2d8ef3516869", - "0x03aa1165ebd146992ee1aee1ea82af214f9c923b99feb07e7c990f65adbe37f5", - "0xa89c64d5ab1aecc7e9f9df766fe96d3b0361d81e8048ed4527ed102d036244d0", - "0x90f26821990029ca0d738302c93a4b60c7ad9643b28f140373b91fa1dad39598", - "0x0f8cce246a5e4fa0140ff632ce99f96748987801f4a27e659f1ccdb3f97a8cd4", - "0x5deb4c4068270fe4e3871f3f70170e04276cec9f47fa4425225da1113de62a64", - "0x8c81ee90cfad026a6f23e6f8f33b47411b3602adf490dd6971f4c3c7c9671f57", - "0x05605b9ef167d70a2eedee808c560f61b2bdef0fbc3f61b02bbc52026126156f", - "0xf209a2efe21982cedf5c399040b52be63fe75bf1fe88030a00647c66dab02f96", - "0x6095b14b43cbe789aaea67d7f69d9c6722c17f15dd76ced663dbd747c1131bdb", - "0x94f74e0e64f18665575e26277b3664499dc1d01e4d6dab54a7adf2001e81dcde", - "0x1a901003a7c13e1fcdd13d8c12942c7282e72c94808dc9bd32d77e76cd63ccd0", - "0xea3609e61bcca46eae1695a0bd4188369932fedd20ffe5f93cf22ae871b4f1ef", - "0x027d38050cd0dd5a429d8c3e70b01ff44c8694547383971e2fd85cf312aff759", - "0x232a3620d0ef161d9c72810dcc19b6b76cf024cd551250f14254de046c2dcf4d", - "0x7b168f089c9708a92753916472b83b818994da06a0c64873976ecfe67b4e98c3", - "0x19440d9074c298034d77782b69844e72260d5f0badbc540d240eabfc5bb77a75", - "0x23905d3a64259e5c5496d71fa37e196ac7902b05d095e51bc94e1be6b7757d36", - "0x4aef396e9d58860fbd300b3b797bd51db5e57543fe0ea70291f20576b741103a", - "0x3a9f028ef7c907be3d7ee99cca564df1a6c7cdf461ce09f4765425980a027a1b", - "0xc333a37ec383f09e571492a27056325f7fd0cfbf061fcb5d20e53023acf073a5", - "0xfdf4499c98c4f4b4e31947595682baba0adab78dc11079f50de2aec0fd018a6d", - "0x58a7778a69ef7bd43ed826c54b79c5f070f4b99e2dd08888d817b5f8d375c52b", - "0x1cfb736673e8fcc6889240f1ff69b0d0fcfce34d28e9ddf27f6bee1d8fd90a50", - "0x6eb44090b9092b101d37b897ef88e7c7fc432a5daf7ea514a3cbdad3e5ebe273", - "0x72a902396914af491548de693380bd904776361624264f00a6baf615ea75d74d", - "0xee06fdf85bf3aae87a5b824259ae7dc4eb007f79f33e1d054e8ca7d1a0348b7c", - "0xf49996ee34248d37bfa89924f5d5900f5a4a37156fa172c8eb15ceab849a499e", - "0x2828c1bf01a0bdd6574e71f15b70e0b865162f3f9d85d7f0456bf17052d13db4", - "0x717c4e7d9822ff7e9f684428e7c1dfe41434113cc41b63177bd77bc01f222e85", - "0xfe43a2f383a77c3242a060322795002e5e85da68e338e0b04ef5bfb544a6f741", - "0x06d82bca611ecb5abac06b1bf1c5862f30e2e17c1ee4c56d83b6360666df2cfd", - "0xb51ceb3e5de3c80cde925de8cf8716e1f71cb6b36c3c93f796e0df03851eff41", - "0x93b59443a6a29a396d9770a8cc3a5514ed3b0790317f984b5e50af37d66c9c0a", - "0x8466aa0a7c0d9b667743784848786bade20f65fa6d8450070848f6c2f2f7afd8", - "0x5bddb05a4edfbbdf444a11f8b5a56cbb7274ad316f50084dd9031caf7c2a4395", - "0xa7a3cdc5b9487a826ba813acbc56467b892d07c693fb6ec63f026d6a20812109", - "0xcd0004cf696bf13fd6ca119b7a9bd8b8e82b7bf4b23d4ec82b7f3d813c8ea75d", - "0xa36e92341fdf9f6781420a0247929464c9c9609f068696853d80f0bfea672884", - "0x967ed9f10b5c76f44e18cf2ce8f6bf13904ad6f1b5ac57eb1a9c640eb8fc7461", - "0x5304fafac069aa22d2bac19c0ab61e9d2d8980cfdbe60c5cc24db80adb8d3195", - "0x6ff81f47be444691a521912a31fe83e497b87cd7f13e61d6b92ee7f0b8a44e1c", - "0x2db2c3f22099c4c59f5ed0507e87287b085c6756ecfc86ed6af6c5136bd0f54b", - "0x76314abadf3ba7c7d6ef2e4cd53c2e6541c9771167e8e5e4a240c6a243379b0b", - "0x4cf46c44d6dbb42a0d38f492ea90202c07f4f91abcfa3bb999a9e781c41a42a4", - "0xee8b17aed9e8e0e2dfa2be0734aad690cdf04375289957005af85bc0dcc1ddc9" - ], - "cur_hash": "0x5377110dfb73129b02e62329cfa303ffaf4b931cf4bf527a6aec97dbb98dc6eb" - }, - "withdrawals": [ - [ - "0x64048d00981975d1f6828a807e783d6ab550ee65", - "0x10d8516" - ], - [ - "0x252919b124156491d184da4444cd2d283e31b80b", - "0x10d9ece" - ], - [ - "0x252919b124156491d184da4444cd2d283e31b80b", - "0x10ceec3" - ], - [ - "0x252919b124156491d184da4444cd2d283e31b80b", - "0x10d7f0c" - ], - [ - "0x252919b124156491d184da4444cd2d283e31b80b", - "0x10d4753" - ], - [ - "0x252919b124156491d184da4444cd2d283e31b80b", - "0x10be061" - ], - [ - "0x252919b124156491d184da4444cd2d283e31b80b", - "0x10c99d1" - ], - [ - "0x252919b124156491d184da4444cd2d283e31b80b", - "0x10d4fc8" - ], - [ - "0x252919b124156491d184da4444cd2d283e31b80b", - "0x10baf52" - ], - [ - "0x252919b124156491d184da4444cd2d283e31b80b", - "0x10c78f5" - ], - [ - "0x3cd0c38d5c34ee95fef849256069a3b5c09ad349", - "0x10d1fbd" - ], - [ - "0x3cd0c38d5c34ee95fef849256069a3b5c09ad349", - "0x10cf7fc" - ], - [ - "0x3cd0c38d5c34ee95fef849256069a3b5c09ad349", - "0x10c5024" - ], - [ - "0x3cd0c38d5c34ee95fef849256069a3b5c09ad349", - "0x10d1464" - ], - [ - "0x7142ca8a9618ded97f8838fbe5ecc4515e840a47", - "0x10cdd11" - ], - [ - "0x7142ca8a9618ded97f8838fbe5ecc4515e840a47", - "0x10d60ca" - ] - ] - }, - "checkpoint_state_trie_root": "0x5c2fb1152746455e14b133a1bf1763f72172e681f6d693f4de7ad3f7430bece8" - } - } -] diff --git a/zero_bin/tools/artifacts/witness_b19807080.json b/zero_bin/tools/artifacts/witness_b19807080.json new file mode 100644 index 000000000..f731db5ca --- /dev/null +++ b/zero_bin/tools/artifacts/witness_b19807080.json @@ -0,0 +1,1025 @@ +[ + { + "block_trace": { + "trie_pre_images": { + "combined": { + "compact": "0x01033779ac18716333eaa8b1ee23faedf89eaeb26376210080ea0b6c433ce26cb792037574323ce6b3c85015c8634e605f2bacbf96585b00c9025895db7dccaeb25c20031f0277ea255926ec80976c3e0c6cff328b4a62975ef75615863a6d9a8bfd7c130382e784b1da96aae9e2b3caad0bd92c48222ebf7839320089918d98d273e65a2903d3d0866eafbfb46411b41fca6e7c1a7c3a1f44811db62bc91ee93b17fbcb58f903e7fd542c7765c5510c6de407df112d9346c3052bd3a9a3f0833c10806e926f67036484f5d244034b7db0866222fce3efb5d3cd4a9a33cfd04990f083d653bdf98c038f9965353925cc629d7ab66d9ba3be03455c55f95aed5c04fa48dffff53a58f203cdf44c07525e702674e29efa63ff73aebe88d24953b032bd1f46439fddeef250034370b17e98177b64553036bcc9ebe6ca2fa04734d35f13fef50f29586884145603f824149ac38976ee93444b078acd3dd902ef9786b3021c0fe1bdce84794f00ee0323d249d3bcd11058160ca35a34d4a14e397ea67921af26d2d953d63b16281aff03f5054df6157a58891c4f4ba4c7051f366f6f0015b57b09b2657f0150bcf0b8800343d15dcdc3d5efac93795c05c6913f0b432dbe2f92b3d806e244a51d3464755b03966389c702b0bbdea6b282ebb6c06da03f30ef74cbb4e487fdffe04095bc8330038c072e44eb5d38675e81d7c7623f99c0df3f18326a47316c48ff10db723700770315cf6bad672c89529b200568b8102ac1fe541fc57e13b5374576275b7b927f58038a887dbe5a4535228bb4d1dafdb8d525939c3d9344bb4099160e8ce05a68d6920316130e3944bd82f5f54401b06685da78738459bebf019ba3646cf5e1f2f5213c03ee3a842ddd1243db9a564664b85d829006a1134c449f2d5eca7e9d20acf9d63403e1b69cbba0289fb3e370ff8d74ffd4dc1d85db400a299870c09135d3e81ec071033c09ed77be3902649b5290ad18353daf5a4079c65d1786029f7798bd64fb6a01038d019f3435b0e54f223d69e39fe824e75200074d91a30246cfb34a3783c4463703c02b838cd1d3db81a706700e30b4e53728205aa68a3a849592afc5105c201c2403c3020c3e60fc86465eec22d520e147ca83068e17842f542f4bc3682699d4555003154fce6043787886b3efd46ce95281528132709ed624c382db1345b57f7a74c50315f236830317921dfdaabeb920a6f308e16ff1c06ab19a03f223e65fef24bf9d03a5b7f77f93a8111cba1b112864315f4b44412563c68650c9596e5c2427c5f4030307ca7dbe52955c286b792595fb42cb1233e1d53da1760c543e08be13268a8ac703a6f5ede0875bb7ea0387e36389509a0e2d178d5dbf6e5341f2ec16d34774072e037136a6c56b17ec61ba43b8168fa4828e6da00a570ac196efc3ad0f1103869bcf03562d59a51820d47f520c975e0b2bcffac644a509749a3161f481f57b6e826d210605581e03084b81013b6587e6696fe7b719a0725c12b952ec24385e6e61292f819007011bffffffffffffffff05581e03a91df29e0b231fed9f835ccce160a54bfcd07d62ef0d072dc5a25a2f20040205581e03e485f8fa3ecd23ef16485af319ee03a7abd373a3eab3e24a2d671ba0e00c0547adbdfbbb33772c05581d02ba92575482222cc6c6f2ce3a509e5575c0916abc26f8e6abc20e7bd50847af1e725a56355005581d02804bb3cec0269ac8b062c4cf2e0f525b8475d86816e5ac95f70d161b0c19014f4905aecb9e914af729fb0219802003a6cbf61f05a10a3503e7bbd2daf98492e120eb1fd3f31e56f8b48bcde38f091b03f1b574431f3838d9cdff6e701afd5a058652dab5ae5523288a83d5fad769613903576d247b864a9c9c193245be5262a86aceeb51240a250d7b4354c293d83a678d05581e03ab49bd0a6b152d309d49ddbf8a3cc062892cdf65b012d545ae5f08dfc007011bffffffffffffffff05581e03e1125504e79dd63cc52b693a4ba421c6d38bad7c748db6744acbe40b500c064701d6362429ae0005581e034a81699f33d820054e21486be616f2b85bb7ba689b731c302b510d17200c014701e32940b051a005581e03088dc52df1c821d602aea4b9943d4b4cc7cb1196800ff10c7f86ce315004010219a7b9036d65aa2c676a637aa985cb3d802520f895945c67174283f8c5bfa93ae95762a803ce4bd618fe6ab413cfaf48e72f832ec7a0aa585f19b457ae53cccec66cbd0eaf03d3a1afe00fb4b4e7400888b75c939e55c20e727c2be54b51d28eb70739ce949403366c6d43540c31a7a47103bcbd722eb849cd7b09c62ac9f7cbe4df25b3a62d2003d8fd4c90f8f39e168662557028a400e8b3f15c4d530767d43a42550a82ce9d83039f2b339675a4ca1b4e665e10c8744f53ffaa3142b4f36c183cc7df6f9ff416a303bcef9c2acc4cdbc54f26c7dce6f1a0300d3bd105233a67e9e8c0c5983cb37770039ccc5386d9fae80059b3563315468a416b4f433576d050e7c7d191cd10dcb7fd0347902918c6df4e5fa13e2ac68a44cc489ec913f80769e2a072c6f3f57dfa60fb034cf8a2f7c9d765d7ae76fb7fd93dbe58e02be9ef77243dca012fc2c111a66f3c0219ffff038a87e71935a6c03fc655e37d2084542a110bcab781d81219a42d2e306ad10a0603d4d30973bfaff35861879d23bb71c42725bbabade2b9040ac316bb1dd59de35903068325651b8e9af1bd5c24160390b29a0c08f1fccd3268098a6664a6e57179370318dfe6a2a838248d8ac46b0451c67ff807dbe83ffa91ea46b8b75d8a5a6e8d4c03cc085ac9c77c645ab1a40132fe16d9235b45c78b586af5b414f48dc7c2b9ac9303fb79045fb5143671222e8a6161093c5770a4eca03e97140314df8ff2681c71c70385fe99fdf3e1bb8a92eaebf1b34fe9d660bb233d6d9cb0bda1464b793975ff1903d4052bfde7f1a30f7440f67cdd7888d19a9b1df7de37a21c21dc38e116c892870357cc88a05e470e52a1bb4676b7b74daacfa3f2a92879ab3bdc69b93cee0b524e03aa2b65f9b49f4cf77c5cc175b840aa504d6707e2512f85188cf2438b842e857603ce301c228dd726f20c40c0b0f996b91e3000e2128bee8508fbdb8b40e2155714036893ee48254ab45772282751648ada94fe08372e84e1a8a74cda93de1c5064330219ffff036bc65e4f8f9c4db613e4f645916134eb50a304b1234f5704752caa9bcf8a4b4c0312ed86326ad3fd17284e0cdc9d89091b8ed48520dd6c1cdd8c81a7dede60c1e603be907c5d58601daa203e6fdd42648f058eded9594c59c64ad5f03c32b3c8ac8703678483977ecdbd9344d9b2f69f416c85c6388801778696d8a0be51f164b515c60314a6b5a25b62f39e2d9d7cf4e8872b59d753c2851c4b92485eb495f5c302e79e034cfb9ef48fbde023d36423b0f49f033498152fc8503c61132f81bfe0f5c26e4b03106403b51c750a735d1bddaaa349a10026932ca8ae81ea2b81eb1f8a29bff12b0219ffff03ea3db328a4986acceaa5ffa34bf166bf8d881b4ca8997fb61b0f35296a83acd8034a30bce55dfe93b665912c3e5615b64ebd1e0af1a6fb2a226eb8ee24cabe22a80219ffff03673fa84aea0b0f8628d895697bb08c09903a0cb92db83a2178e6a441ab9510b803006f4006055dde70dc4c2b87106be96f079a15abee5dcb60aa5290785ccec50303305c223d7c65d90cafc1c5830f27cf58e26fd0755791a4d35d9c0b5827649cae03642a97b9d56cf442f8082fdf83d1518930c26a04a08efaf10a5d94338ed4594903d7428c54eaeecbcd89277ef5dcbede101b2ac47701b877a2c79856fd0693717603b974990a02b358f377db44a1d11510515ac24e2e7c1d459389986b633a3d5cc603ec29e57dbc7e1129e75093fb04263f443b35374da59e0a7f0c927052d52ef6d3035326307f35e9b6e255605cc5901a11224a938f62d63f835278cc37641da250ef039ab4641c650ceb1a60704b98b5e2ba0b83290e35b20afb1bfe1908ff8705f6f303588ff352d7528fa0cfc6e6de63fa2524a7c4e7bfc8143bc1cb98b4da1f261ace035c7bf11a00d5750b8921627801e5bbb6518dc31b17ea529a1d9d93510d4aad9103666d74a52d44aab3ab4f9e28f48dcc592054d1bd1d13dca508bbc097e79628370321789df6e00ce42c74c3f015dc84e9a915cffddf24b4a08fd6be4a605c031667030463b307a2b6b552ee059829fdbe0a1ea37a5faa78bfa8586bfcf3520374c0d9031f8e6a3427ef6ef844ba16da319406f23e6cf14ef9df5639f87fb37d3479377803b268d6ba72c73f7f1cb163c74466ee1a5ed0dd0cfa2fe3f538699fcacaf8f783033dc408e1316c7fb267d2800307da7bee9d586dbcac8db366be4550de02972899030fb7bbd1977d6a5af1621e97701e3263eb41f498c0a2750bf106e5321575046203bf80bd77ecf6565b46d1154c36ff145eccb12e2e906755f44e5d5d08e37d834803934a0f18fe0401d10cd9078ebe9523349c2a8787c8f7d102c6ccc3c9adfcf7fe03f5b11eb47d78118e03e374fa2aa010742f33f6f60e78799849bbda689975dbff03d4ec39a2a6f526978d0520c221edb1b552e1647d6ea988121e14885c1f36614003909c700e36b36b646029de7eb8c28933eed667ab03c80f6521808b175d481c33037b3b3abc93d2e5d47d14875c12fe53a4d1ac82f3967bd1ed34378441f2acf2e805581e03580a26e0f75462fdd7aa448d121fc649e46fc04a182e29cec4e8546c9008471400668dce880005581e036cb5135f76e47ab0ceb25810c334b735e4bcc472003720f904ac837820084831444faa80e0200005581e03b421c7951915ff8b0f31869caa956d7f48f5c282aa771b2b63f6058c10040205581e030bfab676abff632c74d8327002ab236cc0fc98fc76f27305bfa6c47be00401031f9dd64ecce4e8deb8daaf5ebc66fc0c4e0f88f77092c41968f588a247fbd4df05581e0344d2efb90a365cd28896d14e3f60c86d9b7c0e006e433db674d3d7a2c0040603f856fd0ac73cd3d02b9907b9db4ebf7cea663ccae0598922600e02e5154669f10353af315a0fcaa9dff012916a9a129f20c3b74bae1a8bb08a9a316de5f8e60f0e05581e032640cabedae07e7b2cf2f87380f1c03d6915bfc8ece216a02ae2f248500c190a18480936e2106de2f64703e9c928a377a0a5c361cdf0424feab2e041dbaa42e8be97c67b8cdd32b61193660219ed8d0359c8e5cd5398897f8ef29cea5ac0a112c084e0c4bf1d09dd5bc0df1f453d05110388a34842733985b3a2ad89951f47c279207ce4032188c01d59d5ce7398017208039301ee60769a5cc1e1b93db3f8ceaca862d1b6230cf3dd894d795e9bc3c653b703706e7308c91c196fa759e9a744ca9b43ed50402e3f21bff848e3a92ef1fb9298039ac17e7f0739eb47bcb4b59bad175fb16879e316d937ae265e52fc1665dd179e03f5d1c4bdafba9c9f85e12c76b60b321aad40ed2dbd4c115c82f64f53c83f2ae503a707123a8de89a86642af5d17a5f46a405002c99f9fd9636df48acb117ffa6e203aa0fb7b0887e5cb555f44f9d7929051fa9657c39b09e32086c569ccb6e6c64f60219ffff036c5a1f0b13a2612419148584c832446b97df1f9c67bea20032c475945b4dc62c035504938b4588e9f1c328c0f5226c65bdc483f463acde1ed92560075f96ed6f33035e150a796ed8cf2905fbb3da7531ade6a8d6c63d04f5c92afaba93a76071a91d03d0cc3d19e35ab6988464ab4a30834b7998161c82e5ce6eab15b19b6c12c7fef803b9c04de2fb64d6e60e9be70249151c84aed39118ef3a52f9c027749de938f8f003a5b52af42d4ce1ec784a3a642e8b2f23f438e782cfa8844733cf0522f967aedd036529e10404bacdc1bf05787b9b90448fe39661fd6227e75e0d03098ad579966f031dd2dad2608d11e93b1e8b3efe63c3157cfcf2aa5fba24b3a78494cb303a3b1403120d3a8b7f9f8aaffdb4dde5a0095937a6a16f14a3c6fa2e560ea79171018a240310b5b51c5754134f5f36b7318e6188e4053ede2e7fa4071e8260fc421beb6e66033a20fbe50aa26dd941bbde42353ed1db1218ef553664db1e476b1294c3900c060219ffff037d41bbf4b13fa70838409c4730370f6d758580cad57a84a2e70a3b293959805b033dbfdb3f8c910f657e3842c1572004a8c27645de87d67a4546bc9d627ccd079603b73b9136efd9b214d7273738ce8e47ff73490ebc8f5e781b0d2c799c3a0f64c9033ceae6fb2fb39225d3be1f6171efb911f4598725efb952ab08d304dea121e14003cd9f727d584242a4a67dd2bd3368f9f4181855c2cd227475fc10bff01948356503ab77af421ad2d237715766947479e19bfaaf0734a6c64f68dd4846c7d3cfa64d034e68c1099de2910c199ea73b57042f601287b66ec29a68d556edee14601c32a303c2fb15dac72b853eba1ac2afd03d7de99db6097600f264ef8cb42bb87064c7bf0219ffff03ae72ee11061fd18790a03a54f046e273bf861dad63d9a8a72341f09b2fcfe1750326c892150c6bcc75c040f7772b6c6b5335411a530516b2e3a4bc556dc6ee0fa9030ea23e6b71bae4ab262177837ed3d9d54f8d111c14587ff8a4cc38e8631ffc37038acf9a4c95ae79f42775f2dd362f0a51f640b40e92f3402e7c162b3c20c01241038016d5786f51cfd02ca203f36389f83f1e9c551a36e6cd2bf2caf9f7e71bf937038f4567a77cff99626d05f8793482ee0d973c7f8d213420244710b4e70638bc7d031d669fd05a028d7b686956dd23115a125e7f3f7eeba3750f1bf309fe9515c19303ec69863dfc84d9a61ee5ce2040e078b46096927815d9b26d6a8c79df062c74a503ad65c2085d87c1de63c1b67b9fc9f67d7bd5e46bd94c388fe8dc85d00bff57c00377eedb3bdb67415acd542cde19c9d40094635be6ab36140adeab42e96b92cfb80219ffff03d1dca78a8a141a285a6bdf05d94f21e002557d73e68b191e11f7cd311f3ede2a031acac9d38ca38ff98da8d0f9a2a8789f968c4e379ad3ff7cd8bb5b5f9da385650387ecff14def0afa74183e00da5efda82df545c98b7e859481aef8f24a2cdc8220343625f41f7af9f1037d70e983a3b8d0db1adf239660b16c3d73e730a4f634d0203f6a481afa4faab91d104cdec398b537be074a99936e5a3491542f42bb141e10803cb3437e61f8ec2cfd51f006df4f9f2fce562b234ace6a91cf2b9835ff172be850352e759d1c87ae5e9391fefc457ed50d3814da8e96d60998afbebe028b8aba2e403bc4e03f127d3dbeca9755bf0b0e5314d9bde3db19a70fceb0debe574cd5dec2503cb08fc461bd03abb1fe6df856afb879627316c3007f98dc20e8f12f5d3122cd003b233519fbb55d2567ec4d592e2ffe047ac13f38b495ca383c356d5edbb8063690356f8eb2f637eb2b6da519d6ea20a5b3183005fa5d08c0b7572a9425fa6d78845038cf30f717edd9258c60cb2ed43e6c814649e63819111d0263f14722a4f871bc5030b98e0d643c86f511c800e28512adb9766b8b7ec83a1eb9f05063de4bae18019037e3b21f100b7d02806a88460dce0a02a1aede13252b273bdb2521a4e898200b203f2ee256b3e1dc4a7c7596e54ebf4a0eb8c2a85feab0603a2577db8d3abf074c3036443af6e39f39a0d6baf98a77c15bc9d2cad412dcc8ef690a32fb3aea772bed9034b026b8214597b35919c7f9bd9780d32fd2daa2d558fbc69adf027c74c9fd068033e0cbcc44291723f9a212e32fa81f7a09908d83c20eefd3c58aa4edeb84658d8033269463fca63ebe488bf9a87a1db205958c54cfbf43c24d9503311ee0fb53d4b0392890d961f6c0ca76151b99a3aa4bed0ac198ccfe387380daed62bc854cc0ed103cae85bceefac339e981320952d8e1854f73a931df8837c8b3e1865923e936532032a5ea55e51c8ff67c6a3f13c521ad8d267f4dd0cab13ab2789ef223df2a4d55005581d0248c6c547ec55378e2c0a61680666e229104d5132fdf9ee31729e64020c014705b19fa46bd50005581d022a37715299ed85873b9ceaa8a2578309efe1ff9d3d4474247d96e7e2084801ff973cafa800000219024003d6f4261e3eb772dbc22561849021fbc514fa158bdcf3294d8ccfd5e00bcf520503f9073dc0c6318b7a3d4139205bcf2dfc19e52d92f786eb0b7daf5150a74dd725021922990353d71e82ce260a30018c76a6c9a39a8e6c5c37c78dbbe246c4af86c5bf9b6ce6033383bc0b2d03bcb5906a7f396ee41a2a5c36507139118801a24359015558cafb03b8a5aa6fd604059e03516b4e1e0cc6f4fc2240893e74d3a8966043bd8961a164031bb390335989c555bfc3182e0476da76d59a5175fba90e61f4d2bdf614c8a6e703c11220ecf272dbd7ed4459cb7674ce12265841898b5fbe2016a8457cb34c326c03c5cc4dec75d39dfc41755fe91a224265d9e5a22d75f99eeea6a8e9c85468e86903c26a8ee1eeb84c898a479372826a7643d2da4dbda3aec9641465611d590039a203e7753a5102330e4bd9c397090d2cba9105d869ac7ca7362de91bfd8250ceb14103d743b3c0c2aac773abe7e6ba4d1e3f4cab1cfe7db51d8e1e3272c57071f4304703b4adc0b2abdcbab1ed7259159d8dba7404bb90b1ec7167f6f2d4e43507314419036efa33540d8af22ada129bd6e19c8566075088a3aa6e613a4ed39378eaf842e403a791e80119ee3297e129e171432558f5cd5dce6cdb079d123646a012d7aa7b59037d3c36e13194d7e8e3242ae072796a3092458f296a90f3424dc469f932c3b92c03293059d7f335b0215c61193acb55440962ccfc8a44178ea82c4ba8fcc7c082b80219ffff03215151cadcfa8461c6ffb721016f5e1841b26bd20093c2285667ee1a2c835a7003e2f94aa7aada4eac688ab1468a0e76028ebd508f0c5ab7f7fa208c1900b9ccc70308c5a4259db46efdf4f4c5e5881fc5f183d1375aa744815dd56e66675d84133803e7134df08bdf9dae09e63915e3e7a3829c48ddf5868e2aff91249b14e66c5374039685124197604d3b54ac35482805264b69dc564bffa18c9ba6ea1c78d4019dd80319f4c967aca3a82efad1f8b56b53e108ff25374eae7600738e9c037464c8b316037356b0e8a7a3026197d2d03f11ee324f39d2eb3d4f2b3927fedc7b74293bef3f038f9ec361d5b1a6e87ceebd85118772733e207c8f012c8d73bc979acbf587dba603983d177bab22d5da014ab752200b7143ec636c96a92c26db7c51c3f0e1d29c6b03196af95e9d505162163089eecfb5f79da1d3afb9b70ea9ec66e2febe4b037feb032c3ff131a66d8aaae1b63e9222c53f51b1463f004bd1a13c730f889aaff8c964038e0cb7d3461eecacadb4fdaafc6adc39aa323dfde36eb857db249943990467b5036ae293a1b476ad4710c1e3b322ba6fa130111f8292a7578372c2d0895b8962c803970b99093937dbdb65b1bbfcec734f02993474eda5944f37ffd3c17f7f66e6be0219ffff03c416d48e7f3140778c6721437cff0075abcb18241c7a39e2c2c8e48290b9ca920379c2eb811d777888ec28c959be06d36f3865c4d2911b2b7fb3f7489d0c0698790362e0c01a7c9ac342ef5452e5ffb086067b26b0c1f26bbf8c4a827e94632f64c9036a57017047137b45f8170bcbd25ec0c37a62eda63e203342c36277d15a07fa6003268cc63bc3aa8f9ae41f088a286dde6bad38b9fd4355730c48dd0af45f905e24034928871f92cf6bcbf7a57728558549b2c21b79c6108d7961b1e9845e6a3c8f4003f6c0a1eec7ccd3df5cf1d836c60c8bd98271796a5385f1eed73822b2fca6a9140361d754ebd451cb85f21c738fb583c375b2e973568464354c846f82b342a8d8dc030356de2f217e8d3f5eb624cac231b69f64780159d74cc15ed19deaab698078c403ef9f6af60f28590fd0d268dbc470a085ee5918851f43a6184fce09a1b77921e70219ffff03b1d87303aaed9bb71bceb48c31cd23010095a95c4911a68fec063ae654c781d60307d0ee4fe250030e124f0b2e0b445daca1b533cfc64f7df3af8cdfb515d9712803d5bf153ad911fc391f4603d4d1cb07cc7f4eb94a9c355c1be5b41e25112e76de0381b723b8407bd8f11bdee313379a9affc5abbd4bd797bfe2100f96e098558bb1035b8509dd3007587a15cb92ae6cadfe994c3788b7b715b71a717cb6afe26bac3503c9170c41846c880e6b215223c6c88eb214df88c646c5b46cbf1981e2f481a4ea03a879d359b8c140ada36ae63352f961b9a70656e89cca7b1e6b5521ee2f5351710219ffff039fd6b523cb251d7a170bd581ad2e9e258e3e62160ca3badacfab6434ee18798f03466346b08dc08b429818c54672c4323d1c9d157a4f519b76b6d0f955eb08c86b037a22764d35894484876ba73ca1e6c2d03bb86ca89b34397e227cd6a3a8798fe2038cb78be7dd175e1ca67c2ba8e025f660e1e99ecb1b7be03fa1aef4523fe9c9dc03656e6cb353651309affab0995d8dfec147e346d0cf13d7abb9391aeec38b6307033ed40d526fec01ca7bd213ccd7d8a62055831a04770726711e8dddfb880d86d803f2b8056eef9243343e36c8d82f36b27646da99817f5d5e82ff31d58b9bf0be350219ffff03df3592210cdaad452cc40f0e243d920afbb033fcd9490f2f6cc926dea05cf14b037816658c639149ab3eebb38f6ac22cf38752b7e4c6ddde32a6c3246707d80fd903f5ac862b401525c3cfdad2a29ce5294bbddd3ed338b6626fc8f9c44fb1cf8d90034be77e83d32bf9f4fe2880b66ad8908983dfc1dec078d9e31d227d786b8cf148039f0e8639747e8ab71adc53e8b07f01206b5900c0272eae6594d37c750768abea03c390f1e47a610cebad6c27e089e33465b8fccbf3ad3b3b7e2a07457d8ba632c303cdd9b0d1509c100dbb97d416ec662e3077cddbd5c47aef42ba982f7c0cd4769703c4243f4af1fbfd6a87252ccd6ffc33385970802e52996d723483078a268f13a803a1ecedf8165b6b65ed1b9994f5d3c514620fb494dc86171e6194e33465eb87cc03088ec9d7bf047917fdc5049955ba7851e13907e685f43c2df16e24591c9dde5a03ed094da69c124333f8345d6a2d61fd3009f13d18af68fafe7b821221dc796e5c0337a03266a9bdc253aaeaba6b66a4a347f18829c6a23b248db62eb21389bae960039fb3a610d2830303b2fe3efc4c420bb5d26dfb8f99f1ed9ab0df274e2ce53a5e038b0e0c639fcf93b1ef48bc870cf1c2db6dc710a364386b61e55e29ccda4d584503e41f647481abd0f1ddcf9362df2eaa21515cc62a98184a3e7e165189b69b9d3d034ca3128be96a17ef0e09f3d1a4df42be6cd707c193b33d55133225873d9426840355ff618203c69889c9bac81fe7c29259b911a8bfab94d550893576bd8eba8a97032cf9f0cf6d3a8d5299973d08bc171ff6c5374b9549019a33b69f1ba8224fb9cd03b0dedf0a2c83674687d5d90a1f5f176104ff7036cd01ff5c0125d954c047f974035ae9ad796ba4067225e90374539826e4e4388c926dedf59477cc5eed7dc593490316ced7b33a98ee1c651fa2fcbe1a0374604f7c909b84f062b589aec7f44f11d703f2867144ee588e4d55ea4761c23e21c783ee2cc00c68975aefd48df592f2b8da03f9164129351a46f77d025bc0509050e9eea8cd308710920658ca1f3063b1cf6803052017d82fb6f8a4d85a9cdd48f3f0fed849d1cd1aeba0fc2884557428a4ede403ee4b6adbf3362ff64d07641f516d8ec746b044f977bb448911c4e0af555365e503cff85de233b523d9d1457c82daa68ba239ab77f439892acd0a2068bf97ebdbff0385d0525a0a0034c5bd08935cc25dd3f51edca9ebb6fccd04f02db7502705cdbc05581e03e46afb6f2a29b471fb70f204efc85a293e3aafe620b6d7184495207d900401037e5088d9117486b2f086a8ece3ba2d74100ca7b7abf188cb800645b99b0b36de03546360b87e0fdc3a10341394d32f120453ae1f8f3d21372cca6361cef94f473305581e03be7b9938054f9dbc0e1be10d1cb4775560fc4652f94cec4c823790c2100402038bc989e7c0e91f054a8fb35ab7646b78f1dc692e67dee5cbf05a5e390a5b96eb031355d7d9562a49fdb90376af866d3927f7b97c55954ccf4909fe488a99284aef05581e03ea58b11a2d9b9076492a1cf0a1f28ec2abc3ad5597945d915728e2485007011bffffffffffffffff05581e0352bc53f1d7c8c3f1070c02f6f0313e53ab53b786b0dfc5fa833a09db20040105581e03ad08a72a553101e869eaa555d3ee8dc641c102d9db971a2c88791db4e00c05470178bdc1aac140045917fc608060405260043610610113575f3560e01c8063751039fc1161009d578063a9059cbb11610062578063a9059cbb146102ee578063bf474bed1461030d578063c876d0b914610322578063c9567bf91461033b578063dd62ed3e1461034f575f80fd5b8063751039fc1461025e5780637d1db4a5146102725780638da5cb5b146102875780638f9a55c0146102ad57806395d89b41146102c2575f80fd5b806323b872dd116100e357806323b872dd146101c6578063313ce567146101e557806351bc3c851461020057806370a0823114610216578063715018a61461024a575f80fd5b806306fdde031461011e578063095ea7b3146101605780630faee56f1461018f57806318160ddd146101b2575f80fd5b3661011a57005b5f80fd5b348015610129575f80fd5b506040805180820190915260088152675a7964696f20414960c01b60208201525b60405161015791906113ef565b60405180910390f35b34801561016b575f80fd5b5061017f61017a366004611451565b610393565b6040519015158152602001610157565b34801561019a575f80fd5b506101a460125481565b604051908152602001610157565b3480156101bd575f80fd5b506101a46103a9565b3480156101d1575f80fd5b5061017f6101e036600461147b565b6103c9565b3480156101f0575f80fd5b5060405160098152602001610157565b34801561020b575f80fd5b50610214610430565b005b348015610221575f80fd5b506101a46102303660046114b9565b6001600160a01b03165f9081526001602052604090205490565b348015610255575f80fd5b50610214610486565b348015610269575f80fd5b50610214610500565b34801561027d575f80fd5b506101a4600f5481565b348015610292575f80fd5b505f546040516001600160a01b039091168152602001610157565b3480156102b8575f80fd5b506101a460105481565b3480156102cd575f80fd5b506040805180820190915260048152635a44414960e01b602082015261014a565b3480156102f9575f80fd5b5061017f610308366004611451565b6105bb565b348015610318575f80fd5b506101a460115481565b34801561032d575f80fd5b5060065461017f9060ff1681565b348015610346575f80fd5b506102146105c7565b34801561035a575f80fd5b506101a46103693660046114d4565b6001600160a01b039182165f90815260026020908152604080832093909416825291909152205490565b5f61039f338484610970565b5060015b92915050565b5f6103b66009600a6115ff565b6103c4906305f5e10061160d565b905090565b5f6103d5848484610a93565b61042684336104218560405180606001604052806028815260200161179f602891396001600160a01b038a165f9081526002602090815260408083203384529091529020549190611067565b610970565b5060019392505050565b60065461010090046001600160a01b0316336001600160a01b031614610454575f80fd5b305f908152600160205260409020548015610472576104728161109f565b478015610482576104828161120f565b5050565b5f546001600160a01b031633146104b85760405162461bcd60e51b81526004016104af90611624565b60405180910390fd5b5f80546040516001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a35f80546001600160a01b0319169055565b5f546001600160a01b031633146105295760405162461bcd60e51b81526004016104af90611624565b6105356009600a6115ff565b610543906305f5e10061160d565b600f556105526009600a6115ff565b610560906305f5e10061160d565b6010556006805460ff191690557f947f344d56e1e8c70dc492fb94c4ddddd490c016aab685f5e7e47b2e85cb44cf61059a6009600a6115ff565b6105a8906305f5e10061160d565b60405190815260200160405180910390a1565b5f61039f338484610a93565b5f546001600160a01b031633146105f05760405162461bcd60e51b81526004016104af90611624565b601454600160a01b900460ff161561064a5760405162461bcd60e51b815260206004820152601760248201527f74726164696e6720697320616c7265616479206f70656e00000000000000000060448201526064016104af565b601380546001600160a01b031916737a250d5630b4cf539739df2c5dacb4c659f2488d9081179091556106939030906106856009600a6115ff565b610421906305f5e10061160d565b60135f9054906101000a90046001600160a01b03166001600160a01b031663c45a01556040518163ffffffff1660e01b8152600401602060405180830381865afa1580156106e3573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107079190611659565b6001600160a01b031663c9c653963060135f9054906101000a90046001600160a01b03166001600160a01b031663ad5c46486040518163ffffffff1660e01b8152600401602060405180830381865afa158015610766573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061078a9190611659565b6040516001600160e01b031960e085901b1681526001600160a01b039283166004820152911660248201526044016020604051808303815f875af11580156107d4573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107f89190611659565b601480546001600160a01b039283166001600160a01b03199091161790556013541663f305d719473061083f816001600160a01b03165f9081526001602052604090205490565b5f806108525f546001600160a01b031690565b60405160e088901b6001600160e01b03191681526001600160a01b03958616600482015260248101949094526044840192909252606483015290911660848201524260a482015260c40160606040518083038185885af11580156108b8573d5f803e3d5ffd5b50505050506040513d601f19601f820116820180604052508101906108dd9190611674565b505060145460135460405163095ea7b360e01b81526001600160a01b0391821660048201525f1960248201529116915063095ea7b3906044016020604051808303815f875af1158015610932573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610956919061169f565b506014805462ff00ff60a01b19166201000160a01b179055565b6001600160a01b0383166109d25760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b60648201526084016104af565b6001600160a01b038216610a335760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b60648201526084016104af565b6001600160a01b038381165f8181526002602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b6001600160a01b038316610af75760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b60648201526084016104af565b6001600160a01b038216610b595760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b60648201526084016104af565b5f8111610bba5760405162461bcd60e51b815260206004820152602960248201527f5472616e7366657220616d6f756e74206d7573742062652067726561746572206044820152687468616e207a65726f60b81b60648201526084016104af565b5f80546001600160a01b03858116911614801590610be557505f546001600160a01b03848116911614155b15610f2a57610c166064610c10600b54600e5411610c0557600754610c09565b6009545b859061124a565b906112cf565b60065490915060ff1615610cfc576013546001600160a01b03848116911614801590610c5057506014546001600160a01b03848116911614155b15610cfc57325f908152600560205260409020544311610cea5760405162461bcd60e51b815260206004820152604960248201527f5f7472616e736665723a3a205472616e736665722044656c617920656e61626c60448201527f65642e20204f6e6c79206f6e652070757263686173652070657220626c6f636b6064820152681030b63637bbb2b21760b91b608482015260a4016104af565b325f9081526005602052604090204390555b6014546001600160a01b038581169116148015610d2757506013546001600160a01b03848116911614155b8015610d4b57506001600160a01b0383165f9081526003602052604090205460ff16155b15610e3157600f54821115610da25760405162461bcd60e51b815260206004820152601960248201527f4578636565647320746865205f6d61785478416d6f756e742e0000000000000060448201526064016104af565b60105482610dc4856001600160a01b03165f9081526001602052604090205490565b610dce91906116be565b1115610e1c5760405162461bcd60e51b815260206004820152601a60248201527f4578636565647320746865206d617857616c6c657453697a652e00000000000060448201526064016104af565b600e8054905f610e2b836116d1565b91905055505b6014546001600160a01b038481169116148015610e5757506001600160a01b0384163014155b15610e8457610e816064610c10600c54600e5411610e7757600854610c09565b600a54859061124a565b90505b305f90815260016020526040902054601454600160a81b900460ff16158015610eba57506014546001600160a01b038581169116145b8015610ecf5750601454600160b01b900460ff165b8015610edc575060115481115b8015610eeb5750600d54600e54115b15610f2857610f0d610f0884610f0384601254611310565b611310565b61109f565b4766b1a2bc2ec50000811115610f2657610f264761120f565b505b505b8015610fa257305f90815260016020526040902054610f499082611324565b305f81815260016020526040908190209290925590516001600160a01b038616907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90610f999085815260200190565b60405180910390a35b6001600160a01b0384165f90815260016020526040902054610fc49083611382565b6001600160a01b0385165f90815260016020526040902055611007610fe98383611382565b6001600160a01b0385165f9081526001602052604090205490611324565b6001600160a01b038085165f8181526001602052604090209290925585167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef6110508585611382565b60405190815260200160405180910390a350505050565b5f818484111561108a5760405162461bcd60e51b81526004016104af91906113ef565b505f61109684866116e9565b95945050505050565b6014805460ff60a81b1916600160a81b1790556040805160028082526060820183525f9260208301908036833701905050905030815f815181106110e5576110e56116fc565b6001600160a01b03928316602091820292909201810191909152601354604080516315ab88c960e31b81529051919093169263ad5c46489260048083019391928290030181865afa15801561113c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111609190611659565b81600181518110611173576111736116fc565b6001600160a01b0392831660209182029290920101526013546111999130911684610970565b60135460405163791ac94760e01b81526001600160a01b039091169063791ac947906111d19085905f90869030904290600401611710565b5f604051808303815f87803b1580156111e8575f80fd5b505af11580156111fa573d5f803e3d5ffd5b50506014805460ff60a81b1916905550505050565b6006546040516101009091046001600160a01b0316906108fc8315029083905f818181858888f19350505050158015610482573d5f803e3d5ffd5b5f825f0361125957505f6103a3565b5f611264838561160d565b905082611271858361177f565b146112c85760405162461bcd60e51b815260206004820152602160248201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6044820152607760f81b60648201526084016104af565b9392505050565b5f6112c883836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f0000000000008152506113c3565b5f81831161131e57826112c8565b50919050565b5f8061133083856116be565b9050838110156112c85760405162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f77000000000060448201526064016104af565b5f6112c883836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250611067565b5f81836113e35760405162461bcd60e51b81526004016104af91906113ef565b505f611096848661177f565b5f6020808352835180828501525f5b8181101561141a578581018301518582016040015282016113fe565b505f604082860101526040601f19601f8301168501019250505092915050565b6001600160a01b038116811461144e575f80fd5b50565b5f8060408385031215611462575f80fd5b823561146d8161143a565b946020939093013593505050565b5f805f6060848603121561148d575f80fd5b83356114988161143a565b925060208401356114a88161143a565b929592945050506040919091013590565b5f602082840312156114c9575f80fd5b81356112c88161143a565b5f80604083850312156114e5575f80fd5b82356114f08161143a565b915060208301356115008161143a565b809150509250929050565b634e487b7160e01b5f52601160045260245ffd5b600181815b8085111561155957815f190482111561153f5761153f61150b565b8085161561154c57918102915b93841c9390800290611524565b509250929050565b5f8261156f575060016103a3565b8161157b57505f6103a3565b8160018114611591576002811461159b576115b7565b60019150506103a3565b60ff8411156115ac576115ac61150b565b50506001821b6103a3565b5060208310610133831016604e8410600b84101617156115da575081810a6103a3565b6115e4838361151f565b805f19048211156115f7576115f761150b565b029392505050565b5f6112c860ff841683611561565b80820281158282048414176103a3576103a361150b565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b5f60208284031215611669575f80fd5b81516112c88161143a565b5f805f60608486031215611686575f80fd5b8351925060208401519150604084015190509250925092565b5f602082840312156116af575f80fd5b815180151581146112c8575f80fd5b808201808211156103a3576103a361150b565b5f600182016116e2576116e261150b565b5060010190565b818103818111156103a3576103a361150b565b634e487b7160e01b5f52603260045260245ffd5b5f60a082018783526020878185015260a0604085015281875180845260c08601915082890193505f5b8181101561175e5784516001600160a01b031683529383019391830191600101611739565b50506001600160a01b03969096166060850152505050608001529392505050565b5f8261179957634e487b7160e01b5f52601260045260245ffd5b50049056fe45524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e6365a2646970667358221220fabb245980ce7b674f8c8938e6a9547420e1985f631d17befd4e60b6ad4ef03964736f6c63430008140033032e9a1bdb7719f32ebe6e8748056096c302e0117ebc7d8a695d3c69ec190c94a4005820033deaaae292505f99da7730d679d66dd76ee315a572765c6b28477acbef84205820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581f02581cebdbe182d4ece6688321f0b6da020dff6b0c685984e2b533880f40895820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581f02b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db94123021830005820037296de936eb2802bae692c3ad184fb80ac4733c4763fb05c067aa104af4a104701a0ab99c148670219018400582003643924d70380e8c3b27dbb7ab991599d58393812896fce01b82ea17b0358505820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00582003b584627eef1edb923dd3a5730dcce1f8a65d4b0a3f0f498764366473bd3dd0460bf1d763426a00582003dc1e32d4b868a9e50e514d63a1a3eca30cca8aca615c5fcb6e76aff3941b104701c0467370689b02190121032eb736af20c66853b0524fa3afed1b5087d4db0a1f8dadeafaa35a66527298ce03012b76fc2abd9f42db1bfc99f3b77279886f78830f4f07189ff0f2e6b7b64509035df46df06f8f5bff1a2799ec89f3d27c5046d439c4571b046701e0ce90f8e52e03a5d1e37bc4c238dbddf3583b32bb2bcedca6da92d63419e564218330c41a9d8503136b9f41d0a75818fce9eee2f181fa25f7637677ad1b6f3607f2e0e70fe728f703dc5492c96e23328e3c7c6f01c8df680731771f94e16cc90f5a2526a9010bd241005820020ff2aeb056ea80f584f0f596a56da0db1c533b5770e5f58b5d85a81872d3214711c37937e08000034ba899bcb365ee327c8ff95b40db4c5b0554d304e76cc3f0cfeb8171f0acc15103b56094dc76eebc806678d38a7d72e95c1a316b34199cb574a00e003b29d39f0d005820033a29332cc87cee6bb85ec6c4d48fa62a05e0e689d38915df8e8711fec6f6d04701d57eaddeea7903e9d858e542f01f0cc9a78fe3c7d155ba2eb1ab3bdfa228febe0cbf35f9d236000058200345f7402f34e0165cc412d391d9a6f3650cc8a9ecca726e8325318ffc9b4a205820fffffffffffffffffffffffffffffffffffffffffffffffffffdde4d9d227fff005820033a52a2e3bf74152f03427aaa45125fa9d276601ae28f16b4f256e23e4411f05820fffffffffffffffffffffffffffffffffffffffffffffffffffdaed140fed6c900582003008b69b457c61a29303e67f4eb817a578dfb867f0eb8e306bce681219d22405820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005820030c54fed9cbfaf5d6f13c3cd9aadd836c032fae0b7297b466d50befccb91aa05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0219447103a6d4faca4f3fa2cb2c42ba6b209e87b2dcce1ede3b2020f8ec09364e73bf092003c5302a75aea5ed51be684447bafaf15e1bbd6b68e84e4eeb939c45c5da08e6550219bfff03fcc803681c88970cb11e9491d7e4704b0d7d7aa4f30edd837018edb9e8b7974a00582002cb45a65ac413176a5293c5dcc90cf019a7e925c7b4dfea6fa65bd0ffd552a2464b6d0797f9fd037ab0c4b1c17c34977d9af54350635a3f1a3fa0ff837d6d211d24a7ab6120e2d303d881e51d2a3eb57f1d64063521cd848ef9090eb55d20f6031751ffcf26dfd9f5005820021ba19dc1dd14b9e06c8781534275493317db4006d593ec78d3893979430ad35820ffffffffffffffffffffffffffffffffffffffffffffffffffffc914e0f9a02d033f4987be9456d56871914d690843fb7e31e165290b44ef1737612c9f5e63c0e603f25d24915b46dfa25f6cd54056caf222533d999c5e1103feb1fc60ee6ac5af1603fff9818874c16d20b922dc8e9d94bfd5e4f0ec644571582a901c30f5b1d812b5035708bcff448dba53f838fc064ffe210269d0db44b8e57acdcd7d31afad4a664e035cfccc1947fe9ca4d21a85d8aa92601d7478432dbce2f2623097d5692dbb235d00582003dd580b09fc78b641fed62f71d95358bfc448b769be26f97308f55ac78f0d705820fffffffffffffffffffffffffffffffffffffffffffffffffffe82f2c4fc936600582003a449eb1a2da34c7e8b4afc3e53beabbf30b220bbd1ba3783194d707ce7c3b044012e343702188803fa06a150af961eb306023fe56cebfb3cbdd83faa0dfa374bc07b895dc4f8b93603d063c607b5ca7772092eccb711cbecb999f6280ed84dd14d31b1fe84e5d9ff590359ecf023d1f4253e2a66415f209d7c6a67c6327d03490806aff85ee070d1968d030663d512bb073f73fa945cb7ed99c268e607c2a83afed42cfaaa60793fbf197803bdc8374b941fc71ad48960781eb2ff806ec903c0b307fc21290a269e65c88e0403eb57fbd9e4894fe8eee12f15e7e6a7dd7a4b11cbac681aa07428762a641b6cc90219ffff03adb353379cf9f856bf0cdd9a8444c5559651e9a9568ea91de2607fb1b841972600582003b2a13ac4575f81a191570ca0fd326662aa0c579a23ef07ae9ad92d5a14f2305820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00582003cc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c68046b5e620f4800002194040031621d5683860ce7af2b9a8f0db55ae1004926a3ddeac35f8179b9cb08b4b5ad500582002fdc970a2c954cbd9cac4b3dee87d3d5e28dfc9aa628581ce61b29abd6a13535820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00582002a7ba5548113732fc6857f152ceb343dff7a56b40eb54168639003b931e6aee5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005820020afa2884108caacaa604f3a4b1c026c537454cd211d5e6e196134963a61a48465343947ae07603f3669d5a9877b0f0e2f6f472f8fed2a84efbf6b50be27a8089a0ee9d533ee417037c23c104e325371f5295886d89d86ff5537636b1a384b903c735b5421da912930058200214845ccd09051265cabfa666bbb0983955c377677c980b566c05854ebda2b546d08b1108e3c20396d127107e2d18df9282865aee758bce86dfedc6bbfc2e404466f36ff8eb4ebe033bcdc1e6636d1e90ce729e75fbc776c1296bfcb6de57e755fe58167dcb5da67203a4af2c85e40dd23e183a0fd1b906db0bbbf698eed40aa98f0da677426e01ad3203060640284caa2af263844bddc8d8bae0c7da607530e33883345c2ee2b26cd6a903f8aea964a15ce2ff22be121be942df099b810503fb7e906d3d912c873e38048200582002250a8d3e0beca5a210e28513b383dfdaba031209252ba1abb8c2f45982e37344012e3435038507f95e571f1c5a21e5eb4ad1ffba56544b6e854b304c0b603f58830685110b0219ffff03a9fe5356b92c4f2f75402dc5efdb22611131143ca8c0ed55b7fec4f3d65f286603bc768027381098e1aa78bd68ced7a5a19901967411e1f37861337cd1989dde930308860eb562c7d32cd98b4806d4a838c8fb728ecb760cee5c4e38396f08ca9da7037c09ec3fdbca73eba19660211c5e4f054c0855631c485c2cce9335cd3e272acd03900e5f5c1bbb1a833fdf50a4cdb694a76bfd9336a7e9f4b35523022c762e41ed0328c84670fbca73ea7a4a082e89242cb4a572c7ebb817170074df470fe56f619703a80c916a5497f6e34928087211b925696d3929e8a3d5b1c4344d1a85e0b69e8e032198b566d6dc30d6376c408249bdcfd9637d3486cca59986434bbf66378f082600582002de8ffda797e3de9c05e8fc57b3bf0ec28a930d40b0d285d93c06501cf6a090547a250d5630b4cf539739df2c5dacb4c659f2488d032f9b96255409c98217aa93e564f16b8826f00ecdfbeeb646d7bb75d1ad339e8603b3031686b66e62da21f1ae8918b9c6d56ebe9867730fe92e3ebd2c8a7f9e0cc6005820025905e5d91b29ae58ab1639725131b91ac3292e8ea575a2748b4812b4e41f6147018bf0371c2c5903e15aa9b31f0606afb3d8156a855e5677e54a94d43da4fecabb7815d6a0633d60036bb3a6401d2b8110a788fde6505ba6309f1bdf1629f1324799e32d627d616e6c014102035076d3d2724e2a05c0b79cb5d2b587f062af5dc4853edec5845cbd23ab3fa7c800582003206663ab66597cb8c43f3d5842f783638bf03eefb1da8e601574d37e747830470581b77f66e00000582003e3fdade540c47b00ad7d42b0eb73b082bbd06c915f74e4b0afa5d223837f3047011e4b76c252fa00582003865b01e3623aef98ed3b8e168ecd858969fe6677fa05fda65f424b5129d71046581d0f000e4100582003672453513140c71c3ec7e580e55beeef49b4fcd93598d330b62e50783af180472abfbcea99faf900582003209fce85f2387e995f88614873e4912fe3614ed7d6edafe01b3193a18b8370460c1c55657d43021985c0005820021540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af410503f7fbe063205d32427c30579f83642876def3afd2b894377ab22e23683488afca0219ffff03fdb2f8dbe707222b891f6ba7647c81ba2de38018a8ef28ecaf0787d39d111d3403aefd71484378f910465fe3052755e494e480812dd3580f67ee4c509bcf3e4c170058200219031e838b32c426f108a641482e188d703f0f5bbbf0a7688b21794bea4cc25820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03366914fe7402daec0c4d451e3ee126bd192fc7c3e3b751244325b1e60fa466070058200239cd439f121dac378d210574545cc78c432dc31550c7ccde2748533d40ff6344265e2262031dd7ff23121c7d58df21c4046c6a49c2b18bd6908adc24e06b541ce5082c9a8f03fb3b03daf6042e4b3f9f650298559b349f3f6ed6438b1b585660a128f6a1dc3200582002496e4d7297e0f840d838f0719c8eb46740450f6270da99726357c7a43a983c5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0387cd7a86e266db7bb2067f637b78b5f2aa32deccd17bc05e936a2ef6cb889b57033b7c4063ba7591ee0a39e17bf281c700e924bfefd9596f44b9f4cee75901c8b6005820024fbdc3ac2e3df30aee772dbed6092f873eb66696ee517bf1bcecb2fbb4ff3c5820ffffffffffffffffffffffffffffffffffffffffffffffffffff2530ef3c9f0b033892562f1ffc0e79c782f4c6a5eb12cb15f70872add76048eda5eb5df08db0720308b601d9cdaee90591f15e8f0b7eeb33a9f96010c85c6d05fa307b33265f1a970314f4f0fd94243f77bf37bc15d478ade52f50a74fcd6a277e733782a9b5a96fe2005820039e4ced25866b3c5e41c122849ae9bf3471bf5be80e27d1877e54a8e39e6a105820fffffffffffffffffffffffffffffffffffffffffffffffffffb655998199eb700582003d398d6239c8afd00fb18e63dd471952b090293e8aceb6b98de1874c7f1a6b0462fdd6181d2f40058200372b8338b562eca1dd7279d5f07142e395ab0720d40d6e241d72afae4c9b650468af2abc28b160218d00058200273001a5bee02924ce048678a9d4b655de5969942503dd9a34a040b3f644ed14703d530ec238af7037d1c4eb68a2e90cfef5490eaf572d4c4fbe4b3e206bed26a27ed7cd906ae51960219ffdf03257455b0469086c626d6f0400d29480f8c9f365a4e83c08d2195e797148eb6910392829198c5e270a756c8ba4498d4b90cff5a43120fdfd9743858830cb2fae0d00303d45eff9f42682de3668969d3d58310153b9cb93349cbc388c1e522b65bdc91034121557e2af943782bb05c7f667cfc5c1891b1729376bc7a7b390247ac96492a0058200212914631d52cc0febbc8823c5a8220c9a21e0af51039bb0c7ec01dc88a712944012e34360365cad8be14ebe6e005f53c982fa429f998c59cc6db45d16cdd0c35e399b2544303c2778431b33b3bede19824c87a1d113e13cccd4fd4e9b98fc69de9b2dca2ada903265a704ee1b4211db47d70f355439ffd25948b54b170537e8a3111de05c15dc700582002acae78e0fe171b7d0d1168ce676329146cb1e11586f9dad3d0d649a05e4bf4464035f815892e00582002f7504ecf3a0de04ff6df3ace726e18ec5a6d0c0f148d7d55a973a060f4e745466dfde0631b9b0058200256ca619e7c1eff29c8f1304e1d85e86187eabd3cdd5796d382b8541e0e24bc44012e343300582003b4a454dc3493923482f07822329ed19e8244eff582cc204f8554c3620c3fd04201b203221cc7a6fa87d30414e9f1509d92d849662a96f26734c126aba3e4964ff6877a01410a005820034aa1ce9fdc12d538db10f82332d81a95772e2cfd4ef76aba162630ac3c6d405820ffffffffffffffffffffffffffffffffffffffffffffffffffff5b73a6e5e02700582003d03c7d82aa7241953d458d631c40d5253fe671a336906c75fa69246fdce0e05820ffffffffffffffffffffffffffffffffffffffffffffffffffffac745826451a02191980030d4f1391475dad081bf604cdd83e461e24f26acfe274356bb687b8ebd5c29655037b996d3d81931c7f48b149f26bac11b515b23dc4a97538220c2368d6d8e9eecf03b5c7d42851596d007fadf8b31301f1e099798956505b6d60ad00fa4b14c9b2cc037d6cd29a70918eb5b02b46595ebe64d7ef4614b5cd03dffc4c360876884dcee70219feff036336f442763e83265b7062cc92d87f0c1f87738aa0139099f2cb6b831be6f620039134dafefdeaccf652f477e8bcd0a4f91e2f3156ab9b2fd1a7859c324abe490300582002c20db3f959450f741ba3ef20cfb24d16cae2ac1f1f90f7a0636c1a4debed6d4701fdba51dffa96039dcbe52cc885ce8cdf8eee3fd54bc70d14bd9b88c148251abd5fe8ebf0f09f430392783a56c4de1de6354e84436f796e5f9a037030bf404e9634040af083de3b7303580ccaae9b9c735c666bae0c0d19049bd6e9b8a6cc8fdb39dd26d0ee369712fe00582003a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a80410500582003b25a6a9cf8d8c642adc8dd2ab93a86967be708f854f059cf110638072594705820fffffffffffffffffffffffffffffffffffffffffffffffffffe09347e8dff74005820030a55596ebd9f3fdb116c4264d608b2af816628d6224c3cbb8173c5ab0b318044012e343500582003bfa2c111124164089039b1eb1ff7fe379cd061d62e1ffb566ae8fa8597dd405820ffffffffffffffffffffffffffffffffffffffffffffffffffffc347b7f89fa3021984600304968147ddd214db1f19dc5ecf5e83716c60c0b5d17ecd22fe309d46d574c53703cb6dc1811dd11a8b27a8ae8683265e811019ad02e5af111ae3880d08484a03f000582002fa80cd85a4d732ac3163d3f32e62d965787158987a7101b078a3cfd0b084cb469eda25fff730034e5198a59bd9fc2a23c245f5f6699c8b3a561a36123f75900bc0c531bf30bc3003b477757b73f244c8b0c39ce5bea166eaf2bf148df7140de5f81ea16866e380de039616068f00f1d44ae45967d2cbabf9a9898dc639e3067d65e6a46344906ea050034011971aa62b633a531c695727fa8c692a7187d53ad8af0616bc0afd7d3a418100582003389a9561e9ffcf046a1652cd5c01c174469925e89428300cad875653a7e89046108bfa5e407c00582003d7b5282bd9a3661ae061feed1dbda4e52ab073b1f9285be6e155d9c38d4ec0570100014fadefe2a5eb1bf9e1d3dc4a9fc85daa5c5c660b02184403c1636b3cbf1e37fce574f5bc47c24de7cba2bdb8f1a21a6f2e41aeed3a2c52ef0219ffff03b8925cf8c50fccf360910ae843b0b44cee4ab7eaf6a1a89b8a81a053807b4b5703ce24de7aacb728d438ad90cc00ad8ed8c3c560083cab19c8d2365d770696ebb1031186939e76fa5b43e71347832fc419c30674c2ede04a9d46c7c73e983f243eac031fa29a280f6fa02b6d0bee57c81ef144f9e639c05c2580084f008e95e1a6d240034bf37a98e952c0561385ec982e32bda3a94ca8739b787c5e596de29e3b1a2db403d817b0955fc8ea4cdebece578c49818d1d8e502373177b1f93c6cf363b9696fe031f2bd62f2f00c8cc6becfe71fbf3712411dac644960ea13313d16dbbbf6e64a2033aa07a34f57a7b14a90ee9de1ece5b751bb1f7353cc726f19506cea4ca8f18b2039c986882bd3741668f81a81476d21842cdde382b25f2baca82405cf587d70e30037e96d58e373d56e37003c356f7635ddaaa7d4e56f2f34458ec667f33a2c0099303b2e459c16bfdf16efb4d6e4fc9032fb8128db368f3720efe6da4222b00d54e540335e0d78811fa83b549d510865498cc460eca2a95800c554f2cf71c62a1e5ed2f034335b010ae272034d47e002563c167f3de7470d7dd82a4a7850834c4260c03e4005820037dd6eb4d641ec1d44c19e430bec0dd23e5e58f2be599cc8b316ae74bcf38105820ffffffffffffffffffffffffffffffffffffffffffffffffffff31e7ff7f3d3200582003966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c704123005820037975c301132bbf6e020a379212eba432bf6560fe65766d638c3bcc243c1fb04649b4c4bafc02005820033185fabc38217e4634710db266b207250dfe969df5d2130e46069234a56c004701dfc0dc932cf9021980c802199fff0345c40afa684be0bd8dbad049f90ff7dd4c00fe9beef1ecb2291e179fb9cc13dd03cfcfe4e58998f8133049c961646417df57d839d7356740b0eb1263d0d050a1e103be2400afdc56fd48653c231ab8dc29fefd496a7d1fbdfa21d0f981eef7da3189038ebfa2d7ee6c69991c8d180e34b5f4b1777f92623146d3fbf83591be06d5113d032a5bdf557bf062f6b77732ed65ea93c5b80ace51dfeafafac7f569ca6d397bfe03e9db214ae3933c459daf61c9266736e9965e5711fd034a3266b98d42823571d103389a163a3d9b97d9f705b7e02421fe6feac9e068bc3a303ee7d24a7fe59fdce9005820032211b0ae7ca5920c14f1658fe62ef14d83454c71d6e21daeb2aed9203faf905820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005820032222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f055412a5a8444226757424297a7d16ffa40acb93f94000218210379849ace08bb8fa3b3e7d3832d75bf92b1bd9a1317cb8c9775a56daee8440c4e0058200278e4407bdaf57e0cba74af38c58720c02e249c3cd60c4c099b4221c83c46ac4624e153f5b1fa005820027fe42264939d94d0dc08e0e258744f0bc01416076bb44882467836fa6d01585820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03d3ddca332029b3fb4c9ad693b7a8321d61a68826757c131258a5178b6effc01f03530045d61eeff2f5b2b9ca40a08b25cb0e99240b5dc9d7a30e103cfa2b111d37033ea2b0e9102333c1910a24107f5cd12b9ffca5ec779eb693c05c2ec7957aa40103fa1c8b568be2bca61d6d4d04cc0f27321ad5ac6e092dadfc088c5168234bd16a0387c13dc687f0e79ba561eb553a9ed193dcd1fe627fd0ff2283215bf06dc4c6ee0219ff7f0219ffff05581e03378cb0c4994cc58a9bcd8e4bd8064923067bcd97f52e6e75e3842611c007011917fc033d20d3bee04ebbecf658cbe19b68cd00d29354f27ff62e636d93d3dafbbc145102197f320385558081cce36b998d546fdbf7814c28eee6728338ce923c7dd70381580d371a0370c453f427259f5d2224f63a9bd99b3752d4fe522236a5cad429df827252b88303879537ffb72241e607572a00032583f1dd3109c2a82b0153f00dfbba8f52ec2b0351454853043ef77794075256f9c73cc8adb43d604f532a4a3459c8b0c4f6287d03762a909098b76df8cc0752500301509bcd006e878eeca3fdbc4c6a6f26e16313034b87222ffdac15163f7c052c5ac47861e611443825fed92cb6f1fad6cb84952f038b9f458e66ddc629b539c33971919e1a95c31e70a55a61a4a0c9ffd1276cf8730374564e1926738bd9c29facd3cf32ea04ce8223f9d5f625d51e2037adc5c76b8303825adaa3d4c269a4fd335f19e8a3a930fef93513b8ef7d1d1a2b25c1b26a8c4903a89ec299173e383e89f566928b575ba18663c2775fe0d0ab03827402c1f383ef03f05c4ea9a3c73a2974e34bd6b1320759e7111698934c06b00651f9e37eb3883f03e27b5ed53e2e7ec1ce9047f33f0036021fee09f4ae1d8eee03e2588b270590bb0219ffff03e675888ff08f50c630a5023943646d3d1c161f0d7109ec12dd57c993b6e8445f03f42372b51a032c2464cf901bb52bdc4d71b9fe883f0ec0b869aa9e387b8c74f203495af9ca43728fdeeb2eff392d21bd9949342f5a331b5e6c603824872f9a0a9e0308eebd94d3d3fe8dbab143cecd02912ed4c6949549580c81762c8f60fb267f6a03d7ab61dc9d0ad33f423d773b20f9348c6d6f8de45451bbb86447abc90cf8ab8a038cbde9b440a9f485dcd1f25d3f63c5ed91ebb94db6b40cc4033561c4bc612dc803508333a96ce39918bbd89355bcd90acec561b00f3559e2343ccd955cc3ffa3bb039cf7e5366e72cd68f0ec2ebe6dab2e288408b364100baf3f3ec2001c73a5002b0219ffff03c5c577424167226cecc3845c15130024e42e3179d91cfa5013bf3a823ce971460347b52c07a9a38ecc633089f5b553bb121f1c278f4f3d7d9f64c728e3e3e9bba4038a982769c1ceb325d66069464bd90f3a70952479f2cff01866835653bfdd6b3d03137657762ea6e30b24136283f4def353cf3eab4cde75525f9d306e4d89a7390e0317a1acb1863ffc1e49b8d47eb42a2ea76b85e4ffca5d2754fccf6f6883b00b58034ce1f038ba274923dec642bf79ae6f33006f43ed337e9a5ee3615361673696280316c14754b1baf265246ce8f7cb2aacde9f71835a4bd2d6e3ee0d733b9c7db0bb03567562d4c59ff97a729c572f5bee860b951efee2b67094e32e3103cb8d7b229003ef41a5ace95f2ccef5a89455ac6dda09bd813c90ad6c615b39ed6319941812ad0325e99aa68daa10726cbd6067af2bf7b5e9524c8936e8419b3a6dd59289fbc8cc0308111add4075a207ad530b9d0f0b42a32e81b60a26862be516c32185c9d7ecd6033caa6d9afd7bfc9132d6448eba130bbffee2ee07019a24026fa8f0283c47f86f03762ceb54d4f4c1a4390eb285454cdf26038de58f51f3bb50b4da9b642f55f73c03db6aea606ba942e267bcad4a92273cded9121474e2cee5cc78e8da752e35a11d03dc1ba8221ef3c12fb63e6f4c075f0c2690f7e9d28a5dfc63e898a3708b7e7d550219ffff03ff25ea72f718a31d35abdbf3fe3f529659fab8c20aaec12188bef573d82ef3a0035b93e868b700d752865d11aa1068bb2a1b722979ef928e0da02e239dda4bc8f10379df07187e98f0f2a7fa137b22d993dd89d8d3edeea55a6cad140d088264df810317aa0471662d5096c90610a7665cef4306ef3b2c0a7de29e937f39f8ff9202970344a29ee083cec8776ab8d69b0483e3a8b39b3cc07c6b8f0677b98f62cac6685f03f629bf32e0a88893616d208f901082c7d6a2b72ae6b6a59294c492d187de662103a11d41cbd2a3ba4771b6a4773aaca1807d7c5f73ea71742c0b7c20ce01b5fbab031e67a536d9df078fb58e2b8ebbc65f642127981d9ccae39bc83a224792f9877a03903c196542be38f792ac875f610c0e50d92fc5dd73ea47f0be0e6c2b69b7737f03597140da1f550d077229c1760d617dfc5f577468913bbb78b0494ea906a8085b032429e0a7b8d96d8f50bd1677d987eec0dc1af4b8413612d3f7088ba94eda259603385a0f8c13dc2d429d874649d54eb239f185f4cff7fd2e9d4c964002d3a976770375d411576c429ead9912a647b813ea12a693986768a42187add5f57ae32074de03ed4549a40945e22c09cd6a5c7ba90ac6a15ecb29e65def3df9d6ffb07f73076d0219ffff0219ffff032e5d6c73efd9304aa21fbaa8ccdcede91625225e2f1d3fe88a3125a7a466f61d0388553627e5c52c776ac754df2194e0e93dca71d3db2903362852a17f6688c24c034e03f4ed69293a99a19aa45b7de904a2f12f59000113179669d6964e8157a3fc03922fa6614ec199efa1506d6664a2345438fd1cc54178e0b1d64529f12fae45080390d949d2730dbecacf2154cf727d74a5cbf67777c41466b85dad7a3fd583e9990326865b0dd79045bdf664832b466f3be30eb3385b2ea3502f773a492828a72e5803ed01b055043c85afcb9e4679d5d5f47c57cbf091413a78983994b99d7740564503fd9c93e462d82e10da65dd7f9957bfa9634dd63c3f0580ca3f1506c0308a3e6d0373cfce1c4f399a93e841ac2f6328182d667546b315c28565025cf56fed75f03d038abbde8aaa21d911b71c6be8c16ad39f94e5ef3880f2bf4fc43a1d357d678ed40344d82579e708c39068d4f8e52fca5c261625399335dfa97748c619f4bb45a43103d6093299f13c6d9d00cc08c4ea903739324d895edd8949a15ab0515473b64b3c03fbcd27553e56d94fe42b2a7bb02e035078acf809a3cbd139870d8f09ef00077703063395c3ca00b39a9a7385dcffb498d54431223fb88f1fbf9457a247800851e103f9d7e688d6290063a97025852821d8581d8885ccd94d4e5c873c1dd578a67805037131fc0383ad605143550d78b31217fbd5a78c79013635c6340bba34aeff09c703d84ba39ff6e6d7162539d15742bf366475a7017fab909acb568e2966af6ccd2a03ca9bedba41863ff70540e7a89ed5eebca8cc5d715ef45bb237379032c34c4638038c55d2fbccba96ecdcb8eededd7bd07ac269d29fc31a3060131f7ae084f9f97e0359e941664c53657d8aab3c80eb04c3e7828b453d7978037041ab7a9f63586d2e0396a16f23a797d660d8f3a4c94bddc24cb04d287ddcd4ea32ea73381ed0d8c5fc03f1e04e565562deca18675923b272991da7b6df6aa22b49b44370df052fd7abc6032d250932ff79139a4e11ccec180183c44893e46900b78dba09f2e9db34a2f340036c6f7a506b705841fde64d2953a89cf76947c82f564abcfc10f60ceee8515451034f5d1ed87445a6728ee1d1e10454052324871f7181ff1fb6dedd5f7838565463037ed094472862137d6f93b7c724b590007aac28d8a03dc57f277cc888e5c499d1031207815b8724fbebd84e275db707e9b44e47c267da45d4c8d2eafb54982c99dc035a14c99e6ae97e283ecd212831b5fe466fe92f310acb865da4da894115af0afb03d6d5eeea1e72cc5d81d6045d18e7f2912f5979b8a4724c3e5cb6c1bbea988b0603f33531467798b7788090d8110235625efcf33e45bfbb2783ce64f7412cba750f03ce71c3edbe186620a39b2c9427adcf647dd0c6eefc0ae0f25a1879f86be4de8a03cdd84fd36ff3c3052a6b2de8017dd05a571d815c0c37f2710322da8a0d51adc4035b886c7c31a01af81c3d9acba26396cf496686ab9c0334dc73ead5fc94719b940374e0f7f6115bdf47166cd51f67968e30f045a0d3a85a9cbb9f511f8e05c09536037aacf29e645c0213f44eb1deeba746964c0eba977d3f5888e856fe4cc8264665031076ba0aa98ac11158a2c2baa3277b63c21f99e9d0612abe228376bdf8c2ba2f05581e03c8cfd2a2195f31d0d5c3bdd473d8c3dfb34bc915c24ec04d84e84720600c014651dac207a00005581e030a89984a2e4d476ebff2ac1c2f2df274feb59b54c04285a805b598be500c0347016bcc41e9000005581e03e54a02bce1d9fa1028366a0e299a657ef87db82ba07bfa11d811514ad00c19012b481058bdcf28eec00005581e038426c4518ccd1f062c426b53cfe8fb82259a40f2268d750baea0742e300c0247acdff78c161e9e03de216ab95c2cebd2bd88a4d0a4d83f3bfbfb2da32d19d174469df0ebcad4faec03c3d732adae8d04ffd2faa32b25d577dfa119ef17440318f9933278515ef3761305581e03b953a07f730c3f005e8b9aabd7fdb5556b49eec8b53f79ccbb9c11e3500c1855466a4aaba9014005581e0346e04e6a2b4175284acf03d2a3e7dd302b581ccf48bb504650370b12a0040205581e037e82ce55a952139e79ad57761b3ab5de3d8245ca64336a7a8da980ebf00401031d7b302ffe0b9d2ff0be04ed6fa453ad6bc8ff44c13e855f98a4903b5f590e4902191f4f03b5efcba6536e0581474464b9620f573c4b581897fbc2bc97a89ae8273f4a22a403e37cfa9d1f7b3fab0c6c8b7bc52fc3d1ad330fc14f1357b89f39a95dc1411ec2034f8dd44eed80fef14b7d931b972ad914768f779e7a3747261a2a7b19c27bf27d03fb52e8346e660f781ed894c9b373c35e64553af4bb4544669310c2ecd13ac0f803010ef8ada87041671c047c58ad24eba26701e6db6b10ed3c7e299f97b1304cae037eab63904940c38b258e52e74449ba505cfad0aba7484dd755fce955012e1f47036de6500d66dbb90624b117f2b611f38de322276869166acb2390ae4198d54bc00219ffff037bcc955b2ffa142ae8b172e326f23073a2410d46b86587e73cb628e84d8d3ee103d035a37988ae72947074396c7fafbbd13ac13b648beabca615d35bc8cc3a1f430336dea29fc8eac50c7b4e9a51c7514a459eae82d5a99b156e5a59f2a00a99687e033b8cc47d316bf4a2b9c384858745b64a6a75f6ab7baa949b6cd8d8342dfa13d103cb0f4f4cbd1444ec264d8b1380ec16f997bd5861dc2ea2ff60dc1323110748560368fc753b01435a3a51f24e72db9379db9544af0077df886dbf1138d40b9d39fa03b87a3c261d5f213e5ff05aba3b503ea58c378d7936963f6d0511af86d839207e031c04ae51ea7f600c37d3888edf86378fd7eb9a4cc67897e85e82470d44d3914303dd35813900aed6ac3157ac2eb24d5405b5ab808d3175e527c1902d66b371325203389b582aa85c5c794824060be73f284f51735d468740e066b0b151c1b1cd000a03fb7f3aa6b11fea6ac8add987b5645e65ca8629335eaf598bedd5c24ad319355f0219ffff03c4107551bf1c4524fbed9ce1a945c1a6e7a74ed64b59bfd112d10c6d05c96702037f4f5c7a4c6e9a145dfee645bb3ae1798c52b3ec8142051e1cc1b6e89fb4add903a79900fab5a29745c74610582aa22379a91e0978f9b1fe286fcb7027cac83b0703bb4401992c3fa73ffab291ce57990e5dee011c3fb86ccb2e88a7abf297682a4a03e1ed6c3ac971958b2333e007f6fd083f50741081341c80738f90d628cbf66ad50219ffff03239c82e304613079bf7cf121825e20135110f7d707f8227f31a254cb94b05a3403eec90064d95c1ebc04fe5b66a735cacdb439b76f7eea2f3c688f7600c41d8b5c0219ffff035b6d5c074b857eaf08a0431677575b1e5233d272db815494fd1557d49c7ac46f03bb220cf68a9f842a25d11865e54545de02f8c61e6209d6398c6103ff356408120346c8bf16613bc50ec34bb6f096e317af043ce71838aeecf72e743c84da078977038fe8e160de7fdd70f751db2609943011d1ca5564e8ef8efbc14be5310b3ea2510329364d8ed0e799b029bf3deff096debea3ec406fedf6ba7059ae8f2780a51eee03935e07b6895190691a8ad3b053746bf2aaaf95e484339aac97f1c72b1f6fd6740364be292226434b839cb4bd388bee92b415ff12d336f1556570be56a0d2449d9203be3e695b0b9966c53d8ddb29b6f08402de9be1064d3561d9b2a998abeaf8b3c203d739e429ed1e072f8476b9e585941f18e80d2e816a99c947fccff0604d5d33c103b2b4b00dacb165faa92130ee87402d900f4037e4e6ab020338a83a257c64f84703600ee5db3a0014dfd1d7b122e866ada568cfe8e0b0fc6da767d79debcd7fa52103288252053b654352f856fb30ccd8d8c264de10cd478131dce9b8d13453a88077035a60fcaa0b588c5b7fb2a95c3606f7b814f9399a44bbdc2b41fd87c14633a25803eac01d934a8a35d7e737ee2f7d0078059f373aa877266a6acca2f2064779682d03dc3e8883b94e63b3657f079d97a9ab584a434030e7c6cb23cf4493da7768af5a03da057e3ccee99bd003c135a894e86f30b57fbf0e67aaff788a94e1e10411d953036bc7733b5f92f4d4772d8862412279e91c881e8a4c7b2d4806606b864905488d0317a9a7d0bc390435dac707520f7ca3866fa38a5dd95a230d190626ca9e84e7d205581e03a82e3876df42156252732c2b5cd04db40239feab766aa0c96e2e56b00004080459567e608060405234801561001057600080fd5b50600436106101ae5760003560e01c806370cf754a116100ee578063c45a015511610097578063ddca3f4311610071578063ddca3f4314610800578063f305839914610820578063f30dba9314610828578063f637731d146108aa576101ae565b8063c45a0155146107d1578063d0c93a7c146107d9578063d21220a7146107f8576101ae565b8063883bdbfd116100c8578063883bdbfd14610633578063a34123a71461073c578063a38807f214610776576101ae565b806370cf754a146105c65780638206a4d1146105ce57806385b66729146105f6576101ae565b80633850c7bd1161015b578063490e6cbc11610135578063490e6cbc146104705780634f1eb3d8146104fc578063514ea4bf1461054d5780635339c296146105a6576101ae565b80633850c7bd1461035b5780633c8a7d8d146103b45780634614131914610456576101ae565b80631ad8b03b1161018c5780631ad8b03b146102aa578063252c09d7146102e157806332148f6714610338576101ae565b80630dfe1681146101b3578063128acb08146101d75780631a68650214610286575b600080fd5b6101bb6108d0565b604080516001600160a01b039092168252519081900360200190f35b61026d600480360360a08110156101ed57600080fd5b6001600160a01b0382358116926020810135151592604082013592606083013516919081019060a08101608082013564010000000081111561022e57600080fd5b82018360208201111561024057600080fd5b8035906020019184600183028401116401000000008311171561026257600080fd5b5090925090506108f4565b6040805192835260208301919091528051918290030190f35b61028e6114ad565b604080516001600160801b039092168252519081900360200190f35b6102b26114bc565b60405180836001600160801b03168152602001826001600160801b031681526020019250505060405180910390f35b6102fe600480360360208110156102f757600080fd5b50356114d6565b6040805163ffffffff909516855260069390930b60208501526001600160a01b039091168383015215156060830152519081900360800190f35b6103596004803603602081101561034e57600080fd5b503561ffff1661151c565b005b610363611616565b604080516001600160a01b03909816885260029690960b602088015261ffff9485168787015292841660608701529216608085015260ff90911660a0840152151560c0830152519081900360e00190f35b61026d600480360360a08110156103ca57600080fd5b6001600160a01b03823516916020810135600290810b92604083013590910b916001600160801b036060820135169181019060a08101608082013564010000000081111561041757600080fd5b82018360208201111561042957600080fd5b8035906020019184600183028401116401000000008311171561044b57600080fd5b509092509050611666565b61045e611922565b60408051918252519081900360200190f35b6103596004803603608081101561048657600080fd5b6001600160a01b0382351691602081013591604082013591908101906080810160608201356401000000008111156104bd57600080fd5b8201836020820111156104cf57600080fd5b803590602001918460018302840111640100000000831117156104f157600080fd5b509092509050611928565b6102b2600480360360a081101561051257600080fd5b506001600160a01b03813516906020810135600290810b91604081013590910b906001600160801b0360608201358116916080013516611d83565b61056a6004803603602081101561056357600080fd5b5035611f9d565b604080516001600160801b0396871681526020810195909552848101939093529084166060840152909216608082015290519081900360a00190f35b61045e600480360360208110156105bc57600080fd5b503560010b611fda565b61028e611fec565b610359600480360360408110156105e457600080fd5b5060ff81358116916020013516612010565b6102b26004803603606081101561060c57600080fd5b506001600160a01b03813516906001600160801b036020820135811691604001351661220f565b6106a36004803603602081101561064957600080fd5b81019060208101813564010000000081111561066457600080fd5b82018360208201111561067657600080fd5b8035906020019184602083028401116401000000008311171561069857600080fd5b5090925090506124dc565b604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b838110156106e75781810151838201526020016106cf565b50505050905001838103825284818151815260200191508051906020019060200280838360005b8381101561072657818101518382015260200161070e565b5050505090500194505050505060405180910390f35b61026d6004803603606081101561075257600080fd5b508035600290810b91602081013590910b90604001356001600160801b0316612569565b6107a06004803603604081101561078c57600080fd5b508035600290810b9160200135900b6126e0565b6040805160069490940b84526001600160a01b03909216602084015263ffffffff1682820152519081900360600190f35b6101bb6128d7565b6107e16128fb565b6040805160029290920b8252519081900360200190f35b6101bb61291f565b610808612943565b6040805162ffffff9092168252519081900360200190f35b61045e612967565b6108486004803603602081101561083e57600080fd5b503560020b61296d565b604080516001600160801b039099168952600f9790970b602089015287870195909552606087019390935260069190910b60808601526001600160a01b031660a085015263ffffffff1660c0840152151560e083015251908190036101000190f35b610359600480360360208110156108c057600080fd5b50356001600160a01b03166129db565b7f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4881565b6000806108ff612bf0565b85610936576040805162461bcd60e51b8152602060048201526002602482015261415360f01b604482015290519081900360640190fd5b6040805160e0810182526000546001600160a01b0381168252600160a01b8104600290810b810b900b602083015261ffff600160b81b8204811693830193909352600160c81b810483166060830152600160d81b8104909216608082015260ff600160e81b8304811660a0830152600160f01b909204909116151560c082018190526109ef576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b87610a3a5780600001516001600160a01b0316866001600160a01b0316118015610a35575073fffd8963efd1fc6a506488495d951d5263988d266001600160a01b038716105b610a6c565b80600001516001600160a01b0316866001600160a01b0316108015610a6c57506401000276a36001600160a01b038716115b610aa3576040805162461bcd60e51b815260206004820152600360248201526214d41360ea1b604482015290519081900360640190fd5b6000805460ff60f01b191681556040805160c08101909152808a610ad25760048460a0015160ff16901c610ae5565b60108460a0015160ff1681610ae357fe5b065b60ff1681526004546001600160801b03166020820152604001610b06612c27565b63ffffffff168152602001600060060b815260200160006001600160a01b031681526020016000151581525090506000808913905060006040518060e001604052808b81526020016000815260200185600001516001600160a01b03168152602001856020015160020b81526020018c610b8257600254610b86565b6001545b815260200160006001600160801b0316815260200184602001516001600160801b031681525090505b805115801590610bd55750886001600160a01b031681604001516001600160a01b031614155b15610f9f57610be261560e565b60408201516001600160a01b031681526060820151610c25906006907f000000000000000000000000000000000000000000000000000000000000000a8f612c2b565b15156040830152600290810b810b60208301819052620d89e719910b1215610c5657620d89e7196020820152610c75565b6020810151620d89e860029190910b1315610c7557620d89e860208201525b610c828160200151612d6d565b6001600160a01b031660608201526040820151610d13908d610cbc578b6001600160a01b031683606001516001600160a01b031611610cd6565b8b6001600160a01b031683606001516001600160a01b0316105b610ce4578260600151610ce6565b8b5b60c085015185517f00000000000000000000000000000000000000000000000000000000000001f461309f565b60c085015260a084015260808301526001600160a01b031660408301528215610d7557610d498160c00151826080015101613291565b825103825260a0810151610d6b90610d6090613291565b6020840151906132a7565b6020830152610db0565b610d828160a00151613291565b825101825260c08101516080820151610daa91610d9f9101613291565b6020840151906132c3565b60208301525b835160ff1615610df6576000846000015160ff168260c0015181610dd057fe5b60c0840180519290910491829003905260a0840180519091016001600160801b03169052505b60c08201516001600160801b031615610e3557610e298160c00151600160801b8460c001516001600160801b03166132d9565b60808301805190910190525b80606001516001600160a01b031682604001516001600160a01b03161415610f5e57806040015115610f35578360a00151610ebf57610e9d846040015160008760200151886040015188602001518a606001516008613389909695949392919063ffffffff16565b6001600160a01b03166080860152600690810b900b6060850152600160a08501525b6000610f0b82602001518e610ed657600154610edc565b84608001515b8f610eeb578560800151610eef565b6002545b608089015160608a015160408b0151600595949392919061351c565b90508c15610f17576000035b610f258360c00151826135ef565b6001600160801b031660c0840152505b8b610f44578060200151610f4d565b60018160200151035b600290810b900b6060830152610f99565b80600001516001600160a01b031682604001516001600160a01b031614610f9957610f8c82604001516136a5565b600290810b900b60608301525b50610baf565b836020015160020b816060015160020b1461107a57600080610fed86604001518660400151886020015188602001518a606001518b6080015160086139d1909695949392919063ffffffff16565b604085015160608601516000805461ffff60c81b1916600160c81b61ffff958616021761ffff60b81b1916600160b81b95909416949094029290921762ffffff60a01b1916600160a01b62ffffff60029490940b93909316929092029190911773ffffffffffffffffffffffffffffffffffffffff19166001600160a01b03909116179055506110ac9050565b60408101516000805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b039092169190911790555b8060c001516001600160801b031683602001516001600160801b0316146110f25760c0810151600480546001600160801b0319166001600160801b039092169190911790555b8a1561114257608081015160015560a08101516001600160801b03161561113d5760a0810151600380546001600160801b031981166001600160801b03918216909301169190911790555b611188565b608081015160025560a08101516001600160801b0316156111885760a0810151600380546001600160801b03808216600160801b92839004821690940116029190911790555b8115158b1515146111a157602081015181518b036111ae565b80600001518a0381602001515b90965094508a156112e75760008512156111f0576111f07f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28d87600003613b86565b60006111fa613cd4565b9050336001600160a01b031663fa461e3388888c8c6040518563ffffffff1660e01b815260040180858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b15801561127e57600080fd5b505af1158015611292573d6000803e3d6000fd5b5050505061129e613cd4565b6112a88289613e0d565b11156112e1576040805162461bcd60e51b815260206004820152600360248201526249494160e81b604482015290519081900360640190fd5b50611411565b600086121561131e5761131e7f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb488d88600003613b86565b6000611328613e1d565b9050336001600160a01b031663fa461e3388888c8c6040518563ffffffff1660e01b815260040180858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b1580156113ac57600080fd5b505af11580156113c0573d6000803e3d6000fd5b505050506113cc613e1d565b6113d68288613e0d565b111561140f576040805162461bcd60e51b815260206004820152600360248201526249494160e81b604482015290519081900360640190fd5b505b60408082015160c083015160608085015184518b8152602081018b90526001600160a01b03948516818701526001600160801b039093169183019190915260020b60808201529151908e169133917fc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca679181900360a00190a350506000805460ff60f01b1916600160f01b17905550919890975095505050505050565b6004546001600160801b031681565b6003546001600160801b0380821691600160801b90041682565b60088161ffff81106114e757600080fd5b015463ffffffff81169150640100000000810460060b90600160581b81046001600160a01b031690600160f81b900460ff1684565b600054600160f01b900460ff16611560576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b19169055611575612bf0565b60008054600160d81b900461ffff169061159160088385613eb5565b6000805461ffff808416600160d81b810261ffff60d81b19909316929092179092559192508316146115fe576040805161ffff80851682528316602082015281517fac49e518f90a358f652e4400164f05a5d8f7e35e7747279bc3a93dbf584e125a929181900390910190a15b50506000805460ff60f01b1916600160f01b17905550565b6000546001600160a01b03811690600160a01b810460020b9061ffff600160b81b8204811691600160c81b8104821691600160d81b8204169060ff600160e81b8204811691600160f01b90041687565b600080548190600160f01b900460ff166116ad576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b191690556001600160801b0385166116cd57600080fd5b60008061171b60405180608001604052808c6001600160a01b031681526020018b60020b81526020018a60020b81526020016117118a6001600160801b0316613f58565b600f0b9052613f69565b9250925050819350809250600080600086111561173d5761173a613cd4565b91505b841561174e5761174b613e1d565b90505b336001600160a01b031663d348799787878b8b6040518563ffffffff1660e01b815260040180858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b1580156117d057600080fd5b505af11580156117e4573d6000803e3d6000fd5b50505050600086111561183b576117f9613cd4565b6118038388613e0d565b111561183b576040805162461bcd60e51b815260206004820152600260248201526104d360f41b604482015290519081900360640190fd5b841561188b57611849613e1d565b6118538287613e0d565b111561188b576040805162461bcd60e51b81526020600482015260026024820152614d3160f01b604482015290519081900360640190fd5b8960020b8b60020b8d6001600160a01b03167f7a53080ba414158be7ec69b987b5fb7d07dee101fe85488f0853ae16239d0bde338d8b8b60405180856001600160a01b03168152602001846001600160801b0316815260200183815260200182815260200194505050505060405180910390a450506000805460ff60f01b1916600160f01b17905550919890975095505050505050565b60025481565b600054600160f01b900460ff1661196c576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b19169055611981612bf0565b6004546001600160801b0316806119c3576040805162461bcd60e51b81526020600482015260016024820152601360fa1b604482015290519081900360640190fd5b60006119f8867f00000000000000000000000000000000000000000000000000000000000001f462ffffff16620f42406141a9565b90506000611a2f867f00000000000000000000000000000000000000000000000000000000000001f462ffffff16620f42406141a9565b90506000611a3b613cd4565b90506000611a47613e1d565b90508815611a7a57611a7a7f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb488b8b613b86565b8715611aab57611aab7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28b8a613b86565b336001600160a01b031663e9cbafb085858a8a6040518563ffffffff1660e01b815260040180858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b158015611b2d57600080fd5b505af1158015611b41573d6000803e3d6000fd5b505050506000611b4f613cd4565b90506000611b5b613e1d565b905081611b688588613e0d565b1115611ba0576040805162461bcd60e51b8152602060048201526002602482015261046360f41b604482015290519081900360640190fd5b80611bab8487613e0d565b1115611be3576040805162461bcd60e51b8152602060048201526002602482015261463160f01b604482015290519081900360640190fd5b8382038382038115611c725760008054600160e81b9004600f16908115611c16578160ff168481611c1057fe5b04611c19565b60005b90506001600160801b03811615611c4c57600380546001600160801b038082168401166001600160801b03199091161790555b611c66818503600160801b8d6001600160801b03166132d9565b60018054909101905550505b8015611cfd5760008054600160e81b900460041c600f16908115611ca2578160ff168381611c9c57fe5b04611ca5565b60005b90506001600160801b03811615611cd757600380546001600160801b03600160801b8083048216850182160291161790555b611cf1818403600160801b8d6001600160801b03166132d9565b60028054909101905550505b8d6001600160a01b0316336001600160a01b03167fbdbdb71d7860376ba52b25a5028beea23581364a40522f6bcfb86bb1f2dca6338f8f86866040518085815260200184815260200183815260200182815260200194505050505060405180910390a350506000805460ff60f01b1916600160f01b179055505050505050505050505050565b600080548190600160f01b900460ff16611dca576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b19168155611de460073389896141e3565b60038101549091506001600160801b0390811690861611611e055784611e14565b60038101546001600160801b03165b60038201549093506001600160801b03600160801b909104811690851611611e3c5783611e52565b6003810154600160801b90046001600160801b03165b91506001600160801b03831615611eb7576003810180546001600160801b031981166001600160801b03918216869003821617909155611eb7907f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48908a908616613b86565b6001600160801b03821615611f1d576003810180546001600160801b03600160801b808304821686900382160291811691909117909155611f1d907f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2908a908516613b86565b604080516001600160a01b038a1681526001600160801b0380861660208301528416818301529051600288810b92908a900b9133917f70935338e69775456a85ddef226c395fb668b63fa0115f5f20610b388e6ca9c0919081900360600190a4506000805460ff60f01b1916600160f01b17905590969095509350505050565b60076020526000908152604090208054600182015460028301546003909301546001600160801b0392831693919281811691600160801b90041685565b60066020526000908152604090205481565b7f0000000000000000000000000000000000005e8b2285f864419ac400be90719681565b600054600160f01b900460ff16612054576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b1916905560408051638da5cb5b60e01b815290516001600160a01b037f0000000000000000000000001f98431c8ad98523631ae4a59f267346ea31f9841691638da5cb5b916004808301926020929190829003018186803b1580156120c157600080fd5b505afa1580156120d5573d6000803e3d6000fd5b505050506040513d60208110156120eb57600080fd5b50516001600160a01b0316331461210157600080fd5b60ff82161580612124575060048260ff16101580156121245750600a8260ff1611155b801561214e575060ff8116158061214e575060048160ff161015801561214e5750600a8160ff1611155b61215757600080fd5b60008054610ff0600484901b16840160ff908116600160e81b9081027fffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff841617909355919004167f973d8d92bb299f4af6ce49b52a8adb85ae46b9f214c4c4fc06ac77401237b1336010826040805160ff9390920683168252600f600486901c16602083015286831682820152918516606082015290519081900360800190a150506000805460ff60f01b1916600160f01b17905550565b600080548190600160f01b900460ff16612256576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b1916905560408051638da5cb5b60e01b815290516001600160a01b037f0000000000000000000000001f98431c8ad98523631ae4a59f267346ea31f9841691638da5cb5b916004808301926020929190829003018186803b1580156122c357600080fd5b505afa1580156122d7573d6000803e3d6000fd5b505050506040513d60208110156122ed57600080fd5b50516001600160a01b0316331461230357600080fd5b6003546001600160801b039081169085161161231f578361232c565b6003546001600160801b03165b6003549092506001600160801b03600160801b9091048116908416116123525782612366565b600354600160801b90046001600160801b03165b90506001600160801b038216156123e7576003546001600160801b038381169116141561239557600019909101905b600380546001600160801b031981166001600160801b039182168590038216179091556123e7907f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb489087908516613b86565b6001600160801b0381161561246d576003546001600160801b03828116600160801b90920416141561241857600019015b600380546001600160801b03600160801b80830482168590038216029181169190911790915561246d907f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc29087908416613b86565b604080516001600160801b0380851682528316602082015281516001600160a01b0388169233927f596b573906218d3411850b26a6b437d6c4522fdb43d2d2386263f86d50b8b151929081900390910190a36000805460ff60f01b1916600160f01b1790559094909350915050565b6060806124e7612bf0565b61255e6124f2612c27565b858580806020026020016040519081016040528093929190818152602001838360200280828437600092018290525054600454600896959450600160a01b820460020b935061ffff600160b81b8304811693506001600160801b0390911691600160c81b900416614247565b915091509250929050565b600080548190600160f01b900460ff166125b0576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b1916815560408051608081018252338152600288810b602083015287900b918101919091528190819061260990606081016125fc6001600160801b038a16613f58565b600003600f0b9052613f69565b925092509250816000039450806000039350600085118061262a5750600084115b15612669576003830180546001600160801b038082168089018216600160801b93849004831689019092169092029091176001600160801b0319161790555b604080516001600160801b0388168152602081018790528082018690529051600289810b92908b900b9133917f0c396cd989a39f4459b5fa1aed6a9a8dcdbc45908acfd67e028cd568da98982c919081900360600190a450506000805460ff60f01b1916600160f01b179055509094909350915050565b60008060006126ed612bf0565b6126f785856143a1565b600285810b810b60009081526005602052604080822087840b90930b825281206003830154600681900b9367010000000000000082046001600160a01b0316928492600160d81b810463ffffffff169284929091600160f81b900460ff168061275f57600080fd5b6003820154600681900b985067010000000000000081046001600160a01b03169650600160d81b810463ffffffff169450600160f81b900460ff16806127a457600080fd5b50506040805160e0810182526000546001600160a01b0381168252600160a01b8104600290810b810b810b6020840181905261ffff600160b81b8404811695850195909552600160c81b830485166060850152600160d81b8304909416608084015260ff600160e81b8304811660a0850152600160f01b909204909116151560c08301529093508e810b91900b1215905061284d575093909403965090039350900390506128d0565b8a60020b816020015160020b12156128c1576000612869612c27565b602083015160408401516004546060860151939450600093849361289f936008938893879392916001600160801b031690613389565b9a9003989098039b5050949096039290920396509091030392506128d0915050565b50949093039650039350900390505b9250925092565b7f0000000000000000000000001f98431c8ad98523631ae4a59f267346ea31f98481565b7f000000000000000000000000000000000000000000000000000000000000000a81565b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc281565b7f00000000000000000000000000000000000000000000000000000000000001f481565b60015481565b60056020526000908152604090208054600182015460028301546003909301546001600160801b03831693600160801b909304600f0b9290600681900b9067010000000000000081046001600160a01b031690600160d81b810463ffffffff1690600160f81b900460ff1688565b6000546001600160a01b031615612a1e576040805162461bcd60e51b8152602060048201526002602482015261414960f01b604482015290519081900360640190fd5b6000612a29826136a5565b9050600080612a41612a39612c27565b60089061446a565b6040805160e0810182526001600160a01b038816808252600288810b6020808501829052600085870181905261ffff898116606088018190529089166080880181905260a08801839052600160c0909801979097528154600160f01b73ffffffffffffffffffffffffffffffffffffffff19909116871762ffffff60a01b1916600160a01b62ffffff9787900b9790971696909602959095177fffffffffff00000000ffffffffffffffffffffffffffffffffffffffffffffff16600160c81b9091021761ffff60d81b1916600160d81b909602959095177fff0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1692909217909355835191825281019190915281519395509193507f98636036cb66a9c19a37435efc1e90142190214e8abeb821bdba3f2990dd4c9592918290030190a150505050565b60008082600281900b620d89e71981612b9957fe5b05029050600083600281900b620d89e881612bb057fe5b0502905060008460020b83830360020b81612bc757fe5b0560010190508062ffffff166001600160801b03801681612be457fe5b0493505050505b919050565b306001600160a01b037f00000000000000000000000088e6a0c2ddd26feeb64f039a2c41296fcb3f56401614612c2557600080fd5b565b4290565b60008060008460020b8660020b81612c3f57fe5b05905060008660020b128015612c6657508460020b8660020b81612c5f57fe5b0760020b15155b15612c7057600019015b8315612ce557600080612c82836144b6565b600182810b810b600090815260208d9052604090205460ff83169190911b80016000190190811680151597509294509092509085612cc757888360ff16860302612cda565b88612cd1826144c8565b840360ff168603025b965050505050612d63565b600080612cf4836001016144b6565b91509150600060018260ff166001901b031990506000818b60008660010b60010b8152602001908152602001600020541690508060001415955085612d4657888360ff0360ff16866001010102612d5c565b8883612d5183614568565b0360ff168660010101025b9650505050505b5094509492505050565b60008060008360020b12612d84578260020b612d8c565b8260020b6000035b9050620d89e8811115612dca576040805162461bcd60e51b81526020600482015260016024820152601560fa1b604482015290519081900360640190fd5b600060018216612dde57600160801b612df0565b6ffffcb933bd6fad37aa2d162d1a5940015b70ffffffffffffffffffffffffffffffffff1690506002821615612e24576ffff97272373d413259a46990580e213a0260801c5b6004821615612e43576ffff2e50f5f656932ef12357cf3c7fdcc0260801c5b6008821615612e62576fffe5caca7e10e4e61c3624eaa0941cd00260801c5b6010821615612e81576fffcb9843d60f6159c9db58835c9266440260801c5b6020821615612ea0576fff973b41fa98c081472e6896dfb254c00260801c5b6040821615612ebf576fff2ea16466c96a3843ec78b326b528610260801c5b6080821615612ede576ffe5dee046a99a2a811c461f1969c30530260801c5b610100821615612efe576ffcbe86c7900a88aedcffc83b479aa3a40260801c5b610200821615612f1e576ff987a7253ac413176f2b074cf7815e540260801c5b610400821615612f3e576ff3392b0822b70005940c7a398e4b70f30260801c5b610800821615612f5e576fe7159475a2c29b7443b29c7fa6e889d90260801c5b611000821615612f7e576fd097f3bdfd2022b8845ad8f792aa58250260801c5b612000821615612f9e576fa9f746462d870fdf8a65dc1f90e061e50260801c5b614000821615612fbe576f70d869a156d2a1b890bb3df62baf32f70260801c5b618000821615612fde576f31be135f97d08fd981231505542fcfa60260801c5b62010000821615612fff576f09aa508b5b7a84e1c677de54f3e99bc90260801c5b6202000082161561301f576e5d6af8dedb81196699c329225ee6040260801c5b6204000082161561303e576d2216e584f5fa1ea926041bedfe980260801c5b6208000082161561305b576b048a170391f7dc42444e8fa20260801c5b60008460020b131561307657806000198161307257fe5b0490505b64010000000081061561308a57600161308d565b60005b60ff16602082901c0192505050919050565b60008080806001600160a01b03808916908a1610158187128015906131245760006130d88989620f42400362ffffff16620f42406132d9565b9050826130f1576130ec8c8c8c6001614652565b6130fe565b6130fe8b8d8c60016146cd565b955085811061310f578a965061311e565b61311b8c8b838661478a565b96505b5061316e565b8161313b576131368b8b8b60006146cd565b613148565b6131488a8c8b6000614652565b935083886000031061315c5789955061316e565b61316b8b8a8a600003856147d6565b95505b6001600160a01b038a81169087161482156131d15780801561318d5750815b6131a35761319e878d8c60016146cd565b6131a5565b855b95508080156131b2575081155b6131c8576131c3878d8c6000614652565b6131ca565b845b945061321b565b8080156131db5750815b6131f1576131ec8c888c6001614652565b6131f3565b855b9550808015613200575081155b613216576132118c888c60006146cd565b613218565b845b94505b8115801561322b57508860000385115b15613237578860000394505b81801561325657508a6001600160a01b0316876001600160a01b031614155b15613265578589039350613282565b61327f868962ffffff168a620f42400362ffffff166141a9565b93505b50505095509550955095915050565b6000600160ff1b82106132a357600080fd5b5090565b808203828113156000831215146132bd57600080fd5b92915050565b818101828112156000831215146132bd57600080fd5b600080806000198587098686029250828110908390030390508061330f576000841161330457600080fd5b508290049050613382565b80841161331b57600080fd5b6000848688096000868103871696879004966002600389028118808a02820302808a02820302808a02820302808a02820302808a02820302808a02909103029181900381900460010186841190950394909402919094039290920491909117919091029150505b9392505050565b60008063ffffffff8716613430576000898661ffff1661ffff81106133aa57fe5b60408051608081018252919092015463ffffffff8082168084526401000000008304600690810b810b900b6020850152600160581b83046001600160a01b031694840194909452600160f81b90910460ff16151560608301529092508a161461341c57613419818a8988614822565b90505b806020015181604001519250925050613510565b8688036000806134458c8c858c8c8c8c6148d2565b91509150816000015163ffffffff168363ffffffff161415613477578160200151826040015194509450505050613510565b805163ffffffff8481169116141561349f578060200151816040015194509450505050613510565b8151815160208085015190840151918390039286039163ffffffff80841692908516910360060b816134cd57fe5b05028460200151018263ffffffff168263ffffffff1686604001518660400151036001600160a01b031602816134ff57fe5b048560400151019650965050505050505b97509795505050505050565b600295860b860b60009081526020979097526040909620600181018054909503909455938301805490920390915560038201805463ffffffff600160d81b6001600160a01b036701000000000000008085048216909603169094027fffffffffff0000000000000000000000000000000000000000ffffffffffffff90921691909117600681810b90960390950b66ffffffffffffff1666ffffffffffffff199095169490941782810485169095039093160263ffffffff60d81b1990931692909217905554600160801b9004600f0b90565b60008082600f0b121561365457826001600160801b03168260000384039150816001600160801b03161061364f576040805162461bcd60e51b81526020600482015260026024820152614c5360f01b604482015290519081900360640190fd5b6132bd565b826001600160801b03168284019150816001600160801b031610156132bd576040805162461bcd60e51b81526020600482015260026024820152614c4160f01b604482015290519081900360640190fd5b60006401000276a36001600160a01b038316108015906136e1575073fffd8963efd1fc6a506488495d951d5263988d266001600160a01b038316105b613716576040805162461bcd60e51b81526020600482015260016024820152602960f91b604482015290519081900360640190fd5b77ffffffffffffffffffffffffffffffffffffffff00000000602083901b166001600160801b03811160071b81811c67ffffffffffffffff811160061b90811c63ffffffff811160051b90811c61ffff811160041b90811c60ff8111600390811b91821c600f811160021b90811c918211600190811b92831c979088119617909417909217179091171717608081106137b757607f810383901c91506137c1565b80607f0383901b91505b908002607f81811c60ff83811c9190911c800280831c81831c1c800280841c81841c1c800280851c81851c1c800280861c81861c1c800280871c81871c1c800280881c81881c1c800280891c81891c1c8002808a1c818a1c1c8002808b1c818b1c1c8002808c1c818c1c1c8002808d1c818d1c1c8002808e1c9c81901c9c909c1c80029c8d901c9e9d607f198f0160401b60c09190911c678000000000000000161760c19b909b1c674000000000000000169a909a1760c29990991c672000000000000000169890981760c39790971c671000000000000000169690961760c49590951c670800000000000000169490941760c59390931c670400000000000000169290921760c69190911c670200000000000000161760c79190911c670100000000000000161760c89190911c6680000000000000161760c99190911c6640000000000000161760ca9190911c6620000000000000161760cb9190911c6610000000000000161760cc9190911c6608000000000000161760cd9190911c66040000000000001617693627a301d71055774c8581026f028f6481ab7f045a5af012a19d003aa9198101608090811d906fdb2df09e81959a81455e260799a0632f8301901d600281810b9083900b146139c257886001600160a01b03166139a682612d6d565b6001600160a01b031611156139bb57816139bd565b805b6139c4565b815b9998505050505050505050565b6000806000898961ffff1661ffff81106139e757fe5b60408051608081018252919092015463ffffffff8082168084526401000000008304600690810b810b900b6020850152600160581b83046001600160a01b031694840194909452600160f81b90910460ff161515606083015290925089161415613a575788859250925050613510565b8461ffff168461ffff16118015613a7857506001850361ffff168961ffff16145b15613a8557839150613a89565b8491505b8161ffff168960010161ffff1681613a9d57fe5b069250613aac81898989614822565b8a8461ffff1661ffff8110613abd57fe5b825191018054602084015160408501516060909501511515600160f81b027effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6001600160a01b03909616600160581b027fff0000000000000000000000000000000000000000ffffffffffffffffffffff60069390930b66ffffffffffffff16640100000000026affffffffffffff000000001963ffffffff90971663ffffffff199095169490941795909516929092171692909217929092161790555097509795505050505050565b604080516001600160a01b038481166024830152604480830185905283518084039091018152606490920183526020820180516001600160e01b031663a9059cbb60e01b1781529251825160009485949389169392918291908083835b60208310613c025780518252601f199092019160209182019101613be3565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114613c64576040519150601f19603f3d011682016040523d82523d6000602084013e613c69565b606091505b5091509150818015613c97575080511580613c975750808060200190516020811015613c9457600080fd5b50515b613ccd576040805162461bcd60e51b81526020600482015260026024820152612a2360f11b604482015290519081900360640190fd5b5050505050565b604080513060248083019190915282518083039091018152604490910182526020810180516001600160e01b03166370a0823160e01b17815291518151600093849384936001600160a01b037f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb481693919290918291908083835b60208310613d6d5780518252601f199092019160209182019101613d4e565b6001836020036101000a038019825116818451168082178552505050505050905001915050600060405180830381855afa9150503d8060008114613dcd576040519150601f19603f3d011682016040523d82523d6000602084013e613dd2565b606091505b5091509150818015613de657506020815110155b613def57600080fd5b808060200190516020811015613e0457600080fd5b50519250505090565b808201828110156132bd57600080fd5b604080513060248083019190915282518083039091018152604490910182526020810180516001600160e01b03166370a0823160e01b17815291518151600093849384936001600160a01b037f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc216939192909182919080838360208310613d6d5780518252601f199092019160209182019101613d4e565b6000808361ffff1611613ef3576040805162461bcd60e51b81526020600482015260016024820152604960f81b604482015290519081900360640190fd5b8261ffff168261ffff1611613f09575081613382565b825b8261ffff168161ffff161015613f4f576001858261ffff1661ffff8110613f2e57fe5b01805463ffffffff191663ffffffff92909216919091179055600101613f0b565b50909392505050565b80600f81900b8114612beb57600080fd5b6000806000613f76612bf0565b613f88846020015185604001516143a1565b6040805160e0810182526000546001600160a01b0381168252600160a01b8104600290810b810b900b602080840182905261ffff600160b81b8404811685870152600160c81b84048116606080870191909152600160d81b8504909116608086015260ff600160e81b8504811660a0870152600160f01b909404909316151560c08501528851908901519489015192890151939461402c9491939092909190614acf565b93508460600151600f0b6000146141a157846020015160020b816020015160020b12156140815761407a6140638660200151612d6d565b6140708760400151612d6d565b8760600151614c84565b92506141a1565b846040015160020b816020015160020b12156141775760045460408201516001600160801b03909116906140d3906140b7612c27565b60208501516060860151608087015160089493929187916139d1565b6000805461ffff60c81b1916600160c81b61ffff938416021761ffff60b81b1916600160b81b939092169290920217905581516040870151614123919061411990612d6d565b8860600151614c84565b93506141416141358760200151612d6d565b83516060890151614cc8565b92506141518187606001516135ef565b600480546001600160801b0319166001600160801b0392909216919091179055506141a1565b61419e6141878660200151612d6d565b6141948760400151612d6d565b8760600151614cc8565b91505b509193909250565b60006141b68484846132d9565b9050600082806141c257fe5b84860911156133825760001981106141d957600080fd5b6001019392505050565b6040805160609490941b6bffffffffffffffffffffffff1916602080860191909152600293840b60e890811b60348701529290930b90911b60378401528051808403601a018152603a90930181528251928201929092206000908152929052902090565b60608060008361ffff1611614287576040805162461bcd60e51b81526020600482015260016024820152604960f81b604482015290519081900360640190fd5b865167ffffffffffffffff8111801561429f57600080fd5b506040519080825280602002602001820160405280156142c9578160200160208202803683370190505b509150865167ffffffffffffffff811180156142e457600080fd5b5060405190808252806020026020018201604052801561430e578160200160208202803683370190505b50905060005b87518110156143945761433f8a8a8a848151811061432e57fe5b60200260200101518a8a8a8a613389565b84838151811061434b57fe5b6020026020010184848151811061435e57fe5b60200260200101826001600160a01b03166001600160a01b03168152508260060b60060b81525050508080600101915050614314565b5097509795505050505050565b8060020b8260020b126143e1576040805162461bcd60e51b8152602060048201526003602482015262544c5560e81b604482015290519081900360640190fd5b620d89e719600283900b1215614424576040805162461bcd60e51b8152602060048201526003602482015262544c4d60e81b604482015290519081900360640190fd5b620d89e8600282900b1315614466576040805162461bcd60e51b815260206004820152600360248201526254554d60e81b604482015290519081900360640190fd5b5050565b6040805160808101825263ffffffff9283168082526000602083018190529282019290925260016060909101819052835463ffffffff1916909117909116600160f81b17909155908190565b60020b600881901d9161010090910790565b60008082116144d657600080fd5b600160801b82106144e957608091821c91015b68010000000000000000821061450157604091821c91015b640100000000821061451557602091821c91015b62010000821061452757601091821c91015b610100821061453857600891821c91015b6010821061454857600491821c91015b6004821061455857600291821c91015b60028210612beb57600101919050565b600080821161457657600080fd5b5060ff6001600160801b0382161561459157607f1901614599565b608082901c91505b67ffffffffffffffff8216156145b257603f19016145ba565b604082901c91505b63ffffffff8216156145cf57601f19016145d7565b602082901c91505b61ffff8216156145ea57600f19016145f2565b601082901c91505b60ff821615614604576007190161460c565b600882901c91505b600f82161561461e5760031901614626565b600482901c91505b60038216156146385760011901614640565b600282901c91505b6001821615612beb5760001901919050565b6000836001600160a01b0316856001600160a01b03161115614672579293925b8161469f5761469a836001600160801b03168686036001600160a01b0316600160601b6132d9565b6146c2565b6146c2836001600160801b03168686036001600160a01b0316600160601b6141a9565b90505b949350505050565b6000836001600160a01b0316856001600160a01b031611156146ed579293925b7bffffffffffffffffffffffffffffffff000000000000000000000000606084901b166001600160a01b03868603811690871661472957600080fd5b8361475957866001600160a01b031661474c8383896001600160a01b03166132d9565b8161475357fe5b0461477f565b61477f6147708383896001600160a01b03166141a9565b886001600160a01b0316614cf7565b979650505050505050565b600080856001600160a01b0316116147a157600080fd5b6000846001600160801b0316116147b757600080fd5b816147c95761469a8585856001614d02565b6146c28585856001614de3565b600080856001600160a01b0316116147ed57600080fd5b6000846001600160801b03161161480357600080fd5b816148155761469a8585856000614de3565b6146c28585856000614d02565b61482a61564a565b600085600001518503905060405180608001604052808663ffffffff1681526020018263ffffffff168660020b0288602001510160060b81526020016000856001600160801b03161161487e576001614880565b845b6001600160801b031673ffffffff00000000000000000000000000000000608085901b16816148ab57fe5b048860400151016001600160a01b0316815260200160011515815250915050949350505050565b6148da61564a565b6148e261564a565b888561ffff1661ffff81106148f357fe5b60408051608081018252919092015463ffffffff81168083526401000000008204600690810b810b900b6020840152600160581b82046001600160a01b031693830193909352600160f81b900460ff1615156060820152925061495890899089614ed8565b15614990578663ffffffff16826000015163ffffffff16141561497a57613510565b8161498783898988614822565b91509150613510565b888361ffff168660010161ffff16816149a557fe5b0661ffff1661ffff81106149b557fe5b60408051608081018252929091015463ffffffff811683526401000000008104600690810b810b900b60208401526001600160a01b03600160581b8204169183019190915260ff600160f81b90910416151560608201819052909250614a6c57604080516080810182528a5463ffffffff811682526401000000008104600690810b810b900b6020830152600160581b81046001600160a01b031692820192909252600160f81b90910460ff161515606082015291505b614a7b88836000015189614ed8565b614ab2576040805162461bcd60e51b815260206004820152600360248201526213d31160ea1b604482015290519081900360640190fd5b614abf8989898887614f9b565b9150915097509795505050505050565b6000614ade60078787876141e3565b60015460025491925090600080600f87900b15614c24576000614aff612c27565b6000805460045492935090918291614b499160089186918591600160a01b810460020b9161ffff600160b81b83048116926001600160801b0390921691600160c81b900416613389565b9092509050614b8360058d8b8d8b8b87898b60007f0000000000000000000000000000000000005e8b2285f864419ac400be90719661513b565b9450614bba60058c8b8d8b8b87898b60017f0000000000000000000000000000000000005e8b2285f864419ac400be90719661513b565b93508415614bee57614bee60068d7f000000000000000000000000000000000000000000000000000000000000000a615325565b8315614c2057614c2060068c7f000000000000000000000000000000000000000000000000000000000000000a615325565b5050505b600080614c3660058c8c8b8a8a61538b565b9092509050614c47878a8484615437565b600089600f0b1215614c75578315614c6457614c6460058c6155cc565b8215614c7557614c7560058b6155cc565b50505050505095945050505050565b60008082600f0b12614caa57614ca5614ca085858560016146cd565b613291565b6146c5565b614cbd614ca085858560000360006146cd565b600003949350505050565b60008082600f0b12614ce457614ca5614ca08585856001614652565b614cbd614ca08585856000036000614652565b808204910615150190565b60008115614d755760006001600160a01b03841115614d3857614d3384600160601b876001600160801b03166132d9565b614d50565b6001600160801b038516606085901b81614d4e57fe5b045b9050614d6d614d686001600160a01b03881683613e0d565b6155f8565b9150506146c5565b60006001600160a01b03841115614da357614d9e84600160601b876001600160801b03166141a9565b614dba565b614dba606085901b6001600160801b038716614cf7565b905080866001600160a01b031611614dd157600080fd5b6001600160a01b0386160390506146c5565b600082614df15750836146c5565b7bffffffffffffffffffffffffffffffff000000000000000000000000606085901b168215614e91576001600160a01b03861684810290858281614e3157fe5b041415614e6257818101828110614e6057614e5683896001600160a01b0316836141a9565b93505050506146c5565b505b614e8882614e83878a6001600160a01b03168681614e7c57fe5b0490613e0d565b614cf7565b925050506146c5565b6001600160a01b03861684810290858281614ea857fe5b04148015614eb557508082115b614ebe57600080fd5b808203614e56614d68846001600160a01b038b16846141a9565b60008363ffffffff168363ffffffff1611158015614f0257508363ffffffff168263ffffffff1611155b15614f1e578163ffffffff168363ffffffff1611159050613382565b60008463ffffffff168463ffffffff1611614f46578363ffffffff1664010000000001614f4e565b8363ffffffff165b64ffffffffff16905060008563ffffffff168463ffffffff1611614f7f578363ffffffff1664010000000001614f87565b8363ffffffff165b64ffffffffff169091111595945050505050565b614fa361564a565b614fab61564a565b60008361ffff168560010161ffff1681614fc157fe5b0661ffff169050600060018561ffff16830103905060005b506002818301048961ffff87168281614fee57fe5b0661ffff8110614ffa57fe5b60408051608081018252929091015463ffffffff811683526401000000008104600690810b810b900b60208401526001600160a01b03600160581b8204169183019190915260ff600160f81b9091041615156060820181905290955061506557806001019250614fd9565b898661ffff16826001018161507657fe5b0661ffff811061508257fe5b60408051608081018252929091015463ffffffff811683526401000000008104600690810b810b900b60208401526001600160a01b03600160581b8204169183019190915260ff600160f81b909104161515606082015285519094506000906150ed908b908b614ed8565b905080801561510657506151068a8a8760000151614ed8565b15615111575061512e565b8061512157600182039250615128565b8160010193505b50614fd9565b5050509550959350505050565b60028a810b900b600090815260208c90526040812080546001600160801b031682615166828d6135ef565b9050846001600160801b0316816001600160801b031611156151b4576040805162461bcd60e51b81526020600482015260026024820152614c4f60f01b604482015290519081900360640190fd5b6001600160801b03828116159082161581141594501561528a578c60020b8e60020b1361525a57600183018b9055600283018a90556003830180547fffffffffff0000000000000000000000000000000000000000ffffffffffffff166701000000000000006001600160a01b038c16021766ffffffffffffff191666ffffffffffffff60068b900b161763ffffffff60d81b1916600160d81b63ffffffff8a16021790555b6003830180547effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790555b82546001600160801b0319166001600160801b038216178355856152d35782546152ce906152c990600160801b9004600f90810b810b908f900b6132c3565b613f58565b6152f4565b82546152f4906152c990600160801b9004600f90810b810b908f900b6132a7565b8354600f9190910b6001600160801b03908116600160801b0291161790925550909c9b505050505050505050505050565b8060020b8260020b8161533457fe5b0760020b1561534257600080fd5b60008061535d8360020b8560020b8161535757fe5b056144b6565b600191820b820b60009081526020979097526040909620805460ff9097169190911b90951890945550505050565b600285810b80820b60009081526020899052604080822088850b850b83529082209193849391929184918291908a900b126153d1575050600182015460028301546153e4565b8360010154880391508360020154870390505b6000808b60020b8b60020b121561540657505060018301546002840154615419565b84600101548a0391508460020154890390505b92909803979097039b96909503949094039850939650505050505050565b6040805160a08101825285546001600160801b0390811682526001870154602083015260028701549282019290925260038601548083166060830152600160801b900490911660808201526000600f85900b6154d65781516001600160801b03166154ce576040805162461bcd60e51b815260206004820152600260248201526104e560f41b604482015290519081900360640190fd5b5080516154e5565b81516154e290866135ef565b90505b60006155098360200151860384600001516001600160801b0316600160801b6132d9565b9050600061552f8460400151860385600001516001600160801b0316600160801b6132d9565b905086600f0b6000146155565787546001600160801b0319166001600160801b0384161788555b60018801869055600288018590556001600160801b03821615158061558457506000816001600160801b0316115b156155c2576003880180546001600160801b031981166001600160801b039182168501821617808216600160801b9182900483168501909216021790555b5050505050505050565b600290810b810b6000908152602092909252604082208281556001810183905590810182905560030155565b806001600160a01b0381168114612beb57600080fd5b6040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c081019190915290565b6040805160808101825260008082526020820181905291810182905260608101919091529056fea164736f6c6343000706000a03a9409bae510ed64997f30991bd0b568895c0ed059f94b2a0c7fa157b6c54ba1c03c39458febf9d85729e05bd59cef4117b89d112c94079c576bb38be1061ee6868030db2dc2aa93eaf5b06fe9ca9029de720df7aca85805301f423e6090424177f680355770f6d3f490777fcd32e6b21ecffadf8acf0166533033b1117c026f30ca7f903caf6e82a98aa63ab2f062b02acb78a98a1301bd606cc19a70a5e3c43d1575ff203d00687cb6d0f43cdc3da0681e56f4cdbf11032df61900acb54a154639825bdcc03b4005a49eb0d447acbdc866bfaf6cf7236fc912bf2c00a133fbc6c9ae35f7764033505601faab00774dec9e01e33dc36bac7fb1932bf7163af088c7a343ebb101d03bc1b9dd3fb6a5a5881a43bff81c58599c70e780d75b55a0fae58b303acb3f47803016020132bff32488bf526671fd6be86925759d43706442c66bc51b69257d984039c837eaf4c67ffe3d80665cbbfc1c437a98b075d1ff34286ea647ac85f31f72503db5b0a176f2b806cf76b6d88965c28cb86c9dcf8c4d434f8a950a5b0116528d503a5358c6a5f79ae42b6210e2df4d430c489103760986acbf001a634b742e2af4803459b91438aeeead7737cb7bb4d139a66a2cb6b477f545012bc473242d6e34aa003bdf4876fe625534fc83cc47cb296acb2f657b20bf5708945ec4134b9864db09b0303345d57f975830c79f32e99db1b7bd1b05ae85a80734e488928a34e7a3082c303b511b279e462635396c5c2e08151c7b45118abd475f3616fa92699a68b1e035f03f6c4d262648234ffdcde2eda5c6a1df943089aaa2067b2d7bed9b47bb4f6b864031a9a97eadaa2e96c70a232aae5d6c14c0995a3a61d5584982f9990bc6f3e48a1033c07fcadb9bcfd84091216785d44a9c97328512bf42528561b379b6eb16b037003ec96c60024e2e95d8ff59c87520b35847622386e2fedfabb4191af98a838778f030b8de65ce98c3fbb4197b3b43a682eb13860aadbddf198afe2d4e5dc166163e9032a56f7a4fd1e71931d251128f2865d182cc8320fe3b00ef7aa2d330185e2b15b036ac56f130dd6914546e5e17e93bd5d03b406c303a5bf2ca66eeff2236f86a27a031c919f8c538e95edc972213c19f7256b183d2130c6ff4170ad3719c9d50007c503a809dbcfe4eea2000d66407a28837ca6dafe6549d52ef565ec99df4d707d0956035d907c4a82aec27bc4ea67013bfc763a8e7c47316b631fbd0f35f97d3ea1c0f20399c952c583a8538e9c332273b9390c53148080b5960da078471120784c783831033710c1b16a0099946a555bd128113ccf7e8a9c380905490842ac74efbe95a88903ff6e430266a49811c8ca1cea5911052aad1acc9ec30e658504686a3f0bbd584700581f028f0685396670ff72cccd173c115e4162d76b7899e3c426cf264f8a019e745820fffffffffffffffffffffffffffffffffffffc94584a2fc867910b23ee9a573203738762bf453e3604edd8bb1b92cb7844765757bf7ca6fddd100d15d2ef0ef5890397df86e2db01fce5b66e2a53d59c321a318caae06d6f4a7b8d4f1381cdbdfdd700581f028f709fab72b19b2c4f0d64da3b47381ddcf738d326ba3c9edaceff8cd26e520b8035d4fe9a8bf66b277a2f5218a9032cf900581f02949e8fe01ebc03bbc0eb2f200858aec6ebbce476409d3808c56ce0ee2fe5510288fc475f48be0582741bf76374f302d900581f023f9ee1b9263c14c142f34feadffd223bf4986ea01226ac408206b46a4dea5820fffffffffffffffffffffffffffffffffffff24f3f0d6960f3a14b49651b22ba00581f02d0f8a7374d603ac7b817e98139e55985288f7b18b30591228bc1a36efa6b51d018c6d3eebfbb724eb04e21bfa3f9492703066588d82452e7440b9e52f3df54cc05f9993b70309d4a980fb0061697651a0e03e3db0928f8c9660f594dc5f79e726842b4b9faa9da487e10642006631928089e03c4a1d95d42cea538ed53611045dac42053250cdf290906d1a3b69c9878847ed600581f03badbf71f64fbee1837c8443357f822a308ce7606f1ee812e188c6bb4d3304e8cd8cfe37585bfd2396525aa8b1400581f0313ee94b42df922917157cf6124f5b034f85f74dfed5525a3e1e1d4a6508051a57f50bbcbb640a2abec354b98206b96210219800403dcc8bc835b3f2c14a4df80126bc1d00ce17599a03e3358943c18b4fbdb4e73a40141090219b7df030a3f27749aa61f9e4562c2d66349f2ef48b443654121ca1d95394f9b62a9595103cbd6536e2787001dc3e647b7f604aeb036093e0aabad0eaf0667a68751a6a3130219ffff0219ffff031ef1d0fcad25b625e83b69a94c4f7eb6e5307defc19ee92585940dfe323f0608032fd3bb81f0a798f48c2132f1776434c8a5b034bd119dd4d84d92e07c1705f7a403fd765f02b08a207635ec4de326b415ec9101d572b9a3db6aa5972c4230e7fde403aed8d0bf34a9a9aa76912eca8a0b52f54a817d56dcb81a5f33e8e493a1e0243103b5d534d903858071ca23b2a144b5079d54f9955854d2ad6b4fec6fb5410898160334c5a3022c962d1a9af7a85d25f46fc1ff8b7e1d7c0b07ff30a2a88944b4dc5003a608c12b1054756ed8a6c45dea02df721263f183b7fca7b132468e76c345ec67031e769b33c503eb1469416457e0a38cec4d24de47223f1568cf5dced0e1370b3603af1656f0c8cab195473e3e26d78d4064e38c71bcb4ef91f8c3e0f10b11564bc300581f02a270d34a878c930c200816f97e27739de97d856508c62f1a70c0f0a87c0951688f5974132c86bc481ef98d07a14a5f8900581f0271f66e770122af4ae0917a75a1962bc3f3f49fd956b7a5f53e1863453893510709abc1a6a92c71b4e7efc399776848ab00581f023f38a9332cf604d720097fe85a95bde0c644e5f7fac7df870ff2deab889c4e553aefc416af25c8b4a3ede74b160379e19c3fa9583e916e40800a0ccefef0dc40897e9111288a2b736b680c960c6d0358f170543ffc5ea8f40b4ae819c9efa998c463c930e44008f7d1fe6d0b66d0020352b6d995882095f8cba2eaa59cfb69198787ebb942a8648ac796f6274e1345ac035d8a50a4fe8862abdf75e25419c8eac42d46b9f880ab1a057de5221d24e7c40300581f0207d2aed1c22e4caee8114f1030a54d68b5bb045decda87a967692d2ae8f65820fffffffffffffffffffffffffffffeed5add836963bbba34c25d5a88e6c6041800581f032ae9de3a703a4e67beb7417ca9d3e1c00ea360d1727270ebb63174542ed04e1710a1db042381984d4765a565050361dcfc1abce5d6cf760509ec06dbd3dec7993b11f938b577ee6ff55e37328b4800581f03f1ea8dcaf4e3082fb14f2f614c5cc395fee9ba77ad1cc0ef543d465cfb905820fffffffffffffffffffffffffffffffffffff901829fa4246ccb593074c2d79a00581f03cd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5630581f010002d302d302aa02fcec00000000000045c7df8041e139325635ef4b0bd00219403803436af0c2f3433db1fce59d30c34721fdad29ccc950fc3b07cb47f654a74b4a94038bc7d5a1e3cdf2fd15cd26cbfecf79c5bdbb580787dc682d78417265731d132c0219f2f9037bff3319d045a2e30b4575a21a2ab6b64123e267c0369b2d116a928af97b218503b61360653831f8c03f18d4f3fce23dedc9a4c4e1328ca22608eeff50975b327f03b9a2e05f35836286c97299b21805e09b90b5c55fc17c35aa69bae7bbf08c781f0313d877437922945ea00abf5e98399d9690507215a518a66a72a51e0c53f8b7f2030f5d791c1282ed1b19337bfdfd9d9dc06086e93facac0d7d89b43df0b624cc80036ab69179cd1827f26168ba2637271265c3ef70578a82d3b8e9e3b03a6fa406e903b10db331e6d3a3474b010ff2229e0a33f4da26ca8c5670465b9aba1e334df84903f9e8bb56141f36cedd13216ed148f93a3070d6fdc36b169e4a87f14a889a9e3203cac01ece6cf9059a76c68750eba956b1432a3ff1efde27e553092ec425007ac903834fb3c9ec8beb36383d5ed9439eff1a7e4f2b6ae727e4c949e13fee01cc9ba60385443e6ff0f3f97187e5f9f41f42f634296ccc749e9fcedb6963e30711d7022a0399087c15a9af7b09a484593c689db576eb75396a92c68e12f5c99c0f5622677e0385899877ea0148bffd805af55438ff13a27339596fc7ae69e799c7f00e89fa600341b689d97e85b11a5c91571cbea96d0f2a5ae08e6a76e6453ed861ea25e79b6b03429488e06b2e859a7148f4229497bb0ef22655194231475a3e3a54c30a97a4460219ffff030c8e6528b775dc2a19b692b963553ad2fbe88dbd0612f7c82fb430cd759916050304d1f495a26259a75532f2997574ff6765400c3575b89a0b152fd34fd49d12eb0350cb3eb259f54874839a7313e27cdedaf1ef9e3a948a7946a4c308d44b5ef030038e9cb8eb024670cb50c2aabdfc39b4f8e87934d30d21409c55c648795d8642170302b08c944cc96e6903b06bece83724f8fafb463bad51c2ff049b9150665b625f03fd1608caa50e20e8aa4fb197e0dcf7c4f43e24910b636f9118a45196e2db74bf03edb7010bd41174754c8f3f41bf0750f2319e3377abc0e78c903bb6a3ef18b424030299ee32bb36d4b5a1c7ee2916b397826e22c7ce2f9dd2f4e7f1fac66871f474037c10008aecda11ae2688322bcdf057cb138ab2b0a4751c3ad0e6f7cb230608bf03d4c59cdddefa2e2df38c870a9e1367e75bd8a83dd1b5bb11b8fedb41a082460f03b66c1167576a8fcbae0ee344150e3682e7be051935c72f69f2bbe92e59d3f924036d6da17e2e9e8b392aa41f532404e564f40dac2174af9c675c897c54130db5fd036a89b3cbb4c90bd1b4892e3b1cf858acb68819b5ad6a028043bc07fed8da064d03bc9753120fbcbf2dc5f2be0cca687745071c9011fbfa2301315662af213ba3ce0374e556991e977b6c4c0bf5a66bef23ff40d0d0081b3f67aabf6b86c375cf8fd2030c5164bbd4f63d97461a2a390bf1a81e318777ba5ffe88fe1a460fefe0f6f14003f9bfa1225818d1c7fb0b87f16d13fab3b19da0245cb2e94bd14d55aac0089b8f03d0fd5a7a33637b19b5facd1590e10b8a4eeef8df393e8906d51141064916c2c500581f02de3ea156a256657977afdda3ce260dda02aa100ef127e64e1238171a200e5081dfa7de03a8ae4f86dd851f10943f0600581f0316a046199394ac214b95e44629cee22356a47f8b773f6a63c7e887fba1b057025c7774f466ec000000000000000000040119743984e200581f036f1a00d0564fb1469582048fca055cf89d83902c9086424d852045a1d830520b974a16e3ef32e40abd2c0df622473ed55b02190c00032021bf42ed60817004baf83216fea40eaa22e39d5c38bf67b151cbc6896967e203f4a06995ee11c2e72ff931bc67450f10db12d6b025588cdac11d0e86907537d103e7657221b054e9dc7ce980c4af9c7725156e716ca1ebba66e9f28a3e97857e2a0350aa7f1a9d1810b5c3a5159c75f36eda04557bf76a1cc0faf5507b8fddf46df000581f026d262cd811a30284b81232e9bf90882750e2f7d15621d1b2845454a40bd04e035ff198cdaed211943e6c4ac12703bf60dbd7cbb369583ca3c5bc5021e757d2bbc58302d593a80bfa6030176e53bb00581f028a798104329a85436c589204bdb85c62de3c8f5b26732a1dfd8e35ca70a04e10f49ba6f671a45838625b819c510389323ea2bed538707f96da1ecf4a0213d57ebd2c5064c2e7f4d50e32bed24e8e03b6675df11704690c9c8ae27f3edfa4751ccf200566caad681751f012c3aeafd700581f020bbac14460fc71bfe9fe4011cb6afde50947dd718d58cb5ea07ec442fe634e02b7ae1237079eedb07ce6a541200357f1e4b0235bf39f500c8f7cdc86272949e92cd64a74783d2cbef0376dc4994c0219fdff032cff2600796171f674444e883fdcc2699f8563a7b6e072a842d206e70fe1aa7703dd35804b97781fd3dec9701f7af8e8d8cb12d32b5fdf77d51a1f938c61e4e5d003f3758f35078bc721a3c5152d65823f0d5bc0c66106eab892fef9d41747718b150219ffff039e0ac21f9fa9b207d1126c4dc7d4db2eb0fb5d81145859d2dceca3ca745670220219ffff03bddbdbb6fa4c722e898472d01bcde3c448ee81465baa829a74b819f1a3c124f6036f7823f33dc31a8900cb26efe909b235878a7427941edd85becd101b7aac3c34034c5d8a4d6cef2a707054a4b189c138b6c99ad1ad2899dd08839169b4c14e03ea03a09f2cbb082e98dafbcb095356f3448402439e1be314179e743151fb6611a43603793a4a912393b8c5c1e3ba64115233b700239a6651a9a6eaaffb8abb26ccc6e5037e0a85faabccc107886233f00bcad8a7b99a5d4cf8d75d62ddba8587b1dc597a0350fbd47b370708c3eb0c36662cdc1b00563ec2848dbafb5f60d2dafe393add4303196fc24869b7404a73ef35a5a1e107a8511121cd328787f801dd1d70ee5f78cc00581f026f67741fb5c97fd4e6e38587aed89a4c5f99bee0295dfbcb0e7c15a7126b5820fffffffffffffffffffffffffffffef29b5a3f7d0e6bb297400b624e55488c1303a9f555ef0ffddb36d4a0abc960bc6cafa5cbfb6dcb801ea0d4252ee5cbc0245900581f02038109903912d00002891821f81f0a003742e7d185c60385589efd6527eb51bbd881ba23f711227efd1b4228521fcfce00581f030d8531f14ffa0edd1b45a359742384f2590e0f5b52314fc8a83c6fc5386051315636a20608c5d3ddfe83c952f198010000581f037fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace0520f6f9f815473230c93883203defa73787d8c02190110034a61fd180e182b77c38735a82ef8c961455d08b6ed71f227bab71a02e91fbd2503cd55653f846e8ee798ee5c6e64940560e26bfa1b1d330986f296e14f504594a600581f024e822ad8405b6f4ecf0e8b15d647cb42e09803d2e97a698b7d9870fdc44c52033401fcc8a02ed0b99c7a0ae488497a1d0703a627bdf904d65b61931928965f7c96dfd0eb3d931f5b058806e208b46f67221000581f02810f14e8c3d6e05584679faf9ffd115180b163d5f56815227280ae2a67bf5820ffffffffffffffffffffffffffffffffffffff8b1c82d67baa165f845dd94ffe00581f0296b1232df14614e5c12968ea51a5d83ac39ac0e2000def3a7dfd1b98d383511d2d29a9f6f630d5c4d7617d82a289d7e6034f5034a4be179c1d3f7b9f98500cf9316a0de5f5ec59d19b4c0e9d557765d12f0219fbed031bcdde4711079fabce471db5c2950b9d418e838dc2515cd452795893a9a83df7035e388749675f518e68949f0f6b1c6f0ed8142c088272fd0f0381743e44c2edf8036dc85e7b6a71436eb70613d5df031131ffc301a80b46e18cc542b0b354d860d603f21ba55d0654baa912c07dea53a9dae4d09d3f53c7d25ac35a9f321b2b95f432036d9b4d4802c6262c2e570aa67e22ae432ec6c826a2806ac1277169693f7dc0c40336dcd5f921f2a83e85c7a06691a7a799499c2224228618a5ef3099d639e3c55903eb35ee7c17b1f53bc08d0a1da3f4a767619e25e0069327c61224ac70cdd2e8c3038a63dedf1bbeadd536ad901183f549c175b9101c09deb223ff76b8cc771132cb03a67cce624b7ae642159fde902398c1d4c3a576aa648c73971094716c69dd4be903a7254876156edbef4b4351c8361290ae780dacddc45c7711bb7c89fb1b8b5efe0219ffff03f063aff4ce3958bf5f6b4e02386a22be93021ee5d06c9e26bd426423971523bd03f363d1e223fb42ab1efe5b734435918135c3c0008e00f5361dff6e5dd340b9a003fd2787c70cc36406507f16879a56348854a53790611b297a7ccc8a52cb4d3def03ab32e9b68161e053b0a9aa8c2e44f56926d0eec6c9597ca415a432301e12ce8103b012b8db350b4d23dc2169726427c241d35de675516a277765b02eb65dc130bc03efb316b796cbdcc5e0e602366f128b41b42cf8e8889d36ace2d52fef2620f93f03b7a988e7735361934ea075822e45ea08658405e84d80258a3787617f24cb6aec03512b472b93b296ff2929c5e5f2205936c81755263f1d91a7ae88874af8edd966033b588697493eb4308a70f89e6e95c9a06719271ed3b481e16b9c11c3b966830403673760184fa27fb85240f0affbab7e5620322c9cec408374c6f8eb0479c531fa0386ead46ed6063d126127df53aaf73d9e893db5de64dd35e11b076afb7df8d51f03b214091ca9fc2fa282dd7ed4d2e281340b708235a16d28806c8aa66aa9f295cd0360336e10433369ad6eaae5df02bab1c22e16409f23cba3cc5e6c883f7b01c8e603af145b9e9a63f8a5c6eb69be3897a2e34ec7479186c698f384fe60102a5a9c4803babbf1a8708ab914140052debcc23202b130acd103a5b312d87416f812eb61880219ffff0361a931b7a37936ee3ab97f1092df4ce983d1be53469eb1d8555f7e0a714e29bd03e802e767c9432d810f42871bff6ec8a065b814e1767d089c046fe4b3c132fc8a0341b1c6ff9e3c292ee3e6222e9134afd81e48931e7188105cf89779c9fcc7696f0353cb073b344d7de6c2c16a01b2a9454f7665a93b68b7a89b049a447bb94b94fb035c22b188fd0cbda44eb98b93648d1bee115045fbb7b14a9a97b6fef5c8d651060313a1a0364af6cd7dd8b38a0a9d0b326568b6051da6b52d1f4913f3f1f4f3c87803ef9a336a59066a4711d67d7a7560051b90d6f4461eb60a3083440c54ddb8956103d7d07b8d990691565c98e60dd3a2dd1d34c83f8a923258fbfff0fe3b36c347e9036554ac39eb32410fdbd96235a15484cbf9ab5cfd1fafddfad0df095261f2aed103ab3ad6cd7789795a0ae3235bcea8d84c6becacbca69e2a82c60779ba6bff475603c15bdabd61fbb63a543452d10953e014b46a633e733c11f97005504bbe5a97110338c4b4ef22fe3014b17c0010e5747680a22b069c9998f94fc577ef8271eaf05e00581f02ba1cf2c48b5a9399a62a63b2116a7c133d0438b5df93e94796943ecd1f7d4e05a1b4816f4353e63449a6025bd400581f0247bf785e9d779515b449783a1a33cf271d11ed14ec1b706f4eef86ae23da5206627b038d4541453768ac9af2cd58435cc703ab43c1190b83f416c6861c57a56ae231952dccf71ed2ffbcf2ec7e39024fef2a00581f0243360222720d8eeabef21c0606cf97bd83cf3c088d4a3d204e51fb6008035820fffffffffffffffffffffffffffffffffffff3938f4be20ddf2fcd54181d821e03ba74d5308dff5f5c64b3ec3d11f46892290c42e42faa88cb74d3f72d1c6c433d03fefad8ae05efdf123e7abef1bfd6fda44bc6f290448bee9a665f6ce640918d8800581f03f1262cf1e4601e1bf98804a053ebd8835d7debb65f979f9bcbf3eb7762604e019650f7ac0fab2b04f6ab6d019800581f035117417fe47c3d02a96753d77ad7889bc91398ad48f31c2e9ef1c975c1d05820010000000000000001ec37f6fdbb55510269284dc500112c786d3c936637523f0219880000581f02563383614c87da8c03f1ff71f7c99ba90044075b850c9836200b3f0328fd5820fffffffffffffffffffffffffffffffffffff8c579131a2e5fa790b2d52f59b803af8d275d7a7e2f042a567fa8df1944623b2aaafae0d7fe8a27b6eab0e6a318260375144daa5dd6b59b7809e62e7d94bc3f172523dcd3823ef9c5892456da33ee02021935df03bd920536bea62a2cbbd3606a9667d6d383e20dfef30652f093ce94216d969776033c1746a75b512594ee06420facc1920ab04af68da89861b960ca917e3191f2560392a1b569bbaada052c9ca9b5768ff13bd739f8091e91c7324e889543cae42c6a034a7a9b5a822ef78def5bc5c3469e92301a2e9ef42a89b48661f0cf6f5c5fe922037c286434ac9d7d57ebfe9fb6ca613b9358a811a3ee37f6a46bb56d91232fa7780322ef4e738a1a9aceb513d1bedcdc272d0c7fc2d9fb40dc5f5b09989ebc5119dc038882de9b6f4a91c84f44067f08d90cdecf8200eb49d1ccae38c441fe955490850219ffff034f2b5a61a17d576d5dfe0c2da646a2e2eab6b39dd3c93dbfb5889bb5abd9ed17036b69fd77250901874972c2e61ed5fe6a8e97ed45db12fe2bc02f1525df3d3b6e03b7acbe0a836d3042cabf60ab4d5ab772b00e81df58e535d907cd077ca82aeecd032c41e58b702a6b9702e5876f9c7e1e58691926f2d7861a76f8536b571489a3a303a41d24c23a0a2f6882ef34ed091a04a4448113b5ad0b77d1011b46feb8fde15e03e72b201a505a5d5e3e1e91e6613f509a46ef2e3d52f12886d1f2a5f9ca27445c03f1abcd9fc69d3004bb705231d43ffdd0b29567bcf846c0515aac64af6747475203babd39e2140f4eacfbfeef6f804d475cdb76a1abfeb76c0e231c7c120a24644103235742662168dfe94544a76c7dbbc9fa89c80e602dbdddbdc3d5c5c65e599c25031529daa320cbfabe683a42d45f90616a8c6cbe1cb77c436f0f1662b511540a5503d0eb17fda48489bd22b68deaa159cc070e602abe29fee71b1c78af7f246d314c03804e965352bd418bc709856cc1591d3ca673a0ad45ee91fd682154a52cb33d220356bffa2615752bff5bf3c3375308ee50525e4220cc477e5c0536889ebf3c89b60219ffff03d34e8e3ce4792ce730e5ea446ddd68b7c3da4a669ccb48f4f2c0f8cc15499d0b037fd1f7100c7a13ecc7ae5479425b40d178f3e2ba96991ba49b5b52ddd8df30a40394e5d71141713bdeebe3bb4465a4976cefd7ff3af1061a2f53bfa67ddd0d5f47039b8b4481f83006607eb987af29e90753240aab38eeb708ed38cca6522fa3ddb0034fff37801bd1ffb8292bb216aeedaf8c4a8fe2e913670eabfece7f6d5e0ab9cc03ab380126b6c4d2aed83093cc4c291b01e1e48c4293a7fe2416a1344a3ff6e30200581f02ef777138e4cba411dc9a767ac7a3203ddd92591952e807b18685d870205f4e02d9433ef737541ab79e5f09b7e80353652b0797cedd0dcc286078eeaa19d6447bfac2782e13ac614e1c578ea499a100581f02b0b15ad32f4a3347914b2c46395feb3521463d0beddd02b85aa680ae331f4e12892552db462a920e445d1dc14200581f02d5c9f25a67df122fb5586efdc297d289427d89ff95b50a4c8a9eb37ccbdc510336bafc106b09e08f8da10d9bc686ef4d0386ed514a9930a23b0a420c99b5bd8f8cb8d50ec1165caf11eac3f57a22e57ceb00581f0262c29930c0510253905f46e9c81e80f03e10438f5af5055c401ec034da214ddbd10e6168f1b6a7fd06347f150373a4367d619814b17e0bf359b43e8dd84d2340e0036e999988828b3f332b399b03db125e419c22f4a92f5eb12c08b376ce9c1eb5025d72ecd996ac2bb15fa94f1800581f02cbfe3fb6d0a18c485d6970cd873d21dd443ff3fd069a95a75bc4a5910d434e029cd2a67410084125629de490c500581f02ff334b747aaf899e44d0c71f6bbb85e4fc46c637662e1e1034a00d93bf855820fffffffffffffffffffffffffffffc428fb20bd91d7a0534a159c31eaca7342500581f036c5e2313860013aa316ef141a1d2cad49ca101920cd8c14019f4c0340f205820fffffffffffffffffffffffffffffffffffffc828dab6d53225f2b7615e1d4f000581f036ea11320a3dc190c8b2deba80987707c0bf2e141ebf5528c6be2ff3a7e905820010000000000000001ec3943b29363d2e50baddce200112eb38626776638116700581f03544f515c5b75294603bc55cbe838349aff0b71fcba6350c77372c8f76470570385e6d07bcaa50000000000000000000000000018ae6302195010030a771f999059b03f62dccbe48e146f40d829355f0fad65afdd83a1bfabdf12510219f5fa0323418dc7fe1d61a02500f275034bda9c1d335ff352279b36f2a331fc147b33f40306e269dc49cfb476748636461693cfa00595f68063a156f4d01be09b4336e1db035b90798dde1ba9b27a5960832a75acfd6f22ba4b0349e930cc9572112b1f7c8b0337fc15ad2a9a75a54632b561bf3a8ec2530532930b46cb7b50154c0183164adc03fb6e231b16c7d3676d5c1937b2b7296194784711a6e43deaa87b54e13a4ac28a030b6194b36cbcb072bbc494cfe5b3b13fdc95a415d6dd2558341802c70869bf6303277521c48520205eab2f78e6a5062e682dfc45ca2f6ed805ca7f8077593b321403fdb5241c2df70824f252fc5985f3f134ed46e4b9bc4a350863428dee99a3999003b213fd19e871e82201ab6fcdc6ab269d168ea0a9276f7726563095787b7b8d6103d6528d094a639af9f6d1238c43c1be250c0cfbf2525d42193a4634020c7f6fab03edc4aa4fa43c1f2d5cc30a00694c52770ddaf5a157c0f7744628a0da5a1b910003e9641262ec2df4c7f411214b8ccd79cb61889dd6df133395859e8fa17c9a2d7a033b97b334b1fe00f37446650ad70f792643a24472e559e385e663a8063958731903a5f6a77270b71c00f62be49b3e4549ff59459302a24632ce66b5db5ea9c0fa0e0219ffff030799b44fb99efb9f4a8e750497262f6b78ae46385607e3886c046a2bffdea62403a3a79bd47f849815e0d1dc39a4139e9659331405d7029288bbb45449429ddbf40361662c50a90f6878bfe85ad62f1fe480026d7a8868e0028adb539f6170912e4703a78d4aaeed34e9c2d2a2f0cdaf29d0722deefccc834c38a2448b573fcd1fe5b80378b5a699ee6b52d115b79f0a0fecf912aba09795095e57d92be804820eeb225003c40a66aada0600991db53827e14be1d86aa979159ea39eb0cff9586cb4f2a3ea0393cd7ae831ac7fd1b2f24aa7391ae85d2feb6854e7bd82aa1df31228f7e31f600317bad6feadcdf436edc80fa3cc2a011bb4cd402b22855c248b547a7380e7189400581f021cbd3f4ff6b2e1ffdc9724674a9dbd7424f3307c61703c94c0f4aa8277f94e0595e10268dc42533fdfb1e31a3500581f02304180e4199135efbe7733fed16c32a46c6fc22b64bd08e6915d78f7997d4e0177bd23edfc737f455439d9adc00342a7d642d810faf29a87b8571f29e5fbe02afd2eaaa977267ed90d6b57947e4c03e94470e6364aa93956f48eaf977686657b0d3c541367c1fb8c4ddc3fcbfbc1bb00581f03cfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b0489339152819261ec400581f03e2898c2da6141ef1728d6a6d3c435e4437a98398c1be7a27025ec8c0ce004d0ef3077d434c0b6377b8c4cf480219148000581f023ae9a21ea4d07d15120a8a6f81b993753a000cef837befbb7c68a148311b5820fffffffffffffffffffffffffffff7ec2a7e9cc6fe18fa4f653882291d4108f50396576604990d6331227986c73ee2b9e2897cc478fffc06a6ff26357cf9c84bdb03de7d0f3505fd7f78967b4cdab85ff93e50cc03020d16de4b463cb361a31f826b00581f023c0c993e7c49514d47779dd63c54669ea03f430c64f9b05e50d712bab3c45820ffffffffffffffffffffffffffffffffffffffa06f4a6417f9d31db0bb8f022f00581f02b043595506c12c0e78b27989e21617b71e63b7e3a55d2f5e472c10973ba05820fffffffffffffffffffffffffffffe24bbc04bfe0c60f8493c54a5dc197e17e50399bf7948bcbfada507c1baf5ec90a15c2d2d4dd58465b2d6f0c32b87e616a905035fbad3d94611fb9aa471cc1231276dc2ecbb572be679fee8c1066e91e8a84d6300581f0234b85b242cefc5dac60a1dd953b1881f44db9e4e8ab3150f0799b32b44b25101013be52bbd8522223bd05058d2c72e0900581f020774babe33ae54966706c76740c86f170435a4f1ec938b624dacd13d408a519649e7be9f42bb8878c04107149b0bfdfa0219f7ed03548eda36e44db26b1e3238b12daeb8a4f0600d4232d304ec38e96ab4874267fd03a513dee84e9fb22c126b74255a2ed981c5cb8b884bef707c0ae05e85c1c21ae503982fe9565483650de8f25e27a38f3e38ff99e4aee463925fde9fe5b94471fc2f039ddd2851a2aea90207ce339258021d954a6be615b388404a2e9f65deff7b7d6403f68d303b2fce068fad597904008c9fc70f0b3da9cfabffe0514355bd5e39c12d0368c44e2ea0a92c46c55708ce3d7c5a1b8670063fe5ad9936814ad9146e619504031e9d4b2f9a139c988905a579e6b35f95b24adf367091895739bc7e13acfab0cb031585757b8290d68b4a3e4b96926c207bc68b6c9dd35e9509fa8c286827b66cf60341d86af9cb9e55d85aeda4708ea5c72c7f1fcf70ccda606d88dfcfdd8a1ea9b8034fc89f555826f3f706cfc024d1068a7192f270be3b78518f79d17aa9061c816c03afa118e9175f2ea6c0c364a6f3a3104fce6ea304215af310d60a9b42aa25ff3f03a0c5ebf6c1d2aae7be3bb1f6bcb008c6d7f64592a2c6f3c64c42f53aed3484b30219ffff03f016351b6ad2e63e8aa51d6b52cfb7ffe023946b6990246905756c0e8dfe695603fb0b06c89cdd39b9c293e4f3eebdfa1a676e7059c303394f22779e9a16c81c7303fff60c7dbd4a77cc8b102a998e1190e6070c04bf03555e9ac61047addfb912a203354435d5eef754960fc4d357ebfcede84bb5cda68214ff7505d2506a06dedc560302b4c77ec92ee579dbfd86019bd9144d4f10110a21b3710ba827f90fea3105d703e80bf5d52315608b107d0123c44c005df917ed88c90950c44dd5fab505395fe703b809f65a75f4950b06c787b0eeae070674a397e052fd462e74dc8a5e8c42c51503f1c38a32b3cece8af2f9cb1cf01727950ad3e911a6c8acf1b854d5e62918c07c03a32de4f2ed74ab765ccc1780c36bc3ece58b8bbdc905ac3a3373e075e117c21603607abcf39cb8cd947ec2fb48f97253ec7ecaeee3f1c8f7a5a45e675ddad38ae803d6ec675bf99209a102ca5522e16d237ad2bf52e050a9093cb8af37cea8c2e6c003d7ae3dcb194974fdab42cd858821a34bd9eec472c5f1872629fd7d26819004e303da670fbe7d1dab388abeefd34e032151b475af94605f76bdc8df338cd678a81300581f0207a4ec6c5c28665131640ffa84926e0ace27570afab248f8c15cbf229e2d5820fffffffffffffffffffffffffffffe1e75d0947fb4ae306f5147ea67b38d8ced0394ee3e003a9c41ce77dedae2bd6fc0c2083858ffa708e35254452ff5f28d6e48036b7b63f9817f31dbee9a852759a41fdc69a9cfa779f98b192e29f869910d8e97038bfa43912476b04701a4e6603d5dd3be092f21ef342b2183eec4c957ad3ee9c100581f0214a316424a65ee1cc2cac6333a589c5b71cf1291b2c85025eb39a2c97b435820ffffffffffffffffffffffffffffffd54c4f7c8432c054b6516447d540bdfbc900581f02e4f5b8634c00c4b684f425f427f4e3105a7a435b85d690dd96c0715f6bb54d67534969d3bfb3150614cf9cde00581f0283ca3d3ef68e03615301483f3a5e619a56c87af63e1023d6216767bc27115820fffffffffffffffffffffffffffffffffffff359389c60a0d9b8dea3c0283d1600581f0258dfd7ff564a85b62fa11d867992f07ecdc8dccabed021e37861f06a43515820fffffffffffffffffffffffffffffdcd1958864d507ba10aaef5d30cdb0ce413031183a65dbc16b4382ab825641a88b929b180c6edc48ef44a55c012df51456a4700581f023be2e1951dbadd3468fb5d5c8407f62ab18103c8e7ec6a116524bd3f9a6c5820fffffffffffffffffffffffffffffce5aa83186accce08eb5985d3ae04b419ee03aaff3b175d1f92adc3ccb5287e4e32b458247c6e27829140fecc2aca04bc17f3032a56fd8149de2f94fdcfc5b65030def24546087711b0a6ebf4766956a94ef81c0320ccdf63b359569ebcfd0a59782c1881c69ecd306b23dc5668d65ebbb393f8c403edac7c8bd2777987e9be58b52bdce1551549aa51fe7f250f42f1a172f9899fa000581f0337fbe85ffccd90a955153fa2054d1f2d72999fc3a961f8ca21187f4fb9905204a8adf2ef2b2c164194aed35d087542fcf900581f034507d586979bec0bfe77864feb7a81fe1b84f1a7a44c9f02d95f6c28bf105820fffffffffffffffffffffffffffffdf042caf79f0ac44ac5ee44be8aafa0629100581e02748c526dc96145113197d472bfddfc33edc8a4959e57156030b2d1a3095820beffeebdbf659f7fefffdf6fa7feffadfaffe7af7fffccd7e9ffffdf7fafffbf00581e02cb6b5095bfed1797135c69be933c8c53931683aadb1ba383cf68ab5daf5820ffffffffffffffffffffffffffffffdfcca261af7b4896be032dfcfaf36c232002191010021906010219fffd03f407bb6136d2f62e49990649a8471f608a5f817fa31fcd504d328289dd803a8e03df32b99224cc97b3fb2aea035e4d7724691dabdff95f29a291dcad836701a98203e412f925034482068b876168680802aecd703cc71c84e3bdce7c86c2840b1718038cdea0e04338c6941957967f90d8afb63d1828b7e60b494312812c09cc097551030b7ff3754ce97c18083c6f8d260715c9ed98f8713e485211be5de882e286db750219ffff036622b32c1c7bfb9e348f41df6fd7047e6bb1feebbd129a01a643c7d0c0389d7e0219ffff035b5b02f9d161fe3ee95333c7f1861820efd97ad1fa8f9368850246ac666dbe8003d3521564268f7b75f68c8dc7cb151ec165836fba4350ec72556142550f388a97030036defdac4d5c34380c10461a7810a139ac78ad1891e6e7bf79085a06604e690339c7b76f6973a9fcabbe74d51ad4daa7183ca137c51f6fb31b2cf8a52ff7093c00581f02793934b943f755112caa96686980b1b7337280b94ea845017e7f87c22df651035a7fda787c083959f00d8d7e409ebdca03d8d15ae27ae5b0d60d6f6fe881be28b64add0c95f2212438618bd5af9397dee500581f0269434dbf0ea894eb6acec7c1074c347fe29250ec4c79a70c425721c43b594e25556937a72285bdddac770b9b0100581f02c8f6eb03b481be333f71428d9cf4004530417ce14258b4c6e8dbe640d8bf4e0d9f7a5752c42facfed022170a8c00581f02e6e7d4503cbffa8f3d16d0a3eb7caecdd9df9b4b4fe5c5dda6d031db25a6515e88a1aec6bfe66d2a5dfdacb7f243736303805f160280ddf3c14e59550b03ce33ceed0e8b9e74df82df12839a2d9614373900581f0274db522868fe88399bc0ed269c760feb5b366b4ac78327d04a1f9d73debe4e2aaa7c407ec6105b8a0a67cbee230324581c9e98379ff626b4af4d704566686b53892d56f723bae06dbce11b9dd23500581f03d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf604e8e9b1766756a8b5a48ba91cf5e7d00581f03764f3cfcfb28d82879fc2defdf54ba5690a340a31265f2f71981be4128f051255f75f62e721bd80569b9fe11c347a3f50219040400581f025139c16086ef4d952c094dd5dd000a98da680e0ed19bf0ede11d7b9aa49b5204d235bff6faa9a0d8ac3250876ad6c770420219dabb031dcbae02c6dfba872583b86368c87fa128b4109e2a197d889d6a30359c3ef783033878b08b3c77a06be5e6d90b938c4a07e3c2ec60f17c4434756cbb6edebaf3de03b0214625fb38f75eaba1d738ec33b81072da34d89cb5f2078543189c95074cff0375c2e3b31ba99c9fb932fbfcbc4a03a375f8ad61ec67e7fd56d49493691f6a1f03ab4c49cf53ce9005707730d712a145ac1a15d4646266ad7138dfb81833956b0003f12d7916b256738a6d160bc371cafbb38f18f28f036b2280e1007ca20f890148035f4755e2408b9fc216358271820c3cb43744c03243aa34b4151f229a6bd633c1034bf30e96bf389eac5b8f5ff136ec5d63fb8ea00e63b272de492b90e5803a328803ce19b478bfa4330d964d45476675269a70ea43cf800055801c445a6192602be6030f78d6d32a4c5b43b165ea8f5ae5830a9a892d129f8dca861f4a85f670fde61c035c6a51741eb0eeddf765af8c454469500c641727b91ce5672c5e0d4ace4265b30357d486179db04db6c087951bb86591b5186243146db894c63cad891cdd10263103d84f76124bde619e8bd743263620204f21470bb6001ba8e4d4e4f2f821b67ad2036e4e381a6e73204a879b9182779c180389e0e40ca3e8d0f7d94b746990b37266034b3d40183aafd90d8bfb8bc09799de7751434407ea04b5ed56ea7baf1f97fba50219ffff03cd2d68e1e192fd651e02f4abd2829c73cfb56989aca5d2109c553d6363aaddbe0378b3c88c367c4233347b0003397a00cbb8718d3a3bb39def7e270aff422efefe0382709d6782d4ef58145dc819ef652a7619ba64f411f9a49eec724a70e35cfe5c03842d54f693b8db764364e9f82c23f45e63a167b595695177aa3ffe08d08f36d7033d4086743081ffe13040f9f9c7db1ed9988ac538eacf37ada140088fb1f997bf036499fa197c1cbb6237531b358961c6151da46b4979c6030f69474a392f3a1e350309b6b14e4923364af8a3fbe2a1d4e9bdbd878c8c0165f7dbd899a80f04248df30347f7edd3b2fec7bdea7c9f572a6e42227837deb0efa7977dda7654f285d894d7039c322c25678921b00ca301c2018e000d2d2e0ae5e4e9d999e7866c0ae26881e9034f934c45b030d717419cb22dbe67ef34a0e2f6eee5cff64dd0845434fae1d7930381bdaf7599575f5915af94c48735411f3420ed964d769e6775affc79e87d5eed030044db15fbce1522c2f4b352461af28a026ecfc7b66c903844ef45cb45813b370368ae27a731d32e240ca272bebabee45e87e33bdf2106a5c6208cce8453a39a5003e519440a1829520acaef3fbfb22c2b2d207c403484d33671a4fe660f48df461c03cff891a8ea3c5bb3421ca14767af282294b4c228779b98bd987fddb2ece7b53403c88b5acc568ffe7d69497f5472afb14e95e847dfe8192aded618daa3c7cb896b0379168edfacbfb1924967b5bb7e306f113f539999eee8ce7af7f5eb59a0363db30392cae48520e6e6fa121c250b499605cf105c06538e275864dffad417690a438000581f0214dadb570a06706cc5010f8ac94e7038cd9b30706cdcabe6208f3b395c6a5820fffffffffffffffffffffffffffffffffffffa1ebeea7f998b0536c053389fa400581f03697f8b1b12e4ca9f5b7298ba1ba7141e1c9bc369eebaaffece9e21fd3c205201726554ad73e901814ae21c01f89aff720d00581f037f5a3d3d997b75eac0379536f1dcc1fffead67291b8d1453cf4447431d60520f4ccae30241069d02d884a9a85fb79bbde602190a00033518bedc6752407385b842305a3a90171584b762b7445d12883036a35ca1586a00581f02d5d1a3791354a10e552903043182f1c2aee4990924314f82f9e29f5260d35820fffffffffffffffffffffffffffffd3615f9271b42da9ba2f067b801826b772303ba21a0935fe09dde5196814362189923103d79620a472563e99eed84b4e1c86d00581f02c6c8c829d617accfb2f90c577868f888cc22bb24c73f5694f64399ea620d51898b3b1550b725d86d326b665cd7f063560387bf7e89e6222b309a84d2287c8da959144c233b8785a5e9623b2c4fa553cef400581f02d1b181357214db009b6653e915340148aec6c22b29bc267feb3dc93e50084e01e478f9b489ba2d76552a89bdcc00581f02899a325a081876398e448fc2e66f6921979b47bf2764becd447a3a6ea6754e01f068f0e874da0dca8c34745dff00581f0226710f052e8fce6ef67dc970a1d192aeccc36780956ba2fbe2b96073bb4a5820ffffffffffffffffffffffffffffffffffffffa7b7fbc3d880e9243ee46056e200581f027edd22f8053c5c4afdbacafb159fdaa7e0a9bf5557809d147aaf66949ae15197e98108a7da7dbf8d9c381b1544697c0f00581f028f9140c1b174b70cdb64920f4a54b1fb1001d6333ef05e11b3b71ef8be975177408f683f7f0b5b64e9a2d04dc85713dc0219ffb80219ffff0345ab3faaad38d306750825b5e805172baf459e014a4fe994bcb1aa1f6c99bfaf0328e74b2cb9dbe673a321ef13f29b12f926748b5e4f4c758057f03891b6efde6d03df7693d023e91975fc426f6abe9b27fcaeba063b717fe8371f5003932d5927dc0311bfdd78e1f560c56b0d8e934723f49b850798ce35c679842beac79755eddfdb0338fb0047df31fb05b2392b28cbaa62f3527f5e1b4e7d71065db7d9a3ceb7870903568accc8bf1e792955e97acf721b488cec3f8972721e1b08281a39ee55dfb56b03da879bc234efb8a2d2961e4f2bd4e7bbcc0a21dcae53c35f68010776ea5bc5c00335e60b28208395f45b33541b9159b8748d307b73803b284fc446829fac17082603ab8ada123035407c04d110fe99ea4343d067ed50ad7ff2e42b24571f6c964e5303670d49780e64f569e8caacf9b97cbd6cf45c56f68f93150d3c548e24c5b67e330219ffff030076db5bb84287a030521c9cb155070ef968c63f1a3009b5d7a4b88ba8346bac032badb1e26ca12b416e29c53d311d2aa5b65ba3f26c71aba6d9be430f1bd7ddd0037e81688edfd5949734a228e86f138b676cd748f019d107a08bb5af600b8dee2503cfd1a539be7216a1a71542467b95fc7a778513b2931d280e745e179e15ebf88303c6871c757f5478306c34effc8351dda2bc93f29cda5bedac4161efb26926cf0a0321366c2a24cbeb90f7039629749b21100e107de01714d95c74ab271a780be7fd0351de48abc2bd81e5dec45089430855a3cb2092fd1797e53519c655c5a40c170b03bbfda65193e744e411d2b9dc7574340c551eb0db5d7e5d6d436f177f18a8bcbf032a78197e76090df39924d55d0db2a747babcf2b6d989feb8dfb5b0a7801d3c6203ac8e1a7499292e45e5c13b062e552b2470766bce5b0dd986aed4f89af22998a003224ace33c0c31a8b9b1f1b9f78bd9ef1ddc50653619204e08f1471a8e676067703623a0e3b4f6e0ea70edce595bd4813c4c800ecfe9333704a27e700345156fd67035a0c11e2f3e5a2716749d92c13626e99f2ad7c5278c98ed4bcea6f02107139cb032090335f7c2b3fa666740c200ef6dd7c274853674062e819ab2862f712b1b63e00581f034d75319c3142c743a371d62c035089d18a717d2a91d0f0f7a58ee1306ae058200166293e970000000000000001ec1876588714887120dbf84200110257f29edb00581f036c8ffba777432a6aa34b94b2cb4a55bacfd8a15149ef3cfdef22ffa3cf005820fffffffffffffffffffffffffffffffffffff99b221da204ccaa829889bcfa7900581f03a68eb6cd8a5f2da94248c19c00debb68ace29f3b59b67b3e2eef8cf686c05820fffffffffffffffffffffffffffffffffffff87aa8b77a990d269887a71e151b0219102103267348baa530bc6bc8d02fe6b109dd01f7d3a26e1905d63a0546acdb5bf48c2b036807f0f629317bf7e506b3ea0a2d209eda206667bdc965d87064e376275bad4a0308d4de54ec1e4306a7e96cb8ae9511f51906bacef6181a4a4ef38a5718d755340312e8d8b1913d3123fd979e248447f2001d375d45c51acd5ebe59bf37803ef6ce0386a0ad911b1226bd97e5029408161d884f9ee9624ee628575746a3823e1b32bb00581f025330bc944dec7f2d1e9dddc7b5bcd05a1ae664a5c600c1c5c5928e81b9844e0dac1632e9f680f869c39a00708600581f026470392e4a1ca535314f3b20da7d7c2272baab98098daefa7cbc704cae6851010000000000000000000000000000000003a0b04bb4759d50a72438a22761453925e333421be4e178ba3dfac66cfbde223700581f02ca7c58b81b20d4c7f46cb9d81e6a02abe34c351bab2beabcbfe1680d55425820fffffffffffffffffffffffffffffff9838efd23fafd8e27be5ed0f7d70840fd00581f02510616274517d748501d8da95e114c05e79278a162f66da660af8c0cb9cc4e0189b267f1c2937ab6d788f298bb0219ff62039ac5a5bd910fa7b92f63e2c463c4cedd7f1b9acc8fb3fc8966337fe918234d3803bb42fc228886f9f96d7462292e5894f60ffcfcbaa191bd768cd5c4b7dd97f6b603225546ca1cbdb6494a84b03eff6f9c487366dfcdd43344587ccd2f0db23c052d03daba5e85c10eeda0936fa9b03c7187a99d403b8b21dfa02c04e8b225fdf134270219ffff0303fa9bfd6108a3bf9e00414f8454b8b68221fc0a257ab7c3860838ab00c3c954033b58a986e8019d1c41620eab7af609350ba4c9e6b563f1bb320c6252cf7258e103006e584da2248aaae606270024fc7134eaf59d5c99f62d4d1513658b5a6c4d3f032b4c61670f51a7bae8ca99c74729dbd2130e6251cd689bad152679bf4167fa3d03422ec39d1082a760f6253bd0333f4397085cf2005bf901c258ab58e48fd94ad203f9f9bfa06a1b6fbfe0b7b6eb26dfc4595a87f43d5b0fca5b7442a6d60453b7c8031ab48ccb520018517b9996f0d861cc266cda66fe2d58bbaac4c48708d685e1bd03ca9223f1e0934602de1d3b37bcbdc9dc75064fc1c92d42d4969ce25a59ee811703b1a7662391b87d9f32a893d7ca10b051ead8fcbdf86ee23010252e6482a236a90325da3b809ec95688084f6761e8871c10ad104545ea2be8cc873f0139405afc60036753e5d793bcd4fb3ef4710f6ee05e1b31b629a59a2e23fd7cc8cf9944c6fa5703bf07a20c910225784ad0f78ff79b8c17bf6602e4b38ec3b5ae940627df8f2d420219ffff03d3414f8aaa4b9c3d86852475d480d0c9c099706cbaf29e3d7c959e4a60dc0c3103504f5df128857c099124f8eadfc732af3c35a6d2738e72a0b380fc77626a83d10374adfcf7025fa87617675543d0fea5364ab79f3d9563fcfe320d66dfd2e45e790219ffff05581e033e8ac06c4bfdb2157482e7a0a264c20bfd39bd733cb3bf258faf72c4b0070119567e05581e035cc3326b15d4214027feba1d57c66e93819dfb9230fb9164794fa0d730040703b118731ad716c6e8af4d155f5c808fb6dc606d84914e7538713b1fc0f91df89805581e03f8426f382a41d09c9d0763379a4a28c2fdbbf448ec3b2323e29e14c5400404033d060472145459f111fdcd47ad128a07a8a4be7d018e0c07f2ef0e1a4c2e89620365f2b060d60d8738e0ee34086ad609cdcb0b23e62c3a0c3d79a9b1ea4b1f4403031f667e6a796099f0d6530a3cdc55500b1f711c4910315aadd09fe9bf32d3d69e05581e031b791e75585ef506d27454c49c394e3f3315e93e149a124a53714d486007011bffffffffffffffff05581e039313397e5cec9598415ccd0f40eab2419de3d1f535ddb7b6610157d0c00c034701cd36bf697c0005581e033ab6fcfe51a9a320ba7905f5943c4c6588b5357d4b7f421ae4cd1e04d00c014501e7561a3802199e0f03964704a77d5cb79c17e67dddabeb1f5650211c6a52002d3248abe0b73c5d89940306f1c94536a974578ea76a27be59a23096e8214ea6448414ac2ece42ea55b12d03a00f6c683040fb9265e447aeb527f9895e8c19a90c2bb7adc5f9b79eb6ef49b10331b400b291891b46aac642b8eb6e19eb945dbd1bb97972c4b14154f78eb67a410365f20207c74a0bce0639d2e7fd13d7927bbb363498bba3cfcda603328627d8f703f23468e936cd596fe67c77ed959e5ce51d4accd939701c0f11cf72e9d49f80830319b0b13ce1942e30f8dd93611b7ad8747815834a092f794bc195514435c9c28103ec205affeef476cffb983a9f99f000fdb042ea6912abffdfb87c4ccc46a95ec103d34a847509e2932e76d90760e9dfc07defe5a093c42c584d322ff311742a41d1031e0dd35265090eadbe34edf5b581c88327cbeb22970f7fb505e9f6fff7a3173a030aa4908aaf795d8a4e75d59dd391657625191cafd87347f2e8addcb53c070f5503121e0517b36d4de39c2b540147815c86fd8cb506ca545e0fa5d3c638ac2bcc2203bb5899c0b5af9775cc10ab2ccf4183bfebbe120d85e20563eb4cb1b2b1c5649c0219ffff037d747532b09dbc1d26a112e3f99a986e71af8e0f4f06f451e4301f0e596aad1703b2179bac83c6f4558c5be896f9d46c002ff8bf7fc009cd690bf7c7ee2aca680a0332273f787883130a022c726a15ba779c2bf47923705fe0b91e1fe5985ce8a6fd039bc6bdd022a2378235f2f96c0c0e80fca056ae5b94a26ede18e6e076bf1720a003744ae8dfeb2c1cccd638b330cf3703513f92cb30ccdc6ff59d3633e48af6715e03524c1c63bcd263a6c8c4bf9ed55974d539749241203e3e58face7cfa16fa82c003e9eee9366290cc4bc8c10c63ef5050e775867103dc1a9c4596e77bff5819b0250219ffff0370fc4eef9937c57ca7adbccf8e75fc181111ceddf2a57b44d9f6b6205d30ff0f03ce0be5123257f25a6c1c71fa4df6a9afcaf9bfbcdd8e47f86ba091fded474696032d8b691c289d8b6cace2ed3aa1d845c5dea587425d1565536613ab1cf2d5cd86034aff47c494c87fc7f45915a29dcdd05fe4a3dee2cc2258b602f478b34cba0b4903f656b53314ab90d53449d29bb1c5230e71611e321882f52826216fd746b8af5503f2614619186390a175e432805686245dd53094972a50299371204a859868a85103f97eb79b750113976f136e87d5294c7bf584678378ac5c596c4a0d79d90e605903dfbfc80491434888169793da5ba612f3ad8b8f4c9ef935f4a3bb36f5c3aca7fc032e7fba818766e6d7f783407ba91a8ca33e2bbc0be23d9b7572b3505199f785c003de46b3c49eaa8f5eb6fae0de18c2dc26a0761b670a09a93786243c239a8582f20219ffff0330f942ef493c6a65d920955a7bd4bbcbe1f5d195c6cc8f7972868150182adf90034754ff019e25a4625fe4b087a2dfccb92dcf96efafe75b9ba30a692fe46abcc00305c983605f0eed0bcb49940363535320a298d99182ee900444fd5f97779bc30703335c6eb59060aa6ac3b669181d3d567c249d1670f0e0d88b92dd6a68f81d1e3d03dd6a7b012af3bf0130abce97eac7862674ebbbba1106ff84b548044689f5ba79032540d804232339db0176313ea5b5c412336213a40be8e19e26720e592ef6492a039b0e5e69662c65e3693881d0fab830ba6317b0e395d0b55674b37e4c58e4abd50356adfeb34885c8d67b99f7d9ca4ca223ca621e56503425c331c9903a476dcdb30308f7d5052ea42f71e30a9672de03bacaedc1f0f8930df659697d69ce47e969f4039924c2b085927ec92e9aa5238509286849e9a66cd90da36bb06ebcd7ad582acd0395352566e236e397685d7b2b553a7c7e5a0c8f45825dd3e99b41413d8eec4ff003fde8f43351294d02c0e8b9c8dd7d315d8a3c7e18e0a1bc162cc6620c5c5f9f860219ffff03d991d8bab348bfe7925d61110205d09e87b30a5d03fc499f028700a64299f81e038c8f93da9b31905c31af72d4d7bb5cb4602d443f9c1e285edfe2b991e0e2af9003f1371ba1d32627545a39fbfe360f0829057bb4b8d104a60a1ec16cfed241710903eff84e36ccf161eaa28b42f91d3fa143307e0ff63f38d0797f937cce161e52b8032ea3a3ef41c6ec40463d2fdd5d54ecfbae27145af49ddecd0f2d82a712c25bc4035271da1df0eaac0e8ac43597d04ffa5be2967d8608f47584a39a20b28b4832dd03b42a1ceacd93248a4e10b141d4787f72a7cff7b5af2649cb432a2afa64fae63b03f91ba8c51d3a5336619d830c93ec78ac209296fc47f10feb8fbb39f6569cb069037728211d9815a452010bc4f7c1fa83d662a0878f0e9fcb1826fdf58805bc0e1603d1c99660c3ded148e04a729df5c45f5bcf25c31eb12282191bcb28b9361b527303a4fd89517328b99f94878bb6a10365c44f515d6208e6f69d91adf833fdb7162103af7e8d9e9a7b41b3fddfb11b67252efe784571bf13fa808c60a0fbd41a887fb80381c0436490f0c39aa850b757e8486b6cb34b991361213ec3f5472a452699ca810219ffff0387813c8ee8947d587eff8fd565f0f0b07d9697294ddcf8389e097ae88985cba1030897e8048e2e806a5e16e05af46bf686a099d0897492b58d8817d25c156d0d2103a8e036fc00d7fd84eb6f16f85a80c49aa6cf256d48afc5648771c4a42269042503068c7dbce1ca5b0fd40d458cf282a35b97e54f2698f7bafbe1daa66e243a8f6f035e2419beacdfff670daa5b9d1c0cc2d1723f9db8a2da15e86218725d9be5abd503408fdb85f41e0102bf605b55416aa39b436bd759a2e50494824fb387eb6e9eaf03b5a2fab75e6db4928a53aec2e7598b6e690b65c07ac77ce0bee5b94e9d33430503b25ed85c9f559970138745c558462ac1e7fb0292ecc81fc0cc7e361f517f2d4403097dfa7b202e8df00f12040b698d796465d90ec4ca5f921e9f14f2740f458b2a036500bc59ae3697259e4a74bf4b35e6f42dc82b7b831bd8d23c678808ecbbab0e03a6fe11315d7832429db054d1ef6753a196a13e3117e524650439510271d01ca4037a21c7b99c57eadd297a8812633e8346142732e739072413efe9f98e29cc2cb203515380238e4e81f070f91af301188abcb2f64d6009da3f13f5a44a4c1d369bd60312f044ca414c4ca664a880a35f2a99269a711745dd7014d83c7c1a429508a5830330d11ed7bfc7b4fbff84be97b91c4fa0971652a00b263d7b0e88b2157a4295e2035f41f795151c9491c36594a4cbdb211e6e472acb2e04575af5f175498b505eca036f69b17ef35251c81c13443c70ebc8c360a90638efabd4e26bbb7bb060ff59ed03b4a39a5418b80c771b7422bbefc82af000dc4049ae9af22253eef8e646471b55035a197ee85cb248b0e9b4a52f081914b952614230aabd59af987992d23ab9b2bc03a4d3218c87c4d77d240906f5e4787f36edb05c879f58b314870c2bf0a2925d0c03e0a5f9c1eba89ab7ed33228bb12b378fbb75d09aab8275dbd6b6edaa2b7149e303f3ce2fdcd9f13fa517de9dd704a66a02ded5f91f155ea099c40821f4abbf547603de016f018239ad60487734f97d13d217fd19f01da1a7d840bdcff7e7b4015c9d0338b87f77965bd82aa9cd2b28e000c5fb1979cefbc2e413c06c3473e12081e56f0365d5135d92bff813c2e85bea832701c859f711557a021548a4197ad8cff1398f033e9bd6b855223fd97a0ed9cec03ce278179847c91ea1bbadc2cf199fd209b63203b9163562c386c38ee8d7a4745b4060afaea6c994eb84d2ced063a3c16006c4e703ac021760761e272d799ef48f16438c86586f4231799ede28d145db34a84969aa0366b29765d4793c92d469b520ae3809acef4f14b76eec1d9cdf2140a42dab428b05581e03b1ca458d01246eea6fbe769c335cb7ab3f938c5a8726fc54777b261330040205581e033d616945a7d42cfd2e5310f685d39212557c18ee821c96579e5020555004188d05581e0352507f09d93289f2000854ca21ec6d108c413ec69d69e55501c7272540040105581d0265a919c155a4eee6caa20efae6287f002fb003186271ffb1ad95f7c60c02470b7c2e85dcd80005581d02ca4549cdbff3ad31d60800a5e862e8ef6deb21f722a281579428568f0c1a000e5ad7485d776e5046bc42cc05581d0252594122e76166b01cc01f13d41fb1c926568c2f2a14b8ef66fdc0de0c0c47026c8613727150021903800352fc2d0cdc5601d1f944529012aa66566856c5b49b1600c7b40dc84ebe04a38d03b94106c7f2b6483fd61c1a29dcf684bb89827596178cbe8bacf65fa13b7e0214021987980380d001ddcee9ea101119e75fa2e405e3c2692ae311028d66bd3f36f87e638cdf037091a21f9119a0e30de0f79ec10a7ddb9ee682de20b1d04eb704d9a3753c8a2203303f30a69832e044400f1e6aa606035d8d7854c10c6f54af14a98457c0e9550d0326874a857979efae008ac4b127574d26dff1ee8389b59349a3dded1837ce9fe103028a841ca5c176a591a1da152eb113146c302f376a67ed4cd46d23ce74407f5f039228bb9a0e66b2462d2f3df607e607ea6310ad4765c1a8ac4631af32391efed103e24f04b273a50516a875591af81a04efab31bf0bdca2d78ecac0651d1f72af4f0219ffff03502dd208560b06be266608bf7855d9f9b1c5962784a7bb37cf5c40b664222ccd03e4b0a420f2f745c2e87d85ac5e86183b8b736a1046a2ef855e935ca2c85a9c1e034fd035d401c8b2714b394786d9c759450384ff6390b1f9b5d344b6b9742b57d7039671b64ebcbd2d5a600ea2ac533eba08daf4b23a9fd4f111bead6c1d142d263703467da5b1b780bedbf41be4fc64f791cdd250cd2e362ff40b812571c379e5b9fe0348557eac8b036c75060266624bd2950728afe56fb12ac0a05fe94fd7a18ebf5f037addf07169f18ff9a3c64973dff35c61fd334dc2e275b02cd09a39f19a2512b803a526d4fb28d0561f6fc8452ed73b1ff29ffa06de7640940a4f5d54948aa3a156038270eb544111e20f7a5ad6050491c629a8c4ad29a1c455c3af6ef8bad054bfc103b63726813365321dbef638b577751ef3476ba9a96a63badba738f111ebf92a2f0219ffff038d4cfaeab5e0b0c4df79902ce6ebad71043933ba01d30803c778334da648855f039726d9ccbf738f95297e2e063ba8c9b219a663ee44b2fd31ac7c7e9af0ff12620219ffff0375701d8ac53c5803fe17160dba7e830087c0f4c43761ebc8daa4e6420b791d8703d8b258ba1e790b8933e1c13e6b05bc2a1beb2c6f7703f185d80f306d2138fd31038a684741e7ffdb317505ea6fa3c4d86f1bb7ca79e8a9640c4b0655acf3c37651038f624d55d66c4692d2e1db274f8e3dde972642e981915fed8e004945d27fa97103e4252717da8cd1d7f1f76680c32d386c95a01ae89d98b21690810d9d1317b3a603e7484171673c9c8b138cd5010b7ea816c515a32100ffb964bd612eeb8fc78df6035f456c3f3e1803093d4f7b57407175519983fd8ef35f6c949e4ea72f6783b80903b438910ef295b27a6ff70bee3a919c2535398315f89ba5c682f065ada39a8b720359c19a2574f3e87948c3eb2627631faa218c8f6eb65fb53ce315a3ffc950ceda03b3ff8f94013115e56b652edd2ae8b52a43622f00c12a0382d82259d3305b4260037a7cf8add3755895a3bb9339ebca6e06a757f5352a10fcc24448f348e80d584b0343daf5610cd7c7fe98e53ec3b4ecb18db95a775f079b06d80c2a59bacabeee4103a078fd3149c07f3e9276bc9fbc8283e9b1e4f899e2fd8c82bf8d23832caf59630219ffff0303a154a3cbda26d32691451246c64a985c1623ed7dcd97c464b9fc4a8495928f03a762200ad413f40b91b721b0769a5f9caf5c4c8f5f6f4d7aa9c2252690d9ca0603fdce3a21c7a979ab7fa3cf1504acb93e35eda98148b5d1e9bfd59b6b894235ee03d0ba94131672ea440341f0e1fc9e5c86e61aeffcde6d9141c91d5af63ffd6391039c4798b654dfe34b249df6f84de2f6eb37b99735cc8e09715349955ef1f8beee033da27527ac1da7b7f224fb441c21c8f5329b59518c98d5c48d772d91dbcc274803e40608176c4de05c1931342801d08880a425c79e33f018850283ef9fd24aa30a03116df2722d5e6ae67d605d84ce92be61205895774be112974ec1dbb2a2bad0a60378190f02a15d45a1ea2fde8e32aa472a8d371aa6b9e1d62c7f49799f4c5493bf03b408c382f0a69e57d127cd388a86ff39640f304f1bce85532fefa723b3995b600341698b9ac9f018f28ca4aa18b567db1923b65e98590f33ff8de3a4344993944403bb1f0150034d7e5c03ff7b50a58964fffd267c2dfe2a70498c9a90745b67357703947025c5500298bb50ab92b164497d243e699144cb4830777434ded310075ce103d596ce52f5ca6970799a9346d9e27bc55feb1f855891aa64b2ee8a78fa5ba0ae037c3e02fced610fc56e13792f84dd47d592bab0449fc3b87c0915f9ec1a1aed9603db1c42ffc773faac1df01ee9e4f28fe1d55282a60c56c7cf05e6eda1f38e64f1035e3bd65780b3c4de8e8a00654de4d4efd772841fa307e6853aeb327fa50750d1030d14dfbf139f14e6628b88bc4b91547cf33ad69c0e493e1afb3aa9247000b7af03e1cb01d2a1df9a6671717aa4f64fb5b7404d64b2eba49664cdcedda908e551f203f2b605970b7b7c3132dd85dc1ef7031d4af99a726150ad45297a5f906f797cfb03ae34215ccaf1c4fa833e03ce2533d1953b64b3a899a3bd52b3f0a7446114ba54038160bbe2b4f69ba2dbc672804ac49360d12511da483bc24d4d9afe7ca5f3552303c91745db06df2eb45e6b137569a4c2b3bcd42b6cf7f66363e98bdb56fe78ac5503bdc9182f5dbb432f8bd38067c95fe2c515f0b6a4a8994edba9096742a20c833c0374cfb47ea88f440c6aaf47d387525d66e3350b132c5b2cb86950c8901fdfb51a0374f5b69166031c7ec4dee73a7506cdb68a114e9306fe328acf1d5407e1a8691603a9900442a14cd891c0b1b4f83d350f4d7bc7b884073e8fb84cc6f73f702d962e030588693480317cfaf4a83dd6e60bf34e1bbe639d15ebeb29be7b9522c5b58da50339a9ac31a9273270abc60490672f0e74d2fe41e63452ed961a355de65c5b9ad603c6cb74633e15b3ebbc963d6bb80001a122351fe36157920136165e7c5562b497036fab87237644cc5d83557514dc95ce40c0be11325e7ed6645f5261f9f40ea67103173a7a1c82fe9b77ba2552f139f3d482cee46e728b9630ef21c242ff53e62e3f0370fc53c90d73202e0ba112493353c52dabf2ea3199af726e9efc42aafd0a1bf603f491cbf32f1972e68d6c47a883522652e0976afebc8b5666cf1fad0ace7dc7a003c20c231ae1c923a1df3087cee404a3f9b42d2c8b7566fa1433885acb5b7488bc05581e0381a4858f151d00bbc606373b3424a475c15bb9a1e2902beb4711e3224004030315912207172c3d593d9c96475a5ec48d19886d2a6c4da8f218b127a4e2d8c7d703fcd0b189aa44b77cc8188cbd309a5cea6125a76b904c3df3853f569d047a198005581e033d2f1bee6a7938a70784d8ead1845ac0430e120fb63fedeed117aa3b00040203bf930c8925e943c5b56035749edf07c99ece773d35852f2e8f0f8492639a107301410f05581e03ebf22c083347a38a76e1e05cf674abc591e5e43c3bedd10af07455f6e0040203aefdf0b549a4dfddb17ab79884222d73a82f71697b1a96128c0256e16d4ec9840605581e031c29047e358bfcd52d4a1ede9d01a1f7ed8abb0fec87eec095fcb6b5c007011bffffffffffffffff03f6e1273e7a2757aae6bc42f33113455d52736a1faeffc865244f06deed9872400605581e03b726d05c2b751555642e2f8c62c1a3fc6d375de5b07a29db3b03f1c23007011bffffffffffffffff05581e03e5df3e50faee4046322232b02122e22521b1b6149b34db4d18dc3101e0040105581e03a94716c9815f4408b46a306d9516288813e70b9cd2ab2d55059897ad900c014701811ac25e725605581d02ff792d852853f0fdc653735c0aea8249019940b289f2524e65dd09e9040105581d029e8d0fcfb587e7ab8a22802818d35e529fbb57e9c98d1da6bf8cea4a0c1647845699f539b358021918000219e4fb03c66de87f6cf3a0de896f63a2bc4fb25f10aa9e0555ad72c5955747c434a68d6d03bb0fd651a65251cc18e519d401a042a1732c6c1a8cbde0a2c23b11b44f78ae41036a128332b862596ae031ff5ff28770b17736161f185bd9417c82d89aa45bd1220399916d95e25185f3461511157f33e45c9e1d10a68119f38b23769b9d5537c9f40372f418c638ffbce9de1f30a457c3d6df888bc5505e6ea0945ee3d72b69b704c2035aa925871a4f4136b3085ef98eba1075de08711ad17a2d3182c0c52e7709b8c603079faf8eebffbdb1e4d8410280fd3d2ab424584a12d340b6f99453b6c02d3a5b0393c1068dd79084b9aecf2709765c4926b99005b768e884185aadf0fc5186b13b03eb6586ebdb4e0553896479204d40756b7d5fd6eb1cb39b7964173e07fae46b9803a1ec8cede4c61f785e8aa21210c001b35fdcfc795f611a58179a1bfe31020e6603d1a3e9bfb2e6bc0ba2f45ca9cb62c0c2271df5d977a4d457eca640fb6c9f15e5037c02d2aa1572aace996ba19036fd7ddb51ac0a2e6cb8943c4df15100f31c0f390219ffff0219ffff03493fdd4a53a841a01710830d6f9ed64f372a96c0d5d5bd574767d5185ca15aec03a04fce8e30dda49245d2ab1eedffbb2f55095c14d4975466245e8e97d0157e0a0354b303864ed7e440c6b4c063d373187b91da1426aa2d52829db970b9a84f09960350636fa0084db8422ce2953d09091aa2a747aa3bf993ccb8d8f46f3e4f057d3d03f2ed07e68bf6bd0cbede25363ce899198b2b42a782e2a1a8b07ae2c1358d412d033a0e13452f3b56a997bcb577f3c597c9e479eeb2d9a05b4eff2a50bbde63a032034d043b2cb3c0f323c285271bb9eb63f439f0b630a010aac02d211fc7262906b70219ffff038d0d35bdeb51f2f680e72dee34d0e89d664a51c1d7151faaac1e71bb36e3f8bb0379c8123b1436af2b2f4525b836e4d646d04de62072b6ed3b20d3a847be8be3a603b2c7b931aa198e7a534ee7e24dcf58e6ee3a78c318d1787c6ba693ac6ea6e54703c768ccd1e6c1cea7aea0e0851b9a3333850ec3b8e28739db271434fbf7e0d12603eed1591fd2abaf02301c8451c31cb8f637138a5af5b4e9f008847a6434fa096b03918d0f9d8225728769a64c885061309dc64c3eb0ef04efdc1baa0100e6ca204d03e5aec56b298aa966765f70ddb78e974c7748a079e0bcf72f7d0ceaad7208e6030331d7e47e7293c29df099b899e8d150e761a5c40d6c3411609bdd7d41db2510d903e21b80af3ac50069a3f7999319ee5d139a795d3d71f8d4e1d1546aabb224bbd90362dad3cb8779c256316a30ddca2f7bed6f17394028b70478b075c9ad9f23527b0219ffff034bbae16ae94cb05f40654c6617a3c5166664ae4ec4fe5d3f9459f87c272d124603b5f8b5f5c046f0604cc09d8ceb6bb425ce5c40bc4ac6f1e5773b67005be5e9d703da2c8b20a62e6da94eac9cd62da39eb5ef892caa27a232790ace8cea2cb2feea031841639ebdce762f2066f96df2b44cfe3e1d676f686dc57c4b8a5d136ba2490503977da587ea197771f781ad3c507b6c34861da46263fa5ec44bb284d49f1c7b9103f193d2c792be598fc97fa44932f3f6f1fda8367e6406e56dd9dbcdb598e8f9fd0333411e37e28250346435c09772319053c282bb61c0813d4225e891fab2262f2603ae88a44105ca9db64c543ac12eac9de13f9940ecc2f4a9159853f695a804650203794fa4a08875afe4c9f54b42cddebf0de4fb879db40a51600404612016c6378f03a8c5c80852d972427404b6e3abb4f5f4034622b398ededba58050f2c28015d17038096c11fe0f5dc1767a4a7cba6863694735543a19d77983512cdee683459aff4030cbe73a9b88aef88b4b4aa86ae0d23583cb628a8a458e74b3eaa52e1336ef85703b0048ffe9cae416a417df5cec87b60f8ad5cac3e0143af20933cc73d1f6195dc03c09514af7455fbda47998004446ea68dc86321b83325a09cbaf07761742f1e5d03e8e7e357b4a2883094bd8e93ec5cb1c208ee36f132a2da2d65d6ccce2db359d8032f5ca4b731e7b08798690b48f42dd30ed4e3761d06324ed9188f9df538f67be7031f8841b8d425431aeb9ae6105ac446c2d5685c5ff69965a440c7c31526bf4b61032cc5309d6cfd154bc813b3956e43a1430bd2970bd684fa8792a30b4c3c3e50c3039aa17156388fb72604faead016cce3303d12c018e821003552adf65424e4a37003ef54810f866fe386a77cd06d6d2af08167c0814545c140d23f1bdbcb2afda7c003b38f0d17ffe24bb7e20b45546ff97be4ab86529e61cee1c2ca0e4d3e7f6ab32e03b0fa2008a12683375f3df1512ae71e947784012f4ef61c182ff4af3d4073ffa605581e037b7477e418ac5a14cf7e77dc59297e2bbeb8116bbc91babfa2212f2d40040103a7e48f0d73eaf7c47528d6ab59ffae7b4aec13c5eb888b7359f014521767c84803f4894f5d93a9fad81ddc3c915bba04e8b24b153184b4a38622d4e353288b537905581e03068fa055a4a7f1eb5000acb98e70096615babccb7b6dd93ba5780f8de007011bffffffffffffffff05581e03463e9b6649e5a09ad0a1dd0c2c26b00c455807a27e0fd38ce5bdc7cf00040b05581e030269386a471b57d4ee9e7d93e34959d7d67a2ed4e4a8facd79970507900403031b460c826a854d61dca82f718e088b8b4c4082ffeb93752d7691bc62c51dc0280605581d02ce38186ac605c94664fdc47f237dcc0f428fa17fda7fd25fda213fb307011bffffffffffffffff04591a2460806040526004361061011e575f3560e01c8063751039fc1161009d578063a9059cbb11610062578063a9059cbb1461033b578063bf474bed1461035a578063c9567bf91461036f578063d34628cc14610383578063dd62ed3e146103a2575f80fd5b8063751039fc146102a95780637d1db4a5146102bd5780638da5cb5b146102d25780638f9a55c0146102f857806395d89b411461030d575f80fd5b8063313ce567116100e3578063313ce567146101ee57806331c2d847146102095780633bbac5791461022a57806370a0823114610261578063715018a614610295575f80fd5b806306fdde0314610129578063095ea7b3146101695780630faee56f1461019857806318160ddd146101bb57806323b872dd146101cf575f80fd5b3661012557005b5f80fd5b348015610134575f80fd5b5060408051808201909152600681526548656c656e6160d01b60208201525b6040516101609190611533565b60405180910390f35b348015610174575f80fd5b506101886101833660046115a5565b6103e6565b6040519015158152602001610160565b3480156101a3575f80fd5b506101ad60125481565b604051908152602001610160565b3480156101c6575f80fd5b506101ad6103fc565b3480156101da575f80fd5b506101886101e93660046115cf565b61041c565b3480156101f9575f80fd5b5060405160098152602001610160565b348015610214575f80fd5b50610228610223366004611621565b610483565b005b348015610235575f80fd5b506101886102443660046116e1565b6001600160a01b03165f9081526004602052604090205460ff1690565b34801561026c575f80fd5b506101ad61027b3660046116e1565b6001600160a01b03165f9081526001602052604090205490565b3480156102a0575f80fd5b5061022861051d565b3480156102b4575f80fd5b5061022861058e565b3480156102c8575f80fd5b506101ad600f5481565b3480156102dd575f80fd5b505f546040516001600160a01b039091168152602001610160565b348015610303575f80fd5b506101ad60105481565b348015610318575f80fd5b5060408051808201909152600681526548454c454e4160d01b6020820152610153565b348015610346575f80fd5b506101886103553660046115a5565b61063f565b348015610365575f80fd5b506101ad60115481565b34801561037a575f80fd5b5061022861064b565b34801561038e575f80fd5b5061022861039d366004611621565b6109f8565b3480156103ad575f80fd5b506101ad6103bc3660046116fc565b6001600160a01b039182165f90815260026020908152604080832093909416825291909152205490565b5f6103f2338484610a86565b5060015b92915050565b5f6104096009600a611827565b610417906305f5e100611835565b905090565b5f610428848484610ba9565b6104798433610474856040518060600160405280602881526020016119c7602891396001600160a01b038a165f90815260026020908152604080832033845290915290205491906111af565b610a86565b5060019392505050565b5f546001600160a01b031633146104b55760405162461bcd60e51b81526004016104ac9061184c565b60405180910390fd5b5f5b8151811015610519575f60045f8484815181106104d6576104d6611881565b6020908102919091018101516001600160a01b031682528101919091526040015f20805460ff19169115159190911790558061051181611895565b9150506104b7565b5050565b5f546001600160a01b031633146105465760405162461bcd60e51b81526004016104ac9061184c565b5f80546040516001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a35f80546001600160a01b0319169055565b5f546001600160a01b031633146105b75760405162461bcd60e51b81526004016104ac9061184c565b6105c36009600a611827565b6105d1906305f5e100611835565b600f556105e06009600a611827565b6105ee906305f5e100611835565b6010557f947f344d56e1e8c70dc492fb94c4ddddd490c016aab685f5e7e47b2e85cb44cf61061e6009600a611827565b61062c906305f5e100611835565b60405190815260200160405180910390a1565b5f6103f2338484610ba9565b5f546001600160a01b031633146106745760405162461bcd60e51b81526004016104ac9061184c565b601454600160a01b900460ff16156106ce5760405162461bcd60e51b815260206004820152601760248201527f74726164696e6720697320616c7265616479206f70656e00000000000000000060448201526064016104ac565b601380546001600160a01b031916737a250d5630b4cf539739df2c5dacb4c659f2488d9081179091556107179030906107096009600a611827565b610474906305f5e100611835565b60135f9054906101000a90046001600160a01b03166001600160a01b031663c45a01556040518163ffffffff1660e01b8152600401602060405180830381865afa158015610767573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061078b91906118ad565b6001600160a01b031663c9c653963060135f9054906101000a90046001600160a01b03166001600160a01b031663ad5c46486040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107ea573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061080e91906118ad565b6040516001600160e01b031960e085901b1681526001600160a01b039283166004820152911660248201526044016020604051808303815f875af1158015610858573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061087c91906118ad565b601480546001600160a01b039283166001600160a01b03199091161790556013541663f305d71947306108c3816001600160a01b03165f9081526001602052604090205490565b5f806108d65f546001600160a01b031690565b60405160e088901b6001600160e01b03191681526001600160a01b03958616600482015260248101949094526044840192909252606483015290911660848201524260a482015260c40160606040518083038185885af115801561093c573d5f803e3d5ffd5b50505050506040513d601f19601f8201168201806040525081019061096191906118c8565b505060145460135460405163095ea7b360e01b81526001600160a01b0391821660048201525f1960248201529116915063095ea7b3906044016020604051808303815f875af11580156109b6573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109da91906118f3565b506014805462ff00ff60a01b19166201000160a01b17905543600655565b5f546001600160a01b03163314610a215760405162461bcd60e51b81526004016104ac9061184c565b5f5b815181101561051957600160045f848481518110610a4357610a43611881565b6020908102919091018101516001600160a01b031682528101919091526040015f20805460ff191691151591909117905580610a7e81611895565b915050610a23565b6001600160a01b038316610ae85760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b60648201526084016104ac565b6001600160a01b038216610b495760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b60648201526084016104ac565b6001600160a01b038381165f8181526002602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b6001600160a01b038316610c0d5760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b60648201526084016104ac565b6001600160a01b038216610c6f5760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b60648201526084016104ac565b5f8111610cd05760405162461bcd60e51b815260206004820152602960248201527f5472616e7366657220616d6f756e74206d7573742062652067726561746572206044820152687468616e207a65726f60b81b60648201526084016104ac565b5f80546001600160a01b03858116911614801590610cfb57505f546001600160a01b03848116911614155b15611072576001600160a01b0384165f9081526004602052604090205460ff16158015610d4057506001600160a01b0383165f9081526004602052604090205460ff16155b610d48575f80fd5b610d746064610d6e600b54600e5411610d6357600754610d67565b6009545b85906111e7565b9061126c565b6014549091506001600160a01b038581169116148015610da257506013546001600160a01b03848116911614155b8015610dc657506001600160a01b0383165f9081526003602052604090205460ff16155b15610ecd57600f54821115610e1d5760405162461bcd60e51b815260206004820152601960248201527f4578636565647320746865205f6d61785478416d6f756e742e0000000000000060448201526064016104ac565b60105482610e3f856001600160a01b03165f9081526001602052604090205490565b610e499190611912565b1115610e975760405162461bcd60e51b815260206004820152601a60248201527f4578636565647320746865206d617857616c6c657453697a652e00000000000060448201526064016104ac565b436006546003610ea79190611912565b1115610eb857823b15610eb8575f80fd5b600e8054905f610ec783611895565b91905055505b6014546001600160a01b03848116911614801590610f0357506001600160a01b0383165f9081526003602052604090205460ff16155b15610f825760105482610f2a856001600160a01b03165f9081526001602052604090205490565b610f349190611912565b1115610f825760405162461bcd60e51b815260206004820152601a60248201527f4578636565647320746865206d617857616c6c657453697a652e00000000000060448201526064016104ac565b6014546001600160a01b038481169116148015610fa857506001600160a01b0384163014155b15610fd557610fd26064610d6e600c54600e5411610fc857600854610d67565b600a5485906111e7565b90505b305f90815260016020526040902054601454600160a81b900460ff1615801561100b57506014546001600160a01b038581169116145b80156110205750601454600160b01b900460ff165b801561102d575060115481115b801561103c5750600d54600e54115b156110705761105e61105984611054846012546112ad565b6112ad565b6112c1565b47801561106e5761106e47611431565b505b505b80156110ea57305f908152600160205260409020546110919082611468565b305f81815260016020526040908190209290925590516001600160a01b038616907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906110e19085815260200190565b60405180910390a35b6001600160a01b0384165f9081526001602052604090205461110c90836114c6565b6001600160a01b0385165f9081526001602052604090205561114f61113183836114c6565b6001600160a01b0385165f9081526001602052604090205490611468565b6001600160a01b038085165f8181526001602052604090209290925585167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef61119885856114c6565b60405190815260200160405180910390a350505050565b5f81848411156111d25760405162461bcd60e51b81526004016104ac9190611533565b505f6111de8486611925565b95945050505050565b5f825f036111f657505f6103f6565b5f6112018385611835565b90508261120e8583611938565b146112655760405162461bcd60e51b815260206004820152602160248201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6044820152607760f81b60648201526084016104ac565b9392505050565b5f61126583836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f000000000000815250611507565b5f8183116112bb5782611265565b50919050565b6014805460ff60a81b1916600160a81b1790556040805160028082526060820183525f9260208301908036833701905050905030815f8151811061130757611307611881565b6001600160a01b03928316602091820292909201810191909152601354604080516315ab88c960e31b81529051919093169263ad5c46489260048083019391928290030181865afa15801561135e573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061138291906118ad565b8160018151811061139557611395611881565b6001600160a01b0392831660209182029290920101526013546113bb9130911684610a86565b60135460405163791ac94760e01b81526001600160a01b039091169063791ac947906113f39085905f90869030904290600401611957565b5f604051808303815f87803b15801561140a575f80fd5b505af115801561141c573d5f803e3d5ffd5b50506014805460ff60a81b1916905550505050565b6005546040516001600160a01b039091169082156108fc029083905f818181858888f19350505050158015610519573d5f803e3d5ffd5b5f806114748385611912565b9050838110156112655760405162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f77000000000060448201526064016104ac565b5f61126583836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f7700008152506111af565b5f81836115275760405162461bcd60e51b81526004016104ac9190611533565b505f6111de8486611938565b5f6020808352835180828501525f5b8181101561155e57858101830151858201604001528201611542565b505f604082860101526040601f19601f8301168501019250505092915050565b6001600160a01b0381168114611592575f80fd5b50565b80356115a08161157e565b919050565b5f80604083850312156115b6575f80fd5b82356115c18161157e565b946020939093013593505050565b5f805f606084860312156115e1575f80fd5b83356115ec8161157e565b925060208401356115fc8161157e565b929592945050506040919091013590565b634e487b7160e01b5f52604160045260245ffd5b5f6020808385031215611632575f80fd5b823567ffffffffffffffff80821115611649575f80fd5b818501915085601f83011261165c575f80fd5b81358181111561166e5761166e61160d565b8060051b604051601f19603f830116810181811085821117156116935761169361160d565b6040529182528482019250838101850191888311156116b0575f80fd5b938501935b828510156116d5576116c685611595565b845293850193928501926116b5565b98975050505050505050565b5f602082840312156116f1575f80fd5b81356112658161157e565b5f806040838503121561170d575f80fd5b82356117188161157e565b915060208301356117288161157e565b809150509250929050565b634e487b7160e01b5f52601160045260245ffd5b600181815b8085111561178157815f190482111561176757611767611733565b8085161561177457918102915b93841c939080029061174c565b509250929050565b5f82611797575060016103f6565b816117a357505f6103f6565b81600181146117b957600281146117c3576117df565b60019150506103f6565b60ff8411156117d4576117d4611733565b50506001821b6103f6565b5060208310610133831016604e8410600b8410161715611802575081810a6103f6565b61180c8383611747565b805f190482111561181f5761181f611733565b029392505050565b5f61126560ff841683611789565b80820281158282048414176103f6576103f6611733565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b634e487b7160e01b5f52603260045260245ffd5b5f600182016118a6576118a6611733565b5060010190565b5f602082840312156118bd575f80fd5b81516112658161157e565b5f805f606084860312156118da575f80fd5b8351925060208401519150604084015190509250925092565b5f60208284031215611903575f80fd5b81518015158114611265575f80fd5b808201808211156103f6576103f6611733565b818103818111156103f6576103f6611733565b5f8261195257634e487b7160e01b5f52601260045260245ffd5b500490565b5f60a082018783526020878185015260a0604085015281875180845260c08601915082890193505f5b818110156119a55784516001600160a01b031683529383019391830191600101611980565b50506001600160a01b0396909616606085015250505060800152939250505056fe45524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e6365a2646970667358221220cd1a2118a4607b0523b2990d6e1df8402151cb294405e0fe19cfb7a9df76bc4c64736f6c63430008140033005820028dcaf8ddc7d8d7c8ec1cff0af39bb6085e0d747114b3e53e8eab85c25fc9b04101005820026b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db05445a70532e427ace9f67dfed997ea7e42020af43e0058200228844264611e87bf6f3a55f5811d7a50ac0200f039fcd09d24ffacda02530947071afd498d0000005820026c5842df362c305f1a74e75bbde32f564084f950af916ff4e2571ab57b38ea48012a6d8e112200000219204c00582103b6847dc741a1b0cd08d278845f9d819d87b734759afb55fe2de5cb82a9ae672047071afd498d00000058210390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56305445a70532e427ace9f67dfed997ea7e42020af43e00582002ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c6847038d7ea4c68000005820025f43060733ea08919ee884c1b0f088e2c90b6a12a123ac7c3f989bb70a235b47071afd498d00000219800200582103df627f2e691bfba981d13cb52cc40c4aa63b23def8c6704de4da43573d28a12047071afd498d000000582002e38a03e7cea2f9e901c34b8b960f33d7bf92c0a40000743ee84738a3c9515047071afd498d00000058200205629435bf2f6c33497c91b581845a7026307c25f9f42ca9fa4dbcf990ee5147071afd498d000000582002de8ffda797e3de9c05e8fc57b3bf0ec28a930d40b0d285d93c06501cf6a090547a250d5630b4cf539739df2c5dacb4c659f2488d02184500582103d1108e10bcb7c27dddfc02ed9d693a074039d026cf4ea4240b40f7d581ac802047071afd498d000000582103797ca6e0d1beaf5dcf85823eede7cb3baeccce81be37693a7da8f107e5f3e9c047071afd498d000000582003b4a454dc3493923482f07822329ed19e8244eff582cc204f8554c3620c3fd0410800582003a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec3444047038d7ea4c680000219018001410b00582002eb37b7e5acbfbcf37bb7477ce1a97e325fed8cbbc157cc8e6ae66937d71e9747071afd498d000000582002c5eb6aff7937d3b55bf0593144d4cc1bd0cd1929d8c24db628c5f95248dbdc4101005820026d7b5282bd9a3661ae061feed1dbda4e52ab073b1f9285be6e155d9c38d4ec57010001d95fc8448cea732810bfa312a3ebaaafa2c6a3cb00582002b5281a71e22392d2aa0bd95be8716e0424f3ccc46fc4db2e15c35d50feaa8647071afd498d00000219c0c0005821037b6990105719101dabeb77144f2a3385c8033acd3af97e9423a695e81ad1eb50411400582103652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f044012e3b670219bb5f05581d02cb4bd96e136ca13b090740d3f00be10f1969f73d302dcac11e7cce450701191a2405581d02b82e746dab702ac7428790fc53bc0262a96826827b3fcddc35c82706040102188a03cfa20bebfd29f1d73e7d6cad1625a81bacf15e427a9561f985fa08ba1bc67f4503562d59a51820d47f520c975e0b2bcffac644a509749a3161f481f57b6e826d210605581e03c2bc73103e30a60b92a2f75102c74e298599640d59e80c6fe56abfd72007011bffffffffffffffff03af35f51f228c597a8268940546953e12e15894466096b84eb339d97fbb38497805581e03bb993e3bc70436604564b6c61f558fc1de7a78559b778e04cda7cf01400c0247047fa8a932fd5e05581e0369318c4aad4e25e697671a74011c5f52a88bb77a09d16b7750f46ca89008479e58b728612c000219fd0d036ee9c60abae91129cb30bb0295f5831c66eec16438f18fd976330cadd6e02b78037ca5ed52c8162fb565e72c9ef1355cb5cc7dda8a6a5c1e48449c8e1a9c640d2b03ef17d7bd346608acd5e4eaf97ee3130758f66fe23d222a8fa1fe837e4002c4fd0315ae3cb0aada2558f82aae70c8d86a2eb04079fcdf1bdc5831313859bf2d0d2803085fb4fa10bcd6a9da0e89e758b31df4bdc3de33962bbe4c73d02905a5e8c322039555281d2b809790bc436b44b98d13f6f22a037dd2a54405cdbb8dd0f861003503c883a3bfe444a7701c40737aecb49dfe5b94d66552f1dc82d58e197a67cd580503438c8dc56d7b0c6c15869637a7c8271c346b8a6c6ebfab5becb82e18f65b79c103739ad4b716a9612c1c30ae5b86c7cd20d6dae8bf473f0cedc3509d035274a3fc03db0c664c2cecb55cbdb936ec9b3549350d4d2ae7d13784a760deb4eed95fa7b203e6dc4a9754c5653682edf43b3e4a1d0df8588dc0243bedd7a974158450799de703d200c5142ac6cdcd0d17ffb299aff76f0c30a81db62fbe41ffbe5a11167431e7031cd5a8ebc4e7b1a14c892d2eb886821b9068a455f4c3cbb7e576ffc3c66ea5b90219ffff036f13b1331ebd56022d8c17e8efb30fce481d6eeba82c539f18b7ba0335ea253b03970a55a7f9dd9d3975d598c06427f6911247c23a4a61dc23ca735bb5f934bd7303929ec690ca00ec49576a6eacf776b9605f65f3dcfcfe9c13677d4d8aeb9d0d1403f8f661e40e170f1ff30e07c35d58ceb1e89486cd2152b9f4c777118e087a4a6f0219ffff036e223791a03afff7a27a8ca4e194a841d845fbaf90cc3376b110fc54f7c0434203d8354ef84fc404002bfe0c5666603b034cc1eacb667f395cc98b7f0d8f66583803dbce0d35a3e0f46271a57b5142df96008e13c67dcf3ca912761ed6cb59a0cc3103ef5b8b2b992c41a3b7fd9e60e4d71f0e441cda1411b382cf0f0a15fd6957e5c703cc968c167c6bccc444881e21b9d814f16cc4ee72883083153ef701dc474c62860392df3d69da7a1aa6085081c977abe28fb83a7cf92d01d63949529cc7ca022bd5038aef9af98cc989c304e1da0954a1a5c0cb4e9ae8c6c00c7f6cb199aedad1ee220219ffff0344981ec9072d8fbaf350f6b182bd797e3a7f02caeee3261318f113a0a310577e03e2ef479408e0c57d15ea7f36bc80aa0fa6c12f5fdb7ceb108cb1aa7545e8851d038c1428fa26399ce8e02e7fd2f2f5758072228ed44b500ff1e4c61bc83c9ed6ee03c8092b2371b0c0c1ebbd04515611b4deb3e56f4965867982433d9aac5d7c2dc703ade981cbb648abbfcdefd827e3b6226f95f4e99f141a85757d16afd6df89b97d03d9683d27b02aa039871e5121f0ebf4544fe0cccdfb79279dd5e98046b674f0ad0320051ea881bc8cada86b8a81ffe289cc931cf98de735d1c40d1d80041d89df46039873eef0b601b9910709ced9b29a387ce74714bbfd009658d07888df4a952cea038ba3c131f779d436e6d7c22ce751110e0f9960cbccbec9d8bae1bfd74f2b3d920336da30787cca2e0b35bf668c67c6edea11121247d372776589525b1236140d5c0395744c74c312e27eafd2e1e46e9790fa1eb293267630b04f8fda1a7ef2a840cf034db51eeb5b18b379e60160010fe2c3c7bd14954dcbcda10ee9d417f65d4a665f038a39a886fffeac2f55ca5657cb8d0785d94bbfc44e6ef3d788a37fc8d51e871703e7136f8beed024146551c699746551980d3c9ebeee95c28d0c3bc479ba6c8b870309e468098ede3c3a4b38e67effdf24acf665c8f215648a2a15acd048b691dc52038d6d3f5728053b789c9263fb2c4f53244bc6981314fef24d1ba4387544bc8df0038dfa433881271d1f16194f29b4848d8455d3bb04832eefeac90d24a40b651fbc038317f338c421a3ff798cee3340331c0e29acbb4fd199daff3678dfb916a60da303a2d833f5221678f65bb919bab928e1ac2a21fb16c768c802385743c1f20f33bd03882b4ff677a16011e26972d50b9aad31dcdd6d90ce5ad017be729be29ac66bd603f3cd42c03862ca5c89e09108921410c77399a67e00a0c87ae3873a4db3d0712203e73b2ce57b9957bc498d47a6ccfa795d9cce036014c96c82550ad4d4b99d8a350349f87b79173f2152feb529877b8ebf378bb494867b7b5fd46e21c38c04b56b39032c53e4633f67dd8f1cd3b97857e2fa1888a08113bd4ab0ddd9f3f0667b75f31d03ebfe5a0154be1c4918681bb0b84185583e2381f1c034dbfeed740abe5a18a73e03de89818740c35966944b33a3f7b7776730866295e9ab6c54dbc38adac9e66efa0354fdd1dcf7c140089c99e10173a00bde1620974e76caa85b89c61386854d2e1e03a4fe5ae7bb248ad34f6d6c3cc546f3945bcabb9ea6832e5b2c126fb2948d37b0038436e7414d7bd51873cf6d66cd7100548bc241391940c4596cb8fd8318c9eb43033ef1123618d733594d9bde1b472f2695835b273426ef0e82d2901eced8db740203c6d4fb8c8da2f5bd13806af815634247d82f88e6f346c80f4bc6aa9c4ea3cc6103d27584f834991d48dd9d39eaf146fd21299f67af62a7f0f8c56f316b58c812f503c22ffb33a73ace056ec28dccf8f19920c8075a47151913e13bb76de334454350035bef61b2be3a91a5d66d92b259cb46bbe3a8a5866efac22f59f73f70174791b503c892127d3f6cceb7c41af2f17aa67450342f091879adb9acf3a7b5a8c1035ddd03556d3ec12e5343a2ef72db2e7ef381104279b2476ce4ae4a56b660b50347595203835ea527241d3ff19f15c1d172c75245fc94e1e2680fe5d2551b6225417ecdbb03fffa2540a1573b228956fa86248745aed31ae6e2c983714f32fd9dd7fc58c36a05581e0329328c87be9836183768908f30171daf16714c1f39a360b617bd23df400c0a4629546746ae0805581e03934cc196d443b1d2c5588f9fe55fa70f673923ac0eef128d9203835d500c0147c7a8357a0fa80005581e038bc60869ca403f3d41ddf8575cfa5be7785783dfb7dccc395b1ad999b00c01470d35cd2acec40005581e03d4abd7a5e810b20841e6aaf7fdba78949602e371cc5ff9aac8f74876200c054702c0246afc0c000458613373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff01550003937866b2e3eeae09cac3da53ab3a3bb6633acc55519aa5b62419c8832d8a0d5903e23744b1cd69412517ebb842ddba816b5ce57ac732a6fd9363683b726e9283a803552df02361ba73b9049088baf173f8a0374b49f5cc58d2bc702a2267c168fe900326943072e1d48562b1c43df99e402973cb58b21353ab9e36143cdd2e464c5a5d031d02ba81346dc86f3f0d9d2bbab2f2404d3200a7a5e6114d98717b944905405d033795bd5ac86c4e17541443163cf72b9d8c5c777b13b289d7e47799300567321e03ed2c9ef2b442b2e5d9aca2ff7982d84cff5e0b6fd6245ee60e7402071147810d034441d7d432b21d0be73ecd8f4c570391057bc2c8d9a3b1f7b2175c2afcae0ab90058200322525773051ef8058029d051a1f4c5d9eeaf40559917fa7a2a89b947411fc0446637ffd303f2b517c7bd2cb565778ec56e4b1bfd06c830eae417e5a559fd449e14e0306efd03fc38564d10da0417ae8e4fef08e4caca4aefc0ab3425fe72082dc30a2025db41038aacbc16b5484f3b1b382ca8c2bae263d31a97d90c53f8698a1a342704fac644005820036b0ff83c28e07db9b87f650bc5521a4928b98f2734605e31b316abbb2723504466372f6b036feeee16e8cd34c4a4e2ad2701022cc4e7b30c157fc8bfed017c26a64df68a1d03bde8679935a676b06f226dd38fee8fdfef7280023b4d573066a721dbc52c5ba5036b90ab86f9d4169f06e40a786cc722c207b8b1a3292b4e2a28fdfc34bfc0e189034410d32bf3aacddf86198a4c818896fb858a78657254ca8d9d773a76229b6a7600581f02532599d1c11e7369ee73db3173b6120a2b3af0c17f34dffef22f3442f09958205ea4091731346538d72cf90a95e0f2dfff48df3e799350a5c640bea8156abab7031b26617514c3d8caf5e8b48c5fe7eb56c37f34e892fe142ca39b492942cdc43b00581f038a122f53eca4d8d94ccd6add8a4fa3332488b96554e8b3d828e0d67bcad044663691d300581f03a82608c1838115f9edfb7aec3769d6fdc9ee0b8442ac2c321a90b1e1c8d05820949babe22c164f1faa1e7ca69cececfe3edad8dd793e5e0c9fabd5a5a0fea1eb0219600000581f020114cbe4c25878e8766c3e411142f2c25bfacd3d1c3938aa3a1517ce6b1758203a61ab97118928d845ac6ba99cc8927a98a9b09dd3dc16d4a3e5dda3a5b37bd002190a42035e05c858c3dc6126f674ed9ec1b6cb5f0fa49bc0f763946b138324afc8c2c8df0219ffff034ee76324509814a1d2d107abd70d1c0a7987db3b741b6dc568765ff3fa0a293403216f39b2bd0fc78a5be014d7c10d5ca17f4cb5040dbfb4bdcb2ba9acffd643ef0372e11e94396a1dc3d3b2bcd2ba5f04298277121840d9e9e5d53490ea3e25a08c03cb5b8ece8d47cca9db5e57a518b00a40f16f6b742dc6bb573eea02ee581f6c5a0376ca3ac70bb67e269832cf892d8769b5aec01647f69017b7b1fbc10fca001c9703025f4268fc74d169599f9e3fe45bce9fbec72e3fcce0d22f32e9761ddfbb86c903d81fad9827042c42f019adaf547024d3726ecc023555e7021dccf0e09c8d9a320386fd7345c676a3e7d0442713d1b2738062024bb4fc7f5260931a24c85ef33d6603110cf5cf3249b42e01a92e778bd9ad1d9c60c8c16677b968b4c62bdf681841ab0316780baa4e9ab6967c4931c43adaf52e1c5563b6c984bb50d3a597564cb2fc7d03ed679cdc905534d412aa05f773251e36e2a29848ee7a75eefe35b0a1b03543600340a80efa0498d313affc426ebe62fa68d353e2988764a32ae4aba6bfbf51d1fe03a5d11f8f61e288dad50737eef189aa0328a9ae2ed3681871836ef1a71f44a5d003a9161f54db36875d6c9f2a88b6f5b1b3cbf6117b29567d463d4fb2a52672117b036a1e9b7769dbacf559c0aac32b5030844cb77a33884311fe95b989395ce0bb350219ffff03dc397481f7b66fac54c693a71f57973d20800cbcc0dd39db91b6d6beebae586a0324552a31a3381865f03eea83fd92267405d9d4c6d8b833155a043236b2c10062034d401f8a8ac8cc7ea008c08991465754708f32b95e8e59e6c6b4339050e3f82703325c5e51e339e8e028bd0ca5f34c25a956e2087f86e2770282bf5633177bb78703266f9c1feb2381842a4e72917acb3adb0dbd4cafe8bd18420884b210934497560383f99cfa9161e369b5de5ad0b00ad9c84552e70bc5f10d868adcadfb5b58bcef035e5e1acb44162ae62b2726d4af15ceae5d916684876b6f05f15dbabe01f87c010324d81147467399c9db37d7a8764392b4368dc4d56d6e5dcac55b251b5a518b63037e8b40363f29defdb9d67efe0a1b33000b87aedf2d865dac2152b2e1a2a8c9cc03f0abb689ba349b34ba85c03da0d043e061fd08063dfe2c2cb7e49510800d8d0903a5df213e8071389427a506fd6e87bf303c6feab688524f6d03252b12ae1ab18103910711caa538ce4699447351bafaa45e25fff0299cfe7f560933ca6020aba35c0305070c61118e6c4ac4c1173490637e33d3bd52f58bbd0bf9203ce7bd03bee23e03c2d8dfc11d4cbc0af6ec50492f7ddcbc0747d5ddedce7409f09b529f003dec6c0301db8c4038417fe2676a7739c631c5f67ea20e33d48f6d4157f1f7a03622967700582003fbc1d063fd648afaa24087e3cbfd5c59ac8a9dc70349754e4e6104f1e78790581fe3f8ff0bd64314c7a13216f4dbea169e005d4a8929c6eb34eefd4f45fcc1ab038b0934374b0e4452204ae2e1f1707734c90e843b6f098a98034915681a30d53f0342034c7626f1585ff5207e7709e064bde06b1f4b63b67adc807646a1ddfc97d200581f02c6be597f7e493807a863ec7ae62d42d79551505c85a7d890c53c7039fd2c58209287be006d02da10be48ce53e3aef024a8014adc23d0b777f3953b51edd361ce03590a944822ea07f84d87fcf520a3fe871cf78dbab5256472c20c22cc73d510e800581f022ded293bfb22084a304fd076f6803ef9bec175bce1e43a9e603c9d272d0f58200fc5440d10f9feacba2a5a8104b45b28ff781b2892f3598c8337e61fd34625b40219080903939c6f686c811be1a5dce8e63ae68592a92e2b5ad91bd5f37fb56a952eb6d6ff031c7da40902002743940fdd2202322926ed0242aa753eba13d45012edc83a57250308d99b804daa5d2bbb1cb143d8d72eab73ae10422b318afd20105bf597a99a4e03eb606f7258ab6e4ffec273c4858c50c400c712a6275817b6f638d24e8015b7b30332b12f8cee009d5f50c0bb3959d5fffee16908c39ce32c5df6e2fe33cebcc97d0355c38bf29d70bbb4b167df6e6f097b7f68ec3ebf506f24bb03d9abbcbc27c737035621e9dd3e55543a1e36d0aace467d69dd864b7ce001e27329a731f86d6445e303e31368b9d85122a6cd61ef5d373488fe32c5395d581b43f08b86f00cc313ff7b0371014a6f5449f82411cd5fb121e70243d6305e286a87e8dd3af89e8a5d3e449c0305b124f6154376ab74d4115d2622436979f339dc400a1e4479b02bd7afbf002f036c2b9277de3384b5a77d165ea4e4f3699f237972db7c912ea8770e055d3f7a2f03c0259f1742d625f2a933bd94c7fa55906204083edae8deff3f2f80ec8e948f890219ffff0342c4ad5304fba46ac0184eb99276d01c5b4e5b05f432a049d2011e966e47ea4f0357a686dd6dd84660ffb48b2b1ec89603ddfc1c4fa0f292a44d1c78cfe3ddfb5b03d1f5df086ab5e3d367e2763df2e4bd2abfd1b0238427945f1a1def291d7c902a03c1b6e4d64117a14c462fbbebad18a455baf68215840cbfc39ab586e30cc4b33403a3f59cab71dae19e15cf0497a5ff305844c63e2b6a8dd783fa4458fe13b738df03f80fe21d7fb08d63ade3011710bf71d060cd91f7e98a40516ff88ec433c8c9120305716a1b0e21eb3859e566c614e76f52c84d291b7caad9e251e8e627859d30d8033e3e367e20785bf1086e03fde650ea930b501fe925cd9a34aa4fdcde63eca23e03aed190326c5d834cd3c5e05a7dd07455dbf89405df83f880426c20f3498eae5c03dc177b41f7a2323b34b4be78006508b3294c57da53531af974d9ac73f47ac9e303cf7e41ab704706cef980ee74b37b7ceb77272d51f9af23510dc0c4f263dc7ec30219ffff0219ffff05581e03a92c6bc4c13a5ec45527f0c18ea8932588728769ec7aecfe6d9f32e4200701186103efc4a95eba2b0e8ca1e7050522d539fcaee456a9baaed13b74846ce6e9f5284f05581e0387d430445038d44dfe5f67a6a29f0e61137f74004f19eb39df5f6507100c134622a41c687260035a6395100cee14b8619641123268781a56f8d9fad4ec4d364c15d99ab81fd8650219d595035d3b51473708235f94b7786ec3d9e1141cffde44d1b3c2f8f50735bc89406bdc0219ffff030cebfba2861bd2195e500fde0315af37f8f19a646878cc545a54b8f105f57aca033cb2c49436d7563a7ede67c0fba8e668dd17402082e216765a5457d2b8d7e12103e45da9fe37e969afed21a06e65aab4b8219e0be940cf6c2d96a3312dde77c4ba035f774524ca9f406192b4860444877e4d5f30a9c74bd06649e043a5c2dae88d8003d60fa00a626bce0862148fce6082480f621fdf13ceefaa278e5233b2972f532b03aa882e77de9c5268423e90105935f45eb651f54826727dcd0f60ed0c10f2be7c037d3a4a4affdefed487969723b53a5ff8092517074155829ec03241729f8707fa039b519826b67d1b6667ddd827e85822817f1993869dc5dec0ea1501f750af7e8e03cf09e161567ed1b6e29369d3cef8499f2d95e76f2624bd19ba1ee8af8ceb0818035b00880acacec5456581db61930702e5a416c16dab3d50468682fab6386b0f130219ffff03442811299717fb1da8525d9983c69db00e5a3c03d5652bd184e685ccb7f32f08033a90df83b4a40124fb24efcfa5fa860a84907c5cdf046109196ac6f7be49c0d7039f70bcaf5f415ebd778713b48e628e87a886f43d045c15ab9571aab101fda5830307ef8875886e41a3368d284a461ef82abad1515f9971c7fe974112160fa2e51603181e879df1290efca0721758aad4c72e49df9288eda58ef9daf22351f6bba84503ab69d28d1523d9836042326370ace5f56c98c030cc7ca5beaa0914ecc8d104ba037af13abe00b7ccfd8eca09cea3b096331e4f9d5e33ec3749b78a29f47a5069ca0377cb1b430cc0deb5ada6abed6c8d385e3fca8be5d3e067d72cb4800ae861befd03e70accd5c589e2695ae8554f22f19077a1e4313f19a6e78983a53b91e5bfa3320219ffff03fcaaa0cccabac4e22069a25dd0a91988b5e23e0b603978cf3bba6a72e9753463038024169a38953377af68b03764d0ffa2a9173f78a514c8d74e8b310a369428410219ffff031805e202857d7a867806b155d24ff3da4b351e73fb0d193c61a5e0eb6c3a6f7703f58832d8ca62a1bb0f7f04c3b74c89dcf4b26f7d2cb0bb25ff68534084bed3e8030046751cfc01bc978c36b688ca2f567c463d622ff42051ef1e211c637612ad8c0390e8657735e4cc3a3f4d22b9c4091fd642dfcd7ce26855bea027c18d5cfa72cb039483bd6c4f03e1ed8f5a49da52216551d0292a29c7af06969d247f2c2dc164ab032ebb6547eb5f487752b34948331dcc91de40ce71c8038ac259fe1d47f583d646039ae024332a4c29464c22bd924be9710e527bf665d8035fb5a930f74f1c91c28d03dd8ba265265bce74de1dc0b678a081a17aa0343fd24ca286507b2f49bc326d070219ffff032059218ffbd59bbd2c643d574b9b0160e76f8af398d553317bbd4c9383c7bc3d031a8549b15a8de9fc463234e894707f434bf3191e67e3e22c6a2fb4fe224e72b203e8ec7d2dca594a84df8979579d5841d99ea780d8792a52335b7c0dbfe1ed014203edea73fa5b74b0cb1514e70ec8a0a7e23840b1d107a8f2242ac79d7c1abacc0003bba764670960a04fb0e7d967d19ccaf44b2fe6f869df6d3c23a11d8971af95bd03edfb28b5d8a788e9cb02d90166d242961a5c25f65e4a573c60cba62f5b96cc2f0395c1cc870f7e555b5d28091b5eca92c61c7cc043340e1b7fd8348bcbca68cb2d030f8fdb7be4d63644cfb4dbcaf49e0b8151d67aa6de4a949da0deb861f014e9dd031eea401960d763474c5684a1571a045de24f89062688169a36f0e520a41745f1035fdecbffd336b25ac511c3928dea987f25315c04a697713495f81fd68fdbb4cf03222a7b39ecad82f5bcf6dd66b3509995361e3ab5bd494c50ed887482ae57b50203a683634f5cd4d5d0eb611d3bd2799c7c7c35a1384bb353b16b9ad5a869f622c403ceb7fdc45ac190cd17cdc3b3a59a3759f9eb5b35b7c4187431ba10dc8c3106c803e19ce551cd9e9e29cdd143e283e4519154b6a8573c38ac8feb16d9e6fe2ccba503733a2fa98119c3b6129e6ef4dd7f457ad37b5fc0573f5a7642dbac6f09e493b503094cd6cc3e2d641a6dd5d30ad0c90d3195c8de75c6a0abc4cdf046f4f0f85c7c03ffab77a99f43866c0a11b8d44abbbcc353154a868598aff5035a3abbb98f030e038a08fc9ec5ab5b20ece1f4a11dc0ed19d097cd220ab39d43e96abcbb8d86c04a03faa85b7af9788ac79906cfb2d04774dcfc9173c55cf311711061c17e42a0e34e035fea21fe93b872b1cfc986cc87fa88e3d5d314f1d22cf968e0f23da5604c4dd503822ff9f069841a5daabe830b8dad40e42c8d8cc8a3485ef673cf863d680d8e7b03f5ac526ada6cae4e2fceacf9ad9c48393e9c39b93254cf7d9b03b89eb6bf59fd03a135a6e349b8547bb3af29c8e8384f3740f8c21adf44ffa6db6afd893c1cffce0374f3ca42e3efa67b0509dfadbb5c6d7821f2e51888a70ac8361e309121b3224d03dcb23dae44c20e643fcd2fb504eaa85d722a4c82508eb10e0a1491c0a71cadd90333e548a9ebd2365a4c95d586c406c1fab91f8bc7360795bd2f162ef995bdbd2903734d40c0cc24c5834bff68e0c54b0e9e0425646e3008194c307dd0a0b894736d03b84e7b77031dfa81f5c2b98c991805575ef8dfcb303b2472240b1e16d1e7a6e20301aca7142cc0eca5777f121f503560692edf28a5b08c856b37600f003422ceaf05581e0313009004d334f90bc1c18eb48eae5750f357946ed62c6e7d3fd92d1da0040303e2c69d13bf1f89fd6cab48bd92c5c38cad9be2dada05d58a51c44e1169b2c2f403b207911b8627fb12682c2f664bb3cba8df89444932fe19ceeb5a0104aab29bd003562d59a51820d47f520c975e0b2bcffac644a509749a3161f481f57b6e826d210605581e0350c0cb385de15c36099ebe2dd6f6578233b0bc2f507450884255a0f31007011bffffffffffffffff04592c1d608060405234801561001057600080fd5b50600436106101b95760003560e01c80636a627842116100f9578063ba9a7a5611610097578063d21220a711610071578063d21220a7146105da578063d505accf146105e2578063dd62ed3e14610640578063fff6cae91461067b576101b9565b8063ba9a7a5614610597578063bc25cf771461059f578063c45a0155146105d2576101b9565b80637ecebe00116100d35780637ecebe00146104d757806389afcb441461050a57806395d89b4114610556578063a9059cbb1461055e576101b9565b80636a6278421461046957806370a082311461049c5780637464fc3d146104cf576101b9565b806323b872dd116101665780633644e515116101405780633644e51514610416578063485cc9551461041e5780635909c0d5146104595780635a3d549314610461576101b9565b806323b872dd146103ad57806330adf81f146103f0578063313ce567146103f8576101b9565b8063095ea7b311610197578063095ea7b3146103155780630dfe16811461036257806318160ddd14610393576101b9565b8063022c0d9f146101be57806306fdde03146102595780630902f1ac146102d6575b600080fd5b610257600480360360808110156101d457600080fd5b81359160208101359173ffffffffffffffffffffffffffffffffffffffff604083013516919081019060808101606082013564010000000081111561021857600080fd5b82018360208201111561022a57600080fd5b8035906020019184600183028401116401000000008311171561024c57600080fd5b509092509050610683565b005b610261610d57565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561029b578181015183820152602001610283565b50505050905090810190601f1680156102c85780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6102de610d90565b604080516dffffffffffffffffffffffffffff948516815292909316602083015263ffffffff168183015290519081900360600190f35b61034e6004803603604081101561032b57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610de5565b604080519115158252519081900360200190f35b61036a610dfc565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b61039b610e18565b60408051918252519081900360200190f35b61034e600480360360608110156103c357600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060400135610e1e565b61039b610efd565b610400610f21565b6040805160ff9092168252519081900360200190f35b61039b610f26565b6102576004803603604081101561043457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020013516610f2c565b61039b611005565b61039b61100b565b61039b6004803603602081101561047f57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16611011565b61039b600480360360208110156104b257600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113cb565b61039b6113dd565b61039b600480360360208110156104ed57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113e3565b61053d6004803603602081101561052057600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113f5565b6040805192835260208301919091528051918290030190f35b610261611892565b61034e6004803603604081101561057457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81351690602001356118cb565b61039b6118d8565b610257600480360360208110156105b557600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166118de565b61036a611ad4565b61036a611af0565b610257600480360360e08110156105f857600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060408101359060608101359060ff6080820135169060a08101359060c00135611b0c565b61039b6004803603604081101561065657600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020013516611dd8565b610257611df5565b600c546001146106f457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c55841515806107075750600084115b61075c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180612b2f6025913960400191505060405180910390fd5b600080610767610d90565b5091509150816dffffffffffffffffffffffffffff168710801561079a5750806dffffffffffffffffffffffffffff1686105b6107ef576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526021815260200180612b786021913960400191505060405180910390fd5b600654600754600091829173ffffffffffffffffffffffffffffffffffffffff91821691908116908916821480159061085457508073ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff1614155b6108bf57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f556e697377617056323a20494e56414c49445f544f0000000000000000000000604482015290519081900360640190fd5b8a156108d0576108d0828a8d611fdb565b89156108e1576108e1818a8c611fdb565b86156109c3578873ffffffffffffffffffffffffffffffffffffffff166310d1e85c338d8d8c8c6040518663ffffffff1660e01b8152600401808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b1580156109aa57600080fd5b505af11580156109be573d6000803e3d6000fd5b505050505b604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff8416916370a08231916024808301926020929190829003018186803b158015610a2f57600080fd5b505afa158015610a43573d6000803e3d6000fd5b505050506040513d6020811015610a5957600080fd5b5051604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905191955073ffffffffffffffffffffffffffffffffffffffff8316916370a0823191602480820192602092909190829003018186803b158015610acb57600080fd5b505afa158015610adf573d6000803e3d6000fd5b505050506040513d6020811015610af557600080fd5b5051925060009150506dffffffffffffffffffffffffffff85168a90038311610b1f576000610b35565b89856dffffffffffffffffffffffffffff160383035b9050600089856dffffffffffffffffffffffffffff16038311610b59576000610b6f565b89856dffffffffffffffffffffffffffff160383035b90506000821180610b805750600081115b610bd5576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526024815260200180612b546024913960400191505060405180910390fd5b6000610c09610beb84600363ffffffff6121e816565b610bfd876103e863ffffffff6121e816565b9063ffffffff61226e16565b90506000610c21610beb84600363ffffffff6121e816565b9050610c59620f4240610c4d6dffffffffffffffffffffffffffff8b8116908b1663ffffffff6121e816565b9063ffffffff6121e816565b610c69838363ffffffff6121e816565b1015610cd657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f556e697377617056323a204b0000000000000000000000000000000000000000604482015290519081900360640190fd5b5050610ce4848488886122e0565b60408051838152602081018390528082018d9052606081018c9052905173ffffffffffffffffffffffffffffffffffffffff8b169133917fd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d8229181900360800190a350506001600c55505050505050505050565b6040518060400160405280600a81526020017f556e69737761702056320000000000000000000000000000000000000000000081525081565b6008546dffffffffffffffffffffffffffff808216926e0100000000000000000000000000008304909116917c0100000000000000000000000000000000000000000000000000000000900463ffffffff1690565b6000610df233848461259c565b5060015b92915050565b60065473ffffffffffffffffffffffffffffffffffffffff1681565b60005481565b73ffffffffffffffffffffffffffffffffffffffff831660009081526002602090815260408083203384529091528120547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14610ee85773ffffffffffffffffffffffffffffffffffffffff84166000908152600260209081526040808320338452909152902054610eb6908363ffffffff61226e16565b73ffffffffffffffffffffffffffffffffffffffff851660009081526002602090815260408083203384529091529020555b610ef384848461260b565b5060019392505050565b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b601281565b60035481565b60055473ffffffffffffffffffffffffffffffffffffffff163314610fb257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f556e697377617056323a20464f5242494444454e000000000000000000000000604482015290519081900360640190fd5b6006805473ffffffffffffffffffffffffffffffffffffffff9384167fffffffffffffffffffffffff00000000000000000000000000000000000000009182161790915560078054929093169116179055565b60095481565b600a5481565b6000600c5460011461108457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c81905580611094610d90565b50600654604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905193955091935060009273ffffffffffffffffffffffffffffffffffffffff909116916370a08231916024808301926020929190829003018186803b15801561110e57600080fd5b505afa158015611122573d6000803e3d6000fd5b505050506040513d602081101561113857600080fd5b5051600754604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905192935060009273ffffffffffffffffffffffffffffffffffffffff909216916370a0823191602480820192602092909190829003018186803b1580156111b157600080fd5b505afa1580156111c5573d6000803e3d6000fd5b505050506040513d60208110156111db57600080fd5b505190506000611201836dffffffffffffffffffffffffffff871663ffffffff61226e16565b90506000611225836dffffffffffffffffffffffffffff871663ffffffff61226e16565b9050600061123387876126ec565b600054909150806112705761125c6103e8610bfd611257878763ffffffff6121e816565b612878565b985061126b60006103e86128ca565b6112cd565b6112ca6dffffffffffffffffffffffffffff8916611294868463ffffffff6121e816565b8161129b57fe5b046dffffffffffffffffffffffffffff89166112bd868563ffffffff6121e816565b816112c457fe5b0461297a565b98505b60008911611326576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180612bc16028913960400191505060405180910390fd5b6113308a8a6128ca565b61133c86868a8a6122e0565b811561137e5760085461137a906dffffffffffffffffffffffffffff808216916e01000000000000000000000000000090041663ffffffff6121e816565b600b555b6040805185815260208101859052815133927f4c209b5fc8ad50758f13e2e1088ba56a560dff690a1c6fef26394f4c03821c4f928290030190a250506001600c5550949695505050505050565b60016020526000908152604090205481565b600b5481565b60046020526000908152604090205481565b600080600c5460011461146957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c81905580611479610d90565b50600654600754604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905194965092945073ffffffffffffffffffffffffffffffffffffffff9182169391169160009184916370a08231916024808301926020929190829003018186803b1580156114fb57600080fd5b505afa15801561150f573d6000803e3d6000fd5b505050506040513d602081101561152557600080fd5b5051604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905191925060009173ffffffffffffffffffffffffffffffffffffffff8516916370a08231916024808301926020929190829003018186803b15801561159957600080fd5b505afa1580156115ad573d6000803e3d6000fd5b505050506040513d60208110156115c357600080fd5b5051306000908152600160205260408120549192506115e288886126ec565b600054909150806115f9848763ffffffff6121e816565b8161160057fe5b049a5080611614848663ffffffff6121e816565b8161161b57fe5b04995060008b11801561162e575060008a115b611683576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180612b996028913960400191505060405180910390fd5b61168d3084612992565b611698878d8d611fdb565b6116a3868d8c611fdb565b604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff8916916370a08231916024808301926020929190829003018186803b15801561170f57600080fd5b505afa158015611723573d6000803e3d6000fd5b505050506040513d602081101561173957600080fd5b5051604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905191965073ffffffffffffffffffffffffffffffffffffffff8816916370a0823191602480820192602092909190829003018186803b1580156117ab57600080fd5b505afa1580156117bf573d6000803e3d6000fd5b505050506040513d60208110156117d557600080fd5b505193506117e585858b8b6122e0565b811561182757600854611823906dffffffffffffffffffffffffffff808216916e01000000000000000000000000000090041663ffffffff6121e816565b600b555b604080518c8152602081018c9052815173ffffffffffffffffffffffffffffffffffffffff8f169233927fdccd412f0b1252819cb1fd330b93224ca42612892bb3f4f789976e6d81936496929081900390910190a35050505050505050506001600c81905550915091565b6040518060400160405280600681526020017f554e492d5632000000000000000000000000000000000000000000000000000081525081565b6000610df233848461260b565b6103e881565b600c5460011461194f57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c55600654600754600854604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff9485169490931692611a2b9285928792611a26926dffffffffffffffffffffffffffff169185916370a0823191602480820192602092909190829003018186803b1580156119ee57600080fd5b505afa158015611a02573d6000803e3d6000fd5b505050506040513d6020811015611a1857600080fd5b50519063ffffffff61226e16565b611fdb565b600854604080517f70a082310000000000000000000000000000000000000000000000000000000081523060048201529051611aca9284928792611a26926e01000000000000000000000000000090046dffffffffffffffffffffffffffff169173ffffffffffffffffffffffffffffffffffffffff8616916370a0823191602480820192602092909190829003018186803b1580156119ee57600080fd5b50506001600c5550565b60055473ffffffffffffffffffffffffffffffffffffffff1681565b60075473ffffffffffffffffffffffffffffffffffffffff1681565b42841015611b7b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f556e697377617056323a20455850495245440000000000000000000000000000604482015290519081900360640190fd5b60035473ffffffffffffffffffffffffffffffffffffffff80891660008181526004602090815260408083208054600180820190925582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98186015280840196909652958d166060860152608085018c905260a085019590955260c08085018b90528151808603909101815260e0850182528051908301207f19010000000000000000000000000000000000000000000000000000000000006101008601526101028501969096526101228085019690965280518085039096018652610142840180825286519683019690962095839052610162840180825286905260ff89166101828501526101a284018890526101c28401879052519193926101e2808201937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081019281900390910190855afa158015611cdc573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff811615801590611d5757508873ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b611dc257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f556e697377617056323a20494e56414c49445f5349474e415455524500000000604482015290519081900360640190fd5b611dcd89898961259c565b505050505050505050565b600260209081526000928352604080842090915290825290205481565b600c54600114611e6657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c55600654604080517f70a082310000000000000000000000000000000000000000000000000000000081523060048201529051611fd49273ffffffffffffffffffffffffffffffffffffffff16916370a08231916024808301926020929190829003018186803b158015611edd57600080fd5b505afa158015611ef1573d6000803e3d6000fd5b505050506040513d6020811015611f0757600080fd5b5051600754604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff909216916370a0823191602480820192602092909190829003018186803b158015611f7a57600080fd5b505afa158015611f8e573d6000803e3d6000fd5b505050506040513d6020811015611fa457600080fd5b50516008546dffffffffffffffffffffffffffff808216916e0100000000000000000000000000009004166122e0565b6001600c55565b604080518082018252601981527f7472616e7366657228616464726573732c75696e743235362900000000000000602091820152815173ffffffffffffffffffffffffffffffffffffffff85811660248301526044808301869052845180840390910181526064909201845291810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb000000000000000000000000000000000000000000000000000000001781529251815160009460609489169392918291908083835b602083106120e157805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016120a4565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114612143576040519150601f19603f3d011682016040523d82523d6000602084013e612148565b606091505b5091509150818015612176575080511580612176575080806020019051602081101561217357600080fd5b50515b6121e157604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f556e697377617056323a205452414e534645525f4641494c4544000000000000604482015290519081900360640190fd5b5050505050565b60008115806122035750508082028282828161220057fe5b04145b610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f64732d6d6174682d6d756c2d6f766572666c6f77000000000000000000000000604482015290519081900360640190fd5b80820382811115610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f64732d6d6174682d7375622d756e646572666c6f770000000000000000000000604482015290519081900360640190fd5b6dffffffffffffffffffffffffffff841180159061230c57506dffffffffffffffffffffffffffff8311155b61237757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f556e697377617056323a204f564552464c4f5700000000000000000000000000604482015290519081900360640190fd5b60085463ffffffff428116917c0100000000000000000000000000000000000000000000000000000000900481168203908116158015906123c757506dffffffffffffffffffffffffffff841615155b80156123e257506dffffffffffffffffffffffffffff831615155b15612492578063ffffffff16612425856123fb86612a57565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff169063ffffffff612a7b16565b600980547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff929092169290920201905563ffffffff8116612465846123fb87612a57565b600a80547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff92909216929092020190555b600880547fffffffffffffffffffffffffffffffffffff0000000000000000000000000000166dffffffffffffffffffffffffffff888116919091177fffffffff0000000000000000000000000000ffffffffffffffffffffffffffff166e0100000000000000000000000000008883168102919091177bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167c010000000000000000000000000000000000000000000000000000000063ffffffff871602179283905560408051848416815291909304909116602082015281517f1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1929181900390910190a1505050505050565b73ffffffffffffffffffffffffffffffffffffffff808416600081815260026020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b73ffffffffffffffffffffffffffffffffffffffff8316600090815260016020526040902054612641908263ffffffff61226e16565b73ffffffffffffffffffffffffffffffffffffffff8085166000908152600160205260408082209390935590841681522054612683908263ffffffff612abc16565b73ffffffffffffffffffffffffffffffffffffffff80841660008181526001602090815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600080600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663017e7e586040518163ffffffff1660e01b815260040160206040518083038186803b15801561275757600080fd5b505afa15801561276b573d6000803e3d6000fd5b505050506040513d602081101561278157600080fd5b5051600b5473ffffffffffffffffffffffffffffffffffffffff821615801594509192509061286457801561285f5760006127d86112576dffffffffffffffffffffffffffff88811690881663ffffffff6121e816565b905060006127e583612878565b90508082111561285c576000612813612804848463ffffffff61226e16565b6000549063ffffffff6121e816565b905060006128388361282c86600563ffffffff6121e816565b9063ffffffff612abc16565b9050600081838161284557fe5b04905080156128585761285887826128ca565b5050505b50505b612870565b8015612870576000600b555b505092915050565b600060038211156128bb575080600160028204015b818110156128b5578091506002818285816128a457fe5b0401816128ad57fe5b04905061288d565b506128c5565b81156128c5575060015b919050565b6000546128dd908263ffffffff612abc16565b600090815573ffffffffffffffffffffffffffffffffffffffff8316815260016020526040902054612915908263ffffffff612abc16565b73ffffffffffffffffffffffffffffffffffffffff831660008181526001602090815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b6000818310612989578161298b565b825b9392505050565b73ffffffffffffffffffffffffffffffffffffffff82166000908152600160205260409020546129c8908263ffffffff61226e16565b73ffffffffffffffffffffffffffffffffffffffff831660009081526001602052604081209190915554612a02908263ffffffff61226e16565b600090815560408051838152905173ffffffffffffffffffffffffffffffffffffffff8516917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef919081900360200190a35050565b6dffffffffffffffffffffffffffff166e0100000000000000000000000000000290565b60006dffffffffffffffffffffffffffff82167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff841681612ab457fe5b049392505050565b80820182811015610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f64732d6d6174682d6164642d6f766572666c6f77000000000000000000000000604482015290519081900360640190fdfe556e697377617056323a20494e53554646494349454e545f4f55545055545f414d4f554e54556e697377617056323a20494e53554646494349454e545f494e5055545f414d4f554e54556e697377617056323a20494e53554646494349454e545f4c4951554944495459556e697377617056323a20494e53554646494349454e545f4c49515549444954595f4255524e4544556e697377617056323a20494e53554646494349454e545f4c49515549444954595f4d494e544544a265627a7a723158207dca18479e58487606bf70c79e44d8dee62353c9ee6d01f9a9d70885b8765f2264736f6c63430005100032032e2bc0c0ff22609eac8f10e1c8736f3e780dcb85055451e7ac674e2667ce4b570058210390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5630480463777a4d8c892d00582103a74ac629773b39717320a4a7c0089e4b0667da26a07887255087b04485870cc05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0058210366cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c688054c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2005821032575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b05820b90c1a14c088c1d56a6b8fd976e6ebf88b023497d5454cd783fa3cb22ebe2d7100582103f6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c70410100582002f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee35820663811bb00000000000010879dcb019bd68e000000000000012a6d8e1122000000582002a2f775275d4ead74b6836a12a0af610b14ed125e826aef45fdd1edce554ffd480463777a4d8c85450058200252222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f543339474491b13ac638f163d634fbbc722cd279160218580219b50505581d02aefb66e9c244bedfa953b8857e1f3a45010a7631ba683bf8c76f33910701192c1d05581d0291f3087fcb4d685046c028fa385eb98adc69b05fe9f7f5e990046f5d040102190880036349f17e9fcb4ca4b31440d7858e575d9f0d6f4029f73060f120236998ebcdc305581e03676fbd2738568fd9e0c66efefbcb7703b2595566a1b6484011fb69a7100847028ed6103d000003ff20557d6be1c48bc106100d951206a4a961c50b1af816568c92944301459ab8036f1babd1aad365df43a426574d9f1bf6433ed3d5841aa3da7d1b7adc99d2e1fb05581e03f503cb9c8781baf2682ced17ee8d6a3b410312a358d58675ad94a0640007011bffffffffffffffff05581e03bee7418d20ef338b3ff21ffa994121d3bdbdea8c687bb3c542e04d4a700847d4464ef554600005581e03c2b3991f35ae9c607c17e57f9db582c79334f33a929ea345fff7e215200c01471a3e3b72f7502d032b513e44f3337828c5d75b8bdd51bafa3b1eb9b47c194d6bc35ed4fb7b2449420219dfcc0331705cd11b32b3bfb5d70a3e504c8e319cd4f96d67676a68b91fd30919a94a9703629f6d7531204106e6edbe9e67ffab3ec8bc34394ca4dfe7c58331e7d1746e8703be49e45138114c6cf393eb1723e2dfc97444bb81b4006301467634cafafb3f30033a4c1de5fe0db02813547929437b8c5887db566e6d51b0c12f9239843d66c9170219ffff0346453db57f270d1869a278dade60b187f322a49943aae3372bb113f3849d476f03cb1e6cc8b6ad6f28db9f5034eb3015dba9afa2192455fe9de6f7a2cb7fc8d82f03ea11dc7eba948738186f53c51eacb3f65dd5e0257df100592feb430deaeceaed039786472a5b2385243c3400d426e7b197c32b599813bfd0fd2eab844fdceda3fd037e2d26860c1a0308b2dc4c49218decc0ed9439387fbf8d61251e4af81de3526d038b393e0aef6d453c3d12a9a62a06db9e9e08d1213ac87a995876db420a5e403a03933ab0d170efb7138e112118d824a3c703af7e98d3f216aca3a929501e97ebbd03698d4e605ca1fa06f9b80f8bdcbe9979e1c7535ce6ecc434fc7411af4a2ef3530219ffff0331328ad597681762e5dda9ff4d95579f469750cd0e93cdc74843c352a8131a4f0383b86ee3d24977d22f3e8d012500ae18b9611d89272bb96ac16af2ced52585a6036ec3189a3597126249c999e0db19c66479dd6451fbd46d54a9059c563487b19b039a722529f68aaec786073ee41cfcfbdaa0deaffaecfb72d8fc018b477c4e958f039b758f1eb9f1931eb22550b73ee674970d08fe7c76d8386cc81c2efed76f92fe033da7b9e572ed69cd245b8cb6f959ce6b6a83986b7d155b904cb00c3c079a3b8403bb0752e3e62172c2de1accddaafb54f8c037f25d307f70b26b014ecf1174c86a03b68fdc79d20fe9e37db7d154d86213765c74c664b92c76678f01b30e496f6d850313376f50413b542806ce74e560667332b8442bd152a6706875100de8b6cfd2e1036d68e4a372ca16a93ea84f36e244a93d3e010bbe83c6e86bdf8082104f7045aa031e0d2ae1f5a9eaf5126fe9973bd426b783eaf746ae9e86ac6251437bec3eb19d03469b7bcdbfa0250673e3a6727a4e9b029aeef9a9f1f565f6557773e73d336ad003eb0bb279af016b8729620835624a1c4667d06cd2a65adaa74409896b76f4db8003f471d9b3fa2b0e94cc6f123b8758ad5dcfef98de9448cc4760670725f5ced70b03f36aa718d6715f22cc414ad81e5603a68f059da2cb137e98b733d33aba0f735b0219ffff037a215217da5c353fdf83cff67421f514e0c6a06859a63f6e9a016133e7768baf03c3fd54bf8e15c40621fce0177167e6fad85b6aa84042ad6bb9003b95db897e4703a8bd733be28a2284f56687d5e9df16b8192ae019ee02ba878204022b0510f8da036ec570fe3b9e8d08dc5c76ec68f701fd01ec58e9263d7b236e619be74f96f4d603e89ccc561fe2b73a3779df36fded2411d4499cdaca3d9c88f87c815374a0575403e1f7f61734f65ed17d0a3ccd2c82c02b2941649e532baa7d6afd5f9eefe20b44033f5353ce899131d33056ea9841924ec913641a5571c0b68eae67f5dbe3cf473e0219ffff03ae3729bde1c448cd7665515f5b58e43ee482b1e1f9773471d458b2925658eddd03aa3d9e84099495fcaac4bca1b3f0986556f97b0684777615df8df2080bd768890372678cad52484f10978d735f381915ac93f3d93c4d3eabe599def609ebcdc19b033ea6185656815ade5a793a0529fcf2dd87aaf05c328f1dc3777f968832ee593c0388b87518448aaf50db386611e44da23c7be5614460b563862e5778da35bf0aff03eb17d7a0c27b6cbcc486b2a9e924afd0449e91871342c5f1fc996e197d3f9e83039cc4ce48ea8137f2be42dded37e0648a1b7c0809f3c97ffdaf6acef974a02b9703683278c6c78a1c7f5bc9c71d1c927816c874f40660ac88f031760fa68e76056603a982b0b5eefd99e38f046321c7d07d635b37556b29a64dd1bae9077fe45fcb20039669cd279adece0ab34f7a63d2281b9e3cbcc395854f753a832ed8e3665100f50336af8d2a62efee81bae60be46828c5f924a853819c3566e6ef05aca39cfb412903d3b6d35624f74dad9482943b16e978f85c1740c64f08b5e7d80bf4d35dbf54cb035146979b790985f73d6aab896489cf0984f7d55753c356a469c532eaf43473580381724697ed2f47d4ed166a3c4b53d5902b7faa46e51a9e202f60bd02e8475795031acee7df0b775954ce7a6cc74781a2c285f295d442deb6371519cca8e7ed65210347a3027b7da73d1241f89d724be5acf5c6c21c24fd33e132eb8acd526dabf04203e896389c449479b8a548befab0c7fbfa47f829e6ded2fbb39f4133ec8b036bee03f5944cf3c1ecdd90abe2805bd4b99058d300603394d5179ee90fd71b4ffe392c03c0649dce44d2236b86042b0f6e2a6099a24f72ccc578e0f4196994fec2977a5d037b557e67a2a59cb8be4f7b9209546dfebec8beff549ac4d1cf5e25934c4e85b103a07467479d8d844764178e00694f1f69265474541f5cd151f3e2d5675787006f03c27d32054e84322bdfb70a37930ee3de85043aebb34cdfde62cdc5c22ed7859103287e2780039aa361044170ed14051f0baf77dc238619da2da89edea68bac033c03b332227b17565726f04dda161bbef5cf2543ef2cd0552fff11611d4a56f110ba0352d83eae12221d3199730d03f01431ae8f311f05d5dfb750aec3690230cf7939031ef7bafc2428bed1014196a346035b87a5f292040f02842dd99f12ee1138421f03c8bdf22c8233cd5b2297befe578ed4795fca563a75487a3996c8bad4fc5bcd1f03b6b6f23f7300a838709cbd132ccb0cfaf80197e9d2034f4a591246c7b6f8499d0303b65196a8cf63075b5c7759679c4a0e6adcc16a841bc7900b0eee4782e5d41f03c8d5e30378a1ccf2976579229d51d39ccb05ce8285933478475108b66e2193e005581e03520f1c1144b24c2ce87e7459e0bc768a2b327757bdee9be235cdfda630040105581e03adc92a3d57103e87d72eae67f3ea13a730225d0e1e8e0af52b59c2b6800c034655e9e86224d805581e0320dc1086e8876e02ff4f7cbb69e79bb49001d246469a012822f6b05d700401034875555289f79c2c2f7431de427e59d5d70fd6f1d70b7aa84e71ebb4cb3d247305581e0314055b1c9397f758dc84a39c23a8056b3bc19add63abac4f3ddca21570040105581e037e2a455dbb9216f5da64ca9338618bd59782bba0426a3a3ef68c0cff70040105581e031aed6efe4c866f79ffb7b91ed02c6af216d04ef1757d9d6d53658273f00c054729084c5e764134038a70a36b03df65c40adce9a36ad23eb646131d89372524efd60698ec88c1a3fc05581d0287d6e269b41b8c7406da2c48373f4c3dba68e9d6d0acfd71821198f4040204595ba8608060405234801561001057600080fd5b506004361061036d5760003560e01c80638456cb59116101d3578063b7b7289911610104578063e3ee160e116100a2578063ef55bec61161007c578063ef55bec614611122578063f2fde38b1461118e578063f9f92be4146111c1578063fe575a87146111f45761036d565b8063e3ee160e14611075578063e5a6b10f146110e1578063e94a0102146110e95761036d565b8063d505accf116100de578063d505accf14610f64578063d608ea6414610fc2578063d916948714611032578063dd62ed3e1461103a5761036d565b8063b7b7289914610db0578063bd10243014610e78578063cf09299514610e805761036d565b8063a0cc6a6811610171578063aa20e1e41161014b578063aa20e1e414610cd4578063aa271e1a14610d07578063ad38bf2214610d3a578063b2118a8d14610d6d5761036d565b8063a0cc6a6814610c5a578063a457c2d714610c62578063a9059cbb14610c9b5761036d565b80638da5cb5b116101ad5780638da5cb5b14610b6a57806395d89b4114610b725780639fd0506d14610b7a5780639fd5a6cf14610b825761036d565b80638456cb5914610a4b57806388b7ab6314610a535780638a6db9c314610b375761036d565b806338a63183116102ad57806354fd4d501161024b5780635c975abb116102255780635c975abb146109d557806370a08231146109dd5780637ecebe0014610a105780637f2eecc314610a435761036d565b806354fd4d501461094c578063554bab3c146109545780635a049a70146109875761036d565b806340c10f191161028757806340c10f19146107fb57806342966c6814610834578063430239b4146108515780634e44d956146109135761036d565b806338a63183146107b257806339509351146107ba5780633f4ba83a146107f35761036d565b80632fc81e091161031a578063313ce567116102f4578063313ce5671461056f5780633357162b1461058d57806335d99f35146107795780633644e515146107aa5761036d565b80632fc81e09146105015780633092afd51461053457806330adf81f146105675761036d565b80631a8952661161034b5780631a8952661461045657806323b872dd1461048b5780632ab60045146104ce5761036d565b806306fdde0314610372578063095ea7b3146103ef57806318160ddd1461043c575b600080fd5b61037a611227565b6040805160208082528351818301528351919283929083019185019080838360005b838110156103b457818101518382015260200161039c565b50505050905090810190601f1680156103e15780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6104286004803603604081101561040557600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81351690602001356112d3565b604080519115158252519081900360200190f35b610444611374565b60408051918252519081900360200190f35b6104896004803603602081101561046c57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff1661137a565b005b610428600480360360608110156104a157600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060400135611437565b610489600480360360208110156104e457600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166116f2565b6104896004803603602081101561051757600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16611853565b6104286004803603602081101561054a57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166118bb565b6104446119b4565b6105776119d8565b6040805160ff9092168252519081900360200190f35b61048960048036036101008110156105a457600080fd5b8101906020810181356401000000008111156105bf57600080fd5b8201836020820111156105d157600080fd5b803590602001918460018302840111640100000000831117156105f357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929594936020810193503591505064010000000081111561064657600080fd5b82018360208201111561065857600080fd5b8035906020019184600183028401116401000000008311171561067a57600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092959493602081019350359150506401000000008111156106cd57600080fd5b8201836020820111156106df57600080fd5b8035906020019184600183028401116401000000008311171561070157600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050813560ff16925050602081013573ffffffffffffffffffffffffffffffffffffffff908116916040810135821691606082013581169160800135166119e1565b610781611d23565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b610444611d3f565b610781611d4e565b610428600480360360408110156107d057600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135611d6a565b610489611e02565b6104286004803603604081101561081157600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135611ec5565b6104896004803603602081101561084a57600080fd5b5035612296565b6104896004803603604081101561086757600080fd5b81019060208101813564010000000081111561088257600080fd5b82018360208201111561089457600080fd5b803590602001918460208302840111640100000000831117156108b657600080fd5b9193909290916020810190356401000000008111156108d457600080fd5b8201836020820111156108e657600080fd5b8035906020019184600183028401116401000000008311171561090857600080fd5b509092509050612538565b6104286004803603604081101561092957600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81351690602001356126ef565b61037a612882565b6104896004803603602081101561096a57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166128b9565b610489600480360360a081101561099d57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060208101359060ff6040820135169060608101359060800135612a20565b610428612abe565b610444600480360360208110156109f357600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16612adf565b61044460048036036020811015610a2657600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16612af0565b610444612b18565b610489612b3c565b610489600480360360e0811015610a6957600080fd5b73ffffffffffffffffffffffffffffffffffffffff823581169260208101359091169160408201359160608101359160808201359160a08101359181019060e0810160c0820135640100000000811115610ac257600080fd5b820183602082011115610ad457600080fd5b80359060200191846001830284011164010000000083111715610af657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550612c16945050505050565b61044460048036036020811015610b4d57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16612d7a565b610781612da2565b61037a612dbe565b610781612e37565b610489600480360360a0811015610b9857600080fd5b73ffffffffffffffffffffffffffffffffffffffff823581169260208101359091169160408201359160608101359181019060a081016080820135640100000000811115610be557600080fd5b820183602082011115610bf757600080fd5b80359060200191846001830284011164010000000083111715610c1957600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550612e53945050505050565b610444612eea565b61042860048036036040811015610c7857600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135612f0e565b61042860048036036040811015610cb157600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135612fa6565b61048960048036036020811015610cea57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16613109565b61042860048036036020811015610d1d57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16613270565b61048960048036036020811015610d5057600080fd5b503573ffffffffffffffffffffffffffffffffffffffff1661329b565b61048960048036036060811015610d8357600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060400135613402565b61048960048036036060811015610dc657600080fd5b73ffffffffffffffffffffffffffffffffffffffff82351691602081013591810190606081016040820135640100000000811115610e0357600080fd5b820183602082011115610e1557600080fd5b80359060200191846001830284011164010000000083111715610e3757600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550613498945050505050565b61078161352d565b610489600480360360e0811015610e9657600080fd5b73ffffffffffffffffffffffffffffffffffffffff823581169260208101359091169160408201359160608101359160808201359160a08101359181019060e0810160c0820135640100000000811115610eef57600080fd5b820183602082011115610f0157600080fd5b80359060200191846001830284011164010000000083111715610f2357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550613549945050505050565b610489600480360360e0811015610f7a57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060408101359060608101359060ff6080820135169060a08101359060c001356136a2565b61048960048036036020811015610fd857600080fd5b810190602081018135640100000000811115610ff357600080fd5b82018360208201111561100557600080fd5b8035906020019184600183028401116401000000008311171561102757600080fd5b509092509050613744565b61044461382d565b6104446004803603604081101561105057600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020013516613851565b610489600480360361012081101561108c57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060408101359060608101359060808101359060a08101359060ff60c0820135169060e0810135906101000135613889565b61037a6139f1565b610428600480360360408110156110ff57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135613a6a565b610489600480360361012081101561113957600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060408101359060608101359060808101359060a08101359060ff60c0820135169060e0810135906101000135613aa2565b610489600480360360208110156111a457600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16613bfd565b610489600480360360208110156111d757600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16613d50565b6104286004803603602081101561120a57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16613e0d565b6004805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f810184900484028201840190925281815292918301828280156112cb5780601f106112a0576101008083540402835291602001916112cb565b820191906000526020600020905b8154815290600101906020018083116112ae57829003601f168201915b505050505081565b60015460009074010000000000000000000000000000000000000000900460ff161561136057604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b61136b338484613e18565b50600192915050565b600b5490565b60025473ffffffffffffffffffffffffffffffffffffffff1633146113ea576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c815260200180615824602c913960400191505060405180910390fd5b6113f381613f5f565b60405173ffffffffffffffffffffffffffffffffffffffff8216907f117e3210bb9aa7d9baff172026820255c6f6c30ba8999d1c2fd88e2848137c4e90600090a250565b60015460009074010000000000000000000000000000000000000000900460ff16156114c457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b336114ce81613f6a565b15611524576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b8461152e81613f6a565b15611584576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b8461158e81613f6a565b156115e4576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff87166000908152600a6020908152604080832033845290915290205485111561166d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260288152602001806159146028913960400191505060405180910390fd5b611678878787613f98565b73ffffffffffffffffffffffffffffffffffffffff87166000908152600a602090815260408083203384529091529020546116b39086614163565b73ffffffffffffffffffffffffffffffffffffffff88166000908152600a60209081526040808320338452909152902055600193505050509392505050565b60005473ffffffffffffffffffffffffffffffffffffffff16331461177857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff81166117e4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a81526020018061575d602a913960400191505060405180910390fd5b600e80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517fe475e580d85111348e40d8ca33cfdd74c30fe1655c2d8537a13abc10065ffa5a90600090a250565b60125460ff1660011461186557600080fd5b6000611870306141da565b9050801561188357611883308383613f98565b61188c30614224565b5050601280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166002179055565b60085460009073ffffffffffffffffffffffffffffffffffffffff16331461192e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806157fb6029913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff82166000818152600c6020908152604080832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055600d909152808220829055517fe94479a9f7e1952cc78f2d6baab678adc1b772d936c6583def489e524cb666929190a2506001919050565b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b60065460ff1681565b60085474010000000000000000000000000000000000000000900460ff1615611a55576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a81526020018061598f602a913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8416611ac1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602f8152602001806158c1602f913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8316611b2d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806157346029913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8216611b99576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602e81526020018061593c602e913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8116611c05576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180615a7c6028913960400191505060405180910390fd5b8751611c189060049060208b01906154cd565b508651611c2c9060059060208a01906154cd565b508551611c409060079060208901906154cd565b50600680547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660ff8716179055600880547fffffffffffffffffffffffff000000000000000000000000000000000000000090811673ffffffffffffffffffffffffffffffffffffffff8781169190911790925560018054821686841617905560028054909116918416919091179055611cda8161422f565b5050600880547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1674010000000000000000000000000000000000000000179055505050505050565b60085473ffffffffffffffffffffffffffffffffffffffff1681565b6000611d49614276565b905090565b600e5473ffffffffffffffffffffffffffffffffffffffff1690565b60015460009074010000000000000000000000000000000000000000900460ff1615611df757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b61136b33848461436b565b60015473ffffffffffffffffffffffffffffffffffffffff163314611e72576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526022815260200180615a306022913960400191505060405180910390fd5b600180547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1690556040517f7805862f689e2f13df9f062ff482ad3ad112aca9e0847911ed832e158c525b3390600090a1565b60015460009074010000000000000000000000000000000000000000900460ff1615611f5257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b336000908152600c602052604090205460ff16611fba576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806158a06021913960400191505060405180910390fd5b33611fc481613f6a565b1561201a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b8361202481613f6a565b1561207a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff85166120e6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260238152602001806156c96023913960400191505060405180910390fd5b6000841161213f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806157ac6029913960400191505060405180910390fd5b336000908152600d6020526040902054808511156121a8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602e815260200180615a02602e913960400191505060405180910390fd5b600b546121b590866143b5565b600b556121d4866121cf876121c9836141da565b906143b5565b614430565b6121de8186614163565b336000818152600d6020908152604091829020939093558051888152905173ffffffffffffffffffffffffffffffffffffffff8a16937fab8530f87dc9b59234c4623bf917212bb2536d647574c8e7e5da92c2ede0c9f8928290030190a360408051868152905173ffffffffffffffffffffffffffffffffffffffff8816916000917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9181900360200190a350600195945050505050565b60015474010000000000000000000000000000000000000000900460ff161561232057604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b336000908152600c602052604090205460ff16612388576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806158a06021913960400191505060405180910390fd5b3361239281613f6a565b156123e8576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b60006123f3336141da565b90506000831161244e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806156a06029913960400191505060405180910390fd5b828110156124a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602681526020018061587a6026913960400191505060405180910390fd5b600b546124b49084614163565b600b556124c5336121cf8386614163565b60408051848152905133917fcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca5919081900360200190a260408051848152905160009133917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9181900360200190a3505050565b60125460ff1660021461254a57600080fd5b6125566005838361554b565b5060005b83811015612698576003600086868481811061257257fe5b6020908102929092013573ffffffffffffffffffffffffffffffffffffffff168352508101919091526040016000205460ff166125fa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603d8152602001806155ed603d913960400191505060405180910390fd5b61262b85858381811061260957fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff16614224565b6003600086868481811061263b57fe5b6020908102929092013573ffffffffffffffffffffffffffffffffffffffff1683525081019190915260400160002080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016905560010161255a565b506126a230614224565b505030600090815260036020819052604090912080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff009081169091556012805490911690911790555050565b60015460009074010000000000000000000000000000000000000000900460ff161561277c57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b60085473ffffffffffffffffffffffffffffffffffffffff1633146127ec576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806157fb6029913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff83166000818152600c6020908152604080832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055600d825291829020859055815185815291517f46980fca912ef9bcdbd36877427b6b90e860769f604e89c0e67720cece530d209281900390910190a250600192915050565b60408051808201909152600181527f3200000000000000000000000000000000000000000000000000000000000000602082015290565b60005473ffffffffffffffffffffffffffffffffffffffff16331461293f57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff81166129ab576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602881526020018061564d6028913960400191505060405180910390fd5b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691909117918290556040519116907fb80482a293ca2e013eda8683c9bd7fc8347cfdaeea5ede58cba46df502c2a60490600090a250565b60015474010000000000000000000000000000000000000000900460ff1615612aaa57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b612ab78585858585614531565b5050505050565b60015474010000000000000000000000000000000000000000900460ff1681565b6000612aea826141da565b92915050565b73ffffffffffffffffffffffffffffffffffffffff1660009081526011602052604090205490565b7fd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de881565b60015473ffffffffffffffffffffffffffffffffffffffff163314612bac576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526022815260200180615a306022913960400191505060405180910390fd5b600180547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff16740100000000000000000000000000000000000000001790556040517f6985a02210a168e66602d3235cb6db0e70f92b3ba4d376a33c0f3d9434bff62590600090a1565b60015474010000000000000000000000000000000000000000900460ff1615612ca057604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b86612caa81613f6a565b15612d00576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b86612d0a81613f6a565b15612d60576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b612d6f89898989898989614571565b505050505050505050565b73ffffffffffffffffffffffffffffffffffffffff166000908152600d602052604090205490565b60005473ffffffffffffffffffffffffffffffffffffffff1690565b6005805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f810184900484028201840190925281815292918301828280156112cb5780601f106112a0576101008083540402835291602001916112cb565b60015473ffffffffffffffffffffffffffffffffffffffff1681565b60015474010000000000000000000000000000000000000000900460ff1615612edd57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b612ab78585858585614692565b7f7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a226781565b60015460009074010000000000000000000000000000000000000000900460ff1615612f9b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b61136b338484614956565b60015460009074010000000000000000000000000000000000000000900460ff161561303357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b3361303d81613f6a565b15613093576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b8361309d81613f6a565b156130f3576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b6130fe338686613f98565b506001949350505050565b60005473ffffffffffffffffffffffffffffffffffffffff16331461318f57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff81166131fb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602f8152602001806158c1602f913960400191505060405180910390fd5b600880547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691909117918290556040519116907fdb66dfa9c6b8f5226fe9aac7e51897ae8ee94ac31dc70bb6c9900b2574b707e690600090a250565b73ffffffffffffffffffffffffffffffffffffffff166000908152600c602052604090205460ff1690565b60005473ffffffffffffffffffffffffffffffffffffffff16331461332157604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff811661338d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526032815260200180615ad26032913960400191505060405180910390fd5b600280547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691909117918290556040519116907fc67398012c111ce95ecb7429b933096c977380ee6c421175a71a4a4c6c88c06e90600090a250565b600e5473ffffffffffffffffffffffffffffffffffffffff163314613472576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260248152602001806158f06024913960400191505060405180910390fd5b61349373ffffffffffffffffffffffffffffffffffffffff841683836149b2565b505050565b60015474010000000000000000000000000000000000000000900460ff161561352257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b613493838383614a3f565b60025473ffffffffffffffffffffffffffffffffffffffff1681565b60015474010000000000000000000000000000000000000000900460ff16156135d357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b866135dd81613f6a565b15613633576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b8661363d81613f6a565b15613693576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b612d6f89898989898989614b49565b60015474010000000000000000000000000000000000000000900460ff161561372c57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b61373b87878787878787614be7565b50505050505050565b60085474010000000000000000000000000000000000000000900460ff168015613771575060125460ff16155b61377a57600080fd5b6137866004838361554b565b506137fb82828080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505060408051808201909152600181527f320000000000000000000000000000000000000000000000000000000000000060208201529150614c299050565b600f555050601280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055565b7f158b0a9edf7a828aad02f63cd515c68ef2f50ba807396f6d12842833a159742981565b73ffffffffffffffffffffffffffffffffffffffff9182166000908152600a6020908152604080832093909416825291909152205490565b60015474010000000000000000000000000000000000000000900460ff161561391357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b8861391d81613f6a565b15613973576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b8861397d81613f6a565b156139d3576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b6139e48b8b8b8b8b8b8b8b8b614c3f565b5050505050505050505050565b6007805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f810184900484028201840190925281815292918301828280156112cb5780601f106112a0576101008083540402835291602001916112cb565b73ffffffffffffffffffffffffffffffffffffffff919091166000908152601060209081526040808320938352929052205460ff1690565b60015474010000000000000000000000000000000000000000900460ff1615613b2c57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b88613b3681613f6a565b15613b8c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b88613b9681613f6a565b15613bec576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b046025913960400191505060405180910390fd5b6139e48b8b8b8b8b8b8b8b8b614c83565b60005473ffffffffffffffffffffffffffffffffffffffff163314613c8357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8116613cef576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806156ec6026913960400191505060405180910390fd5b6000546040805173ffffffffffffffffffffffffffffffffffffffff9283168152918316602083015280517f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09281900390910190a1613d4d8161422f565b50565b60025473ffffffffffffffffffffffffffffffffffffffff163314613dc0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c815260200180615824602c913960400191505060405180910390fd5b613dc981614224565b60405173ffffffffffffffffffffffffffffffffffffffff8216907fffa4e6181777692565cf28528fc88fd1516ea86b56da075235fa575af6a4b85590600090a250565b6000612aea82613f6a565b73ffffffffffffffffffffffffffffffffffffffff8316613e84576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260248152602001806159de6024913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8216613ef0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260228152602001806157126022913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8084166000818152600a6020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b613d4d816000614cc7565b73ffffffffffffffffffffffffffffffffffffffff1660009081526009602052604090205460ff1c60011490565b73ffffffffffffffffffffffffffffffffffffffff8316614004576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806159b96025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8216614070576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602381526020018061562a6023913960400191505060405180910390fd5b614079836141da565b8111156140d1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806157d56026913960400191505060405180910390fd5b6140e8836121cf836140e2876141da565b90614163565b6140f9826121cf836121c9866141da565b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a3505050565b6000828211156141d457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604482015290519081900360640190fd5b50900390565b73ffffffffffffffffffffffffffffffffffffffff166000908152600960205260409020547f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1690565b613d4d816001614cc7565b600080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b6004805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f8101849004840282018401909252818152600093611d4993919290918301828280156143235780601f106142f857610100808354040283529160200191614323565b820191906000526020600020905b81548152906001019060200180831161430657829003601f168201915b50505050506040518060400160405280600181526020017f3200000000000000000000000000000000000000000000000000000000000000815250614366614d50565b614d54565b73ffffffffffffffffffffffffffffffffffffffff8084166000908152600a602090815260408083209386168352929052205461349390849084906143b090856143b5565b613e18565b60008282018381101561442957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111156144a9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a815260200180615850602a913960400191505060405180910390fd5b6144b282613f6a565b15614508576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806157876025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff909116600090815260096020526040902055565b612ab78585848487604051602001808481526020018381526020018260ff1660f81b81526001019350505050604051602081830303815290604052614a3f565b73ffffffffffffffffffffffffffffffffffffffff861633146145df576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602581526020018061596a6025913960400191505060405180910390fd5b6145eb87838686614dc8565b604080517fd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de860208083019190915273ffffffffffffffffffffffffffffffffffffffff808b1683850152891660608301526080820188905260a0820187905260c0820186905260e080830186905283518084039091018152610100909201909252805191012061467d90889083614e88565b6146878783615006565b61373b878787613f98565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8214806146c05750428210155b61472b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f46696174546f6b656e56323a207065726d697420697320657870697265640000604482015290519081900360640190fd5b60006147d3614738614276565b73ffffffffffffffffffffffffffffffffffffffff80891660008181526011602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98184015280840194909452938b166060840152608083018a905260a083019390935260c08083018990528151808403909101815260e09092019052805191012061508b565b905073800c32eaa2a6c93cf4cb51794450ed77fbfbb172636ccea6528783856040518463ffffffff1660e01b8152600401808473ffffffffffffffffffffffffffffffffffffffff16815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b83811015614860578181015183820152602001614848565b50505050905090810190601f16801561488d5780820380516001836020036101000a031916815260200191505b5094505050505060206040518083038186803b1580156148ac57600080fd5b505af41580156148c0573d6000803e3d6000fd5b505050506040513d60208110156148d657600080fd5b505161494357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f454950323631323a20696e76616c6964207369676e6174757265000000000000604482015290519081900360640190fd5b61494e868686613e18565b505050505050565b61349383836143b084604051806060016040528060258152602001615b4e6025913973ffffffffffffffffffffffffffffffffffffffff808a166000908152600a60209081526040808320938c168352929052205491906150c5565b6040805173ffffffffffffffffffffffffffffffffffffffff8416602482015260448082018490528251808303909101815260649091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb00000000000000000000000000000000000000000000000000000000179052613493908490615176565b614a49838361524e565b614ac3837f158b0a9edf7a828aad02f63cd515c68ef2f50ba807396f6d12842833a159742960001b8585604051602001808481526020018373ffffffffffffffffffffffffffffffffffffffff16815260200182815260200193505050506040516020818303038152906040528051906020012083614e88565b73ffffffffffffffffffffffffffffffffffffffff8316600081815260106020908152604080832086845290915280822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055518492917f1cdd46ff242716cdaa72d159d339a485b3438398348d68f09d7c8c0a59353d8191a3505050565b614b5587838686614dc8565b604080517f7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a226760208083019190915273ffffffffffffffffffffffffffffffffffffffff808b1683850152891660608301526080820188905260a0820187905260c0820186905260e080830186905283518084039091018152610100909201909252805191012061467d90889083614e88565b61373b87878787868689604051602001808481526020018381526020018260ff1660f81b81526001019350505050604051602081830303815290604052614692565b600046614c37848483614d54565b949350505050565b612d6f89898989898988888b604051602001808481526020018381526020018260ff1660f81b81526001019350505050604051602081830303815290604052614b49565b612d6f89898989898988888b604051602001808481526020018381526020018260ff1660f81b81526001019350505050604051602081830303815290604052614571565b80614cda57614cd5826141da565b614d23565b73ffffffffffffffffffffffffffffffffffffffff82166000908152600960205260409020547f8000000000000000000000000000000000000000000000000000000000000000175b73ffffffffffffffffffffffffffffffffffffffff90921660009081526009602052604090209190915550565b4690565b8251602093840120825192840192909220604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8187015280820194909452606084019190915260808301919091523060a0808401919091528151808403909101815260c09092019052805191012090565b814211614e20576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b815260200180615675602b913960400191505060405180910390fd5b804210614e78576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615b296025913960400191505060405180910390fd5b614e82848461524e565b50505050565b73800c32eaa2a6c93cf4cb51794450ed77fbfbb172636ccea65284614eb4614eae614276565b8661508b565b846040518463ffffffff1660e01b8152600401808473ffffffffffffffffffffffffffffffffffffffff16815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b83811015614f23578181015183820152602001614f0b565b50505050905090810190601f168015614f505780820380516001836020036101000a031916815260200191505b5094505050505060206040518083038186803b158015614f6f57600080fd5b505af4158015614f83573d6000803e3d6000fd5b505050506040513d6020811015614f9957600080fd5b505161349357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f46696174546f6b656e56323a20696e76616c6964207369676e61747572650000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8216600081815260106020908152604080832085845290915280822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055518392917f98de503528ee59b575ef0c0a2576a82497bfc029a5685b209e9ec333479b10a591a35050565b6040517f19010000000000000000000000000000000000000000000000000000000000008152600281019290925260228201526042902090565b6000818484111561516e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561513357818101518382015260200161511b565b50505050905090810190601f1680156151605780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b60606151d8826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff166152dc9092919063ffffffff16565b805190915015613493578080602001905160208110156151f757600080fd5b5051613493576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a815260200180615a52602a913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8216600090815260106020908152604080832084845290915290205460ff16156152d8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602e815260200180615aa4602e913960400191505060405180910390fd5b5050565b6060614c378484600085856152f085615447565b61535b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015290519081900360640190fd5b600060608673ffffffffffffffffffffffffffffffffffffffff1685876040518082805190602001908083835b602083106153c557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101615388565b6001836020036101000a03801982511681845116808217855250505050505090500191505060006040518083038185875af1925050503d8060008114615427576040519150601f19603f3d011682016040523d82523d6000602084013e61542c565b606091505b509150915061543c82828661544d565b979650505050505050565b3b151590565b6060831561545c575081614429565b82511561546c5782518084602001fd5b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181815284516024840152845185939192839260440191908501908083836000831561513357818101518382015260200161511b565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061550e57805160ff191683800117855561553b565b8280016001018555821561553b579182015b8281111561553b578251825591602001919060010190615520565b506155479291506155d7565b5090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106155aa578280017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082351617855561553b565b8280016001018555821561553b579182015b8281111561553b5782358255916020019190600101906155bc565b5b8082111561554757600081556001016155d856fe46696174546f6b656e56325f323a20426c61636b6c697374696e672070726576696f75736c7920756e626c61636b6c6973746564206163636f756e742145524332303a207472616e7366657220746f20746865207a65726f20616464726573735061757361626c653a206e65772070617573657220697320746865207a65726f206164647265737346696174546f6b656e56323a20617574686f72697a6174696f6e206973206e6f74207965742076616c696446696174546f6b656e3a206275726e20616d6f756e74206e6f742067726561746572207468616e203046696174546f6b656e3a206d696e7420746f20746865207a65726f20616464726573734f776e61626c653a206e6577206f776e657220697320746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737346696174546f6b656e3a206e65772070617573657220697320746865207a65726f2061646472657373526573637561626c653a206e6577207265736375657220697320746865207a65726f206164647265737346696174546f6b656e56325f323a204163636f756e7420697320626c61636b6c697374656446696174546f6b656e3a206d696e7420616d6f756e74206e6f742067726561746572207468616e203045524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636546696174546f6b656e3a2063616c6c6572206973206e6f7420746865206d61737465724d696e746572426c61636b6c69737461626c653a2063616c6c6572206973206e6f742074686520626c61636b6c697374657246696174546f6b656e56325f323a2042616c616e636520657863656564732028325e323535202d20312946696174546f6b656e3a206275726e20616d6f756e7420657863656564732062616c616e636546696174546f6b656e3a2063616c6c6572206973206e6f742061206d696e74657246696174546f6b656e3a206e6577206d61737465724d696e74657220697320746865207a65726f2061646472657373526573637561626c653a2063616c6c6572206973206e6f7420746865207265736375657245524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636546696174546f6b656e3a206e657720626c61636b6c697374657220697320746865207a65726f206164647265737346696174546f6b656e56323a2063616c6c6572206d7573742062652074686520706179656546696174546f6b656e3a20636f6e747261637420697320616c726561647920696e697469616c697a656445524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737346696174546f6b656e3a206d696e7420616d6f756e742065786365656473206d696e746572416c6c6f77616e63655061757361626c653a2063616c6c6572206973206e6f7420746865207061757365725361666545524332303a204552433230206f7065726174696f6e20646964206e6f74207375636365656446696174546f6b656e3a206e6577206f776e657220697320746865207a65726f206164647265737346696174546f6b656e56323a20617574686f72697a6174696f6e2069732075736564206f722063616e63656c6564426c61636b6c69737461626c653a206e657720626c61636b6c697374657220697320746865207a65726f2061646472657373426c61636b6c69737461626c653a206163636f756e7420697320626c61636b6c697374656446696174546f6b656e56323a20617574686f72697a6174696f6e206973206578706972656445524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa264697066735822122005677c3919f4b149e065a5983baa9e2fb099cab5463ccd06429f70b32d8d9bdf64736f6c634300060c00330058210390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5630410103bdc011f506efa58f1b2550ca80958d2a7c39f979f40d537e110b221c62364ebd005821031347d38f9027b008ab764477dff5db62e1ce3cab675df1efadd25bb228ba1ee05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00582103216fe201c80e31523065747b4c3a11b5ce64700caa277669094d49e7d42e4c105820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00582103d1108e10bcb7c27dddfc02ed9d693a074039d026cf4ea4240b40f7d581ac80205820ff1bba8c5962591285628e226cfe21a129312cfd293b407070da1d82f098460100582103517498924e66ba831af2769eea8312cb5261242822e63a2d7cc49a5c707be180500785ee10d5da46d900f436a00000000003b30d5e39bb2a266518592f8bf752ce6d25fcfd24908b3c862f5f487ddf91096100582103e453bb62162b907fe0133f23a577de7bec75205eca5e557cfe3ef2dcc72205905820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005821035ca2f3d6ffed7c02f4752b25e41af1736966153795558b64be92a8283b5b1500430f42400058210317fb9989707d00d29edf83f0ce78ffe6efc3e7e2f0a33e6a7370cf9390ae63c058208000000000000000000000000000000000000000000000000000000000000000036082dea626a28fd5714b7132ebeda7497f1e481e325c85a848f757266e4fd23c0219fd7405581d020ab04938f26cadaf9df6fe1389ad0a110acc2ffd76feaa1dc72a138e0701195ba802182205581e035c0f632b2b143a27400257b92b6008ecc69a7ff00d34104f16bff667200c0147038e3c48e101c2021976da0375422af2844a3f87b243c9644f9f0ace175126b6492d47fd381fa2a357c1cb80038ac19fcc050a9e37b43a0f86466b5d2c5f9fe6e03a54a31532e042a2af4d43fa039b617dfcdc2dc5aea5cf6c052a8ae308801747f0bb7e141baeef835768b7806903eddebb3d9c98cca8c716e36fcb1ca053ddd936471a00f8cbbdfd18edab81e54403fd6aeaae8798f0258a6ccee26583471aacf6b73c9ebc543b7a58e8ebb3e31e9e0301169c038c640981817684b51c3e5cfc459871984377458a9bb7da2ba71e0f4d03378358a91de0e8a9248740f7b8fd7151b8fb651d32714e9e651d71f969f9de4e0393fba4b24677444331d035dca0bb5a72cd32ca6763e82c6ab222e08c1c6f6b3f0378dcd74e07bca5759db73a24e62d4099a3600f084d0458830897538df421b76803996080aefabffcc80a0feba456709477d477bad68d9d4179d600201790235f570319a9ba3f2108048e07191495f1499e56c1a33a96d3f7e135d3410d10cb6e527d0376afc3bf5b27d160ed41d005ba5d67039dd2f6f8f874efb2ce15b84a89675a6603f984c38c2d5a6efa92af5a93f30139b77e3d9c5b0c6ada51b61c69068461cc250219ffff03af6682305ae7b03099c2e54de946ea6b56dd4ed1dada269eba0a47f38b120f320388c8cdc2a2b3326610660efe808a73c1c21992c3d17d85cb94df4df17f35b88903c5ad6f8fb9bedbd6231dd2a9dd8c86c51334fd0116e62006edef50927010ff7503430b0392e91a099347a3199c0c571fd7c80482f7c8e5c2fe49605d3114ededd50380e40eb2eb5e139a0e8ede6832ff0e970845863c9998be7c74bb1d0127e1d94503d4f110db03b2d5f9414b8e8d371a50bac4209a1e0e50865c34707868819b6cf603ea5582cac2968a17342736076a86fe46936420f91c3a61d1afb94fde99e60cd303aec7eed066d191a30043c923ef3a2be192fefcd6d87cfed635c15f5d2d9f276b03f0f23bd5a2e7bf67f7ccda1d98611afc610954eca5d71ef9bc83b1c3c62f343a039fa1c7a07e22cae10147e96e609a945ed6c979f236ffbd190fba130d76f7017303f386a057c0d2f1e078796fb7b44f7504b34adb1e05e8df9a1e595af6621e9bd60219ffff0359508ed025d5670d667c5d4c3d261b629944968c5fff83afee0f637a85d9f04a0375e25eef1c36b06fec7425b95d7dce58181d36a182a560b23f3ac5f2854282130387c050177c7e945a8391a0db9d8b3be6556ba18bef2b258cef6289abb6e3f0540344568888dead34c24a9355c7e8a7bac606ae69b9995d3feadccc83772d1b3c2a03d7c69884c08b9a4c24e359d236ed8a10572d9d1da9091525c7c712a1264aa19d03a1b9ce321035888eb5b1692e748edf6c92dcca701d34598653009943cdabdb3d03d7e4b01565df73aa64da6950dc48f70a102f003deb8feeec644276784fb95b550219ffff03d8ecf4a13835840414860013d56cf1990685c7809c6b934c6e18c78bebea6aa80332bd5487c0b187e28e5402454cb28ac15ebe5f17f292ed11cd3906bab9ff1d6603180550b5c639c09e562e53aeffb402379c16d957743e76d75b59d5938e7a85dd030ee473ad0ad8c3ca34e81f9cf6d174ca0350791acd10efdb61eb8fc14fada7e103bedb52411df67da524a539222e9e2e19e2147bbf0e2fd27bccdac57e8b0f006b03f210feba3d08531a5979973c54165af4ce4c706544daedfc8a55674af3a795d103f0f82fd96b26fc01967f78a7c4cb3427cec2da5ce2c7587f2b3f3df9f9b7e3310219ffff03bd6c5438f4cb63aee0960d50203cbb14e9080767d320058e75d827f2f32480120313d29f4c8e3c7fad64e5865dbc5ca27c234da7c1f68a651df44da2f4948da4e503b074eaa37cdc5af1e5cae7d3c8990f8cba89d45540fcb51fe802c44ba9bf0d940219ffff0352364ad1b316271c2f8b7b05604ce2662bddafe9b5bd7861674349148ebb4a81036bb759bd5dd6f1455ece7a3eaea4a225ebc721f3a4278f5aaa6b241a022041ce03f09d3fe65db5559fe43b961150d7767b00b24f06c654ae541795401b83353ae6030cd43626894c44c1d18c1edeb6896a76cc11a6cac8b892e08db0c76a9f6e1c6503a57e385726bae4d4591ce8d4954aa59d137b07310f60ca749a354cefed2a777b03bd9793bb140af19d25c449a87e56f340f2c7ea439a5a2e85c06f564575f9358e03e719dfdb336a2f2a4eddb2e1d0147fbb096055d8268a416d5c53d4d18ad17c2803e814fcb5f465db443532245ef1c3e26ff54878b8bc0c9342ddc7cb14a221e0a803b589fe3f0efa2175970556cb83f11198fbf26a4d6b3460b32ba0c1b29c8ef20503603d2666dfea444b822e10cca08af350e14f76cd27409b875d9d4e546ec3bfe5038932270520b0a21b5666a579dab3063540060a5d6b0189e295436ef86a3d0ac10326cf37c124f939ef5c7d3349904be0225c6ba054c3108285f6b88608807d3d1903224a867aaabea5993913aa07656725502c819ee0123d38009e7aebc36eaf1a8f03c699c6e495b9ebdaafbf526074b63632d5d9a709a2b8f3a22f4c4468874c001b03bbbd99cf9792ae6ae78eeb4270b30219875ae8d120d497eaf0c4c5f72f658df9039735f29c896d8fd2e5873c5c7a143f5cde13e8cbcd37c472aaef87859d369a0303f36f84e203beaffda0838e787138f9a9ed38ec08fd0f92bbed39a5f649017eec03f7e86666a7cbb9e9478f5d4f20a95d9e093202a2878a6626a27e8930ede112b5037fb3aa31ee298275284260565a045bc33b3e1c0eb3b284ad3022223c0bfe65cb037ee788b804b612219758892881df2103dfb1701825933ece0eb865a355c7400503978f59b94b41585bf997ff7bf53602355f327c758001a66c40da79213114e7c103418a57029b7336ab35dcde64aaf6c05b5688763c4d996af8e19b12ca532da253037952d4b82231bb973797740c10d1da8adaa3591416498df2cf483895190767b70369c40c88a986d2ad437475c38832a39a1d35c152ce661e7d3c2b7739e41ad65d03ad5472fccb9fe8a4e6f6dbde546251fc0a28ec170939f338d90ff7ccd3f8a23903b762ef1046b76c49a11dee46475c0d8309e09bf58d5831db149aafe187b00c5b03d1dc93fa8e17727b09e442be0093007840f9838681dc03dfe23455799cbe29550354f67d476b8244833ad6095849f58377eb3d3feb994cb99e1f110b9312e5a68403a578ddda08950c2deaaccd631b13fee192edacc442a855354102a174f13bb9150367e87802d1d939c4d58d69b9ab35794ab3338f8f8303a5e38d98a904e29fa1ab0327b93e3bec8a3b8794bc9d0c08ba63d671fc99f272d4e28699ca6a6c363b5693034951106ae2c24eb77c97bd2d07ec7b129b3a242bb99744df7437e22f1e94e28e032e4286627a6424c4c7e6d6cfd3e0716f73c0c23317d41ac61ce9add54a92465f03ac0e42480713a4294bb759de853fd2ce9ae7f73a268b6c2f7319e0647367bf5603d3e935f774dc01717b0587ca55a3be582c9d5886f5b7a05c7c2756d9e12f7d40038467c84b267451852fc9c08c93e23bbedb3f06367501191d455e54ee4948856e03428ae31eae9618588c602d7dbd4dc45bbd0f09bd582106c6298761666e14e7b4036b65ba160e6025cd21626b158f610373fbbeb1e5e6efaafc5e14fe487495df3903319b863edbbf5ce2f7fb7e84a43a4a6ebad10737fc8d8df61779fcb16ad68fc603897e27f0b8a59b641ae12e0d9ab54e2aff3b997c73c4e68a0e690ac46d10d97903b6b57e6dc03e26a18d637f9ac7c8d1e9ef09f707f2528398e6a666306bd50e4603664b5351af410fba4b9207727c45c38a950adfc0791550cbed9a9a8370d9f308035323fcb3d61183749bb11b848abb44db7e89a0d90f9f7461f8e3ce6d372059a2038045a8809e3c1f7bc3431084c44db03257f71ac796532fcd2bc6f997128b70bf031de2d69f69e8c8f179f426682bb46db3cafc2dabc9297a93ef921f5a21e67e9a0315a9251e4d534129b846355bf23fe7a2590cf4e87d2d961b5d3544a87d97fab003ee20ea803e208eb1785247b9ae64261d55d8fd5c63fc9ace45094d78eaf15cf103dd7054ec140679765d6482262f0d55e3efc4fdc4efd857b62537590c5c6cb22c0385df93e938c128515fe41e4fed157930124adb1a703282459871c72853e784d3039eb6c9001b4cca9709fa924b2939010df907f2c0838bea811f0e7d5526c8c9fc030a4fff2cae7d62385c3c841da8a176d61cb1ba035ecd31d2742e73aa3d3744aa0339af3aee43c7a8692c78c5a29690e90581fb4f4263a1b2877516306196a4a2cb03f8c290a3d8233cd8cfc9f641c538e808a3356e49ff45d122486ed708a77c64f503096525cfb301c70c6842a25028a5474ec59658795ab52415171a9873cbd1477703de5eb12d31c632181f0fd669c2f39323f78b8ee7147da74d975f505e811d3b9f0340a67714071de96b6f686568f1528fd2515e085228c15c2bdb97253f3707358e03dfca6059b898db973fd8cc0ec55d587f5cd26cdcc54c76e394ed190722d0a9e9031dffb577ddd3f75d8b951b93f0b006f8f9c9406282d915dbce809616f674e97f03c701f9a90e796a542dde21183f757e2bdcbce5f4b9c70ede5f110e6e89e9e7460458ab608060405273ffffffffffffffffffffffffffffffffffffffff600054167fa619486e0000000000000000000000000000000000000000000000000000000060003514156050578060005260206000f35b3660008037600080366000845af43d6000803e60008114156070573d6000fd5b3d6000f3fea2646970667358221220d1429297349653a4918076d650332de1a1068c5f3e07c5c82360c277770b955264736f6c63430007060033031a02a0d9e5db3663cc6c814456e1d6cfead7da963f9ae6178901c22cf7f72479005820029bfa28377b0cbf3f30c94c44d53591ce0f993529b974ca13869af2e152de5554c66c8aa97065f327fe94b6c845acd7e5aa6f6117005820020decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56354d9db270c1b5e3bd161e8c8503c55ceabee70955200582002367a1e93015b6cf6c6d03b95206549c303b115c28581050a0a76640a7faa295487283071aed93b5ffe43fc97c8d387a57cf57a7b02190a1000582103b19ca0c95929a2a946820150c3c2aa0ee9fbecab56f94ccd76692eb3161f8b90410103c6b0036da1814edb8668263506f5f2ca2d7af469aae95ceece4f0c87719d64b100582103fef4bf8f63cf9dd467136c679c02b5c17fcf6322d9562512bf5eb952cf7cc53054a3014707943bbcccc2c36aa8e5e5cb13db463f25038a9c8f495de1c0f701f7379648498eceea2cd5ff0f40b07b3cc88351a2e4d51b00582103721fbc08ebc780272f50db65b723bc840f50ef981305a91aa8affda1e04fe6104101005821036552f9043f7dbde1a3842eccf846aa02c647c3d99f9371aae3c93798f73f1ab054f48f2b2d2a534e402487b3ee7c18c33aec0fe5e4005821039a728692d694b98d801b72ac20272f7125aa98bed3754df76a17b01e5dcc92304101033507edcb9f82807ce6edbda335c3f99b008f087adbba61c670e3a8dbcd1413e803c11545bb8c1038ce6a5af743d780e7f125983e4d2371a7a6605b566793652192005821034809fc24e46d8b3ff23914aa5485e86bca85b07a862c6c61e2b739a9b0c4712041010219bfcd05581e03a7c44e6e6f10dfb3fd54c75fc36f8e7944076f9e43a475efdaa0c8d4000f01492863503fabb4faa03c18ab03af05f0b75e5fc52f54c5dafa6904b9e6c8a658cc7f71e5e66e8321c5d56ba6d803b41b3a47456e6a4475858dcd426c8b16241ae0b927e11e373b04b4e26241f77405581e033e7cc04dded55ed71e97a7d6d0a4baa236f187a72d2fbe7a31bfb09290040205581e032d0f73c5bcc5473bc059937195e69ecbda5067b8717e64978e7ce10ed00c0d463b5d6a61e1c805581e03cf36789ff8d54aa3b7f9ac9a45c6e59558c9f250bd96699edb42cbd0e00c034740f192ba252e000336e9689ab9beb84ff0db471dbf1e295f7d52d3f18d7bf0716707a06a6f8c443b01410805581e03d6acdd4641b5e1ee166cef3b43f06bfdeb0ffbea03f84b87636a6f5bd00c0146574fbde6000003eb1d68002e96989f80bdad20de8a948a869a1468f3e501ed999eacc3f7cecc610388b62f9296ff82271178174d78caae5ca16d6fd85f68fe7979719740c4cb440805581e039d43066fb8d2ba31305681825250e148b5f4785c309c8d1120d5c151e00c0245f6ccec680003e91d804116acafdaeb23fd10a3a4a65817e857d8ec6a423ddff0af6eb05d3f220141040304d9b8953c7907632ac2c29265d3d79c864578f40a153aa73cebc4456e94a7fb0219b6ff03fd1c7757e911ae4c83adb077dfe60fd07134812fc4367fa0628b94607d5572fe03ef36f517a07bcea02cddce661761a82b40b00b338e567d498914d8ccefea8f1c0219ffff03f82714de77045284ed82da2bada0e10561e931e27a048d8cdd64dc1df880f92803c2e4fae8ff40375054993a076088b2e9341f13a8cc98bd9fe207ea695d28d1b90219ffff03389396210692053efd9fcb65227652af87df90bd54a0803f636ce9af8c3402970333f487d2788628ea68791b29052968e68e189ba03e0b4e9b7c147d8f534fa6400301236bd58f2ccc3ee37e4a9f12a875c9ecb5d65f9904970244f2c435a1d1ed58030a46a46fc79d53cfb896d617739840f5467ebca1f5bec5db09ebefb7d5d5916103bf12abd89e00c1c83b527d24a763621f92cbe51e585bab19bd2b9845ab3a266503129169cac2a2fa5a0611da44bb1e193def1f71ebb9da9c31a8f11d7c88ab70530219ffff03ab7247f0f5f55fb3988b0b3902fffe993fc70e4bef5cc32d2bf552de9175c3fa03d553f1143818714e91f2c08e7bd928ee980b9934db42264f8f13c0889d4e58ed03d58757d4a979bbce0b7c8191d31f09af20d0706bbe014ec61ea61a61e1a24d66030cba6a5c550773435f67cb2f30164ce96371fd643ec1a3cb32d71a56d698b69f0219ffff036d6f7d35e5702cb31a57f7438f302b696763333edf8687b9f8cb576de6b50a4203c23910c7daf887f202b36d06782533cc216f0c772e4ceef0ff1de7b0cc513ce603afde653ec955b650c5fe30aef08d51f61de99ad968aee94c48f2a54a53a232240330740bdd4af7f6171c2529bf02047d45e154e2ad5483b3a2a34d1fcc404b70010374660bd74573eec75b5001615d460ebc40e83420e1206f6f022d323493fff69503b973fc0102b1ffa1e5d066d6a1361dddebcdebffa0de32cdd3526a724b722fbc03291d3f0a869d63fcf173b46f45760efbbefa76a80694b979c2bd9d0f8ce728fb03184d10bb3f1957446e61d32fe0c3f79c4bf6c7c64692e81fc0b5d07a05c68db1038927d8649a41eec8455c94ed8300762ff9c4b92bebcc8d5e79aebba084deee9f037f7619d4c3f34d6dc8171fa574e85e058c3b8bbf2895efd97b58f27586b8a99d034ede30a6e02b494c2b3184cc5da663c5c38c1875ee25a99f49daef30ba63ab3c0374c6180634ac0cea7765d1bc613d7a12b8dad5d3b289e8b7d3191079c3f7a192037f918e61096db1c50df1779268cec06d761904f1e7c877fe9a7aa588b61eaf48039c8b0bc2dab6ab3a45479847eb9f46210fe850d8f6e7699036d76f25b4f5551203f4edea215e7baf2c4b90cc8e4bb920e15cb87ea9db03c297e9510fc5c7e1527503ef740e71dfb185ee90da49f3db3387d3ce84114e39c381d915e02498124dd90803f336ec88b29b64bc66200b86dd3a25a87f660ea846f19040963c85aca5271ee403a9e6a0ace9e3173c42857d13ea5a27522d4aff5cf994470560a8bdce97dd92af0314f12d354ad21627d6d7259a4b522025f24c66131b8cca9cff0e64c338a0a87503055fbe66b56e775ecb4ace42d665da13a2a13fee3933e1b68916c4907be4253a03f50f99f2e8268d0d7e9b2599e48c28271a60db0358df7c0a0e452acee8a8d663036c2584a9790f5e314a95ad055c7b9b5b5db52e13be75fbb7ba1acf7b78861ef9037bae4e065fc732aa1a2c2ba04128999b627c01c26ee72c557e0004678ee4c4c6030741835d3eae443c2e227b89a05aa6735b807ea279d0d21ddcea33dd08558caa038fbfac409146c2a0846f5223362232a7f16d55c00464c29b1eceda7f05f313b003629146347868aac64580e2dc448d613e1a8f4f49bc6e512c2574c6a62b3d738003941121fceacd92d983e19a181011a7e82a81ab963819c07f13006520b920fbde0347e388e376c185802674234ac0e44cee6f21a277b9d4234d9a5f92210944f26803866a6fa658aee93b63294366b22f8e52c3df6ad005e3559947da06769a7292780304ed8bc22bf4b6ce55298c16a6d0ca122b4aaee857d81498d2b0a9af8c84f72503571188b3bc8995eca1f7b473358d0bf7b527fdbb07ccaa45ffd8ca77b6c0037e0333056bc198788e42ed1ac03cf3bc44ece69004f1017a9ff27ea9a54b7dab421203d3578a5be899aa434bca75e068d02b179c0648b5adf72733f6fcaba9f7442afb038490c71a584e93fd0fd5097edeb47f4305512c30860d6406afebfbb029178f4d038fb11e57c16b243aba4707f77aef3b4b0eab088012878aa0392670212dce6e1605581e038053ff4e96caa01c2152e6e627b230012baa5a386b231f7d44afa609c00418bb03562d59a51820d47f520c975e0b2bcffac644a509749a3161f481f57b6e826d210605581e038697117fd81c06b06b788ae2a0f80977f36f9340e97ce65e362344f30007011bffffffffffffffff05581e0345f688654aa37a3a79501210323d7f64301f3195d8e888a6e8c55048200c044680aa3629f4f805581d0324e09cfa3143cc228d32c5727d891a57009cb512dd1ce09638de6e4004030459031d60806040526001600160a01b03337f000000000000000000000000e8ac39f0a15dc7eb9cf32c7639e633f24f9ec9ab82160361006a575f356001600160e01b03191663278f794360e11b14610060576040516334ad5dbb60e21b8152600490fd5b610068610106565b005b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc54165f808092368280378136915af43d82803e156100a7573d90f35b3d90fd5b634e487b7160e01b5f52604160045260245ffd5b6040519190601f01601f1916820167ffffffffffffffff8111838210176100e557604052565b6100ab565b67ffffffffffffffff81116100e557601f01601f191660200190565b3660041161018f57604036600319011261018f576004356001600160a01b0381169081900361018f576024359067ffffffffffffffff821161018f573660238301121561018f5781600401359061016461015f836100ea565b6100bf565b91808352366024828601011161018f576020815f92602461018d97018387013784010152610193565b565b5f80fd5b90813b1561022a577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0384169081179091557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b5f80a280511561020f5761020c9161024b565b50565b50503461021857565b60405163b398979f60e01b8152600490fd5b604051634c9c8ce360e01b81526001600160a01b0383166004820152602490fd5b5f8061027d93602081519101845af43d15610280573d9161026e61015f846100ea565b9283523d5f602085013e610284565b90565b6060915b906102ab575080511561029957805190602001fd5b604051630a12f52160e11b8152600490fd5b815115806102de575b6102bc575090565b604051639996b31560e01b81526001600160a01b039091166004820152602490fd5b50803b156102b456fea264697066735822122030cc9d72e095e794d8286b088776326d90cf6b4d62a84a41c9928eb1eb96b3d064736f6c63430008150033036bf9111a4e63036ba43b1ad8b3a21ea3a1bf1583e3d327b04a5fa000cdf1cd8b00582103b6847dc741a1b0cd08d278845f9d819d87b734759afb55fe2de5cb82a9ae6720582096e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f0058210390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5630545c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f005821031ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c680541f98431c8ad98523631ae4a59f267346ea31f98403ea63581ce01a703eb6ece6ed458d96aa452acf103a0200c73dfd99b33a2d69a603f7d48beefb88db52e1984205b63aeaa6bfb90eeee62ec4971fd93c72d7538823035f386ed974528f8a87cfb29c07e21ef4118cd779b15b7b44a5f99dbd3484dba0005821035b20eef8615de99c108b05f0dbda081c91897128caa336d75dffb97c4132b4d05446affe1b4f3fc41581fd20fbaf055daeab80a8b503d5f2071d9b48cbe281d6cedf2cbed725df2818b6c6fe0755b39f69b97f07ad3e0058210366cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c688042271000582003e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6054c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200582003d2d76d1f4b7be834882e410b3e3a8afaf69f83600ae24db354391d2378d2e041010209039b8aa1895cf233ea696284244e5cc7b2d367995715eb2953c9fc35b16432614202190802032b862613ed3bfbe3f9cec1078efd720626732f4189967861f59a9ebdf0d549ac0327cfb13450cbe366c6f97e00de1d7befd7887919cf37871946a2a889d2ebc1400058210328a5566b8a884201ab44e2d991177ce8b88325e02e52cbc3da6e67b3ecf29c60410f005821033f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee305820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0219fdff05581d0365e1068e21405c053274a59672d54b44fa24c79ea53efea0eab136a0070219031d0219200801410a05581e030ea64d72d2dd2d80d58f0e44353cc7296175b643867dedf40d2477be600c182d47478144d653500003e09b1c7ba83761b81aca61084cb1fcba49f5226c000307819efb42abcad1b1c40377c2216d17f562429d033b705155e92fcef8f524b026ad1a42ca5771e8d82b4c03e3d9fa01a5830db36bc5ad827315ea63d76349f0953d2bb2d5570a5b15c00ff903647d5d7eb5af6fbf9ed95320cadf658cec3819ffa536adc7b6387fdf1b72123d0373c5dec0c39df458200472668fadaba3ba732e214eb245cb137876ede47c718605581e0352ba6cb1ba80cd19836ab5fb85366505097beff6427103919802d8ad9007011bffffffffffffffff021967b7036f6f9b23e43e770a16783e16695fa63f9e580d5acde23d3ff4eaf7bb450dcced039bfd18a448a023cff2ffd497548898cefa58a8015c140d52a700e05d9ae3d4c10219ffff0336cf901dda928ebd2e88cc3c743ec9df0eb64b672b836674614bd41be6fa20b303f08168fad6b0b396cea26f4892aad7c5de6df60fecdf61014e8bc76f2f53748103b134808a4a11546f8bb6167bf2ce7270593a55329da355d2205a6d641a6894ee030b0b1db88e87f55903f19e1120ecbf7257f6fb03525b0445f3e81f0953066231030cffdcf0a99a360af2dc18397167ad4c251422da70cc7143beab3d72bcd85b6d03ff18afed4c580a3d24c285db4d8bc1a6ffd99fd265aa72dcba937ab67678285903b359987ad29384443fff76147295e15aab95364155a5b043df2eb23710e78c6d037c4a0e45ef3c635904ad0ba464c6e19ebd428923d9c21c15a41ce81c4b9dafcf03f72a3508db1eab1758a7b804f59e70d93069077158f9600ec21d130375618eda0219ffff03d32c706bab37cc22aef0134add434ec5d1195059b4c408e8bbf22f9f522897a1034d81cb1939b7a12b32a986fb5ed9668a057e929fcf2af32938f5ecd7560cae95035455530a9f5a0024a3f940ae45967482ec5315c99739c71291076ce6a366afdc030d7cd74af3702cc9f41e5f50f4093ccaab2beb69bbf6d70cd1f4cccaa71ba06503251407306071300dd8d78c67cf47b7c4969ae8dee7641a4a909b3f01b00e679203520cc597c7caa9272e0b46a1ed218c5c7094c4be0989fed44c0422b235d5324a038c706cb54cb361a2687e37d8c75e0c418bc22e5e39529a729049cb73ae99a12703f002f181b5556b581aab3a4e448b61b9b5c86aa5b699e36cdba7ebbd9735285a03548a3e68d18d64b8fd4e7ff9a81f4de383619fe636a7963e8004cc7872d5f1ac032ad9a8bccb245f6397e0d65a91e4304a80d57a1bfa1483512c1776e95405399e0332874933c6c6d7ea3d0705bc1ecfc886c284213012699d246526d36fd4f237c5036e3535701d7be395f62105f72ca9981dde21d62967d96b790a68730a84a209f9030376ab238766f1115a5b33874254afbee61ad2e0ca9ffce4d19118d60f8c029b0219ffff037c92233674d33dbca0273e095151636bc14f583cd900880517ad78b29dc158d2034238998146ad8b2c18f0a34c21fc91700469ca0a1cf47cc5284780afbd81bb6003669173f6174a96cd4016b93ecc779bcb69f5365e02debc4ea6daba9bca41da99037d105dbbc01dcdb7af970d387e6c5af8c02a9f8ef8e71b7c2c5b27ce0fb8a3f80219ffff0219ffff03b186caa2538d41b8dca12f97ac3b784895c65de9016e1a4ae4b1cfba5ac0a567033e983d5ee1b269b0ea817d21438f6f82822965b4a37b6ace33953d598830c35703a073460b076b486cf2cb53a7ef1dd47cd72153d52c9d40a9a8658d862fe1014003effddc50a5d0777c8a6e155c4af0641a015dbdd491ed6562d9ef1036ddb51ea003b240b6c8ab4b1cea0c7605ee236b0cbbad021b35f09c7cf3b61c386c728767e803dcf556de2d232d37e3ecc5ee35fc69457d823d25e7f50f72f45dd8f4fb5c0a930356dc69a7f77e2d419c82be3a228d954b8ab0a0ad5ddf3e9dd8b06ab785b5730903ee2024175eac266f791ddb179f2a6a13357a55cecc6b05c100112e33bba8118a03c8ec90acf29a630ccfea290468f284b5d84206a25792171002b15791b4732f7d03b7bf8198a8e73b5476accf662f19506480460a5e604d22863b2bac078708dcc40383654c14b2171246dba1676b1a25926951f64ad1e44cde2b77e776aaa9973ef203c5aebde8074ee94ff8f74d27833159509fa32a8ec0fc210ec3f4391cc7effe9c0314bebe9a1613fdd47f63874da68db4a1f39fc492f73b46278d1c2587f9e5841b03245b052e9ac5b39c5389070e8aace1000cd54e5f498ce1d905db6200c15248f603091af36fad56d652564fbaf5120ae0c2afbd8cf57cb03aa3aed7639745b87e7a034fb27a6582e90c45d0aeab04c5964b60aad642b50f94ac46d91b4e377b5a511e03fba684274bb34cf7c48d32fac8d39ab665a905ee7557d5f68dfca87cfda2dc7703adec40fcc7ab3cc7f0712662f9d233eb42de95af9937dd95ba082d92ebc02690035b64abbe65c04421537d46cd8511cacbc886849b03353b85b3a36b66161a1b8803817eb9d7e8a2d204a274b71182b3146da62e5b1284b199fbe825132ff275e6b103f23e911b3ef6465114298f92ae392ccb4a46f1a7efa2248fa6a86ffb49a0d6550324402e73ffceff9c2c93a52debddd26f1838ffbf4c78ef9f2e62d421ad2bd696032ec506a6e63b9a33ccb8fbdc4f2676198cba04eff249b1c3fc0cd061d01508ff03224b96a96a29c5fb6e5f5a927654495b3017bfb4ab2af004f226de944e05c0d5036ea96670aa7bc642c8b8a41c1601380830309d367f25506df309fadb315f33ab03c6516b2ea9e2b29080570772881c27bbe8a5ad980edea45c143ce94b04760fb1031a3d2bcaa4aae5d656d02224b3b3672534f52d52bcf534d6b8b47e1d31a094b603b56492a8ca2b998c6f2f4f7b1b901f578703d2d54846f4aca309148e57cec73303501b157b052dc0f013a8159310fd0827eb38cc988d90a44ca60d5e27d07438ca0318f809e3a2f48e14c068f3dbff656c3cf118dbdf810ab2eb41edd1bb23872869034328c0137f07e61954349dfdfbe1494e4670e783b257e04e9d37b474c7dfb54f03ff17b8dc15f2c325cd10a460d557d67eacf058ac2f0612085f0866ce39b4b65903243a9f01304270c43dc78314c426eeb71c6ccb0b3ed7c1a55b65cca43e7f8be703e3bb184faa99eca75aa5f8d2e9551d330dfd5d000ed4ac4a3bc1caec8155e20f03b5a8e99289b03256d5a0997f34af8e83d58ecd6a0b49131165c27404c9aa2ea4033e980d9719d068f9e564061def92bf7f0c1431d4254bbbf4134d4b56e021b6f903fadfcbf91a29bebd608931329c96b5811c1343583d8780cb5dc71dca219e689303c195880ebbcba856c416dc363c0bc61c632185933fe60317daf93f8b83e1a2c203578d7e5061b0306312621fe8134e68f5a3d0b59b430ec61739b13830b302c8b703788dbd2c23272499bb1ee5d6d1eb8244881495819586f601ce216de575b43c1603080ec8d006505e440305393abb865ef357cb46eb07a2e49d12a0d4811ff4e8f103eb761f9e7a92608f196a5a769049c7672d0ebd858054ac860d075f22c6e9fb7e03aeb90d8339ba90a180fa43481f2f6bd4c34a3e96b63d11167a687a959837054803cafb14f596be3a83a9ceadef4abc65a5e189ca93c8717a88ff5054391e4e9425037b1f1dc37610377e65ef3b6d9a83b95915458aa8d9d77d968f839d20dd9ce3a2030dca87ff2b319300aa6ba1f70644d41ccb4f56bc1e4e04bf4c6ac6e91d1bef4d03ff225e94f3d003221ae3f25d9e068bb5d233b486ab2007928badd9545fad814303f578ba6ef4f25243b80c1c8cb146563fc96f1ee19de194b8e143969e9fee136c035d79f70921fc86c1b68c54953219565a84d7c4943e71b9f20a193eaf8c1e9ac703f142f0b4965485de3d6941254f6cf57f8f30327ee97e0d22e800a6cdc5f2a33603f698858960c714b534baa70ee7d932aee37457e3d9491f2028b7ab76ef322b4d037dc581e8ffa0a7bdae13ef673135d95ac3f5acc1d3c74e20ceae28d2b1df11ac03a8bc69c936b28a6c39af23f5fcc4a3844384153605b544573093a1f65b5bfa16031aed823221ca6e0df03846414604a669841a222d8e4f931a80e94bbd23c1ca56036635dcd3e6195e9cb56b54aac693b577c7a61efae8a78e083ab4ab09dc0dc0f7039c69cbf3404d2600ba5dd29baab894cbf14eaf90604ecb0bc386234c050659e30322e543fbc5747a2fa6dcc745e6491bef82ee61621dd4f0f0ba122611bd2fc15105581e03548b4e00738642ef76c64430a458d2935ed62e3bb3bb3f3a2470bd72d00c19162c480b1d3e8cbfcc170005581e0357c90cdec79e0dd51ce7237f11dd64489032e3714ea2ed321ce6d4f1c004060303e7cbf53173e4de491288d5c010b4057c59c17d261133f477e22024eec8b14a05581e034c4f5ebd61961ca5f1b4da64d7a11efef7be683e47db94d78dc6ec1d300c0d47bdc4517c499cf403efdb6b712223be44d1f5ef236e1bbcfbb0ae48b941a84685a2073e2fe150b69705581e030e50e356b1f2d5cc60d54f1aacbff790a893b046c3889f2be31a47c060084812a1d8db3911700005581e03627fcc272f5aef2b30392948ad61991871f9e8171e558b7c1134f2b5f00c18184711b353c10a378f05581e03a214e046930d119c8772e4e619de95a950854c7a64fdb08832c351b7500c014807595f8f907a9a600353f3aed87582fca2e8818e5b578a8330637961f4f9dc132ff55fa7a932ba187503460c59fb861f1fa0852ff099b65087576b513d3220558b1d3968636ab27a4ec70219f9f003a82c025aee2624c99460cafe3db7cd7d733a9a51c7d5e3dc3cb233189045a6ac03cbc2bcf31ece53dcd0d9079f7497e9c5eed93282b21160c11fbd687e7f6d437003d18dc0dd814609fd8a314b13e38b14912c55c74cd2e21defa42dace63317eb320219ffff030beaadce9630d5cdeeafb6467ae330e69f0eec5af85cd6c6445fd6c4d435a03e035365b4103b78d8b6420a49b9a0bb0f088f44bcc56dee3f1b320c33b059dc60a4031e2ab9773e06429aa19fa29a1d15b0f85d516d7e8c11b92f98a2c2c3d7222fb70219ffff037145fe75d6a3310cc100e016f5120da353dcfb8888fb5f7017ee196c07f0aa2c0219ffff0302b30b0139b6c26982c18d904687faec24ea0a16c103aedf9a4edad8c965a47803f344d687f34cdea3e0d18782034c8b65c8d9935b256c9704f4aecb3b11827cd2030eaeb8004668e873b93fa3a3ea16a7f64a90db03a7747a2f79e1afd06da7103603fc696cdbf4e499529b60beca74089b7aa1b5161a492a5d883336f97bd0c7b655034de29fd0e14f94db934f8c135d93931b115930530b9da1a4e0ad727be26747200397142835a94b1a991a6b2c550a194cef962527547611cc219dbcbfd7612d76de0219ffff03c50bcb203a41a16c08d3dfd531ae786367868245515553b70f07b94c0e70939603761de03f8b32d4c32186628e8f84d0568af458b452a404d68805e5416eab9010036e48c086cdd9fc24241a2b6e65502c724efac0ec270df7687476fba2c40310e603108985700001cc576ae21d2c6e43e8b59303b5c27997237ef8d83124ce25ea2203676d3ccfbf3ee1f28df40319bc66f4c0752160ede57bdbedefd79adb9275082f0219ffff03f68d5ed6fde67f4406b5b5e54f2cc64e39aeb698ccd7136be39a414ae04103da03253402151c9ca9ff9b6dee891d1e3b0b4d9d14f87473fc880488a0e39e5a55af03ab8850bbae5a0ad03e5c2e0221cf6a540ef64c437feac87c7b43b499bc8adce50341546ad3caab670ae9cae83b9c0893505e82290a5952013b00c47976880acb2603315c99c9894209f3d38ef67f74455b28de0dd1fca7192f9674d9a19fe5fd672303cfb5741e4916b3fe8c8bc7bb1297d2bed485e256197046f36abe5709467950a003b278a0772315357617c6f3eff4a014a1ccda4640f1e0e97ea336ab839b322fb0036d79d19cebfb940c28efde48476de4c452e5e85c2f7f8fdf077c951eb58f4a75038fe324fb3989ba342baccca9258a42038f4b0e757a35b4e9f1c617c4a38651c103d8fb315375a0e5264dfa174f224633ab0ddf1e7f992810f74a32362dcf42ae46039f10542a3cd7915fdfba116433ca3e6e9b1c092e5757bd47ecb44ee487479dfb03e1b70b6d470577a702c3abab952e4d95767583420861efc021cfdbc9cf5d33cc0357194ba875a179406ad5aebb002f81ef8156f695d42172139a808752e03a8e2b03317268799d5bebc6f3ad1a6f54a1534b630856badb078d83290c81a0c6785fb90317820f7ac4e833d407641b67a34816d8248d835cb958b36a2efd37814d09ad0703cc66e3514513fbe365cef60b181488afcd17a713cfadf6f0f728958a648ca458030730feee2006bdfa00b5eaec816058bbc190d5be6c42be714ef74811eb36af320388d010cc2e166bc14c4f0d63654563a017c3ead7d90a0845a948801eaa37def5034d1df4a20a1e941f617d4de0388dc109d0f68458d1801c28646a5a6f85fabd230302ad9013f35d44b208f68c01019d09c04b1421b92cb513d72bb5006926318b5c03b2ad7bf1b9375446bf38edb86b56094f1728ed9154d6439c520527ab83570e980392125ae83569741f781df3fe83e372bc0e6b5c41c62b6f96442661ccbda347be034cd7c3739595cf95e3a4663092229f2ce621c880b5eb7f191c0231b1594a9fec037143eb6b32224df212b81bc6c70983c9c5fe7d9761c05693d45ddb5f164f6b9203974d54d474149197203c18b67c7b7f5f5e13fff780756b053b9afd30dbb3c40603a15cd0fcc6b16f9d20593477d2ee0fb809263904bcb584769dbf4590570cb909033cfcbd4b051641e63b8908e591657d8ba43677bdc56c36a2acf55cc6f0c073dd03b54f1b4131fc4f441424069d2e0cca3321755b5ad1faea1eec21f277541f3ab2036f4f7c303bcc466031d7a429a8ed54254650f89f8363aba24938c4b7c39c8d1803aef155417bd9fa006038ed7121422d0e2ae29462c7ab24bc74899e56128459ab03e7d7cfea7bcaad854ece8546542759d52fdd6defdd1febbbd61d7c641ce1af9d03dee5e17a9d94b4adec65d4c445f8603acfbd99ad4aa9ae8eddfc86144abe979203cbe5ba54bcb58d836fd7891b4f31032a3c84ecdd3aa3b5a77e838177c525e2a205581e03bd37fd152ca264eb62ca42c9762fe745c21220c74b28a045255cf6d5b00c014709faa779c79000045955b760806040526004361061018f5760003560e01c80638803dbee116100d6578063c45a01551161007f578063e8e3370011610059578063e8e3370014610c71578063f305d71914610cfe578063fb3bdb4114610d51576101d5565b8063c45a015514610b25578063d06ca61f14610b3a578063ded9382a14610bf1576101d5565b8063af2979eb116100b0578063af2979eb146109c8578063b6f9de9514610a28578063baa2abde14610abb576101d5565b80638803dbee146108af578063ad5c464814610954578063ad615dec14610992576101d5565b80634a25d94a11610138578063791ac94711610112578063791ac947146107415780637ff36ab5146107e657806385f8c25914610879576101d5565b80634a25d94a146105775780635b0d59841461061c5780635c11d7951461069c576101d5565b80631f00ca74116101695780631f00ca74146103905780632195995c1461044757806338ed1739146104d2576101d5565b806302751cec146101da578063054d50d41461025357806318cbafe51461029b576101d5565b366101d5573373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc216146101d357fe5b005b600080fd5b3480156101e657600080fd5b5061023a600480360360c08110156101fd57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020810135916040820135916060810135916080820135169060a00135610de4565b6040805192835260208301919091528051918290030190f35b34801561025f57600080fd5b506102896004803603606081101561027657600080fd5b5080359060208101359060400135610f37565b60408051918252519081900360200190f35b3480156102a757600080fd5b50610340600480360360a08110156102be57600080fd5b8135916020810135918101906060810160408201356401000000008111156102e557600080fd5b8201836020820111156102f757600080fd5b8035906020019184602083028401116401000000008311171561031957600080fd5b919350915073ffffffffffffffffffffffffffffffffffffffff8135169060200135610f4c565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561037c578181015183820152602001610364565b505050509050019250505060405180910390f35b34801561039c57600080fd5b50610340600480360360408110156103b357600080fd5b813591908101906040810160208201356401000000008111156103d557600080fd5b8201836020820111156103e757600080fd5b8035906020019184602083028401116401000000008311171561040957600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550611364945050505050565b34801561045357600080fd5b5061023a600480360361016081101561046b57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135811691602081013582169160408201359160608101359160808201359160a08101359091169060c08101359060e081013515159060ff610100820135169061012081013590610140013561139a565b3480156104de57600080fd5b50610340600480360360a08110156104f557600080fd5b81359160208101359181019060608101604082013564010000000081111561051c57600080fd5b82018360208201111561052e57600080fd5b8035906020019184602083028401116401000000008311171561055057600080fd5b919350915073ffffffffffffffffffffffffffffffffffffffff81351690602001356114d8565b34801561058357600080fd5b50610340600480360360a081101561059a57600080fd5b8135916020810135918101906060810160408201356401000000008111156105c157600080fd5b8201836020820111156105d357600080fd5b803590602001918460208302840111640100000000831117156105f557600080fd5b919350915073ffffffffffffffffffffffffffffffffffffffff8135169060200135611669565b34801561062857600080fd5b50610289600480360361014081101561064057600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020810135916040820135916060810135916080820135169060a08101359060c081013515159060ff60e082013516906101008101359061012001356118ac565b3480156106a857600080fd5b506101d3600480360360a08110156106bf57600080fd5b8135916020810135918101906060810160408201356401000000008111156106e657600080fd5b8201836020820111156106f857600080fd5b8035906020019184602083028401116401000000008311171561071a57600080fd5b919350915073ffffffffffffffffffffffffffffffffffffffff81351690602001356119fe565b34801561074d57600080fd5b506101d3600480360360a081101561076457600080fd5b81359160208101359181019060608101604082013564010000000081111561078b57600080fd5b82018360208201111561079d57600080fd5b803590602001918460208302840111640100000000831117156107bf57600080fd5b919350915073ffffffffffffffffffffffffffffffffffffffff8135169060200135611d97565b610340600480360360808110156107fc57600080fd5b8135919081019060408101602082013564010000000081111561081e57600080fd5b82018360208201111561083057600080fd5b8035906020019184602083028401116401000000008311171561085257600080fd5b919350915073ffffffffffffffffffffffffffffffffffffffff8135169060200135612105565b34801561088557600080fd5b506102896004803603606081101561089c57600080fd5b5080359060208101359060400135612525565b3480156108bb57600080fd5b50610340600480360360a08110156108d257600080fd5b8135916020810135918101906060810160408201356401000000008111156108f957600080fd5b82018360208201111561090b57600080fd5b8035906020019184602083028401116401000000008311171561092d57600080fd5b919350915073ffffffffffffffffffffffffffffffffffffffff8135169060200135612532565b34801561096057600080fd5b50610969612671565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b34801561099e57600080fd5b50610289600480360360608110156109b557600080fd5b5080359060208101359060400135612695565b3480156109d457600080fd5b50610289600480360360c08110156109eb57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020810135916040820135916060810135916080820135169060a001356126a2565b6101d360048036036080811015610a3e57600080fd5b81359190810190604081016020820135640100000000811115610a6057600080fd5b820183602082011115610a7257600080fd5b80359060200191846020830284011164010000000083111715610a9457600080fd5b919350915073ffffffffffffffffffffffffffffffffffffffff8135169060200135612882565b348015610ac757600080fd5b5061023a600480360360e0811015610ade57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135811691602081013582169160408201359160608101359160808201359160a08101359091169060c00135612d65565b348015610b3157600080fd5b5061096961306f565b348015610b4657600080fd5b5061034060048036036040811015610b5d57600080fd5b81359190810190604081016020820135640100000000811115610b7f57600080fd5b820183602082011115610b9157600080fd5b80359060200191846020830284011164010000000083111715610bb357600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550613093945050505050565b348015610bfd57600080fd5b5061023a6004803603610140811015610c1557600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020810135916040820135916060810135916080820135169060a08101359060c081013515159060ff60e082013516906101008101359061012001356130c0565b348015610c7d57600080fd5b50610ce06004803603610100811015610c9557600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135811691602081013582169160408201359160608101359160808201359160a08101359160c0820135169060e00135613218565b60408051938452602084019290925282820152519081900360600190f35b610ce0600480360360c0811015610d1457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020810135916040820135916060810135916080820135169060a001356133a7565b61034060048036036080811015610d6757600080fd5b81359190810190604081016020820135640100000000811115610d8957600080fd5b820183602082011115610d9b57600080fd5b80359060200191846020830284011164010000000083111715610dbd57600080fd5b919350915073ffffffffffffffffffffffffffffffffffffffff81351690602001356136d3565b6000808242811015610e5757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b610e86897f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28a8a8a308a612d65565b9093509150610e96898685613b22565b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff16632e1a7d4d836040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b158015610f0957600080fd5b505af1158015610f1d573d6000803e3d6000fd5b50505050610f2b8583613cff565b50965096945050505050565b6000610f44848484613e3c565b949350505050565b60608142811015610fbe57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc21686867fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff810181811061102357fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146110c257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f556e69737761705632526f757465723a20494e56414c49445f50415448000000604482015290519081900360640190fd5b6111207f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f89888880806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250613f6092505050565b9150868260018451038151811061113357fe5b60200260200101511015611192576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b815260200180615508602b913960400191505060405180910390fd5b611257868660008181106111a257fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff163361123d7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8a8a60008181106111f157fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff168b8b600181811061121b57fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff166140c6565b8560008151811061124a57fe5b60200260200101516141b1565b61129682878780806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250309250614381915050565b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff16632e1a7d4d836001855103815181106112e257fe5b60200260200101516040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b15801561132057600080fd5b505af1158015611334573d6000803e3d6000fd5b50505050611359848360018551038151811061134c57fe5b6020026020010151613cff565b509695505050505050565b60606113917f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8484614608565b90505b92915050565b60008060006113ca7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8f8f6140c6565b90506000876113d9578c6113fb565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b604080517fd505accf00000000000000000000000000000000000000000000000000000000815233600482015230602482015260448101839052606481018c905260ff8a16608482015260a4810189905260c48101889052905191925073ffffffffffffffffffffffffffffffffffffffff84169163d505accf9160e48082019260009290919082900301818387803b15801561149757600080fd5b505af11580156114ab573d6000803e3d6000fd5b505050506114be8f8f8f8f8f8f8f612d65565b809450819550505050509b509b9950505050505050505050565b6060814281101561154a57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b6115a87f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f89888880806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250613f6092505050565b915086826001845103815181106115bb57fe5b6020026020010151101561161a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b815260200180615508602b913960400191505060405180910390fd5b61162a868660008181106111a257fe5b61135982878780806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250899250614381915050565b606081428110156116db57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc21686867fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff810181811061174057fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146117df57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f556e69737761705632526f757465723a20494e56414c49445f50415448000000604482015290519081900360640190fd5b61183d7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8988888080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525061460892505050565b9150868260008151811061184d57fe5b60200260200101511115611192576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260278152602001806154986027913960400191505060405180910390fd5b6000806118fa7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8d7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26140c6565b9050600086611909578b61192b565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b604080517fd505accf00000000000000000000000000000000000000000000000000000000815233600482015230602482015260448101839052606481018b905260ff8916608482015260a4810188905260c48101879052905191925073ffffffffffffffffffffffffffffffffffffffff84169163d505accf9160e48082019260009290919082900301818387803b1580156119c757600080fd5b505af11580156119db573d6000803e3d6000fd5b505050506119ed8d8d8d8d8d8d6126a2565b9d9c50505050505050505050505050565b8042811015611a6e57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b611afd85856000818110611a7e57fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1633611af77f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f89896000818110611acd57fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff168a8a600181811061121b57fe5b8a6141b1565b600085857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101818110611b2d57fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166370a08231856040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b158015611bc657600080fd5b505afa158015611bda573d6000803e3d6000fd5b505050506040513d6020811015611bf057600080fd5b50516040805160208881028281018201909352888252929350611c32929091899189918291850190849080828437600092019190915250889250614796915050565b86611d368288887fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101818110611c6557fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166370a08231886040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b158015611cfe57600080fd5b505afa158015611d12573d6000803e3d6000fd5b505050506040513d6020811015611d2857600080fd5b50519063ffffffff614b2916565b1015611d8d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b815260200180615508602b913960400191505060405180910390fd5b5050505050505050565b8042811015611e0757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc21685857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101818110611e6c57fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614611f0b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f556e69737761705632526f757465723a20494e56414c49445f50415448000000604482015290519081900360640190fd5b611f1b85856000818110611a7e57fe5b611f59858580806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250309250614796915050565b604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905160009173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc216916370a0823191602480820192602092909190829003018186803b158015611fe957600080fd5b505afa158015611ffd573d6000803e3d6000fd5b505050506040513d602081101561201357600080fd5b5051905086811015612070576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b815260200180615508602b913960400191505060405180910390fd5b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff16632e1a7d4d826040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b1580156120e357600080fd5b505af11580156120f7573d6000803e3d6000fd5b50505050611d8d8482613cff565b6060814281101561217757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff16868660008181106121bb57fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461225a57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f556e69737761705632526f757465723a20494e56414c49445f50415448000000604482015290519081900360640190fd5b6122b87f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f34888880806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250613f6092505050565b915086826001845103815181106122cb57fe5b6020026020010151101561232a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b815260200180615508602b913960400191505060405180910390fd5b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1663d0e30db08360008151811061237357fe5b60200260200101516040518263ffffffff1660e01b81526004016000604051808303818588803b1580156123a657600080fd5b505af11580156123ba573d6000803e3d6000fd5b50505050507f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1663a9059cbb61242c7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f89896000818110611acd57fe5b8460008151811061243957fe5b60200260200101516040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b1580156124aa57600080fd5b505af11580156124be573d6000803e3d6000fd5b505050506040513d60208110156124d457600080fd5b50516124dc57fe5b61251b82878780806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250899250614381915050565b5095945050505050565b6000610f44848484614b9b565b606081428110156125a457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b6126027f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8988888080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525061460892505050565b9150868260008151811061261257fe5b6020026020010151111561161a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260278152602001806154986027913960400191505060405180910390fd5b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc281565b6000610f44848484614cbf565b6000814281101561271457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b612743887f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28989893089612d65565b604080517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015290519194506127ed92508a91879173ffffffffffffffffffffffffffffffffffffffff8416916370a0823191602480820192602092909190829003018186803b1580156127bc57600080fd5b505afa1580156127d0573d6000803e3d6000fd5b505050506040513d60208110156127e657600080fd5b5051613b22565b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff16632e1a7d4d836040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b15801561286057600080fd5b505af1158015612874573d6000803e3d6000fd5b505050506113598483613cff565b80428110156128f257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff168585600081811061293657fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146129d557604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f556e69737761705632526f757465723a20494e56414c49445f50415448000000604482015290519081900360640190fd5b60003490507f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b158015612a4257600080fd5b505af1158015612a56573d6000803e3d6000fd5b50505050507f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1663a9059cbb612ac87f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f89896000818110611acd57fe5b836040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b158015612b3257600080fd5b505af1158015612b46573d6000803e3d6000fd5b505050506040513d6020811015612b5c57600080fd5b5051612b6457fe5b600086867fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101818110612b9457fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166370a08231866040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b158015612c2d57600080fd5b505afa158015612c41573d6000803e3d6000fd5b505050506040513d6020811015612c5757600080fd5b50516040805160208981028281018201909352898252929350612c999290918a918a918291850190849080828437600092019190915250899250614796915050565b87611d368289897fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101818110612ccc57fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166370a08231896040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b158015611cfe57600080fd5b6000808242811015612dd857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b6000612e057f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8c8c6140c6565b604080517f23b872dd00000000000000000000000000000000000000000000000000000000815233600482015273ffffffffffffffffffffffffffffffffffffffff831660248201819052604482018d9052915192935090916323b872dd916064808201926020929091908290030181600087803b158015612e8657600080fd5b505af1158015612e9a573d6000803e3d6000fd5b505050506040513d6020811015612eb057600080fd5b5050604080517f89afcb4400000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff888116600483015282516000938493928616926389afcb44926024808301939282900301818787803b158015612f2357600080fd5b505af1158015612f37573d6000803e3d6000fd5b505050506040513d6040811015612f4d57600080fd5b50805160209091015190925090506000612f678e8e614d9f565b5090508073ffffffffffffffffffffffffffffffffffffffff168e73ffffffffffffffffffffffffffffffffffffffff1614612fa4578183612fa7565b82825b90975095508a871015613005576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806154bf6026913960400191505060405180910390fd5b8986101561305e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806154256026913960400191505060405180910390fd5b505050505097509795505050505050565b7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f81565b60606113917f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8484613f60565b60008060006131107f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8e7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26140c6565b905060008761311f578c613141565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b604080517fd505accf00000000000000000000000000000000000000000000000000000000815233600482015230602482015260448101839052606481018c905260ff8a16608482015260a4810189905260c48101889052905191925073ffffffffffffffffffffffffffffffffffffffff84169163d505accf9160e48082019260009290919082900301818387803b1580156131dd57600080fd5b505af11580156131f1573d6000803e3d6000fd5b505050506132038e8e8e8e8e8e610de4565b909f909e509c50505050505050505050505050565b6000806000834281101561328d57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b61329b8c8c8c8c8c8c614ef2565b909450925060006132cd7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8e8e6140c6565b90506132db8d3383886141b1565b6132e78c3383876141b1565b8073ffffffffffffffffffffffffffffffffffffffff16636a627842886040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050602060405180830381600087803b15801561336657600080fd5b505af115801561337a573d6000803e3d6000fd5b505050506040513d602081101561339057600080fd5b5051949d939c50939a509198505050505050505050565b6000806000834281101561341c57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b61344a8a7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28b348c8c614ef2565b9094509250600061349c7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8c7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26140c6565b90506134aa8b3383886141b1565b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1663d0e30db0856040518263ffffffff1660e01b81526004016000604051808303818588803b15801561351257600080fd5b505af1158015613526573d6000803e3d6000fd5b50505050507f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1663a9059cbb82866040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b1580156135d257600080fd5b505af11580156135e6573d6000803e3d6000fd5b505050506040513d60208110156135fc57600080fd5b505161360457fe5b8073ffffffffffffffffffffffffffffffffffffffff16636a627842886040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050602060405180830381600087803b15801561368357600080fd5b505af1158015613697573d6000803e3d6000fd5b505050506040513d60208110156136ad57600080fd5b50519250348410156136c5576136c533853403613cff565b505096509650969350505050565b6060814281101561374557604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f556e69737761705632526f757465723a20455850495245440000000000000000604482015290519081900360640190fd5b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff168686600081811061378957fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461382857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f556e69737761705632526f757465723a20494e56414c49445f50415448000000604482015290519081900360640190fd5b6138867f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8888888080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525061460892505050565b9150348260008151811061389657fe5b602002602001015111156138f5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260278152602001806154986027913960400191505060405180910390fd5b7f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1663d0e30db08360008151811061393e57fe5b60200260200101516040518263ffffffff1660e01b81526004016000604051808303818588803b15801561397157600080fd5b505af1158015613985573d6000803e3d6000fd5b50505050507f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc273ffffffffffffffffffffffffffffffffffffffff1663a9059cbb6139f77f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f89896000818110611acd57fe5b84600081518110613a0457fe5b60200260200101516040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b158015613a7557600080fd5b505af1158015613a89573d6000803e3d6000fd5b505050506040513d6020811015613a9f57600080fd5b5051613aa757fe5b613ae682878780806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250899250614381915050565b81600081518110613af357fe5b602002602001015134111561251b5761251b3383600081518110613b1357fe5b60200260200101513403613cff565b6040805173ffffffffffffffffffffffffffffffffffffffff8481166024830152604480830185905283518084039091018152606490920183526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb00000000000000000000000000000000000000000000000000000000178152925182516000946060949389169392918291908083835b60208310613bf857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101613bbb565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114613c5a576040519150601f19603f3d011682016040523d82523d6000602084013e613c5f565b606091505b5091509150818015613c8d575080511580613c8d5750808060200190516020811015613c8a57600080fd5b50515b613cf857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5472616e7366657248656c7065723a205452414e534645525f4641494c454400604482015290519081900360640190fd5b5050505050565b6040805160008082526020820190925273ffffffffffffffffffffffffffffffffffffffff84169083906040518082805190602001908083835b60208310613d7657805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101613d39565b6001836020036101000a03801982511681845116808217855250505050505090500191505060006040518083038185875af1925050503d8060008114613dd8576040519150601f19603f3d011682016040523d82523d6000602084013e613ddd565b606091505b5050905080613e37576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260238152602001806154e56023913960400191505060405180910390fd5b505050565b6000808411613e96576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b815260200180615557602b913960400191505060405180910390fd5b600083118015613ea65750600082115b613efb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602881526020018061544b6028913960400191505060405180910390fd5b6000613f0f856103e563ffffffff6151f316565b90506000613f23828563ffffffff6151f316565b90506000613f4983613f3d886103e863ffffffff6151f316565b9063ffffffff61527916565b9050808281613f5457fe5b04979650505050505050565b6060600282511015613fd357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f556e697377617056324c6962726172793a20494e56414c49445f504154480000604482015290519081900360640190fd5b815167ffffffffffffffff81118015613feb57600080fd5b50604051908082528060200260200182016040528015614015578160200160208202803683370190505b509050828160008151811061402657fe5b60200260200101818152505060005b60018351038110156140be576000806140788786858151811061405457fe5b602002602001015187866001018151811061406b57fe5b60200260200101516152eb565b9150915061409a84848151811061408b57fe5b60200260200101518383613e3c565b8484600101815181106140a957fe5b60209081029190910101525050600101614035565b509392505050565b60008060006140d58585614d9f565b604080517fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606094851b811660208084019190915293851b81166034830152825160288184030181526048830184528051908501207fff0000000000000000000000000000000000000000000000000000000000000060688401529a90941b9093166069840152607d8301989098527f96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f609d808401919091528851808403909101815260bd909201909752805196019590952095945050505050565b6040805173ffffffffffffffffffffffffffffffffffffffff85811660248301528481166044830152606480830185905283518084039091018152608490920183526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f23b872dd0000000000000000000000000000000000000000000000000000000017815292518251600094606094938a169392918291908083835b6020831061428f57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101614252565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d80600081146142f1576040519150601f19603f3d011682016040523d82523d6000602084013e6142f6565b606091505b5091509150818015614324575080511580614324575080806020019051602081101561432157600080fd5b50515b614379576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260248152602001806155336024913960400191505060405180910390fd5b505050505050565b60005b60018351038110156146025760008084838151811061439f57fe5b60200260200101518584600101815181106143b657fe5b60200260200101519150915060006143ce8383614d9f565b50905060008785600101815181106143e257fe5b602002602001015190506000808373ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff161461442a5782600061442e565b6000835b91509150600060028a510388106144455788614486565b6144867f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f878c8b6002018151811061447957fe5b60200260200101516140c6565b90506144b37f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f88886140c6565b73ffffffffffffffffffffffffffffffffffffffff1663022c0d9f84848460006040519080825280601f01601f1916602001820160405280156144fd576020820181803683370190505b506040518563ffffffff1660e01b8152600401808581526020018481526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200180602001828103825283818151815260200191508051906020019080838360005b83811015614588578181015183820152602001614570565b50505050905090810190601f1680156145b55780820380516001836020036101000a031916815260200191505b5095505050505050600060405180830381600087803b1580156145d757600080fd5b505af11580156145eb573d6000803e3d6000fd5b505060019099019850614384975050505050505050565b50505050565b606060028251101561467b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f556e697377617056324c6962726172793a20494e56414c49445f504154480000604482015290519081900360640190fd5b815167ffffffffffffffff8111801561469357600080fd5b506040519080825280602002602001820160405280156146bd578160200160208202803683370190505b50905082816001835103815181106146d157fe5b602090810291909101015281517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff015b80156140be576000806147318786600186038151811061471d57fe5b602002602001015187868151811061406b57fe5b9150915061475384848151811061474457fe5b60200260200101518383614b9b565b84600185038151811061476257fe5b602090810291909101015250507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01614701565b60005b6001835103811015613e37576000808483815181106147b457fe5b60200260200101518584600101815181106147cb57fe5b60200260200101519150915060006147e38383614d9f565b50905060006148137f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f85856140c6565b90506000806000808473ffffffffffffffffffffffffffffffffffffffff16630902f1ac6040518163ffffffff1660e01b815260040160606040518083038186803b15801561486157600080fd5b505afa158015614875573d6000803e3d6000fd5b505050506040513d606081101561488b57600080fd5b5080516020909101516dffffffffffffffffffffffffffff918216935016905060008073ffffffffffffffffffffffffffffffffffffffff8a8116908916146148d55782846148d8565b83835b9150915061495d828b73ffffffffffffffffffffffffffffffffffffffff166370a082318a6040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b158015611cfe57600080fd5b955061496a868383613e3c565b9450505050506000808573ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff16146149ae578260006149b2565b6000835b91509150600060028c51038a106149c9578a6149fd565b6149fd7f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f898e8d6002018151811061447957fe5b60408051600080825260208201928390527f022c0d9f000000000000000000000000000000000000000000000000000000008352602482018781526044830187905273ffffffffffffffffffffffffffffffffffffffff8086166064850152608060848501908152845160a48601819052969750908c169563022c0d9f958a958a958a9591949193919260c486019290918190849084905b83811015614aad578181015183820152602001614a95565b50505050905090810190601f168015614ada5780820380516001836020036101000a031916815260200191505b5095505050505050600060405180830381600087803b158015614afc57600080fd5b505af1158015614b10573d6000803e3d6000fd5b50506001909b019a506147999950505050505050505050565b8082038281111561139457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f64732d6d6174682d7375622d756e646572666c6f770000000000000000000000604482015290519081900360640190fd5b6000808411614bf5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c8152602001806153d4602c913960400191505060405180910390fd5b600083118015614c055750600082115b614c5a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602881526020018061544b6028913960400191505060405180910390fd5b6000614c7e6103e8614c72868863ffffffff6151f316565b9063ffffffff6151f316565b90506000614c986103e5614c72868963ffffffff614b2916565b9050614cb56001828481614ca857fe5b049063ffffffff61527916565b9695505050505050565b6000808411614d19576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806154736025913960400191505060405180910390fd5b600083118015614d295750600082115b614d7e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602881526020018061544b6028913960400191505060405180910390fd5b82614d8f858463ffffffff6151f316565b81614d9657fe5b04949350505050565b6000808273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161415614e27576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806154006025913960400191505060405180910390fd5b8273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1610614e61578284614e64565b83835b909250905073ffffffffffffffffffffffffffffffffffffffff8216614eeb57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f556e697377617056324c6962726172793a205a45524f5f414444524553530000604482015290519081900360640190fd5b9250929050565b604080517fe6a4390500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff888116600483015287811660248301529151600092839283927f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f9092169163e6a4390591604480820192602092909190829003018186803b158015614f9257600080fd5b505afa158015614fa6573d6000803e3d6000fd5b505050506040513d6020811015614fbc57600080fd5b505173ffffffffffffffffffffffffffffffffffffffff1614156150a257604080517fc9c6539600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8a81166004830152898116602483015291517f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f9092169163c9c65396916044808201926020929091908290030181600087803b15801561507557600080fd5b505af1158015615089573d6000803e3d6000fd5b505050506040513d602081101561509f57600080fd5b50505b6000806150d07f0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8b8b6152eb565b915091508160001480156150e2575080155b156150f2578793508692506151e6565b60006150ff898484614cbf565b905087811161516c5785811015615161576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806154256026913960400191505060405180910390fd5b8894509250826151e4565b6000615179898486614cbf565b90508981111561518557fe5b878110156151de576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806154bf6026913960400191505060405180910390fd5b94508793505b505b5050965096945050505050565b600081158061520e5750508082028282828161520b57fe5b04145b61139457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f64732d6d6174682d6d756c2d6f766572666c6f77000000000000000000000000604482015290519081900360640190fd5b8082018281101561139457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f64732d6d6174682d6164642d6f766572666c6f77000000000000000000000000604482015290519081900360640190fd5b60008060006152fa8585614d9f565b50905060008061530b8888886140c6565b73ffffffffffffffffffffffffffffffffffffffff16630902f1ac6040518163ffffffff1660e01b815260040160606040518083038186803b15801561535057600080fd5b505afa158015615364573d6000803e3d6000fd5b505050506040513d606081101561537a57600080fd5b5080516020909101516dffffffffffffffffffffffffffff918216935016905073ffffffffffffffffffffffffffffffffffffffff878116908416146153c15780826153c4565b81815b9099909850965050505050505056fe556e697377617056324c6962726172793a20494e53554646494349454e545f4f55545055545f414d4f554e54556e697377617056324c6962726172793a204944454e544943414c5f414444524553534553556e69737761705632526f757465723a20494e53554646494349454e545f425f414d4f554e54556e697377617056324c6962726172793a20494e53554646494349454e545f4c4951554944495459556e697377617056324c6962726172793a20494e53554646494349454e545f414d4f554e54556e69737761705632526f757465723a204558434553534956455f494e5055545f414d4f554e54556e69737761705632526f757465723a20494e53554646494349454e545f415f414d4f554e545472616e7366657248656c7065723a204554485f5452414e534645525f4641494c4544556e69737761705632526f757465723a20494e53554646494349454e545f4f55545055545f414d4f554e545472616e7366657248656c7065723a205452414e534645525f46524f4d5f4641494c4544556e697377617056324c6962726172793a20494e53554646494349454e545f494e5055545f414d4f554e54a26469706673582212206dd6e03c4b2c0a8e55214926227ae9e2d6f9fec2ce74a6446d615afa355c84f364736f6c634300060600330605581d02a7e8b8234a7992da2173c0d9eb404853fe85a65a0881149522e12b2a0f014758d15e176280001955b705581d024ace4a9cf9849185c4b4cd9d2d43e5360f36d915f743ac51fcc9c7f80c02450135f1b4000219042005581e03dc8fc5260164a847b23d38a881b05ec9e601d397d1188021168d7c0da00c024701923a550aa0c803fe15c6a27562a27ae904cb1b828dd828705d1507183fdaa98bcdd0bb1025ee5305581e03f23857a9fa3d0da9ae7310c160afcf49158a232640406bae60c29650500c01472296500eeb160005581e037f46e0bb2789267d0a30b8d43a6d3e2a2e289f0fcb20ea1bc8ee7216a00c024744d7c56d940be603da1a91bd88ab16e57730db064e35880953edf7a3e2f40ec22e887166b95f4b18032be8a5093959bbacdaa2d02b061939a3a07681b5a5351eef8de8b286f8df80ba0399cc8746fb4acef4aa25ad53d2d12e8af29520040e86729cf0fe867839a233a005581e031d2088553eaa5823aa330be942a76137ea4d7416fda40c5c72917239f00c0147028114c7f49fc0034277b3bbc296a60f66b308aceaf2f9ad4e766e1984e39f2bb1256df0c51448a70219cbde03821c17b99606f4ef0404c1e66f9b5847282857f6d43c1d1e2282e3e20358c1a203c4c9fce98e5aa03a1c2976bea62991500383da391e6b93cd7ee617b79075830103454a7715e43a8f5665f3175574c0f374cd68bdd08d8d39beda757efe065a1d010309e22729a72f3d87a3b4e2cbb35b5508a5b4967f51619447d82fcd412c0c60810365c9ef940b1bf8951ae99a658391bc322d955090fc733e730f446262316cd707032dc7ad83f81334ea5c5841d73bfd9a6a887e1d78e06e3905881fa83d258880c403bd0a711bff7996b670c2882e6431d8feede57819c03c580eaa092d1a8f53519403513d856a2f501db5917ad941a82b2f1cca5130e73032c160f94b21d3018abfb20219ffff03ac86231aaaa6826ce152d88375c5f38b82dd0e76a5564441cd6e64288e669d3f03c04a48f47576ad78e3231a2f36d5eda038467ee085887710796779878707d8860219ffff03c3228b8b519627dbd53fe57eb03c86f32d4d32132441c285d89a469f6e98969a03c637d8e3c7e4fdb1139d38880919ec63eee53dc169b0653d5618c6f10763300603c6f89a52369c69f9bc8492439a33234d8e9ecfabaf11dbe3d7876a929ec36c37030ff43ce6633838d3969f23abafbd709cf76075b4750bb94580edf5a0c1b48d4c03bd3dfaea945940faa25ecd693a00b0054131e61c8c072b4b89d5bc0f2a2a0e5603172432d96087eae407001fe525820f99fe41ece1edad5f0fa3cba23417c690950305ca546ef52bca7e571ebca61b3f8db71bc97d3fee9cb79efa729ca89a2de4a703c70aacc6e77a75e52d1e8174f822c17d81513b92696184494b26d4103a30f86503a643a5935a0834c754539b1acdfebcff308652d88c1e9cccd169917b3942b8f5037a1a1a35115e38ca23b634b63152607fdb28a56a8926f0598e3f9dcf5f99165c03a70951a56435c3e8f0bb049a3672a8b2e4dbc0840d84d8ea713cebccb82a363e0219ffff03ed8be2c4096e580a056e6330110e872763a3cfe26881493162860768a33c0e88037328b7c8743732c0eac311177bd28da77294ef3971a2122660bb904b030ddffe032552c7a0dc460a9b828bdfda485a0178f1a4c69577af77289e8fb436dfdf432c035391aee21ffc3ec7a0d439d03b75883bf826bdab6d234abe33064f868521a87c0328d325d76bb005dbfd5e53fb9654f65ad41a1963a0c5a5555a63b750f78e7bb40346889b6d6058db34e6ec212e7cf7498d357646101fe15701bfe5a7fc9f73e02f030bb04c4408821a55f3d79cbe22dfebec2478ef58bd1173604245e324eb2d86df0307f8769ef2df566a4570eb7316af39b248e7485614e5f82ddc8efb91b6d69d6c033ff286d99ca1d1a1f7c2159645d37f27801abc91a6b7fb06594e718992fbfc7003c8b6ba8237dc86c85e2b08d3e0ed9cdd8f8b063d136acb768bd3c1ba6afa859c0219ffff032a0fe47e3339d3eec328f9b8795cbd3c5935c1fcb5cc4c984371ad9af32f4b4303cfb01d920e6782fa1c58ca90a576e87018bce6b89ec1e62fd703692a7f660a78032f77fabb828201453e93b311254e5e08ba2b0a54b4cd6007d8a533fc2ee829720369ce2364235ab7f2141197dd70c31ba9f6dfe1c1f25df244ef475a60ee5e63fd03e628c6722bf5db5edec7776acabd962d6e1cb3d9d7b087dc0f3911a57a4c8da203d5c238ca585964af5a1eeddd92eda4e0e0b4acccaff38232f6babf99ddccde71037291e2aafc399e03815c5c38d9ec5bcc86cdb752637990ccd9c8174ae77498a4031dd8636b55ac8a984c43dbf96637dd122dc44fffc40de239b65bdec927aa3a250344d99deb2681bfc61b00e2a78f717b74c99593614bd1b1a28830479e8933047e03763fb13fd8862a3e1d962cd3d4761c84cdc05c0c811b50a4dec9415420eceafe03cb9ed56a60711bca9b428b466aaab6d5f91f77da615c2cfd571693223670831703f606025ef127336a77e7330c2f344d5c0227e8b2325192423a0ff9e283130e870387a5d18dbfa90051e60d86e934df53943f531e1aa139130d83d6ca094d83c4af03a85cf85433f3b13a17487c4b5979427223543764769835aed1ff096fef477b8503ab80ad10f93cb63a964e8c0e63b336c9174f124035e8b40d01fc465165456023034055d8bec32e330335dfaa6db4b09b5952e350ef512242ba4da987b1d43945b403c357e903314ffe92300957f05be5413168fcb52441ddc0842c48695c6b72b1ef0395459aaf6080b3f8ee9e971df38e176297279b9cbb006b01351910c395de0f2a0362b23bfc7f42168ac33c83afd45b1743f1ef1742e329872b3a0b5a7186da2698036d75bd0f47f761d7f60baa3ccb48a6d3641d0fd34a5b854978783887fa5cc1c2035e8eed0985ed0b2a6a215e8ba4c006afc18e17b1a68ab9b9464320df77ee9b82037d4ce2e0d91b6025cec6bb65065b9a2bdcd8be7d79d771e2327f2283d400d2cc038b4206e28bdaae19f596bd3e8d9cf465d2ac0e7ffb8eb0555a600b1aed3c807103c4acf523da26a9eeb53f093426b97aeae7a442b023508d3e941f3a0d3770fd49033e1a3a6888bba34fd74c4f96e7d784e08a3927c99274767c8b45383243d181e7035a92bb6dcee1c9c9bd2ee3548044251374832475ed9d9e701e7c36cc21f00e4803a9c2664e6e250599e437fea69cc9569dc34e789f5f1b83f8addc5a8580575d4e03dacacfd3dece2d8f09187158ed3ab28e4521918316962201543b53909d25d89f03c21469cd6bbb6d51d3b7ed8ff4a9050c7df6f43be97c18c19ae59b39c75a6852037d337e717ecd4ce5f45c4ea8994e476ce38cf937a4f2924dc40cfa8fe5870fe905581e038e5a09e900731c937b9f9cf98dd46edd55614471018486770032d540d00c0447275e593373250003f8559abbbe0eba3bfeca91b590098c70a24bf7c768b130fde193fb5bcc98b88205581e035a76fba5f3ab8fe576d44c03683d3a2189211bafe0e5d16cb0630b5e00084780b6527a8c355805581e035653eb197c3fd399f1a5cd68d1de6f19edca5b60433ac2b56c7722f0700c054730ef6fc5c179d805581e0309a9efb64b9fc582cb3aa2de516620aca954f1cf2b57fa3c62a951f0e00c014750434de6c4000003ccb10237fbb545478774d6e858eb7a1327708de6c205c7195d3a5dacd596120903647d5d7eb5af6fbf9ed95320cadf658cec3819ffa536adc7b6387fdf1b72123d0309649ef5d0e27de54607a47f0520208f93df8363061237d10e681ecf3ddd9efe05581e03469667fcae85f0588263efb961720305bd89608714ea1d1d740c8f27e007011bffffffffffffffff05581d02d5cd67117eb060eb8283e6f3aaf1a31a9234b6478f940d7ca4cbc360040105581d0299652947601cf1972a660924be5f208f708f0e0e2bc9ac6a34afbe120c014702ce80355f63e80459088a60806040526004361061006d576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633659cfe6146100775780634f1ef286146100ba5780635c60da1b146101085780638f2839701461015f578063f851a440146101a2575b6100756101f9565b005b34801561008357600080fd5b506100b8600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610213565b005b610106600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001908201803590602001919091929391929390505050610268565b005b34801561011457600080fd5b5061011d610308565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561016b57600080fd5b506101a0600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610360565b005b3480156101ae57600080fd5b506101b761051e565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b610201610576565b61021161020c610651565b610682565b565b61021b6106a8565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561025c57610257816106d9565b610265565b6102646101f9565b5b50565b6102706106a8565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102fa576102ac836106d9565b3073ffffffffffffffffffffffffffffffffffffffff163483836040518083838082843782019150509250505060006040518083038185875af19250505015156102f557600080fd5b610303565b6103026101f9565b5b505050565b60006103126106a8565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156103545761034d610651565b905061035d565b61035c6101f9565b5b90565b6103686106a8565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561051257600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614151515610466576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260368152602001807f43616e6e6f74206368616e6765207468652061646d696e206f6620612070726f81526020017f787920746f20746865207a65726f20616464726573730000000000000000000081525060400191505060405180910390fd5b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f61048f6106a8565b82604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a161050d81610748565b61051b565b61051a6101f9565b5b50565b60006105286106a8565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561056a576105636106a8565b9050610573565b6105726101f9565b5b90565b61057e6106a8565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151515610647576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260328152602001807f43616e6e6f742063616c6c2066616c6c6261636b2066756e6374696f6e20667281526020017f6f6d207468652070726f78792061646d696e000000000000000000000000000081525060400191505060405180910390fd5b61064f610777565b565b6000807f7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c36001029050805491505090565b3660008037600080366000845af43d6000803e80600081146106a3573d6000f35b3d6000fd5b6000807f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b6001029050805491505090565b6106e281610779565b7fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b81604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a150565b60007f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b60010290508181555050565b565b60006107848261084b565b151561081e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603b8152602001807f43616e6e6f742073657420612070726f787920696d706c656d656e746174696f81526020017f6e20746f2061206e6f6e2d636f6e74726163742061646472657373000000000081525060400191505060405180910390fd5b7f7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c360010290508181555050565b600080823b9050600081119150509190505600a165627a7a72305820a4a547cfc7202c5acaaae74d428e988bc62ad5024eb0165532d3a8f91db4ed240029032d1394a5a9d2e8c5c8588d8d55fe94829ab07e41ae5b788ac1ad3fb89e25c5b20361de97f7cf7a84eb3f54545791e3563cd289889db9dc76c0d20a52a92b0e2681031f9750ff62ba05c63a63b38fdf3a653a33740eb9e4107d3f087336be33a8264303f4ecef637b0d537e42418fcfe713245d8888c97671e5e9bb62be5b03d7eb042f036c3e86f9176f176b2003402059adb141fd079cc84e28e7168429d72672f8ac870349b348d7327d208634e63b3991e1dbca2220c269469fceeadd143352346fa71c039562514beb09cdb6535c5afcbe206feee98477324f24ba7729ef3928ebf7683203d8acf0d25c0bb6205c64c828c9c8f32a18a57971b5ab6c9cbc12026b8d4411d00301fdcee0b48eb5454f00f9d8f333f7780bf0a6665aa1e79a716bb3e3125e702703f3f6b20be0b51977798f87671efdf26a9d2c5a2bf266fd7d03750e79006692830366c1aec8af137b356a350572d02e36fc0454f3e6464c0a33dc72e01fc2047c6d03c9c5da42d072b3e68c8c5f8f31b21122e99961ee4098fba75d26a320f6c1f0a0034bfe2f8aeddea7910851594b242ef3472e0a8e09219ebfbfd2ed91d71328e0930335a733225fb422ee1157d0da9d6a0060c58d8a4b8d3244eea5d80be5eed089c903bc2643a050ff27fe7f8c50357254442a1d4c5d96dc82d626fef4e66ca1a50f5c03843c1062c2071e81e9dbb732917464e9aafcd6923fb7e2a2605eb7bf1615a345034a10f0d4db5c8b82dd25febf0290e8ebf1ce9441b9b6b25c7df5f3d830e46ba703d91b06a7a5f21cf2cc5b1783c7650e0b2d52a4cdd2917666bb2f589be79ff4e9030d7a89c9c8173a8f28da43ec8b0e92637ca5ed47f9255f45723533f111b1e54303ed9dfd8de6f453358de865d52885eee7868889ee299e26fb9c4a98e7cb52809303b19275be6462fdafcd464a07d4bdc4be4e969d29ef71e8d3eb1c678a7e8c36a8033f0c80aba7059a3d27aa9a1a1351950f0e5141d06030ebcfe52ec410ba295186030eefadb417a643072bd3b66f8745ee6c979229b8ceb8ad7b238bee14ef788bcc0380863a35c1837440975f1fd5554d7d0101ca893f600c0540a572547746f02c1a03ac198e4d0a3e41d7f2d2794c4c7e211e7f90ed100a668aa4bbdcb9ff5c5ca9e10377f374dffe53e26ff75445321643f9f0338f34bf35b67ccd278e1f95b212883d0346cfad0189823dade2dccc8966e987b1fd0f4397b0fd63ff02db29127a519ae90326efb0975f574bd9ae5227a5bb4de5821b0b0b52420cf2199a4aa73ff7ed3076035550ad58bef41e4e9711366bdd072ade22c34761f780fab6078ab9a700781aab0377a7ff0a597eee267c9dc15cd8cdc6a3ae588458f19514697dacd0212bfd54e403e2d583b748cc7eab122b6b089828ce086f9a8206244a67d6b8e4fa113c01b19d03299753fdd35358c6d2f319b1208f0a81b564a8c2679cf2b0b369afe3faf997c1035bbcfc4fba0f9324577b55dbdcc402bd4e45def541b4f489144ba8632b41dda50323a694ed2cf402dc9175a62cac8598dacbaf75dd275a65a7acd230e52837584703c0170493779ec092c574e976da762b94d26b76837df96ff2b72e9c120ab7bec103beeda31608a04610ce04832a7259fc1304c4683b820cd441b8ebfdc4f4081555038cae0d86fcfe3184d9f868080553d0b53e398426acc98bb8dc2908cfac3c4dec03f47b5da58a03fcbe648e2982848791323d744768ee73d7da4c57b5ea188d8c7e03841e883246ab4980e65341503ecc125a7446b1f690003578fe339fc7174757d100581e02a818348987efc21198638a43af425294e0cebbabe1f3c7676a812e34a654807a96288a1a408dbc13de2b1d087d10356395d200581e02abbad38fa3fc14dba397b83ac7e8a7bf4220ddd174389a427fd84435415820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffc46535ff00581e0248b7e2ccdd56cee52699e9315c16e7f3f0dc33119ababa60cdfb8409284417d7840000581e02ec2e256dd7a246351e4b2aeeb0835725f8a68497b181b9542f1d7eaaad5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffff6377579e0219246003474a7793521241abe86002c251264040d1829c2dbbf0f3d3fabeebe519d0c310039c9aaa33d7a10b78c48ea7a91247af19de6b08be5a050478e67c6ed4b06ad52103398def5974c743e0f2687072842f3f0b88654a88fee9d446b459031efe6d9a95035cec8bc04e48d6815da71d69d016d9903d542ee96975155d8cc68c9eb1999ffc03b365e1710127f7adf4b5462712752a6bdb245c5c5c85e81134d95a91f26ca32d036965770c9ae2ba0f13e32cbceb3189c23ba602d6de52a16c8aa3094e8a900ad40306f94e88e5ffba7f14eb583eba0f4175fb26297cd9dbbb1f81b93025e5bce3470219ffff036615f2381952420ea069acb391891d322869efeb4345eb313e95ba3305462a2803c7904080e21f20c4e1811d1ac2633b93ddabadcc51bffbe9bd51a3eab9631f71033db665b6343c3a8119e60fbad4919e07517acd01692550e0ca4408add2e5f3b2032a449f44af25a854288ead943241aa04bfd541a84ef7489676264d5b56ad50e5038b0a79d4639c8b1b50473f8dc93a494f8162c6dd00b462e9b571feec8bdbc5bb0392217fb0c69100419f0a9396956835213549477fc4aa25c0c29636f44478373103716d726da1ff96196f3c5c77741227aacab17c425fd716d6e0842095511a8b4d0219ffff0219ffff0321e292f19666315f76eea003fd8f287c3017d8c57331ff058e1a986c92f2e674030ea925bb77556b665219380d1434e54d5edd73a61aab22a0295732686be83a7e032c17c00e8aa40e9be722a2c6f3ed7e553a409a65d64a7ea69c40351c449414e803fb9b087c1f986faa4a50ef12023ec1ef4cfd29b15fda5a9c8f144137c93fd2f80373afa4c3f1380708d1ff4b4c3d608fd51cfd374902a6ae62cdc324289a96763503acefd0ae1135f4b745757daf247e92663c60a98d240afc08fa387d1916ef5ace031eaf3f24064b3287f0782c7bacdf8887910576ca3e7bc61e0a032b4ba67b3eb803ac8bd69adcc2962b15d58ac1bc89de9da0a2d2027eb0426402f33b7869ed8e55033f5f1eb8f9885e38c2e1570f52c88a40069b6981249a0b91b9c94b66e7766648038022d7ae7170a4e7eea710b0f693003a7c75ececb0027bbb199db90fd2dccf36035d71eaf0318abccefaccc22cd8b96c64503ee1d2d1040993a566b98179148efd03843adfe5989196369d00878f39372282d75d6a778da5fbc2d8458aafa80af22403b8d08742cfa782f2700d970f5ab44c0e5955a368590f6b59d1cf3cbc2beb02ec03172704fd69329c260e4fb98f619acc217be06aace3b5b29b3e89192c2c10a64e03024aa9032772e17014747ad80b20b79466650e50fab474782c3bfeb7c888c9ca0316769a3d167d5822293a3a445cab476e34b272c0f64ccc4796cacc0b7d5d67150390b046d21a57d466265bb0999ece404581dbf350584a283cb26c1d950bb8afc603054a956fbd472a005936a8bd30d61f00b2d2427c60164a276d55c55b75d89723037a7c37c334e95ec6ccc08d99a0d1b16e8db6772becbf3097e2b0566c4459c77e03be84ca7cca885365b4bc0497ce9c02826c74a1d1a72d66a365afbf09e46aad1903dfa48864cb490c59067e6fef08f715bfb4b19b594ed881d098456609324528e90339fe04deceec75d31177cc0fb155cb2f24bcbf9fc3369f2606fb0a488e95dceb00581e024c7e0ef0e5392395ce7452d4e5eb01900919f7ea753570abc24763692d4649ad9291a8f803ce57e5edd483c9ec130333555cd60c9d3082ac31dc303830ada6b26902dc100b037abfed7573ad322f5d2171c142351733c704e3e57d3d2dd63cf21a8be9416a2300581e02260608623466482f88cd4120ffe69d5e8ae6875361c2028d7c5b6967b95820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd2393f00581e020d5ba211db4292cd0fafab9264dbdbd95376e3f2f2edb44a004868a096438b92780219d088032735124218a3ae77f5ecdd298cf5b489ed351035e3bfd973e7c5dea78e372c11036f9c564c5aabf460e5d73c3d745d266a8cc2a7174704fbc56c8d720107ac93e1036808eb62795c554eefd360a80b4b6453dd32fdbc7b396651e69b650c3106a57e0356b1507f4f284e0a38b2236ed687625684110b7ad2c11bb354da6c1e7d778f1b03e757864198898ba272c20cf57e4a4cbcc895e385ee7bb2ba021f0546abf131ff03f43459b2af725d03a741fc1a8284a74a2d40aecd366ef8215525b61651b0a84103495344efdc25cf9d851791218273f116fc0ab61656de7a5244797aa9ccf1f0e2033ca334630a87bba2b41bb9eb9c4f4d2a28566c82ce3c18d1e7b3bf9adf9c22600399362d71dbbed84b8201a649df869ff9846deb7e07f0550d90f40f64c1d636ca032b4b20cc2e462ab9e0d3bafb633f6818e7ba308bacf39772b80767c13b3561330306453a692740961918f0b57fbf6c780981b2867d8d15327a1a97fcfb8be49a37033c3da4ade83fde310d3646f4a8e53dc27423aa7b1d622597956531e092ec7ad20346dfce934080fde0c280820d3c993669c7205c34c115e35b55d21739ba97f88703b95b767f43fa266bb212c3d5410ea4eadabc27f92b18c4ef12c014f3320182b403495e7339e637de0761bbd1da063acfd7c6c50171686f7e47cbde2f2f4c28da830219ffff0329ac7a52e7d31e5aa14019271d40b16fbbc490a3a1f23a29a3c1e8249b55ed6303f768ca52fd5d698e633ef27d44ce6353b8393448d3eb715a4dcf34c2ca92b1a7036a76f9a322a9bdadd57da28e30aca7c49a34587f534889117757f25af6c6dd550385ab65b9e71fa3af5017c930921b74f90adefad7cafd0f7ed77584f696512fa00360277d7bc27d88ab2ad22dbef2894a5a19c0262bff12e2612bba356367321d6503ba8d57cbcc09c866f99e9b99ccd33060dbe8d6bd2d63649b9e11d837ccc5e974032a527598e16584640a8c3c5c4c400f506351aa74fa805a19c228f66238dd39db0343ba844175610b34d409f498376c5f13218cbb5a16a56490420b396cf37c3b0603485850ef9b6f15d866ed058ffd86d9b96c60721509f321cd59944130e2010a4503b655335aeea4e25c8401f4fbc28bffec389a3539ba75ca6eb343933eba9fbcb40219ffff036c5bab4428a7d8caa80d37780deccad43786eec73607d0b875f7e3e1e65959660219ffff03333f95f716d138a883f291352e329fc90df6eb67aa1dd01105bdf9afdca04bcf0341faa5518580bb597aff3d222d039d107bb79f89f22a5f5ba5d499e5002c187f030c5b71fc636b3826521c291f0a7c23d92d8fb9d2376c7a8143f95cb0318f9af20219ffff033245bb0e0055b8372342eddd5c068cc7e24d5e54f60a5ccceb261b35fa608e240335d5d07bff8ecfbed0155e5f43d8c8a341027563bbca7cc5ff58237a8ed3dc6b03d63751f68e29c6eb2168f25f2c4ff1999e0b5870e900f52339829f382ab5814a037a618e0078da5d35c3e1934400a518c8c3368e93be6879216f9ea5831b78c3050314890765438ce0f0c4aa705016320f9d502cf2b452097943d7816aa362d5c9f103d11aa432bf2f33575f82003bae79668b33bf4a9d7d3c553cec0e2ba6eff60d1703a9cf4fd46e3955fa96a567eb5ac95c57a7c585d0af842f6f903a0e12bd0237d4032bad1e3c05b2e943776a7bf5ba7f60b05b15c0696d9be04b84107696c5850cf503e84842709ee6ee1a676fbe5688671555121bb9091b5e01bab4524dc1b8a0db3d0334f8b83f73ab2a9255e951ce2f920041d6b968a0a96430fa317d79959257ac6c0326775cc2575a5d1068ebba6ec134300ddc2084b57cc7f22fb9152b51798fbb0b03c033239784910d7819b87b6b709d145217fe95d721e84f733579b22ba98ab8aa037fbfd12a44d70b0807b6625d72302463507fedd4e56454ec601116500032bd3c03a49290a991d235d373bb73441955d9dadbcce70ef112cf5298e371052620261803b74c2604c04d0028714f476e23fefc1a4e64bc3d6ef7aed6a91135352390885603c76e894328a1884bf9ce797398df8eca8e8c7420d1aa54451746c565e9fb06b003ca40cd7717c851aa3030aaaf6f95bc054de0e26ec90ca781ad6e8edf9c229f0b03d429d2e27808358b0581a5f9a4bec929abfa13a99edd1b6f75f1dff61597cd8c032865b9acb09ddf703dbbda4e3882a820d734ac3dc2bb5c097cfae977749601480324a738cc928b6fcd56460962f8419d15a437029eaf76fad44804ec47f9dd90b20356a93a5f503bd0f071ed0cd5f307cebe218ccc7bfe36c2cb9935b81c45c7a0f00352b690b249f500f92565ddf5a8b870e268a8107090556ff85e1e2004c135797c039987be903ad6d1be40502c359af41e7676dc4789086c6daa8b9e51af2bb3622303d6e98ac179a85020217b607755763f181ebb81679f6507c6a025a12d46766bd6033f3243fea4e2b32a2bbf54d01ed18c983af831b08906517fdfafec6614d7e0b80395cf9dd0a7df374e9ef9591135d41141bdd9de3fba21429892243a38551a37df03a39d477380e09d6ca96ae3547d9f7496991724188e49ee039607092a3335569403244a3d6668b821faa32c0aec83000a7fc9a0e2445f1a462f8b63c4ec15dd7474036aafd97ed0a6d78795f6050cc46b76c873cdbed44d10eb384c51c7640f64f44c0352a1515b9e0b461fa369b3ae2f003b69b8fb8cab87b5ee17de2d4bca12d0fb6d00581e023e9edfe0972ff2c6d0da382f0db3d5eb6a897c60a4c173ad1a225180705820ffffffffffffffffffffffffffffffffffffffffffffffffffffffff09b681b800581e02466c0dcab700c8ffdf1818c4db7f67fb16b68580b759516554cac7ab125443506849d7c04f9138d1a2050bbf3a0c054402dd00581e025d12c224c54a6f8f2aa61e0649e0be7d377ecf7aa8797bf2ed54bc1c055820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e02280b6ef72746bf93b1bec6ef41cf206dad2afc0fcbbdc7a63fe97c3aa85820fffffffffffffffffffffffffffffffffffffffffffffffffffffffff02c4e9402191046030230da90dbb296f36c6bca75969c68c0a93378d67cd3fc8d6ecbdb338f7fe3c403fb4abb28ba7238a79d8770c633ded45e33eb81d69c773ff8fa1404d0be7e55ce0391de9aa784be32dbeba3511a825e749d7f8fed9d842f12a301961e9e5afcef6c0365f9ad6d524d3b6cc405b6ba3b7fe6afffabd89baeac0d1c284a70b06ea8e2a80316ce5044ea13e749b4d8bbed1d085ecb28605b8975cff9f1e91bdf882ad75b8b03412aa50fd6e7cb1644342d982bc884f5194f25c2176d565d573ea2af9b7d88c003ec68034ed055db71e87bf811afc327561d4caf47b07ad704dde8acdcc110b9a601410f03f2529f7dd28c690e44ba05686250da407c66e1296fb04eab25780b73d5e0a80b03ca688d8f1b676d94bbf63f856ed8ea8b9254f51fcdd30594114d1d620192fa1203615d0dcf330f01699694aecdbac9952bdd5612f1d632485274f6aba9d3a1549003347018c85929ff716efc3b8aa184782b1b4c96617fa3fb04079e7474d27b0ca80305e1e5ef1edbaa438dff47ebe6ba1a77990bc9f4c5b334aeb59c12b23823942e0349a182891afcb4f22794605e7d18b0ec6732338a90d6de511c679e72cf149c270219ffff0382373f312f60583b2113fabb83fcbd1175d24564e11e8d6eaff59f883cbc8ef80376f13320caff2d2c3c011c3d8331bb8072e348abe0dd9fad416028c5a4a5337403a963ae3e2b5a469e82519ec5713379a26182193c0e511a1a962e7e912a806a9403588dc86207c03b956ca9f0fe1104e455bc4ccfcc6ced73233383bc3c0a646eaa03d631ae2d40bf1f4acda614cce5d51041cbe08e7824089aeeff20c66ef084f9cb03f333fd1d286b483bf3a2c0c5412645ff364b1600aa68b4f1ecbbe001407e768b03707bb0469811911b14c4e5a0e77f0b10cf65e63421493ea20db9106809b684ce0304168ec8b8c3b3524cb9394f3f3bb965d1a455098c4272401816026bec0d26cc0302f94055ffd733e2be3110515a661b0074dd5bec576ca1f730b0d7b22a14a17a03584e636ffc2eb4d37fbbb1027140e5e9b98531976427674d216ddf5225ead69803049e6efa3afd402dd8ca621653751414c15f8ed8805595197a009744d84e742f0219ffff03781991cea7c297bf1b91d82e22bb177291114785e59696b59732094553c8ec2c0219ffff035d286aa0b1a975830bbd8e2cfa19bbbae44b9a26f081b41ae4270f54ac2223bd03c1f1429240bab02949a5cec02c14ee7385ee4f935782b7c6cbca311821b859a0038a9843b70ce61dc095e8f96e4c618887c97a36183a7ffc761f1a11487b8eb394038b26dbe304d2a06d205790a7b87539b3bbb45f1dc1d737d31406635c1583481403e841f0e699a67b7bdbd57997cad375eea76ea16be40b08c257317ee04cf0236b0306b8cbb4bf0924961b6ac52638d0fcf44b122bc85d253ac633fd2b50f182d5190378167faef717bcf56da03540e11a71e6010798d2172d2516daa3d39e85834c17032ce5405078c8c54644ede972f114a91c09de8e70d7640815f79415ba830ccff003805167ef59891627d38f4a8f631a78d5f8f8dd372bb21a7653875ce9d53327f803ecb58f9e2766b773677e56234e7706570a85cde591f00d422e45cb93eb0ff158035957cbdb0937cff04db7c5e64fc94c5e8a39b08e92a99c8496a92e4ce58d3f9c030e13383ac19b6bb5e519fdd173fb82b799cb2619059073f98258c0d0b266046f03c50c3e24ca8c234cdfa8992e2771c53906543a910205fb4e886057c7c112bb3103d04511fbbf6e043134f01e5d8864e67f20fec3444c573b1552f10031c7ea6928030c50bc25a5b62ebc2a68a3c815e40c390b81de3c6e492c90b5ed2421e5df2c5203d9acd5a7c1f87250f1efc824f5a368c456571b3b7165c4d891af321c4c56c9eb0342b294cfd2a053ee9a54960aff00887b5a331c576bedfe079cbb748f4f7ebda003c93e31896e85c62273988d2cb56bfa81b0ea0337ea0607c2135f194c311b8a2e03cfd3d432e8a991857d1eb9a9fcacb1da80cdbe57037dc3b474a92b1179bab7d703ba3c8f71f6291b4946d0f3efd4c331b0552a9ea6f424b7a18330ec3a791e19e703e939a2318a2eb569883a64b273ca5d9e5d85ba03b6563105d0255fc3e9970cb003966525cd0b791c19e5976a3648e3e1c58c0a71d81d4fd31ca69f33320abef4dc037fab5911ff909e0c0d37ffa2684f6e561c18314198fc23d9ee9b57dd17a9393d03625f6b7951ac82d4bfd1c1090b848331fd3c5a474ee4f70fa78871aeb89161d000581e02c144e173cf8082c512806ccd405676c7cdad094f72e9da9ab7325f3f5f45e04cee622b00581e02a35dc01ebf694423b5d6836373c2b27aef22f1ad357b7d9c17fb8685d95820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd2393f00581e02ba4f4607a8b6f7353472b270fdfb08ffe4239edf77539dc83410e0d15b5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffebc94ca22400581e024e0ef26e8e902e27244fc3ca30ccb7212c7a0509089755772470973fd25820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffc474783f0219f000032dd8b08ce72982dd6394eb699107b5202d21874367a7cfd0514e70480ae164f70352531a3ef545c00dad792a65083077f6745f083d08f7b2e02b961b59ba89bf07032baa760c817b2323b937daea1373ed494db370bed9e3a18a6349424afdb734b1032ece76be24f866a40fcd04c8bcd25f735e9d1651ca31da2f145f2c4f0e4e281303668a1935fe5251046cded5dc0da0a32d5c8a9de0e28608ef83ea5c6c505ac5e60219ffff0304867d762a10e906c583f589fbb8873c8904c3f024db1ccf0281068179182ff203fc85669dbfdf8235b010cebff93cdc5caa01785cb49f3be34b6ffd4bc7e8986003eb92a6575fc2ef9532e40423911bb44968c116dbd5e5ad723cfac09fc7235cda03e8bccdadb18f1e07ad7f186a382bf8a5fbc9130249f70a3df1077b0d7023d686038cc0ea94022a69d56b17da369f10881cf13c493e4b047ba2440062fe881c94bf03edde4fb02785af3df1ba049536ccf7254d1a6373ca3162e3e9fa4398d77b5e5e036e2cddcb6846cd08c220ed095820b25978dbb67c70ce297fb1cb44839b30254e03553994a6d3c403994e864e0d444ed578bd2fa7893b9cd16a72aea9f73522d80103c97b4dc84f4ff9301997440e76825b36b198b994f92eddcaa6fa289954922c2b03f7cf5b9c8a691caa2f6ff14d8d2dd92f3aa8a8aea63c96523dbbfdb555cd7b320219ffff03bd3363fc8a5ca5c763b14220aa6fcfb848a8626f735d227be51f9e8f888491da03ddc58e2fa73dedf498a8a3559efc57027327282de068e7cb3f67561a317028c403fc87b4f06643dbe5407ed51da6a15b454409b4d8732ceb91e5f18efc9c175207039d8d1023c7fde532147a361a1978bcd803ae73dd67bddb3c45ec538f1d6c38ba03819d3db1be69857f5dcffae79e3da636729cb257c56f32ae62e4fdc96d9843d4037e4f59da007e23b751caabc463f05d2241a2c33a74b30f9b3bba334c741fe0f803301b429f36c9b66a04b81ad5ba83990a452fc2911568204c94b700f2b19ee87303bc81528ccc6693585e05d5e5436e6cc8c560ae1ff249479e267bc2a82b5368a30219ffff03afb23d059183314694973496c77bc1d2b250b03da8eeb49ae60f7be65a261f7603a7bd3ba278d59fb3f5801852ffad4fa6beff960f3c9f7951e2979009cec12a8c0374d303faa00a645b7844925338b61452142de531e6a3fa182d9f5b1abb49522b0219ffff034dd8089448b4d40b270442e3de4c51a2ed08bfa743e4dca537554f099a798c9b03a5b34b1323a206258c00952b1d05051af0daf577755851bf7a5c85a5c61da4f1038bf623a60beb336b68724da260a4a348f82cbf3c1b87d5270b5a03ad7c1fcd3c032d075419e2b3c2a8a0708140c5191c3eabdfb41dc66c5dab8f34e3595b9c2d93030f8a52d1b7ac88303e6a1b77839c4d6c5f0d843449434d375dcdefd11993b31c0369adfa105355de14e690b9f68e93257a54824fb958e8d22655f351e3b3d7ca8303764af3a837761717f7f8738fadd216c858287cf0bed05e49b7124de43137ad7b03abfc45d335acdbd07f69c645f54888265dc9c6bcb417c6b1c4c1b32feb450015031f430921cd4f12c732de2b881ff6095f7409ae24e27d56ead5c96c25e44d0bf6036be0262efe13df242959a9064e2df39c9533fd61163eb833f2b5db176eb990030300caad84e5ef6204d244ab83a616b9cec12d0eb591ac799d70482f7aadcd4c90035a87d7d9f81a6b16524a52c16a274384e353543626128187555aafcf55520e920375280e5baef939fcc2af6c0f20f5b6d406a1db938d35cbfdf062b8762a771a0a037a36ba81ed3bed7bfce1c8de2c047fa18b33fe63e9c3da9e77dc9aaabee93e7e039e0a4b980355871183e3d0f1bbc177bf2e23e5dcc5576d51f448f65af119783203eb1111d5e9d537a9279d4898cb69997c81b1fcd3c99941a5f706c790aeb5ed7003de3cd56c62d7a440bd982b253d51fee955ab48fab80797b63e3b6f1f09245efe03a4d3a08db70b789cf1ad66bcbcf7c31b9b64ae831ee027368fd0b15c2b1e99e30315565d1cb6eadc7fb2d7fb52ef087b952358a5aea0d78592f7d6bc40a6b9be8e037c389f3c68c99c0019d0a07e9500440abca50d4090af1140542ede054d91b346033fcdf0173b07815d3996855255cf1795006a5ffadd92c90a85e15c1143c7479e03a5dec9d1e78358ae12d1dccd20c9859240faee9b14f7152a31cf41bac08659a3039e8a0d5413057623af98700691b2adf90135b6a0128fe51c3ad3c8fda4e837b303928f3f22bf8b27acd4a3ab7e864df02d9f37f976049597ff313f7702f2e2e5bb0386a0f49556cbe546c42ae3caf4733227173388127a3d5cb5111d24dfd4be090d00581e02812c5a10a56d26db280b70c0afd5e480044d135397aa6d5210b29ee61f4c204fce5e30444bad38fa535b00581e0256314af12aa1a62859483b57f6a460b4a8147494e90b2b46baf775255a43016bc600581e0228215899711e292069b2a3126138bf052b23c82af7b3f0b31bc7631a855820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e0211a283bf1f3c72fddbb6685655680bfb71e21e3be1bf7b410292969e25422710038e64c4df426e92d53cae8145a5be45c1e3f0b26f9484b7aa0fcea250548f8c8d00581e02527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6544914f61d25e5c567143774b76edbf4d5109a856600581e026c5232a972abbeaca7064450c7b385ab511a2f17f27461fc4afa9cd4935820ffffffffffffffffffffffffffffffffffffffffffffffffffffffed6614a38f02196d9000581f0324b32423619184f0f0fbcc857a2cb681e8ffeca0dbf31c292434286f03805820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffd96ef04803ce3484fc11cfe0a1f3a62751064a49dd0f0a1e1ac0500c909936347cfb47425d03c1ebfa1ad79b92a162cd8eae219c53928db1cc371606dd4887bca2f57ceb135c03c6c1605c818a87d02ae7ebbde8ee415dd55ceb2e8b76ebe2b1a0481a4ec829c80336dd821befbd5b0b7d1a18b83c78999e69c0056608c961ec8e3e3c24d497e09e03928233f6111f64eaaab353745d782f3432a218946621071ed252a53c1252c38b0339f1a65e322ad4f6fdaa9e9ae26c741f055b99b5eb05366462f8ad58eec97489030cdaedf1f014a2e1d547c4aacbfbf248a0412d1e293eaeedab411101212dd74e031ce258e1a3d5c8e3451fab1a73f0e78d9baa004811c75111d05d4c0b37545121030b76f7b2ef016b75916f53e192ffc30891bc8b373e024dfd46baef467553bd4c03a67457f425c4fc360a80333f5c2cdbc2054885a53b3fc6115058c3351836094203b1b18769f7d535cbb8cc26edb157136a148ee6c34d72b0b8bf73ea20b51c3bcf0399241872c4f405b54ffa8c0f0a62eae76ba5057d6f0c819a5d8ffdfe277207db0219ffff03b9720665f0c55636c322687f50451186d87c3ad4149403a6f3dbdd37294ce61e0219ffff030359814d94fe3dd3685711353711b79a5ed6a13ab2ee46f3e2663592e1d45fae03edd95b09fec4941545b0869e21b52e098786042f849d2b6bb046cf65e43f24f103ba6c32665f18bab02021b03b1b269fc6b04ddb253c491a7c75784fe5e445b635037f91ee5c485707361686e0af1254c487c3db44d86becce40e1a2295504c1e9f50300284a7b8fafaa99eac25f43619267a40847434cc2e866e4935bb9279f2c423c032b39db872d99a84289f831b247696af6d9ecdfb757e66643438bc06eb0b58cc90323cf93efd575f487b97593cfde043b3950700ad5884a0250d13da0edf9fd534c03f5fedea2dfd01b2fbbe7f25b194fee02cb65b9aed5ec6c5567e58e6fbd31e2b60322116a4f7084f169161e8ec3a5d2de796335e576afd5b9416910a89a67102b4003d1a95dbcfe573904397b5f69488b1bc589839d319c5a6c55efdd3551126a050803c0fa5ade3c76c54271f167e5f441b485f1eb75940a2e52f648c2cb96f5052d9903db9ef4e1326d4246c472ccbd47515d13a169d957a9c068fec21f4b55e212009303e1970882eeceeaf8e2b8541bcb174205ed78c7b11041b9bbafb4ffa9ba899875032fb8dc063f1b265d76868071db31ab763ea566da8bfdd2b1a7fd4ae052ea7758037c40701fa0a68f3d926c3dcafdab297088fecea37100d78467c831170e7040900219ffff03c4fe2a4d5c993e4c9dadd857e8661db60872b725dd80141becc0f521ae33eab5039a1005af66d8db3e16f2a30040aa6d784e1f1499ad6874c2284810536cf9905903d378ebb7d7ffbfd47a58868608dfe594949c3a155ef04a580263a948ce930b0403a1466dc0cd09bcff7afd87c8455df683bbb69d08ac5bdb43105ffa5f5089502e033754236ab4f269e1eec5004d5158b66eb8d8bc74e28509c17f0dad9d514de8a0035578ead177a4ac0b28d35bf5e81502e14ee60056d73af310d0b9acab5a97c18b03d4bf054b11abee6a131ef65a071e65ded19f0c662438fbf9c1aaef570b843d5b036feb70206553ecd8ddfdb2f927bc3657c1d6750d6142699ee8b88c699c6af4bf03c78502d7e06a37895aaaa104b6c407fb36df95f7b3e598ab61cd5f90482a8aed03d6c71e8b8dc6f8e768002b960987ad7f7465ae6b39bc54c89a95a6ef52279f6f033d1cc383b7f838294ed79ac2cdc8248cf6979c09331f5c953e7a5497e95166b5034df4ec14864c789279a98c3a23e3ef021d4800314f69ec1289079080906bbee503c33f76b3e8a263c3d8f02f83cc3a5c122ccb6f58bba0f8d5719d6ea79077cdb303790eea44945c0a0e06df36fc3bf4fa5d576f76d7e7c1e07f5b3d1e9793a165820219ffff036f283fd3772e345ea810fd7588034c657843cbf2493a4882705bdd2159d5855d03691ff46ab9910e40fc5c4a9015ca3e6fb37eface4bdde33dde68ef75981a900c03b200da8b87f48d6ac016041e2298adaeaa9bf7ab0d0499188f481d60a524e98f0338de70e837a485f014e665a1d90950c82e46b25993cd9bcd1f8a32ffedabf29d0219ffff05581d0292cd7f3f78137497df02f6ccb9badda93d9782e0f230c807ba728be0070119088a02190a0205581e030864713ac2db5e9db0684a29a85b09f8e2455d445c5cf00aa58fcacae0040105581e0382cbe9e9e102d9cff9caf9f978abd0677de42e743578f991d9bdaeb2900c0b4705e5e4dc9af40002195ee703da7d51e611525072c152ef23a7e893fdd90b7ebe1046737cc06a98990dc7ae4403e6b005220f8de7edfd7f06e487749e81895b4eb9f0c56a1da05f1b50aab8e45603616cad53e3488e7876a76561c3accbdb1463c59490ee7d969eea01a64032d0dc034073a2da2953d52313e83f8d7a05a6d27ee5607a4fdc044b6ac35631bef4cb2c0304fc7644d8abe69d5c754e754dc4bbea20bab618d50b57e640a83684673b19790362a752d529651a3b04552c14d59ad4c95736479f82eb39e4ecde3e115d45f77403af9fbecd149a21467bffd1f8ead409d8ccead8dc6d55bf82af70813e74dfc22a03a259f47d33fd03c890d78274e276ea027e6e3051becde575d0d8f6c9e49e80930301899b4da112719a28ffc94405856d1b0caff59c59ebc63d51d096067b1e3a380378a6818c13eb82b0af880762ca82ec9d6e2f7ca13f6d269acd6e565cb49356940219ffff034a74e5a806b7451653b0befa3239b8b886efc5be2587972e99b18d6d249d6272038f133f5399443fb4f07ac8de92bbb37eb18385ad1a05bde32614025ea2cc66fd037a7c89b08d595c99f6077d62659a379f7e23c00066db48981c2ba6ba2363f89703aab0f11075bb9f5b7efc5d64264d22cb076235437efe4a72fa7d53b2dd806edd034bdb44579ce20a1d8529a5a4036a1bc2603c1df75c0eac1df592987a8838242a030c2ddc578d8251ede31ceb3b62657e60f89f62037618ee52ea707209830341a903317050d1976373b607272b71719ba8ac011157da354335f724fc84df6c0ec7c203c4df3c3c06eef83a067db420408477ab74852387bbddffeee67b9057cd7a97df032512aa10789e9ecac403054b1bfbe828e21192de35ca6b2ad86fd55e94bd7f5e0396c597a2a7fcaa88c011339d3950d4de0df1b0d6dafa8b708fa0b3c51d27241d0219ffff03880ba104d2ecb59ce7dfe6a1a379170dc289132b322beb4af1d2b09cba637fef03086a82299f43a1c4032a761c3a50e76d87da1de97b4584013f8bf265640d55dc03b04851d759bd5c80782bd19c8c1977d35f2664b5d7e5a3d44606fa5e65b41b0803f3054aa76f4916f43faca0ffbe08ba17e78c42fc3c22b1711d8d02397f5c165003bc6f5ad2fa95477cbdef51a2ad783820a7565ca99131b8159afff6ae1ca2e5cc038977f27c22bd0d4a018959e14a893efd25ec65f32f1244a4f7716198b4c99cfc036f7a13aed16e8435156ee3528dfff6b80fd05ccc369330da6ceec8619311d5a70219ffff03f90dd51f44d709141150a2da1e49951c34b3e442c256842da5f9b39563ce84c50359fdbeada5f0d028a51a3b9f51b86c49eb43cfb4f55006f8972936e9abaa119103a51bb05ff1ec353ac097d28242a104337d78273caf03398400ee4cd94c58b863031ecba1c5906136d682ffd2bad19a8c20dcb960108df4670781f8d877e61d1f5c032bd7118e2af4fcf7262b4910f9eee20698dffef1e0fc421853c014c325c789e703f37ee78e0e2ca80646734a98257d948fdfd02d63699a4b95f986ecbf42ff57a103894032d0927fa34528691d4102f6be31ab85e171f8b05b53acee703e5372a7c303c3cbde7dbf4a562b01bb3aef58c87b739295355f203e26518f6a7383b77397fd0358a108675607f3205f9f676eae1ab68fc18e77752ea8b67cf9d4d727354391e603fcf1f399d06cf3d2e409e3fc936321f81e416f6f5da2bc7f3ecd6504c1fd66bb0219ffff0368786cd632fec2d35eeb27cca819e38b22d33ea0a9032692a237365dd31dd07503ae40391f2b1a8f4d07178aad646eb6ed1957b099fc598bf967d59f6d064a244e03f8c0fbcd8d3335886a610b40e010cadabc880448a710790a5a70a5750e5c5b41032cf17c74d32a041d58277c8ac5f25d7da3da9ab1bef73a4f7c903ee0a27629350219ffff0378086402f8d6b44c3f5a3b4056d7ba5680c41628e195bc6313acf3d331b9cc6303833c4e7d76058db1ea66c4a5ef6fad4ebf56f93ed74f88d8a800bc00be3ca04d030c9eaae5bd57d2cd14952786a16962b0e45fa0014c11af532c5a121677db628f03f5df73036a717655223313324bad57920a41fb72b2756408e8d4f2c37b649c620313119e73936f8ac3e8fb8b43766a615b9fc7de768d4aaccfa7eb1ed8fbc28f48034c4118458d2ffc44dd371b439321e49c58172cd579841260029e3a33c0a976f903fad5446a54651706d8d1f3d3ce63c52c68fab513e27b61f6f10fc5e8cd5f21e1037a16c64c4e5375897cbf68a79080950f008cd364985e001320bce7b237f5143e034290b94773898fecf8e221c7e3f1a9b7a9c4574acf0430721cc9b5b610e6a7c4030dc98d971398b98ab75fd66a07a8bb607865e06176b1770a0c9637d5b242410a03adb37c6f26403a5053e42b83a8a3b5c8c5c4928b924a5f5f92acc5e6366810c003f48a64110664ff6c292d4565c7f7b66e2167da63ba87b733706d6c0a4df94803035a5a99b0b757bbe5f08cd2c88e02e0887efd67c697f5ab0877549ff7a4ad713503b4d842012ba753f21a32e0dd426af2de97d8237340d672da8ce536a2333ab10303977048204d5146d171cce54f27349cc7dccf4cad69df2d37c591aac2620b15d5034a79886110e15f9aaa2e5889cdb0ed9231663bb72b6695e47968de85b8586593035fc5f29ea3c6d3a7d3765c3a9c3ef3123cf71c0c79e5aafb8bebe6f4c35a0e9103e5aacae1241a19bc118334a7bce9debe74ebcd530e7a4a45554e2b616e7e7ba603fd6b793d4e92cce4e20951b67e706c35c5a3688514e9fb253a731b8576e4254703abd3c3ef2ba060ddc5a47ed1a8fe44633ecfc9e3c4af8b192e26e8682757a9a4030881234bca7ec4e477c0af78683ea6dbb9c65f0d0ff365a005ff9b95b8b9dbe603cf03476199c6aaf69e5f292a749934a89ddc2477658b45cdb55ce5cdc7de10b30385876833fcdde8bfba4c1b5874bf6d9e693fe5eb5a90c5c10142f53721f5f2fc032006ef00535a98e2bcdd761798f0158c6755768c13a41af53e85457d6f8d6d3103755a6070f4a7ac8b8331dee23d75f9da0346606e691436f43be1e2ff0349a0d1036e79df47a630d6a9c878168bca35a50b7bc5064ee27825ba75da630f87eb4163039891eed8e83b6981f157786535e74aca42a89fba169091ce7f72337ff91b176b03aa7c261b60bf7181d497153d2b1cc6cc48043cc5c857ffc449185ed01368481d03d972c583efd5e10666c4f4e31c7e09324884f27815a5c050cfb015c49237701703a31320db79c937bf962bfee62611c2379f29eccb203af22dc6715229c0976e4c030d420eb06000526c12093ace41cb334d7f04939eb63008b5bafad8cd73088b2903117e1d2713a901c9a9d5f1500f034a8167f6259f07577b41e94f86bada0d4ad003644adce2bd67502e088d365e5482a7f01d896321ad2794765871c0c9e19ff52803d2d958748ba7c1dfeb6a22e1e2aea8d9646611e6c3cb938ad9c1ecc5b192cdca037af3d5ccd4ce3958e45f51645277c25b74520406b3cd78e76a82d8ade1363d98035b65aebd14f20f7a7b5b5828b6b32711f0afb503ae76e6b3711170c574cbdd910396d21ebef9038831943b5eb66428189c452a3012c18e07f587e58cc911aa8d26038686730ea5552a75130d2fe491ce834085ff3279a72fce63e812a91caae77d5e03e9277c8ac913f917a8fde9660dd57f792ab0fd112d01b46453ccc2df88e764ae03c3c0fcbe978a40fd7d8fe5236abf13ddad4950e1bad5608079cd866abae018b8031d942c5f793bd295e61894015b65c2a0b772da5420e868784603db7d45fc38ab0365ca75331b2170c759b99680f25803d5fabba9cc4c897a1d09058f841a58083803e3981f17dbc1a00258432c0e34232f267d05e271590ee7a9783a51870f9873c8033a7c4e1b76aa60dc9d631272c2ce6a72e82914e0cd8ffc759889d5f24cdedeeb03ca06c2b4c97d9941e56c3c752abe4c2b0b2cd162e22a5d25f61774dc453deedf05581e03da7d3b581a6515e83a053ac10dee614e37c74397b0001f77359de74100040305581e03e08b15e58c41c3c9db35bc5268e123e5e61f633bd60f8dd66ce320d4d0040105581e03961ea71a2092ede541a5313d58a72b669c614b45d7c9e73ab7f40b85d0040105581e036be6c90bff6b129f08f2d3732f40a37dbc9a95e1c60843ed138e4969d00c1836470135ec955aed3303235db60b9fecfc721d53cb6624da22433e765569a8312e86a6f0b47faf4a2a2305581d024eee85039fc9d8260336899b904d35911f52859f57c1e664281186f20c0147a7a5422d062ab804590c346060604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100b9578063095ea7b31461014757806318160ddd146101a157806323b872dd146101ca5780632e1a7d4d14610243578063313ce5671461026657806370a082311461029557806395d89b41146102e2578063a9059cbb14610370578063d0e30db0146103ca578063dd62ed3e146103d4575b6100b7610440565b005b34156100c457600080fd5b6100cc6104dd565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561010c5780820151818401526020810190506100f1565b50505050905090810190601f1680156101395780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561015257600080fd5b610187600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061057b565b604051808215151515815260200191505060405180910390f35b34156101ac57600080fd5b6101b461066d565b6040518082815260200191505060405180910390f35b34156101d557600080fd5b610229600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061068c565b604051808215151515815260200191505060405180910390f35b341561024e57600080fd5b61026460048080359060200190919050506109d9565b005b341561027157600080fd5b610279610b05565b604051808260ff1660ff16815260200191505060405180910390f35b34156102a057600080fd5b6102cc600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610b18565b6040518082815260200191505060405180910390f35b34156102ed57600080fd5b6102f5610b30565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561033557808201518184015260208101905061031a565b50505050905090810190601f1680156103625780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561037b57600080fd5b6103b0600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610bce565b604051808215151515815260200191505060405180910390f35b6103d2610440565b005b34156103df57600080fd5b61042a600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610be3565b6040518082815260200191505060405180910390f35b34600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055503373ffffffffffffffffffffffffffffffffffffffff167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c346040518082815260200191505060405180910390a2565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156105735780601f1061054857610100808354040283529160200191610573565b820191906000526020600020905b81548152906001019060200180831161055657829003601f168201915b505050505081565b600081600460003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b60003073ffffffffffffffffffffffffffffffffffffffff1631905090565b600081600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101515156106dc57600080fd5b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16141580156107b457507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205414155b156108cf5781600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561084457600080fd5b81600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055505b81600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190509392505050565b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151515610a2757600080fd5b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f193505050501515610ab457600080fd5b3373ffffffffffffffffffffffffffffffffffffffff167f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65826040518082815260200191505060405180910390a250565b600260009054906101000a900460ff1681565b60036020528060005260406000206000915090505481565b60018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610bc65780601f10610b9b57610100808354040283529160200191610bc6565b820191906000526020600020905b815481529060010190602001808311610ba957829003601f168201915b505050505081565b6000610bdb33848461068c565b905092915050565b60046020528160005260406000206020528060005260406000206000915091505054815600a165627a7a72305820deb4c2ccab3c2fdca32ab3f46728389c2fe2c165d5fafa07661e4e004f6c344a002903258c851feb945236d9aa49190ce6a1f954a22ce7fb40e9853c155966b29bc40f03680ba046c0992e4f9f3f73f390ee95b35db0a589eb8ebc8925e450376be8361103bd26ecd72a5379df4385174dda3343359ccceaaefa7e53d6367a04d404306b7b03f65d4bee287a821d23dbe32853da771b450507ce026da5cca1d6cb04258de31603804e1841e3cf2f01ec5a65fb346fb17a9b8087b450657d72146166966028457903db2fe285085d8d887defd67a82c95a7b04e16732675ee52c793c5ddea46534cf0357a2a70a8d7681d7a1b0fbc49e2878f01379de812f8770d62db9c210f139d786033684de958ac1c8c5a8cf71a3356d73e490025908e3488b63ae4498c0aa03f4fb03d77826179b282fccd0ef638e4fdeaf4bf367250f06aa9eba068428df00a4d6f00362470b311e783d332595cd3c4bbe4dc825db0529361159707927acfe5671adc4035829d57b692c44350649ece5e2520ac5ea184b0814ceffab6e4a42dcde102778031164c93dac507600f62223b0d125d0c8e7789bd616058a3fe44dbf04445a5a44035e6c6a3de260d3b3abaf7ab1b8092cd0749b16026ef31be5a0098f356b05e5ff034e4597fb87f732899878120cab91c46037e71e437f574e53918a06d36fda5ced032272e0d26c583d18a7a08d001b99443bf6ec77b3ab5b2378d41c387b63b3d47d0312590fa557d104f681e79733fa2e25f62e37f5a5d0f9fe842a43ffafdf7344fc03b671747f51b1de1600c15dddd85ff23f061c7f9d904e289988b8163541f2116903c93a9c3b189f78e05a52f5ec546c4aae2a47a642c3803197fa1ff7bbf4563a2c03ab86a9a0c49994c5a2cda65842ed81d283c585939edcf27124f23d987169c33803348f9f773e6075dcf53a623b8fd2a0c8b3b66194a8c6e179e7647ecf5af1a6bc03d58f17f0f2c9fed1484f087c800f05a86b8a43ddee0e2e3178a71d397908244203a0c748fefc2168df5b99fadafff88b9e3ff5e460f84058ebe2a8414e65faf91503ab26e18fab352e2b3120cb64a59eecd238e188488136e83c9321c0665866312d03b3fbbcf89da76c5abe29a8fb2920625a6f0135126108058f92383cd46c08193503edb0ef2e69d07120ab83fa70ff2a41edb2b5935e73ad6a78f6eb2fd31e4fef8703036ac77c468e0109e271b9d6c4aa3384e87001503d275f91ee2c078888bdb62b03c5133909a2092ee8f69fb9994c2b3ceec2f366c4dae5e7a067faa49ca9c8ce4603d63f33303029b9ac31d64d217f0b35c9b5bbcc2c1e713de21a3427e98843572803de43649d4dc30ed43b7318315c5f9c07fa295a4d7daca314d42b877c63cf049403dcf9d9ca099c7a2bc926504b977ad5139ad7ddaea3443e4726268c62606fcfb2033b6bcb14ecdbf83c18434ecd297315aa56f2794d03145b7e4d60ff10912e92f003715a79612fcb1f5409855d6760ca3ea2df22c6b99aa5d2a1b25eaaac158286c103b079bd51c810a078b3f356610ebb247a705f2fac062d76328b823f041d30829903c54a43dd80209311302a06fe9c2f10b8db85c5a4c36f676754d6deb425eae36900581f03de4edf38227e7aa6cfcc2d47a4ad10bf7837ec493a892b7ce8ed707d6e505820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0319431d102f4f154c0d6c26669c52157b9ecd33a103b1498bb1e4ac6db5245bb203d69f17f18f715d8b51b112e9b3cd82858e083c21828b7c6e460800a5e33aad09037da5b924acfa93133a58fc24fd7c404a0a3fb1f3fbd5ebd3afe43931660ae0df03a981b4be5e82eef2f5d3a540926d88c64fca7cb8462f7620ed089a18d4fa00860365f23c275d19d10cbb5ba7a8266a27aad06cf79bcc0f8bec863dcdb450483c4100581f036f7427aada1d17cc75f85a8c8d3f1e8c7afc2699d98cc32c03d14eb1b5c05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03226d148e6cf40fbb0e0a1f4201388589c81bd6789b7a95550d13954b1585c0b903335c69df45796d3fd14657a2fcf87e7d0605b44fa473fd07daed648a93ec29b203861553b8a3d2696cd6dec12c9d26805af7cfe33675721bcc9bab52c44a59767b03108244dd50955d0d42ffc3d4241ff2cd5506efc59378e7182a3379199081cdcd0377f1cc94390d788f778e9abc25d122f06f86a5bde750d394eb95158c27a8c88f0305f8f75e7f80cf17073894162f08e1afc79d65b452c47796d85efbac61c13db300581e025ba1f0881c3765cdab4b84d7d20b340828dac5bfeef3f4cba15ef7bbe05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e0301e36f58d79e973c58d6333e4c15a9d54357b52a2c560ab74013b297505820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e032d2f7317c77f9337fb9faadfbaf38b2220fb967651187c715b5c8c47e05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e036cef84d732bef714ea0adb4b602e9dea2eba7850b0554a7a7907bbb50048928b0b4491eb4ddd0219800900581e02a95e615330244b354748bcfcbfad8633457f536ecffec7decbdae8d63f4672c73a2bb00000581e0249c669b03a6e82f72916bf5cdedd57d7c7cfa7601eef8a24631c2ef44b48014401eab384000000581e023e9214fb7fdb497b62fe7c176af51c511e58331badaaa87df64518e2f85820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e0292a97fcd4de9c2ea1f6ad5e5766d330a31c6df01698dd8e3ef20e7fe255820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e02c12c4a7e136bde0738cd13453ec43e2a7c7477c7d619a9f22899fa3d655820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0219c0cb00581f03f218ba386554a53d8f5f98dee787c5e6678bb150027a61cf595e2fdb92505820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03c617af8fb9b62bdb1eff1f090b2246af4c25583e53ae23f26e09e3ad0ee0c6b80219ffff03e2c4c0830a1d9273158498a8fe9a401f77170e3a86eb95c70960f25b1ac57b4e030a500779d4c16d0214671dfe9115cc25279220cddcfca9d7c819fdf4971aaad70219ffff03ea15e62c0e436d052e35a511359053b4d5916192589f2c926a64792888b5e295030aef44e42d967dd913c5b99c4a93912345a945854630bb365457c2e4cf39b39c03ffbee45d95933d183569ed58de393e56969716dd94568bca6e736e87f07f822c035bb0fa263e336ffb84a4fea8117d69a0410d867eb8cb06d8104df0978bf5985b03dc31d8137d9abdbc5040409c3012efa98e90513fc126e7f3e53188e7d2cecac7036296d62cb8143b66a1d1a95e840668e43cdb6c15a72274f6a6f01cfc1fb20bf703816882fd0933ecb6a1974ddf92d634aa910d653560228401fdefc1030388c97a034984d10fee85e494bc53a17b032ee6a8f8d41c9a5a8dad27873c42511d05bf420219ffff0348934b15b99eaf13c5250f752336d6fb8993345f6d44eb7a86e352bcbb201d470219ffff0389c04783905adc49e9114b07e4ed979d47e827648bdacd6d7d19f31059585745031854825d08854929afbd789ac57974d351b19c09c4cae516b44e341df350f48903d1655595038d84f7288f07827e20e2d6fa33e8bd55c27508460ef32f306dae8303361ac274a8b9609257b578e4dc3fa163fd7ffe033cc156d07e3a90838697db1703120ce29f2b558198729eae82fef06629ff5466cc586d63e150949044acdeb197037817932b0f9622c3b9b61b1243d9d72eb5c2e2bbc69c958547b3f77488b0f76003a2fe44394ccac2d4d526d4d555b2e196205f2a191725d10b720335ad503abdf9033fb4a62f3f591e80e595b3a373802badc09417194bcfecc83cf82f9543180d2b03cac3ac2ad715f2b4303a4b0f491b5770f0345ae67d7517619bc2f0f97bdad07d0345ac6c46d08b5348cd1bdd2f291ca2aa7eb913a03c428447c03c520c95310aef030358db319d6e8c2cf850ec2195886e678bcbfcb596f4f992e28173ffc3c8a4f0037d4735cd7262b21f800ed5516be6a25e6f50bd17ec848a2d8d45b6574e8e463f03395d59c6d7233acba9810cebf9887f0daf006301ff079337d849252c45f6dbc00320988122a400f61477df1d24abc11447f6481febf1065d00785281b84c20775b030ef45c09c32347fdd59abe6097cc1ee939322af9f6cbdecc36c63698fd82dc9303800c28e21f2053e0f61b4c9a2968474c3023f64dd8ac008cb9e4e241ad50684b0385987ab8251440898c6b6c9c3691950ca3bc0244fe6f75f3405f5f8a408f6a8f0376965ac98184ae0735f799df4d920e0372165f9d3c98f0ab585977d653c5ac95034a1cebcc94862674c15eba3a7f0a8fc555449b4775a6eead99c417e6fa66cc1103b5f326758f1f17ce11e285a5b203fdfe1f6ffca2ddcfac755acf020bf292b745030d59487a27391a56617c4a7da48822bd16b57f5f882c393ef9790114d1c58f1203023dcd9ee49c1563a0257425e72e4008c7d9c3c522c577ef6754fed5a258323e03aabff8afae7bbb8518ad90fa27d09949e4c7fecc3270fa4c747054300c5e316803fa42aad7b15d6d11eadbc7eb15dbd4d2fb35b658e531f026ae02fd54314e36f603e3c9a332b20c96a2ca2a7547b8ceda085e2f64cd7a9d41813b32f45da5de4d3203138ed42f68459d89e7566321cbc5c933f1daf22a0a230f4c1536a1267fb178c3038d30a4a7362161311aa15ed932cb85478373becbc3f273336f5fbcc9a5610b1903bdfe26133d0bebbea5b741e93ce6495d085b1c70ca6fa5290fe8549ad77244c80381565e82d24d473615b8dd19e84b484775cb628ac39f5a9389d40bd2945ae54e035686b9c790f94f0299f5947476ce5e86b6ddaa63f91441be306872b37ad13cc3034cce4282c5bb922ee7cd826a002e40f59760364837a4c2927b185594e45cc1ac0332d361fc51da352f0ef8ad8752c565fade5a5a8784614c6e6ab6a4fbc131c5a50302325ed5207c523e28ee0bb61e3571b33052830a016ddfabcd8f3f8854d3ca9103d63a2dfdd7d6843bc07f8ff3583cd193991099bd078f33f68a07bf77025d5f2903ea4fb828cc7df5b2859ad7615e64a2b51933c4ffe611f2eb2a991abc9a4b930c03f603344e828ff1989b017051377b024fca8e468083c9a84b970e3546f417581c03912f3af8cd84d62076743b189c7deb0a13bd3c8e8d24be86b09e6968afadf49e03d8f84b83332e48ad175452982b6fd450565a6a629347e3cf4fc4cfa93d597ef803647965599a11864f282cf146aa646672944e93698bd380ba0fb3dbe18bcff8d000581e02aa751d2942ae858b817ccfeb59566840eb4726669f491ae7d8ebadaffe46221b262dd80000581e028f332911fdacbda46d6832b1707d125514b343f6e798896024087b0c205820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e02ad4c21a767eda5b20a0c8b959a7bd51c58cab64d3ca11acbf1dd0624ef5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e02cc94798e7bdf2790cec79e819e0090fafe127cffe4f52e3f76111f1299486ce4eb2514608b4f00581e02e7f17351be8102794fe3ea23a1829528715469b3859634dc1ca4b6743d5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff021995420368c120bc43b6a65acc4214be91e6ad698846b7b5eeafac03956ae02a73699cf503fcfe0990b0a11905d05faaf869af920a86f0ebd125cf40753be168b12905472b03cd18a46f9506f76f8bd4d0e5b8d9883df5586ada1bac1349d33aaddd7c5c846203d11f94e1bc5f8dac25f5c881d1c9b21e60fb761d21228af01b8f453ae6520b100363728cc1ab9920c1227be2f898909e990e7658a468bbd30486c44fc9408cf1cc0376362b4562b4f7fa290a7681f52d472fb4dc6cf760aac5ade7c94692a39a4f8a03bae31e91f417c97fe6fbc6da47743fb9614c062de98deaea1cb81dbae15894b503013c5078b47fb0de8cae649af9d298c02e9dc25c0390231e16cbf81a9c33f1ee03fea100c6fb2ef8691d84415e9afa81004e2f02265696924a2b5bb050444e78190219ffff034e45f0069f1f5e3d7589775cff216fda1f366ce644a3741af1ca967e02ddfb260219ffff0368bd7498f3093d2fb1486ff138506389cd9d3c393d336e369d77bce0e4d9125f0319d188dac77d882fe3700f098c56e60abc002588561941597bd485fb76dfa26e0377b7d516f8ca8887f3f26114f830602a44a122bbe78d2ba47a44a2a09397632903a534a81794408b65fd2cf25e2f7cfe19bbf15f5f17e7046a530502889cadef780219ffff037925663c1a344cc59f4fb8ff151f41a47977deb1332f4533ff9c2efd8e9d31cb036fff43401f307d9d9deccb98013002ad6d56261693a1114583250c7780fa443003d6dd01dae403071419cd4f7d72a3b0e00236456667ef4908b61b2098163d70b403a03f65ebf1bd035789686aaa2ece3a0c33f086eb1f6d3dac9ce1f12acccbc6bc03bd7d5ac863333741062cdd8da18706b8b1e3163b34e709e3c0225a5be58c8a15039c749db34a32b4650418bf9437da11d952187b3a0a3390ad74705baf71f92ae003261f6fe9f2e1f727f8f135bdd220ae6cbaaf3ece2ce15e62b5aa0cae5b828f390389fbcd19ea1754dcc42ed573dfcc2067d90d4e54aebaed3881595715aa47371703c99f3bea8df0d196bf1e6440c77c93dbb0252fa9c4979b186e52b1033aa35b80031ab3889ddb8708578838ce297c17b99b137a9bd515fda90c596cf1f323b26d4003022523ce81c20714ce1187f0d5c18beb8511d27908c71a03770444f6f559ef490325350931e7b60926b17ed605c80592f22bc73f8d88f43f19d4408b7b6f1372780219ffff03cc6275d5a1cf24976ef51f64a9daa386f2a5dd4e19e66604c2360ed0ca885a5503733b14bd6ee1e9fc788c87d764165db027574fc104b9d5372926ac9942d7ce36030b4325b60b37d5a1b1363c8c53ef5f453df82ff19cb5d8a6772212731c15461a03dd30c1cb2b8f681c60ad21cdc1edcf21b6f194bfc9728a065674194516af41630367a34bd572e09e68c20b518d4071dd7e191a798e3c81931e34fb68914656950603b37cd690b925a31e3eb14c6d252a78a19b9bd7bab3e6779cb6967f8262c5536c03d3ffa2e001edf1ca4b232e8896b3cd8140c80ddd99ecabd74fc7c4babd696b5f037f2ed8f79fde90813314f9a05b986d4f39f9ffd34fb6dac2465c9be57eb1cf2603948791b65deb8242a36ba346eeb9acde968f4e43f2e07c14e5494e1b8b9b3619032a60935cfba406bab17a67a103cf458700f83819ab4da64267f0fd5a0ae1b6f9031f6181976a1cf0c100f6c3b648eae53380605d3037189ee9cce454e9527d38ff03eafdc2bd120397d5b280eae2241ea9527f11ffc835dec9d0fd019e0b483268f803888682cb1d206ae4e12aa0e3c1079a0a061e14f998625f97131a019a8c7bf83603f016f91d202f89e2e7b19eb33762cc54dc521a89953931d45e15fc3668beb1af03d35d8327746a6066ec17b28cc71b212a699292ecb4deeb878d4a43a2333072f403829db624900659d648f96073eca5238d4dbcbdb08a3772d0c33c19a72d50e35303bbd42f79969cc66fbb1e980acb4721eeca4bd56a226f972fd3ac0e341ac8e7b8037e4c1e1ba2dc4b60cfea527642132c0543a7e3a70070d42138ffda15994bf070037cb332298c3d2cd403a65abe9a608ba9cb827046ae9b301ff428e360ac3538b603043355d4eb15da2f523f1229d6aa239e80b2931108404007149e9583df097972038939b82a7a6535d6c3d02b359bf59a8613d935020f4845133279cbbe84ac7f6003891dc5ce015979762cfa844c7803cf05ad253635c5db49c69b8b81d4af312f76035a1a3e40b5ffc335c914ddfb05a05b0ea7efbddb7f2122ef07d5bc70b444e0f0034df06ca2011ea5159c5ccf6acba5abf438713702cfd22b8cfb66faf89f550a1303c60f2c3acf9769d1ada019c32f79c5ed807d3b4bc43ccf9f19699341ed1aa4d303e93262f8ad5035eeef9ab1f4205ee5459ca8c6f03132f5fd46c1c8fbee5e821f03b04193f82ed3e46d5ba6d719b9e4cd25332b65ab0609c16d47a655614e5300de034f2f0fb8745c183bb61ee1b6da1b261eee6943e4c1f768ded2a6002eb6b52ee803c311543859b884bd36f93f699a9f00cc845cb241b5b6320faffaca645695d61103b5b8ab144dccbaadcf81df22c4367878970ebc1f47f0bd46484f07066aabb10300581f030b152b91eef7c7f6f99d5a1a7856c862e088efd599fed03e711ebbf8ea80475ea8138e89a6200343d9aea046f63d09cf757ce7f76e4bfe96ecdef50c64eaf989889f66f5dcf6d9030665227d354e4b58aa78b203133a2303bfa06e40b991609f6d36173e1b1b155a038d98dea62e6f9d696a67d282780c11b81880946fefaebca861ce02529d1cbc2b034e8ed3acbbbe78a3b8e27a46587e9628224e3f6582ac8648aa9f014d39231ef603d0d6177aa8698ad24b1913616dc18c13f67120eca250cff6510815a8340c4f2803daff8476daf67f10cfd41e0b4d4db51dbfde14c065f277722f530378c2f324b900581d022a1e34d72db498e4638a83f823581777f4fd4eeb81b3bb9517d3d0f4481a0ce48769dbdff200581d02a39e02329de2638d5a2c976adbfcbfb31fd3b1ceb368f2aad217b3f35820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0219048001410100581e024456a117a782861622dc04f36a4f6b00440d2a1674cade0d00f67182f05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e02059de768331bd21006eb35d7aa8ce3afb2ac9351638629013b590279b941010219808103e1eff0e20b45cc8fb2e31282bb5bc1a54c3f36b31ffb76862eacda1e85454869034362073ab3b2724c5ff643f5455a0fbec1d0241a229c0a3bb71e12a793cd075b0219ffff03dfa3e440ed356a72c2cea68f7eaacc52ad84ce2c495abeef90dfc44280ebf401031d403e9ebba858efce254d2d1ec98afcc77296f4c484dbc4246d13c9750effaf03db8f0aa7d52b0376ca134456da9c6eb9e0cd386120722ec549d898b0fd56af4b03635dca055624cc68f5933df3544b746584e761f880c0a2f12d8923f58216830d03bc009fe7dcf06946990d2b16fe3dcb6f194605c1d8208de677ffea55369d4638034c58699a16f374be091ea15b6c7354c47a1b17db08e90a4de9c58af5ddceda3e035e17e8a86d4c4963c8828dde1eb7d272e4cb9ae476b4c4f3ca8de473cccd856e03c6de533fa8e803d1591b625dcd24e28089c9932bcc8e0a07b86104d0d9017ccc03f04b8ea4ce62e9765e39bf6075c4ccc365e83eb83c123145c50c297ea0e6aea503770c96dba5fe73e8d9a09da9f11216229e6eb1f1325718e64af8fe1dcc3490880219ffff037900e173711d64a50daedc593adb20834305ccb174bad6a1b30f5f6f9ebbc2350330d8ded446f35e8160bd87c680597f613428871c364a9c4c8f79ec3ebcf94689034cc075a50b8634b21ee37995e8fba8475caa1c4bb622b5f99837cff1b9b25acc03b3b4cd22dd6b240a7f0b3424c70555224b70b0d80f953c8f4b8251e697626bfb0383983ebc17e853a23b7302c8ae07592e915496e509e39ab801e8e9fdf1fe3421034cb2e323a259b2540399a985e0c1fb1afbde275008c492d2e33b6d7e6a931661036ab21ab6e0101e876f74d1537d7dc8f92974c6e1c6a4df7dc1f17790413c416b03b39189ac497a44c7337766f647c713b9403af807804b88e7b12fa5371b154c1803d449bd68ee60693b15f6aa9fbb7733c92b169b8c30f36d54dc676543313c13d90219ffff0354476fa2eb40443365ba6aaebd43a23a76e1e61707a55553e3a82bd5fc97b8470342c0d24898404150da8c4eedf15e53b36304f741e6b3e28908ad460ece3d7e0e033dfcce0671f32ae060937fa37287522e901950d5b290697634baaa8daa446d0803aa199fd7857b1c92eccf8c8f55602517ace85bf49185dd130c2e33cad6e1bfe4037f98df3464b3c137c8ce6ea27667732c0050fbdfe77f38e83bf73fd79edcdb17030213ceb97ded2f6aa5e3acb882fd07df89766e0f6e1bc737f68b9f5f0545faed03ee265daeeefe96f4cc40c17e1bc287d0285d505c6215e88b78fcce92ae6d1b5b0307864a3a39f254442fdb8497805fb3feff8ea8fb498a9057825908f39acf919803bf1a2dc61745941aa7fb9f585d96a4280fa62a5fcf907d75eab09ba2924c3b87030410d4c9be9ade62dde511e95525f60a19d28faaddefc03bc36b8686ad34abfe03b257ed99dbbe821871e505d69803e932c17c1aae58e8ba7d99eba206f9e0edaa03c7daa178a4ee51831d298d6da3bee598ad9947afa192005e0c9d6e982a95e8c5030cb25a034e9f760b6a7f679cfba3f9b8d15d26e0eb5a24b7df2dbd2f611f1b44038a0775258a991250298e085805aee827fc61309f11d3ac34d2bbe9119a56aadb031a7810bfa0fb258d6a2174f10337635d10d48361e1ea9f6e468570669b4fc26a03240dc452bc4706b486ebca8996e836e69c1097182837a84ca0c2efa23abf191800581e02c7147a0789022c899476a7a616ade1e383a45e825273c59ffb1928fa895820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e0270bb6130920b7e3de64233e96ac2464cf45ed42ab19fdfcab7c1d0289c4810879dcb019bd68e00581e02a08e16a4f7ac34f62963570b7d51d49022c4f8338b9f0212688c57e14448036c9fff493fde700219a080037d162c546197069408538bda04ffad292b3762d155602039db5d77baad6d65e2031c098c61b2e837499325f1e316bdd1a94c007db2689295cd9aeddc6f2d0a7bfe00581f0330d38e63fc14f140caad1f74146c8c8faa25444c1f110c879c27b50513a05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff037e7e48d080e5aec741eefcd4bf881aff6f6f425c4d42804e57957f3b24a1a8fc0319b3bb329b7ef8afe95e5dcdca66e8d6bc540bd40f658556f0edf4ff366a20d003fe95a34d049d09da2b72fc7ccef5ace5de1c38ef168d9b0db5cc6e59e2cfa1a900581f038bea4c07bf7078595129aa732ba195f03bb29282dc0a09794a354ca027304648cd1036cd720343956a25a5fabbb70c98609b15389311acfc68ca7b07064c6a076778fecd8735031cabf3d9b30a39efe0e72f794998b3cca6cb65f660f6df3eb8e6170e9ab0c867031af490e71a45f88b0aa47604a406ef139496c35c97ff6a8a4b72fce61f5ee8fd00581f0353ea2eca8dea0356b65e5c390ed2fca04a84587a33f7622cbf8694ca18e05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff030391188753556e4c38006d8d38560940879fdd51e737dc0325dcf344746199fb0219ffff03d8d2550526966798c174dffcadcd88b42fcc886f7ba9f9ff8345f30de6ba45f103a903dfd40cdffb120fca1d4c7b418e30c2e2bc6ed6dd81b4aa4296fd55a8db6e034eb3627aa39fc8daffbda020561bdea7dfaa707661a1294cdad2728fa219707d037bed3e434758a36ced0d118ea5906b4a431f76ea353b698042b68a9e876c9cda03a3a4eac738433912c67f6504b4c1ca668121749cf56474ddf47294b5788009b403ababb01d83f20e4316fccff1e39aaae01a90b7aa8071afeebbf9ef1689b063a4031cb164208893f950d74e06b7adf29794b5e5cf65d965b7a87f40b685b227973203fd62692b3db8d4582e8dca1a0ec9a2469cb6af27f02eda61248b440ab1f85e0f031decb3b98f0bb33ad91b6fb1682e25dc8df0c00db468b15f2de8994f542f63d50361f37042a2587386fbb2d85b5e15e11433817f00b86e0d1803cf02849696221003eae59500f8201c7c647a4226f03796e11ebef1aba03a5b42a4291beff08c941303939ede8e42183c50c236cf0bd6fb88bd21fedbe349bf269ee93002c7796042b10348612e053a12209c11f6bd107a89a4328e3d7afb809cb305e86bbadde3e7b4a80338e1cd81ba6d69ea881017ba289154ae82c0ca5d74b403d9c6c1486326d3973e03ed8d3f78ce1b1bcdf71c5d193092c7a454b4b83f54f48bd92ba2ddbdf255e1620219ffff03410e12b3e537433a995f528fc7dde618a2ff37052b2d0368272b398b0daaa27e036145c12b876706ea099b3ca16815903471ab9194dea3861fa0c91e5181511eee0219ffff037e8e5c2b9fa2f31d015c14470659d1821c79d15c9386b0e924d3737cf9c286430219ffff0348895677416516ea11a8bbc1b8898fbfe1c9feeb9a607deae8e64921da201e790341e7bcc329711efaef00ce10ad720c35fb7bf215b70b1478dc098fb2155c7cd803e8659f0f29aa240c6e53d039d62e2c0fb36d9048780c540e5d8f0c7725f1fcc30333c0c2610886b5e5c4252b28f1037638ff69dfdcaf3fa2dbeea8bd98779b729c031390def807310d2bce05d17a4aad9e328f366fed3ce21cb1f0bea605aaddd78503dccd50d6967df0276628d99b3b38899174936bd8b950948b271ef0a9da5cc73f036c2b157d9e7079bd5d7b9bf55c32d17f4e26232f0b76ad36e94a4a327a39bbb9036b97493a62728a111362ef972086d7df94cd04c7f0aeed3dfc4acfe83e43f8200341fd07d8c8d0341d98555a2879d60efec9e93313c4659c9434a5e1fc17b8380103757a1f087d42d814c121698cf037cd7c30b68773f17bb5cef3c212101d12a63b03432ec5dcca81020a377a3415686bed745a27edff56cb516b00d8552dfa2c715103e9c16c0858a0e7a281cd85f620ddd84328f89c193a7ab8d14541dd9c9c104d3100581f034b2c64d372e60b3deb584c7a735e688064d5472dba43445fe883dc3a9d90423c500367fb8f58870b4d6c24034690ffae360e87fcaefe9c1c1ce7c256d06a488fe81803d9289f911d1bc2e8264e4e3e40e65d8d43a6d44f1fde2ba30b433fe83d9138f003c9f36cea93339355c5b16bc3ed108f851b829cdb5d3cb66065790bfcbaa5c1f603a29ceb75dbdb55aa60bec7a236d8e85f6f9673bf2013dfb6b1b141fdd787a687034db86cec4b566b614c14d223e622296d8c19b1d2077ba14c9457a7d8f5adc4a800581f034d1c8bfa4a97445926fd822a2ead1d6ec09cd234c58e3916929781163fd05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff036d6b75e482c8186329f886bc9c645571d31fc02c6ff4ab4ff4943c806915dc5903cdd938d4ed85d9bcb934431c98ab38acc2c9cf9d4652d7a3a34123d66e1644e700581e0270028ead38d9fd7f9171a618887d4f8576de72527b089b7a129b7affd05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e023ef8cdd718d82e58f2d37179323158c100e8815fb83d9a09270be655085820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e026659b8c90a58f0c6881882d50aa05277ce0d585fcf7750f0bdec8c7d155820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e02b219247b01c8162aa97ca9dad7fe28fe9def6ea389cb95b204325054d74a04d671e9d45d09a23fbc021941b003c3e19b99041442fcb660bfb63d0f6232deabddc898f3f854e97237255ac9e1b00219ffff038ea856e54df40c0cf75d4390e9dcea86288e58c991b4d297bd0033b604282be50391834a439aeb5c9679ffc45e2f172c0d102f20af7fee07142a0b067ae33db38103073400b3269663420f0e8b214cf16aae51b06b862c85363385e76ed82e9a87e203c93cdc1af3de19a8906c3d34f1eda7ef0e9aec45740d69352090a917cc3e4fee03f339543a04a3242c1bc69790d6d9b01fe058d86b99a269e3e75445006a29fc0c03111278dfd3b7f44ef56e9a7bbb3c0897b625a8803d08e2782c845ba9f531e6ee031ad3787fbc3fa4e0267028febd2cb68693773d00743a4360420df43313a9d03b035387016d74e663e4fd5d3807733efcda248d1071325900ea29ab6777ca6fde27038790101908b6a8d44859e1c03555b4e88614d73a96499c64c354c1b2f527a4fc035508d6416b45e7ae100be067f3055e3c62026053303a8719531ae6b0c779ce0b0315c70bcfbe0deedb4fe9bf8c1f9d0589a44fed484c98465e20f82e3f4171ded103bf42f9654b359386c90bfa28621de2f12f5d07a8f6ff2e15b4f8b45ddce1833803f4aa3e01925a76b4e6a008cc2c9621d508e015aeb90adc9ee8574190d3bdb7e20219ffff03a924c3f66432a20af24e2a6d6845994af7767d99e139a7ea7dc8554ef9f9622b03e20f06bdce293ca6f61115267c074aa28bcb609fa315397d772ff827bf73c525038c1afacaf68b977971cfc08f1598baea72165f20742bf8862553bddae85bf04c030578fc023b41e359a204d256938176c92e24b0f8340a5e32b10ce93171147ad40376fd0c507641276b508c167f55b47e55680ed62fb8387eadf9564109791a66c40331ef7238abaad5d8fe71aca593ee4da0dee4a9023f3a2f61e80abfd899b292ce033dee5399f5b2ea924dba5f32bd54b71657cbb0f28176b8f90dceeaf3b0825f78036a9976cf65baef66069f2a9c6573517bd2daf4ffd6cda394ecfb9c44e9d31bbc03e68a768702b60934d9e992cd00936bd17734c3453dcb4e4c4103c8c1d62bf756032b4b79f8a4f153ddf6c48af50f8ef7151ede2b5b9dc1459104973c9d31a2e220032c8a99cbcbf16c939ce1e9058f3c38ff840ef13c5e84425162a09142d6b08ad6038ed4a4c58731ec584a9bd2bd0b55713a1f6ba8ffae2c28fe284e45e7f6078258037e792507da80afb9cb1966c4b4891249a5754478922d3240ed06118ecfac00b003598f7c80a3d8b0b871af7685dfbefc4fa7dc03cd9273cbadda5b2d3ac662622b03f135755931ea2f2555953576630162004de00fecdbb9be11a2847b050c4f61010219ffff032d1d23c62c366521cf551722d186d8063101a32f89e63e50739eda96aa5a4b80037145708b9c31061f6540c9c800cf392ac738dd8540115f3d969229d8ec2f19b7036516cd98c967fe090235097820b53a8510986da56aebb419f8722a07285536bc0392dc6b1b71f2f6d107d2330fc84631f05e7cadfdc4463169dc0aaf6d58ef075f0345b5ad6becf99101e9138e6c884250cc2a7a44ea9165e9ef57a6f4038c8e65d7030a4ba9c187a3de63e7241e3afedaba5b5607ae1fe6876d4fbee40700c6013a6f03a663f3c91c0a06a428b375b8f5b71df40dbb41e48e6296a2770c1b68cc8c106103c52ac0ab6cba6b9a5c5905b80c9fc22b20af8e85aa53d475cbed509af689b95a030594a02c0e4fc4363f5ee918a0898b5c0abe8ffa23418291ee6734c8c3e22dbb031233fcbbd7468c35ec383549febab3a48960f014541b5b4fb1df9abbbdd283d6034d6bb5bfab50cce970a530f8ea83472024829f1740cfcf2825ca85c4a016b0ef034a09b27695f4b7e899cb1273b8e2df566460341bd65520985d92ebc809a4228f0219ffff03460b8107956600a9c69d0dd79b940fdc26f04eb0704f34bdd29024be35311b7703eadc1c47322f3a478c049d0abe2edec04308bff53e9117ef9fafa8e39f5221ef0370e77609739ef27f86d577a385703b83cb1b4a0026988647656903d9c10adc9103dc3b72c558b399b9b2a5a50db7eff99f466a23611d3ea0c7b8e1301d2f4f19ad038c4f6839c54c21d5003a77300c5167380f455a1b14aef6c251aaba47dd430bdc03378cf98600ef88a9562b030e5c2963e4b0c9c4ba2409e86214c638e86a2ecf9603718b723601a055cf3f8161fa1d6089483ed131826849ce63158ca9b50ee249ce03375c270fe698b94b40d71429c5777f4a2778a8a0ebdfc3f74da4c25752c1e953035a00ad6a19523f39fa41e9f82605b1ecb26a615007a3887eb3b660948c938e160306cd16ba555ae1a531a57bdb595b30cca5c215079ca4dd9d312e646994c9e6bc0363ee4e9cd78d9858555b3ecd0db9a64264c4b72196a82db5c2e611eccfa0fea5037108ceba972475e937951839e0240061df784f04e2986b4e62b6f7a0d8868ba303802a00c8be072236f5ef41677419ab16ebdadd2adbc371763ad4bda18ddb915003b8b0a38c5cc0ca4f07f80dae3d66289b1168978674268767d2fbfc83435b51d80311d98c4d19a15aa2b84fd08f4a390ea4cf882fc128d234fd0e7f8a4b22d4713303f3988637473cb24aa2aa0821daf55dccbcb0f529975525538595cf92cdd7930603470ed35dc8514ca8dde794a1964b0a0591dabb56ad7fba451f9885e4700a3d7503eaf7638513e790e643015de695510f1dea0b8461ad8f5aaa660cab92d63ea45103c3b357662b9a2f68dccf5847c22e1d9eacd996a3e652aa456a1555ef1322eb6c030084babec65d302bb566bed91104546585cb799a178fa33488b34895029caba30308282e226b5860f010e6d07888740d84593386bf762a5122a8053b5fe7eabb2f03fae2bf7a45a62bfb6b73a1696e2c1875ad15a044e2c0f9eac4de5c6bc808b70303fc7e3ef1cfde6a680dc08f851710255b4bd910ced4c9571612364209a4b5b35801410f03c04151e22172a02f3994ddf2adf7ac75f363a4530f5e4ce2284c0bc24c7855bc03fd8616a39ffab29ed12fea6ec318c723e2f3fe975c2ffcefb5091484ef123a390356a91a12d2c3161686015921d6740560520db69ee0c8975b32aa0429875efa1500581e02b9c899cbc8d27e98f25813a5f3a94d25566496a4363b9d2796a5d5f1ac5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e0248d06926f3536b2f78aa61f4c65e4a1ac60560c237fc1946e4edbe69225820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e0264d3d3d247e215ffac0b4523255de41efa0d9b367ff2a2d74d4c0ddb924602583b5c8aec00581e02d5ea7fbbc0a0b29210188deebf2a0870d3315d08fbc4d700ed556c9398472386f26fc100000219201303df9dec1696fcd71c498e7e965bd196ffaed8434f189f18d7daac08511841167100581f03e091a2b4b63fd2e06b9f9a632b6eca438156a3b57d8df78f45d074fd74f04641097079d91c00581f0327f6ea78e840c909d2cca44a3f98f1680170fe43d0882128eee3116a26605820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03bc607a5416f84d99e8d263a3a8276d681514a33f26ad3007eac795620794cf1b03c6df7523717d317c72556a25f20ad9dc8e149544244a4e05095aa9e3161cd7dc03af73a8342aa41dbf7afce758326a3351f5844e5ab7bce8152a196023bea0735e034c41f1af52adc6cd3d97e1fc122fcae6730923d821b91c0ffc318f5cdea0df010219ffef0395bdbe8b48e283e8cf93dfc53f038e9077365bc089f7901eec3659dea5678b3c033d800b6e9bbd16a3daea40e691648934de3785c506e0ed9451db5f9253f615b7033df2ac579e9979ba47453117013c327699e201d2d07f29439add69647784746f035f2069e9e49dd9ba8b7ae6a250aae435721da42d1f9824fb68f2939d0692f8b903ae4703482f7c55078ab2049e463925b867a6438a7453b80caf240f24c590a5930371bfe6178d19cd6d8b1b7ac2dbe5d5a129999ca55e0b36c6564aa6bc40ac912d03576065fe41aaad51f62645114d720d3b137523b4599b8110d559ca9bb477f472039bc70b06ebffe41b889b6e36368e496dce03d899fd0b64485fe80c09203f91000312e3d86d5c521510d09ea9f1f4cbcbac7bd72a2260861eaf5b6f5d2b645e76e4036bab120d8d423629e3df9a05f1c224e0924752358b9b21ef042d4b3bbc668e7503e4950becdf875ace3f51181ffa7517b5852fa16d017c983cedd55b590545c09d0324fee56055009ed0c12000b3289de4a6837116efdcf374aca2f12853a5c2daf30312000331af2fbf375aad88951bf6d44859e53b4f6c3920e0d8fcb45ea8f6d0d7032fc4a9749ab9afb7e027a5ce05662acd35a3f1fa9a35751d6799e61d65cfe5310219ffff030bb18f59d92b7859b02e0ce9a9bac8d581f87e6b18a46e433b710ddfd7096ba103ba452be15f07daf63e7f7d171f7efd5e77fc6b8f4045ba75e5c3e38b46537f4f0219ffff030292e938808c94e3948b6f6b0e408736a1d719f049d64dcf023d58cd564debfe03f0ba748b7aebf0f1591c7976759caed7d6bf4d57f257cc74a2ec9618e7879cf30327e0188fdd6d8f9061d0ac866a7690855b11c3a5d6df05e8108e40e6165e7f1303ae722dde3e1493fbd35b26114be51dd1f9984e7589de31905768a9bb09a895f6039baa815ac60f562fcfe5f339eef3c1f1c632c32d03c94d0675b53449e2bdf2880335721b7e877e87abd7253f2b9482211e0e172048ccffb3ff40d21238e2461acb03e1020466e0f757ad3c5b360603437d2538629a056454eab08ca4140c6ed505c703dde60c5987a77e1e2683837e5c3460cf0958b82c5b1cf75baf31554d7746548c031eae9d8a152cc883cec30f4ff7b137ade919cc1ece7acec24ba90b0036d2b3770301dbc256136aa7b67928440941067e609ad6d0eeac252c70e3a14ccfc099ff340219ffff0378f7594c50f6597727fa297a07be2653baf2d58f2323b7ad870561430cb76e0903b3ccfaa6686d7017ffdf1bcff96b59d6eccda77ed42d02e9ee99c3af9c61028703759df184ef6b0c62be3e64be574200e05f6e5d86560ef4fee520a29844fe318303e6f36ccd758dd9cb984856c94526f31be76d1c666b1ab31da5d26b6fe2d2600c03e17b2d1bf4032a66c9f80c4598619355b691138869d42ec77d9f2ed6353eaa8f0338ebdab00d328c0d7736345ff3ac28337ab309b649ccaadd1ab790a401da617603c39115dc072a40818d0442f844891cc0fb203eec2af848226d6957f45ec40b140309f103262bded39db0c4960929141f2fb90d549fe0329040f0b59c6643175d7e0311a73d5463c79d3d46bdf6dd66dbc3d680e992c62721d4d61fc3d0fbf1f618e50333d7146d760dd04a91598cc8710115fe6788171cad797b82d81e92803e86aa6003f00ec633c2ef13537b0e2fcfa5bfeb6fcbb8b6d3316f9dedc30af6492169c288037e8062ef64c85c3ae863235d203235c841eff285f5e0ae5de6721a6a8e968f01038e996a520943b148d34edb14815496cd1b07d5f43c3543fb0a5c9ea5cb173c2003957aa423115ecde6a45e4b26e5978731f01fa00a5ee8f2ce669158275d501939035b70a34ef300c73b28276d778d54113697dd2f73365e77c287a3b6a86b3a9e6203d4def8f38374f0c80f600157c67257161b627eeaae884ef2c60fe3ae53e6194f038c398a0c41b9218a015d54bf7bd78ec3a80d8ead82559ae3710fe4ddfaf6616f032d9b7daa67e695afdfd5ffd21e2ccbdc29b00348c28f03077e10cde7ba11cde4038f669f4fac88afc6ea1baa9f43f0e7cfea6fb742a7644ada66613e99e3825ae3038c3b70cfd2c98c24d7c030cbc35eb02f6155aa7d149470a1cf8a7dbc968c0c9c03b2b363e79c648e13a9659e5210c0726e8f998b3eb4ac4798396d7d7ff005269d039ba38f61895dbbc90d5c18134729ce05c69bfaf7968c2cfaf38fe63ffd113f2e03739b3f99dc008d2228cd053b3708a71b3f6d4fedb950e69b26b640cb4e2d651d0389cb682f051ff7a5e01dbcb05af968aab32c89800778258e97963cb07a10a3b003ef7f4849aaddd8735c0028c66bac7058db41f0a805e6b1de4096950a236526dd034062a675e7c2dcd15d4930223046d54987d407116d0083a3be73d8fd2bfff28103f1a4f09653bae53d394ba374a6605d8aec25c77e17efcca846375e9ea18b1926035881587ffb3a0e01710e2e6881fc5a34e4ea4c86533c354098fac495459cb9a603ca80b505aa62463c9182cfa3e9f0c28292112664d8fd3ea753896c79f6626d4203ef540ba75184e69b0cc004d495f23b89ee474939fee3f25b105c5b04f7a2127a036b0bd3c41610c77409e80cc45c7aabb146a5bf17499e196c111be0face196a8703c83fe45ca5c6bbfae8e8f08f2dd2764fbc69afa3add4f1982b3e1b746963a21603cc7b26e623070ec97b3e645d4a4e32258bff6b0597b13923dba3450501a1e1ba03a72571c1a7548f7e4d279cb5673acc2ce560f0e49b4474e6fc250813a33df499034a589c1ff7c954c05f7551736df580c4e5c1813679fef8b09e13bc2028e6826b037c522072578d99241a8cc402e952bd4bf4e70dcf7f7a30d8eace2f084eac40e40370b6a8b6bd75d2132947f3e05baeabe14238a4ed553307e98f48e69b8e81a82200581e023c8e0285cf1900e405e4bd465e219a261ce68dfc8bb46bae3a7ec61bb25820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00581e02a8075e9efe61170e277642bc016106cb05fbbf530bb940c05351b2d9e95820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff02030340c3eaa78234b5a8d7b6984550b7aef4dcddf380f80fd41bdd8f57c7a34bccb903f066e73a5ab76f356582aeb52015e96dda876e378db9b82a07ee3c11c92585220342599fdb9b56cfcf70c3a0af8e644d8b32935d475f77150d74900ccb8110ba760307c244a0d9cd3dcf5742468050b60a7fd532b1de2fa60f542f31efeb01d77d350361d9e735c3026a7766ebf912d3aebee68b957be15a2ca91ef96781cd527d0a320219ffbf034cbbc0f85695cfacc3e746c0d3c448e746fd410c73efd2df67252acc9815a97003335be98874939f17502404c12fa25e1491be3398c5109fae930e2ed0776b8c65039a46c6f3750758a94b903ebc0e2f1fb2aebe90cedc7239078745476cc36f4aaf031d383193ae50cc33165af736bca5744a9c0332706d912ea1002211853cffbb750219ffff0323dd4af0add6fca7acfb323e16be1c572e9377ad7772e1d40c9551914012e63803014fb94630ba0ac447c09091633422f8e4be55942f31d3903d13a01f4d09ca570348c84dfaf54ee090897c37fff0ff89b6bdd7b351c6aa1a2669496e589bec43990358f085928c05a9013004526fb9724517ea61e2d7e6c3358fb138ce3a91bbd32f03c26418b2df01e76adf9328ec71c3103b9a73283852c0ea4d6fba7f95bdaa3a42032669da92223b262b55858d142ccb8bfa9bdbe28087b85e9f4dd82d93d7babfab0219ffff0361a9d95143264d77a28682c43ab0f604d04e64bdc2a4926e34321a57591452db03b658d1304c092f2763384a77039b677778560ba456fb98b73be8819e3630efea03c2d99ad1032009f2e3bfd21dd666a2764803b3bec755dbb528a931eefc9bb3e0034b0aa3b6bfb5b79c4f4cce4fa038642a3eaab67659a5166377ce10aa17ba04eb032def192041b0b61907b0f7de46016ce6880f642cc5f9dbc0af3d37acfa9ab82603109e02f812a055e84fa9fa007addec2de6fc4f8e3cc3e6dcd816b2984943a63e0376128f9b6d4edd2045d2b8be90accd625f9717161d313fe444b4d7e0e862e54f0303af19af8483f0d78a21fdebc68a9b7dc413c66985fd0800c5a66bfaabdf4d440219ffff036b3d71c6766011c9cf957a4b4bc82037930a894f6285fe344586fc300a4b87b103f7f9b644dfeb110bfed9368de4cb4dd99b306742b6d80df44bdbd55978a5fe3d031bfbfc2466d347bd82e44d1c45a51eff69eb5c955c1e16fb99f21f95abeec3680336498192c0617aee66e529df16118c3df59cc95e153acf3ad0957cbf46e164850219ffff05581d02a65bd257638cf8cf09b8238888947cc3c0bea2aa2cc3f1c4ac7a30020f014b028dd64a11b84a15281fdf190c34021920020219702b0307134903f4a3fc60a027293a65e45512f0ec24091d5202024098c18f1344064103cf0d9d5167dc306e9c588bd9bc1f554dcc8e8e5e51bfef531e7b1dec1ff48677031c3b349a7ee937b2b4babc92d591b01f6c8296a446b23ca1e9686aedfeac1f5503c55b2981a6fe08260cb6a076c76858d56aafdf255d0a12a2c50abe35d468c7e7035fc60798e8fac6ae35e11c4236223d5067c6564cdb37c8a57c767fc5b7db8968033909e6c87be78bf54588e521ebdded1d467e8104d18958f7dd079010ee22155703e7aef0d8c9d7ac18853b8799630d1f904cbd62db280abf5396a568b84bf92b460219ffff03354b3281fa942b76ace66c6ee41878fee419cda1ae02936da5ca105b3c6004610219ffff03924da55ffa15305d6893846b17b0a47814a7938f68af06aedc437a2f242754870326c16b16c25cba7c92bcd2e9b56bb605430d8fe5481203e15b8595d1a0bee2650316fdb500d03cf52523760e0fd5cbc12639496fc0c413f48a480e717dbd2c432a034fe668ee86bcbcc4e3f135c41627919a93f7a85a85909a3931b6d33b0f813fd1037c0229cdf04ff808953e4b53012610248043fe56aa742d58b2dad85f893befd903966c29c92fac61bee4555ab1ce3a2a018667b75d3079af6dbe9e2e806f5457590219ffff031a304bc48b1174470818d4ee0b9c8fdf163ad7049b79b1131b1f1dfabca37db40391d5dcea1fe0938aa767940bffb1542b77f6a2c89ec4cd28b0e886655089064903e12c105dd84744cf8b47fe8d70c88c6fa16ca99ffe238e8bc3d50378757c81e403a20d2aa221e50d2943e024e84f53043631e69cab315beab59e94a5db0f7e0bc50371986351a43f8225822038fb53137a245ec33cc69fd31a2a6c2ff638cdffeae303f5ca806ec15a9f1f0eddb7110f5d2ae5aace1988dc6113b43d389461780ff7690350c1150d61e2fb4a2081af60cd42a736e84a1a71530857b44ad9e6b254c4bfd803fbd2bb2084c8523ebedfd52595ad17d714c086e37da462c997141b544126b06a0219ffff0394ce008a9c5fac1909a7cdaa71631c0a888c40967f80c3260f13c8c7dda8fb5a0390b8b53fb713f4dfb3d0ee239dc603ee646f79550d1c4e1fb94efb0620682e7f03ad8051f6e6f7db4bcf48f3c22dee67ba019eb3667819e42abbf6b504672df1da030874eb954854f7cd25361fb55ec16a3c3d0345156fa61d641e6462dfd336079c03ca90312bc1bb9377b4bfffb91fb0d1d50972aae675c44616dcf9cf41e8cd5f1a039a16d4fea6acf699cfea70ecae9edd1e9885a6290d1ced29e6b9da9517c2e52a03f860b29b876db755f7166ed17b4c00ed540816add8d804ea72102288660af7a003a081a77c673b3424af44d97cbb6b9556e005dcb25fb69af2acc2320b189d4b7f03ef3eded0f0bbcafe06d84ec07630b5c1d74ea16fdced67c348b735886cbb8e120219ffff031fa89d3a19b1abb13c62ca9449f9fd43a7846473bd9f5838c5b62349e59656fe0339ca649c1c851df0fdcd92162ca44da01ffaa0f3b79137b6da3a82a067353aa4033cf9d4ac267b0df8df74233e4a03d220e265191ee180e950b90ce5bcaac25e1a039c1f565b01853fb21255633fe45c170a7139ee72f0d5e302faf46e36656634e803e7aca9d7a27962fe3f37df7858196f5cef307e4891e640694d4c9f54c559078903f00d998d7d3c74b2766ce3d5fbb5c22e16bc261fadd59f454a36007c5b2b146c036611738bee850dc5bfcc62d9c6fed990d5b12b969a431d2d5996c51e24205a250348c5950b8e4b69565ff9285efdd068ebab913325a05f4c68bb59068c689fb63003a19a4e049b780eea24b79bee56da4f9e57ac73f599918af2ad1fccdd99958e4703f0766d6df8051410147c141b93e256ca938dd150e26fcc1a98bab4d125f632c703b2c94547a2133af864e24a69de9fc446d27b79fa4b99f57512f590b758ccfb5c0328ff1efe218cc67bc7065e3973888a7e0c469f936f6cdaac0c1f31043210dd2903eb3b0ff9280bfab1dab6f286f3645b1b0475683586d218681c2bc4def4dd3368031092dbee68bf6aaaa5a97206210473dc922e6c14ca8c3912ef520c18e5c8953d03106e9f7a8a4ca630b86e6a57ca1cb6a198bfd8be5d854738cd8d2a2efb7dd676035546db03d1cdc0e2257dce93b61c37e62dece3afe1a7cf3c13d9c06556ef829003e69d4d0726e34ca3a8fb159d643ccc988133f7b90339866ad92ff2d46d6361700392ef9391941ae78df861730a47686a6a81c911a19d6e5d614123570d859364c7037584c6a59f6226d885c6d47ed58cfd73d6744ed47a3e82c3764585341125ac8b0349873963179f3ecaad389de81a72297409bdf2867e24bd6194a756a9ea548a8703cddbb8161b899aa7df6ff95eb0528d95c4d9ff1672a9ee412e515570dedecb4503e435461b429da52971234d244c0bf2d2f18955e3860939181c22dc159369a59403943d07322bf0bfec6f644f7198a06496ed9ea58e8b932344d4e67a446fc15a2a03108892167fbc916d48dc178429bdf5a7f1f648fa9f2c99e1b78cc6435e765d7f031bb52f732699e351626931489c638f3040c8142005855159b07bc9c71f0630b103949852bb79a59a87142792be5efc32b6ffa284da4ae4509ca4b7d3f2c74026d103d4e39663108df2e9d5aafa5a95416792432dfc4a788d1fcd4ecff860f4cc439303019a7e106e3e52ce086a0337f5baf9937953c5b6e8ad9fb57114b3068d1ffc6703551f898d59354984f56d4db53b97e4ddf8839858166d5cd5e92eb6fa5188788103d82457438286ed6d71a70872336bf4d5e61a1573f4982da5e17e11ba58feb42c03903f2f84292f0c89a6955fa28e483fbeb449883e8ce8cd783b56fe702ab19e52035d255cb040697c20ff4372ca83681e56eb434da618c3dda0f5e1f8121cce2bd203c7ce299078965bf10577b0a24ebd4fa3036e1734a6f0604a35a99f7dd7e1a13b033ea4fbf34c5ac42fc65af993bce42b75d2b0705e75b6149d4faab46f74eeacec039c10e49992bce2a8eaf5468b60f0031f7d8085a889e3a4c6f2331982ac2dd70003184c253be5bcdaec4394755e2afff465f47a638ccecdc1a040a83f7d05a64d830301a27c97a7dff56353dd893d7b6d4549afe765e8e8dd75cc65bc875ae23885a6037184801c58056bc28c7f9358d71c2580c41c33e40d77b24e89a3943ecb4e34be033087799e0d2507cb10b7f17374c67304b949389aa29fcb8b8b8f261d37254f6c034249495c48341ef12a81fe1d542ab1976243169e351ff71eb8bdb8e8129a483703cf3efc3b248c6222a56012e92895164f41c3e62979fe504bc57d486b2bb9b8810354d717e24b731744ec12822e425079e99774ce643dc4fb50c26acf8e27e9a1af03e7e7cb5186dd32d2dd7754450f3cde21d587ece95e6d23bbac4d53de6a5c4dec03d4ddf2fe6067ba7063ed87953d7cac507e6b181d3f09de058860cbf012fcafd003a27e20ce0c79ff4ccc9660a7cd46e65b20e00c00710b511ffb36b69495d738ce0368d57539080679a7404533a9cb2494f94cfd17e7d6620f4e0c01e93ba95758fb03e21135952f7aa362911d27d0092278b2447ffe9c3f10013fe096f6448a35be47037b51ff8f822e9e5e19264111d1ed1bdf484d77fd2fa13593fc8bab078ae3f0a3036a9774ba04b7384912932efd76b7dcdccb44aa591d95625f76f85dcf08ea92f105581e03ff35d8f124bac6825dc56aa0ddb06ed0b4176305064ccfb542592922a00c034705e6a70b404555035638c95a3214e4699134737250d82b9e1103eea8ee520aecf5e446f7e785d28b03905982d3bc98b3ad75d5ca7a92446cc8923fae39a2352245000f9d3e9dc0f3ee03bbd13d463ce2111713bb0d6ad117b0ba991bc32336d501e872703c4ad6bc3c9005581e0383f749eee7b3af4ab92eae05807aa2b87c171d49569a4c42e20fa7653007011bffffffffffffffff05581e03daea18b99a136dcc10a63007b9a72f0299ddbf88e872ee246d6d2d12e0040205581e03885a281463e7ac124c775221384d850e24cd64b776b2a509fe42cbc7100c0346a40176c6360003e9fec2f19f4b290ea820001f6f8070c13c85171b2fa804cbae15891f17ef4c8203ce33220d5c7f0d09d75ceff76c05863c5e7d6e801c70dfe7d5d45d4c44e806540306b487d15c028b6df56c3ebb9b7086965eba3a240857a647faece2ff13269f2b05581e03a04ee069ae211b6e7fdf04b983cf718d84c1a5292e7f6207d7da5209f007011bffffffffffffffff05581e03e2fc072d5075007b6d716db18e76aec7aa1aee35fbc10f89dc9276a4500c014701d0567516f4a5036b61b5baf495897d0e083bafaefd44c9b30d2187584b0289d99053aef0eb642d05581e03401b1cae6bcd3c75bb7060f78b139f4a5b88cd3c75ea33d330d99a81800c014744e2e46807f36805581e03598881691c31a20b4379d8984d2f42431bb4b0669700bda6fdeca1f990040305581d021789de32c933fd61ae78d893f5c2f5269f128c722bd5dcdb040d332f040105581d0253f09dbef230b15d5cbf9d641a17d48e723bd944a9e0bd71869b1833040105581d0225b869e5d52572858bfdee6128895c52dae6cbeccee22f46ea4428a70c188e481995458b9ecf704d02190422033826722f4f10e7f5593b1ba3d251a7d917da55d61f802745b3df29df550238300219cffd0382bd4d58a6d9223a91497bc5c50a71db4098e4bf56db7b53e2c3f9bcf8911ddf03eda0c2b872a2e8004d912b7b3e3add407fb89c2e1935792b3b1747369761f494036b4389aa833fff43d1200a2b8d10cff80998608fc1cd9727a830faaa0990918c03f25a53d819c2be142584b1afd1baa8cd4bb389f88d938ba051119f47204c0b20031ed4c8beb960985fd9cd4998f0c5f09e259e213b59007591e89993d9e3e972280219ffff03714324c0346974bcbf735158e66793fbb8c7fa526a6afb0c8c29b2ff3d7c2a6c03c071cb10dedbe5705efef507f9e0ce0a8d7da622f425b401717e4d826477a51c036656773f56dacd0e751b322dce3e39a876d95b20d9fd9894ed4aba5646998921032707856409b17fde1be8ac642b20d38e9a0c86e36d92dd99531d358d7a3de1420399d94492bfca6335faa3865938f0b91a4e774e3c569ba6871cacfde5cf4121cf03b6548e9eb142a6a10b627e760cec7fccf00cdfb570e12044049bb545bf8fa26503fd12edee6837c3ad78cf34516f02bd237f8c374373364492917c95f2463c4e4b03e75ab60d63f32db41de0b8c4ae74386e4ac3339aa21f8b4b8dcf09a25277b0140305e3c069b09a5d6f764e825ec0ee8924473b9abb78336cebd31a11a60895b50e036a6075ac55e17a4c53693f1b1b6c932bdbeb58a9672f15bacdbf87ba01e221fa03133a3b4e995005bb215a3af63194f58966e256e760e7b10d6600e6e926473d4703cb484fc7d3f2443d3e613fef6a1635d61015eeb788dd55e78bdc220b1039aa87031e4ab1cefcb4df1ab843a9c99c73bd256f7075bd014f19d34923e0283e810fdc03e73b91d94b5ce180e52f902f3f44c51aa72eded520c5b8ab924f0b7be8aecaa30219ffff038433bda393dbff28847dc3b1c401d09939944bcd6af8855e167ea5d57d5f36f1032f697ce9dc8e87f850a921e7fd318f5e69ba5b8bb7f10ffd7796aefb2cf12b5303786a68e63a44deb121d719756cabf5b870bafe9b174b0040bd45c1a3c2fa80d60219ffff038252aae62902d5a39cead22caad08f18894571c955600d14273c666e808e0fbc0219ffff0397274ba63303faccd7be40da76dd8295ca7ed8b2e028875707ffe3df82812e80031c2729090731c72ce7322c825e0238cb86e438afdfd127b7147c352579dea79b039e1cfeed62ad3f8bdba5bce63d73c0586adebcd09eae52221c9c86bad6783ee30219ffff03e19272ac4d0944de700e54e84bbc6aa82c1c6eb55fbdf72cb1fb3ffb04cead0b03adc76950ec2d7900fa63770ba216225cc60dfc61e7280f230a04c80915b53120033ea1e6b0c1f051d253779f08bd5f78763eb44b94b116f08e83ddc4191f1e661703db7931db2f7c3859ffd14bb399559b830abd33dc932e82fed25a30a91729c1b103d9a057fd11ea43ec4f3a9338ba7bc78c97523322f3780ce9f3aa1f0a55a0c1f7038fc62e2a390dbdaeb9df1d6e25f70883bd1f4079f9ffe7a01145f78e9fcf125a032f2cc32eeb6c15c6cd712d83c93880c0b9d616630deaad8c365138a351a395e703d9f3e79c4ee1eb800049dbd54e8683e36567e70a9f9a1c7d37e7ecc12d44e69e038ddc99093c69494bad8659bfc20a5d10c6c38907a39e79a040676275fb7ad9ce03f86dc389bb91c0b712521c76d4bd40acce707cc771b450a2d51d483dce52ac2f030c9672009c381eee44d4c85135019ac54dfe1a5aef7f6fac6ffe67c9d92595cd0359105eda31ccc4753ab417de14c7f60f62b25a1299cc965ec701d985efe0413103bd6bd60035ffbaabb297761601c55e40c7e6a6cbc71f64e3b907a7f16c8956c5036945df6ddcb3fc549ac0578beeee21d014657778e412507b1682379ce9d3229f037e1aab16b65e2bf56b3a6930857fe2210be08cf780a660a4692eba6fa3264bb703890af9a8ccfc9ae978e47633448ad35710f298d2fffb7d2a2f62bf10098871e20337361355899b501836e7bc93db9cfaa978f0495c66323e93b03f21a2221b0cf20305c1f37a1f32769a9640f8e275cbcb9f062d79ed1d16e0e2000fa2af2bf6c98703c5f7e41ea5ff56cd109e967dc6c6e367d0569e99f30a6483fa9c3b68bab982ce03f72f695c04751effa1788640b6a2e62dc59a3217941bf52cd2fa93689340e88a03e5f779f7027a95e9a650553e67aff9d78e5ce7144b5dba7772835219f13add48036ad41ecb9a2892d127330945bb52eb0951dacee1ec0517c1c138085f841a3d6f03fb7c8abe933abd924fb075551df00ec83d20553680b17847d0b46c1bdabff85b03c0ce4c2867fd7fe0997b2ce82d50dd7333a270eeab086b40d2f2bcf91d25848203311d03a45932503e6f3e786f88174d35069f98ebc2aee4645d5e53d305f84e35033170813983d2a97e427850d58cce3aec7d9d3a39c004e88bee912e3cd61491210334c55de6bcb8667625b51e47db5701e0a4439d04df2e0db2a114556c6f4929c803b0a62636ddea40881f5b630a31070b4b993d899bda48b7c9e6c4816d14cb7b5d034bf2a99b145d78bacdf9dc26ba3cdb049c1e3bdc771a7baa9af1fdbcba39455f03122b099a228f933080c06dfee4ef7564a07699a8adfae533944199143fadb7f6031f6756ccad4047b0f851cefe1f7596e29b6b2fb338b4709e5fa672e86c5cfcdd032763176441c5fbda3110efef38e42565e752981a2933755e82e3a818f1de826d03d84338c4bca31b4f55e96dcda485c38b72c34574d9b983c6ea3a3d71d8c86cb7033c66a651794508b76ecd212fbb82702032cb83543d20a968a9e168b681c28362031957c29a5acd3c0420567b9ebbdc812a03faae5f2da6ad01eff5ddd04d375a4003677313bf4288eed8684d4bad6e1ff659254d239a8785dbea1398135f3841cce403a715c5ce192d5bd421bec8ee0c28d426dcbd7e3feed4983e0313a398ec5a3efd03820f9d9286d73e0364cd01ffbaba5c7a70670a3bf803ffbe6ea95a5e9f817e7303e0bd40096a54dbd890f9c9a4cf4bb06e0f4fa960676d5db2e7f601f8134170030343b03412e798ed51a67f96ff5327e10f7fa1f5ad6a82ba26e3b13cb20fa8cd3d0330397302a3d898044aa5f7aad8de80771600777540a00ca0942abf3b9e7e33990336a0ae182f155e5ae17dd7d82679cfd5430df902bcd1d2eb0ab62adf4de981e603527215f58f40903a68296e3ee96ae0eef922b1f065f8c4560c052cac0768933b05581e03d93aac1412f22d76bbad5dd38f89f405117e29eac41d7620b52eb834700c0245b82b46260005581e03ec2e80e2d2f87d3456c8ba30821592d6e9e90731365fa27eeb227bea70040105581e0360a790a99ad83fbc7ef78a36b399459417f9303aea86115ac63ac30850040205581e03f97dfbf09c53f062c580273f4511c81e434436f3a8992e39f5596ab3d0084703a08a1e097800045959ae6080604052600436106101dc5760003560e01c8063affed0e011610102578063e19a9dd911610095578063f08a032311610064578063f08a032314611647578063f698da2514611698578063f8dc5dd9146116c3578063ffa1ad741461173e57610231565b8063e19a9dd91461139b578063e318b52b146113ec578063e75235b81461147d578063e86637db146114a857610231565b8063cc2f8452116100d1578063cc2f8452146110e8578063d4d9bdcd146111b5578063d8d11f78146111f0578063e009cfde1461132a57610231565b8063affed0e014610d94578063b4faba0914610dbf578063b63e800d14610ea7578063c4ca3a9c1461101757610231565b80635624b25b1161017a5780636a761202116101495780636a761202146109945780637d83297414610b50578063934f3a1114610bbf578063a0e67e2b14610d2857610231565b80635624b25b146107fb5780635ae6bd37146108b9578063610b592514610908578063694e80c31461095957610231565b80632f54bf6e116101b65780632f54bf6e146104d35780633408e4701461053a578063468721a7146105655780635229073f1461067a57610231565b80630d582f131461029e57806312fb68e0146102f95780632d9ad53d1461046c57610231565b36610231573373ffffffffffffffffffffffffffffffffffffffff167f3d0ce9bfc3ed7d6862dbb28b2dea94561fe714a1b4d019aa8af39730d1ad7c3d346040518082815260200191505060405180910390a2005b34801561023d57600080fd5b5060007f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d560001b905080548061027257600080f35b36600080373360601b365260008060143601600080855af13d6000803e80610299573d6000fd5b3d6000f35b3480156102aa57600080fd5b506102f7600480360360408110156102c157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506117ce565b005b34801561030557600080fd5b5061046a6004803603608081101561031c57600080fd5b81019080803590602001909291908035906020019064010000000081111561034357600080fd5b82018360208201111561035557600080fd5b8035906020019184600183028401116401000000008311171561037757600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803590602001906401000000008111156103da57600080fd5b8201836020820111156103ec57600080fd5b8035906020019184600183028401116401000000008311171561040e57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190929190505050611bbe565b005b34801561047857600080fd5b506104bb6004803603602081101561048f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050612440565b60405180821515815260200191505060405180910390f35b3480156104df57600080fd5b50610522600480360360208110156104f657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050612512565b60405180821515815260200191505060405180910390f35b34801561054657600080fd5b5061054f6125e4565b6040518082815260200191505060405180910390f35b34801561057157600080fd5b506106626004803603608081101561058857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156105cf57600080fd5b8201836020820111156105e157600080fd5b8035906020019184600183028401116401000000008311171561060357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803560ff1690602001909291905050506125f1565b60405180821515815260200191505060405180910390f35b34801561068657600080fd5b506107776004803603608081101561069d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156106e457600080fd5b8201836020820111156106f657600080fd5b8035906020019184600183028401116401000000008311171561071857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803560ff1690602001909291905050506127d7565b60405180831515815260200180602001828103825283818151815260200191508051906020019080838360005b838110156107bf5780820151818401526020810190506107a4565b50505050905090810190601f1680156107ec5780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b34801561080757600080fd5b5061083e6004803603604081101561081e57600080fd5b81019080803590602001909291908035906020019092919050505061280d565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561087e578082015181840152602081019050610863565b50505050905090810190601f1680156108ab5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156108c557600080fd5b506108f2600480360360208110156108dc57600080fd5b8101908080359060200190929190505050612894565b6040518082815260200191505060405180910390f35b34801561091457600080fd5b506109576004803603602081101561092b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506128ac565b005b34801561096557600080fd5b506109926004803603602081101561097c57600080fd5b8101908080359060200190929190505050612c3e565b005b610b3860048036036101408110156109ab57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156109f257600080fd5b820183602082011115610a0457600080fd5b80359060200191846001830284011164010000000083111715610a2657600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610ab257600080fd5b820183602082011115610ac457600080fd5b80359060200191846001830284011164010000000083111715610ae657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050612d78565b60405180821515815260200191505060405180910390f35b348015610b5c57600080fd5b50610ba960048036036040811015610b7357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506132b5565b6040518082815260200191505060405180910390f35b348015610bcb57600080fd5b50610d2660048036036060811015610be257600080fd5b810190808035906020019092919080359060200190640100000000811115610c0957600080fd5b820183602082011115610c1b57600080fd5b80359060200191846001830284011164010000000083111715610c3d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190640100000000811115610ca057600080fd5b820183602082011115610cb257600080fd5b80359060200191846001830284011164010000000083111715610cd457600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506132da565b005b348015610d3457600080fd5b50610d3d613369565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b83811015610d80578082015181840152602081019050610d65565b505050509050019250505060405180910390f35b348015610da057600080fd5b50610da9613512565b6040518082815260200191505060405180910390f35b348015610dcb57600080fd5b50610ea560048036036040811015610de257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610e1f57600080fd5b820183602082011115610e3157600080fd5b80359060200191846001830284011164010000000083111715610e5357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050613518565b005b348015610eb357600080fd5b506110156004803603610100811015610ecb57600080fd5b8101908080359060200190640100000000811115610ee857600080fd5b820183602082011115610efa57600080fd5b80359060200191846020830284011164010000000083111715610f1c57600080fd5b909192939192939080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610f6757600080fd5b820183602082011115610f7957600080fd5b80359060200191846001830284011164010000000083111715610f9b57600080fd5b9091929391929390803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061353a565b005b34801561102357600080fd5b506110d26004803603608081101561103a57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561108157600080fd5b82018360208201111561109357600080fd5b803590602001918460018302840111640100000000831117156110b557600080fd5b9091929391929390803560ff1690602001909291905050506136f8565b6040518082815260200191505060405180910390f35b3480156110f457600080fd5b506111416004803603604081101561110b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050613820565b60405180806020018373ffffffffffffffffffffffffffffffffffffffff168152602001828103825284818151815260200191508051906020019060200280838360005b838110156111a0578082015181840152602081019050611185565b50505050905001935050505060405180910390f35b3480156111c157600080fd5b506111ee600480360360208110156111d857600080fd5b8101908080359060200190929190505050613a12565b005b3480156111fc57600080fd5b50611314600480360361014081101561121457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561125b57600080fd5b82018360208201111561126d57600080fd5b8035906020019184600183028401116401000000008311171561128f57600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050613bb1565b6040518082815260200191505060405180910390f35b34801561133657600080fd5b506113996004803603604081101561134d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613bde565b005b3480156113a757600080fd5b506113ea600480360360208110156113be57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613f6f565b005b3480156113f857600080fd5b5061147b6004803603606081101561140f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613ff3565b005b34801561148957600080fd5b50611492614665565b6040518082815260200191505060405180910390f35b3480156114b457600080fd5b506115cc60048036036101408110156114cc57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561151357600080fd5b82018360208201111561152557600080fd5b8035906020019184600183028401116401000000008311171561154757600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061466f565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561160c5780820151818401526020810190506115f1565b50505050905090810190601f1680156116395780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561165357600080fd5b506116966004803603602081101561166a57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050614817565b005b3480156116a457600080fd5b506116ad614878565b6040518082815260200191505060405180910390f35b3480156116cf57600080fd5b5061173c600480360360608110156116e657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506148f6565b005b34801561174a57600080fd5b50611753614d29565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015611793578082015181840152602081019050611778565b50505050905090810190601f1680156117c05780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6117d6614d62565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141580156118405750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b801561187857503073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b6118ea576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146119eb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60026000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508160026000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506003600081548092919060010191905055507f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea2682604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a18060045414611bba57611bb981612c3e565b5b5050565b611bd2604182614e0590919063ffffffff16565b82511015611c48576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6000808060008060005b8681101561243457611c648882614e3f565b80945081955082965050505060008460ff16141561206d578260001c9450611c96604188614e0590919063ffffffff16565b8260001c1015611d0e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8751611d2760208460001c614e6e90919063ffffffff16565b1115611d9b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60006020838a01015190508851611dd182611dc360208760001c614e6e90919063ffffffff16565b614e6e90919063ffffffff16565b1115611e45576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60606020848b010190506320c13b0b60e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168773ffffffffffffffffffffffffffffffffffffffff166320c13b0b8d846040518363ffffffff1660e01b8152600401808060200180602001838103835285818151815260200191508051906020019080838360005b83811015611ee7578082015181840152602081019050611ecc565b50505050905090810190601f168015611f145780820380516001836020036101000a031916815260200191505b50838103825284818151815260200191508051906020019080838360005b83811015611f4d578082015181840152602081019050611f32565b50505050905090810190601f168015611f7a5780820380516001836020036101000a031916815260200191505b5094505050505060206040518083038186803b158015611f9957600080fd5b505afa158015611fad573d6000803e3d6000fd5b505050506040513d6020811015611fc357600080fd5b81019080805190602001909291905050507bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614612066576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b50506122b2565b60018460ff161415612181578260001c94508473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061210a57506000600860008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008c81526020019081526020016000205414155b61217c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6122b1565b601e8460ff1611156122495760018a60405160200180807f19457468657265756d205369676e6564204d6573736167653a0a333200000000815250601c018281526020019150506040516020818303038152906040528051906020012060048603858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015612238573d6000803e3d6000fd5b5050506020604051035194506122b0565b60018a85858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa1580156122a3573d6000803e3d6000fd5b5050506020604051035194505b5b5b8573ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff161180156123795750600073ffffffffffffffffffffffffffffffffffffffff16600260008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b80156123b25750600173ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1614155b612424576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323600000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8495508080600101915050611c52565b50505050505050505050565b60008173ffffffffffffffffffffffffffffffffffffffff16600173ffffffffffffffffffffffffffffffffffffffff161415801561250b5750600073ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b9050919050565b6000600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141580156125dd5750600073ffffffffffffffffffffffffffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b9050919050565b6000804690508091505090565b6000600173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141580156126bc5750600073ffffffffffffffffffffffffffffffffffffffff16600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b61272e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b61273b858585855a614e8d565b9050801561278b573373ffffffffffffffffffffffffffffffffffffffff167f6895c13664aa4f67288b25d7a21d7aaa34916e355fb9b6fae0a139a9085becb860405160405180910390a26127cf565b3373ffffffffffffffffffffffffffffffffffffffff167facd2c8702804128fdb0db2bb49f6d127dd0181c13fd45dbfe16de0930e2bd37560405160405180910390a25b949350505050565b600060606127e7868686866125f1565b915060405160203d0181016040523d81523d6000602083013e8091505094509492505050565b606060006020830267ffffffffffffffff8111801561282b57600080fd5b506040519080825280601f01601f19166020018201604052801561285e5781602001600182028036833780820191505090505b50905060005b8381101561288957808501548060208302602085010152508080600101915050612864565b508091505092915050565b60076020528060005260406000206000915090505481565b6128b4614d62565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415801561291e5750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b612990576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614612a91576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60016000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060016000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507fecdf3a3effea5783a3c4c2140e677577666428d44ed9d474a0b3a4c9943f844081604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a150565b612c46614d62565b600354811115612cbe576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6001811015612d35576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b806004819055507f610f7ff2b304ae8903c3de74c60c6ab1f7d6226b3f52c5161905bb5ad4039c936004546040518082815260200191505060405180910390a150565b6000806000612d928e8e8e8e8e8e8e8e8e8e60055461466f565b905060056000815480929190600101919050555080805190602001209150612dbb8282866132da565b506000612dc6614ed9565b9050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614612fac578073ffffffffffffffffffffffffffffffffffffffff166375f0bb528f8f8f8f8f8f8f8f8f8f8f336040518d63ffffffff1660e01b8152600401808d73ffffffffffffffffffffffffffffffffffffffff1681526020018c8152602001806020018a6001811115612e6957fe5b81526020018981526020018881526020018781526020018673ffffffffffffffffffffffffffffffffffffffff1681526020018573ffffffffffffffffffffffffffffffffffffffff168152602001806020018473ffffffffffffffffffffffffffffffffffffffff16815260200183810383528d8d82818152602001925080828437600081840152601f19601f820116905080830192505050838103825285818151815260200191508051906020019080838360005b83811015612f3b578082015181840152602081019050612f20565b50505050905090810190601f168015612f685780820380516001836020036101000a031916815260200191505b509e505050505050505050505050505050600060405180830381600087803b158015612f9357600080fd5b505af1158015612fa7573d6000803e3d6000fd5b505050505b6101f4612fd36109c48b01603f60408d0281612fc457fe5b04614f0a90919063ffffffff16565b015a1015613049576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60005a90506130b28f8f8f8f8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050508e60008d146130a7578e6130ad565b6109c45a035b614e8d565b93506130c75a82614f2490919063ffffffff16565b905083806130d6575060008a14155b806130e2575060008814155b613154576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60008089111561316e5761316b828b8b8b8b614f44565b90505b84156131b8577f442e715f626346e8c54381002da614f62bee8d27386535b2521ec8540898556e8482604051808381526020018281526020019250505060405180910390a16131f8565b7f23428b18acfb3ea64b08dc0c1d296ea9c09702c09083ca5272e64d115b687d238482604051808381526020018281526020019250505060405180910390a15b5050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16146132a4578073ffffffffffffffffffffffffffffffffffffffff16639327136883856040518363ffffffff1660e01b815260040180838152602001821515815260200192505050600060405180830381600087803b15801561328b57600080fd5b505af115801561329f573d6000803e3d6000fd5b505050505b50509b9a5050505050505050505050565b6008602052816000526040600020602052806000526040600020600091509150505481565b6000600454905060008111613357576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b61336384848484611bbe565b50505050565b6060600060035467ffffffffffffffff8111801561338657600080fd5b506040519080825280602002602001820160405280156133b55781602001602082028036833780820191505090505b50905060008060026000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505b600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614613509578083838151811061346057fe5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050600260008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050818060010192505061341f565b82935050505090565b60055481565b600080825160208401855af4806000523d6020523d600060403e60403d016000fd5b6135858a8a80806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f820116905080830192505050505050508961514a565b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16146135c3576135c28461564a565b5b6136118787878080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050615679565b600082111561362b5761362982600060018685614f44565b505b3373ffffffffffffffffffffffffffffffffffffffff167f141df868a6331af528e38c83b7aa03edc19be66e37ae67f9285bf4f8e3c6a1a88b8b8b8b8960405180806020018581526020018473ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1681526020018281038252878782818152602001925060200280828437600081840152601f19601f820116905080830192505050965050505050505060405180910390a250505050505050505050565b6000805a905061374f878787878080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050865a614e8d565b61375857600080fd5b60005a8203905080604051602001808281526020019150506040516020818303038152906040526040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b838110156137e55780820151818401526020810190506137ca565b50505050905090810190601f1680156138125780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b606060008267ffffffffffffffff8111801561383b57600080fd5b5060405190808252806020026020018201604052801561386a5781602001602082028036833780820191505090505b509150600080600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415801561393d5750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b801561394857508482105b15613a03578084838151811061395a57fe5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905081806001019250506138d3565b80925081845250509250929050565b600073ffffffffffffffffffffffffffffffffffffffff16600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415613b14576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330333000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6001600860003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000838152602001908152602001600020819055503373ffffffffffffffffffffffffffffffffffffffff16817ff2a0eb156472d1440255b0d7c1e19cc07115d1051fe605b0dce69acfec884d9c60405160405180910390a350565b6000613bc68c8c8c8c8c8c8c8c8c8c8c61466f565b8051906020012090509b9a5050505050505050505050565b613be6614d62565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614158015613c505750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b613cc2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614613dc2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507faab4fa2b463f581b2b32cb3b7e3b704b9ce37cc209b5fb4d77e593ace405427681604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a15050565b613f77614d62565b60007f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c860001b90508181557f1151116914515bc0891ff9047a6cb32cf902546f83066499bcf8ba33d2353fa282604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a15050565b613ffb614d62565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580156140655750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b801561409d57503073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b61410f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614614210576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415801561427a5750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b6142ec576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff16600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146143ec576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf82604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a17f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea2681604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a1505050565b6000600454905090565b606060007fbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d860001b8d8d8d8d60405180838380828437808301925050509250505060405180910390208c8c8c8c8c8c8c604051602001808c81526020018b73ffffffffffffffffffffffffffffffffffffffff1681526020018a815260200189815260200188600181111561470057fe5b81526020018781526020018681526020018581526020018473ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019b505050505050505050505050604051602081830303815290604052805190602001209050601960f81b600160f81b61478c614878565b8360405160200180857effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152600101847effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191681526001018381526020018281526020019450505050506040516020818303038152906040529150509b9a5050505050505050505050565b61481f614d62565b6148288161564a565b7f5ac6c46c93c8d0e53714ba3b53db3e7c046da994313d7ed0d192028bc7c228b081604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a150565b60007f47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a7946921860001b6148a66125e4565b30604051602001808481526020018381526020018273ffffffffffffffffffffffffffffffffffffffff168152602001935050505060405160208183030381529060405280519060200120905090565b6148fe614d62565b806001600354031015614979576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141580156149e35750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b614a55576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff16600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614614b55576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600360008154809291906001900391905055507ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf82604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a18060045414614d2457614d2381612c3e565b5b505050565b6040518060400160405280600581526020017f312e332e3000000000000000000000000000000000000000000000000000000081525081565b3073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614614e03576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330333100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b565b600080831415614e185760009050614e39565b6000828402905082848281614e2957fe5b0414614e3457600080fd5b809150505b92915050565b60008060008360410260208101860151925060408101860151915060ff60418201870151169350509250925092565b600080828401905083811015614e8357600080fd5b8091505092915050565b6000600180811115614e9b57fe5b836001811115614ea757fe5b1415614ec0576000808551602087018986f49050614ed0565b600080855160208701888a87f190505b95945050505050565b6000807f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c860001b9050805491505090565b600081831015614f1a5781614f1c565b825b905092915050565b600082821115614f3357600080fd5b600082840390508091505092915050565b600080600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614614f815782614f83565b325b9050600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16141561509b57614fed3a8610614fca573a614fcc565b855b614fdf888a614e6e90919063ffffffff16565b614e0590919063ffffffff16565b91508073ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f19350505050615096576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b615140565b6150c0856150b2888a614e6e90919063ffffffff16565b614e0590919063ffffffff16565b91506150cd8482846158b4565b61513f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b5b5095945050505050565b6000600454146151c2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8151811115615239576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60018110156152b0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60006001905060005b83518110156155b65760008482815181106152d057fe5b60200260200101519050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580156153445750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b801561537c57503073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b80156153b457508073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614155b615426576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614615527576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b80600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508092505080806001019150506152b9565b506001600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550825160038190555081600481905550505050565b60007f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d560001b90508181555050565b600073ffffffffffffffffffffffffffffffffffffffff1660016000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461577b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6001806000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16146158b05761583d8260008360015a614e8d565b6158af576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b5b5050565b60008063a9059cbb8484604051602401808373ffffffffffffffffffffffffffffffffffffffff168152602001828152602001925050506040516020818303038152906040529060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050509050602060008251602084016000896127105a03f13d6000811461595b5760208114615963576000935061596e565b81935061596e565b600051158215171593505b505050939250505056fea26469706673582212203874bcf92e1722cc7bfa0cef1a0985cf0dc3485ba0663db3747ccdf1605df53464736f6c63430007060033005821028a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b410105581e03a2f4a4cbb1b67aa687f36aa61d0631958879d58861d3dea13daecfa6f00f014801edd3c37b642e1d1959ae030d8329487da35d8659e539b873e49a33cf3313fa1c19bd69d0a3e977aabaab0c05581e035547497eab9a72da4aa1aab6d3969904b40c60b1e87ea3c2e5e811fca0040405581e03c5d0f11268cd65335d623dd7e5e2bb1dcc90db4e6689bd66bbbcda3a40040205581e03854a5402ee10b25856de85f60ad0442f88de4cf0cf694b708a3c9b50b0040205581e03e3f6dc65e4447e6a86ddb7c9c96877b095b09b5634d01e3566c83a77e00401031d93f60f105899172f7255c030301c3af4564edd4a48577dbdc448aec7ddb0ac0605581e03fd36dedfa3a6ad18222ea91870b9d2e2da28a043b5c8e10dc17e88ef1007011bffffffffffffffff0219b9f303004ed5e0a028dc92526454d9335bb5dae020ec9903b913a045df183f0329206e03e7c3087443e5e9feed69d364c776586eaaef2f68f9b0a583f0db0e3960aa17ef0219ffff03c9ca73ba388efbfe5a8d71f2f93e0a1baccc3f2f67a829eeefe9bc99e6f5c8b503883f40fbb618e42c0841557a14b661c74a3177b9f3831ffe587783acfa8d421203f1f76a9ac3f97e42a5cba5eb39cee4a1d2f809168811850631107024aa80957c03736b8d5253a17f8a2f58185f7e80b3aae289393c269215d97c48e8727b2d37fe0380fc247e482c485448db036d5b04af0e509162546438d2041af95a1ed530c8370219ffff03d3c9b38e8e1da178fccf9433941eeb6698d2caa6ae9ffa97f3a81851e4a28b800219ffff033000c03b9515874d23d65ab4670c936b8e7cc1b76dbd76a1648c2c8c906beaa403b973cbce4d1450029cfa5bad310ddf63371b7faa23de013568baec4f84fc904603f2e8724e21761190048f3d05ab5a261d55ad32e287c59836c6efd5a087238f26038d0dd165d8777565080305cf0d95f48dac525a254e208323fa2fc859657fa58a0399dd8915a5a9606fc5b5b10b17465f6bba95105dbbcab2b532693da77bd290d203503e2bc44d69fc3610c48612ec44e19a17107695bb919c62a511f45b215bee320337af8d66ca200d6fa2bfdbd00b598d3c62f76c7c55d26163e646ac291d0fe7e803fa89beeba1b278da580c610a6e369898f2f0322f6168b75dcd1e2578c09fb6710358e8ac36cca0de03c25fad084e80275a640b8275f5a0c3e49d98460ec34d39e503218930fa681c2a7db0b95777e5c1c943dd013de905be7998d87ee54c336fcf0303545781fce91c2b85bfd1b4dd6955c7ffb3c3045c95ba59be04437ad5fef6154c0372ca12f11250bf8b4761d411f21f5e8b1b9b384f4abfa8e0cf2fed860af435190219ffff0353072a05b0780d4b829ccc56a191398fabc570b851bbfb96840d63be04000c97035f524e62032fc2be179cf7106ad180e485524cd429555bd8e98dc153c529843d0344d364b01627a8c9fb443cff223767a6963ed84719697b3bab46b24ba89826bc03bfaba6a73ad003970a1d58eb1929d95d74e340defea0258a6e4ed9150326c7e0030f01658194c95866bf92f246c84a3114ac0aa4ebec14e435aa2ff8535b0b006203446f80e8ba2aa0ce93a2f6f0da3c97c2454d1857283fe1d5b7655d7b209a809f03c6e483191da083a48abdaaa0f0a0cc79f2d5a63add3559e1403099140f98189503d251a53ca5846232ef41032368ee6c558ebc2f358e90ace4788a144dd6060c7703f74ddaecaebfc9b0edaa7127460246843ba49283eda7306cd3a42e9ae1e7864403a2e1c6b889ffcf3b139c4d6fc47496cddc3933e956a13e7a5656b4204d12e91e030ce3c3900e9b899efe4688a297272d8d0d0bdf01e5a9a818cc62892c8cae4f1b0321b1cafebec83ee379c6b076f95b1b2a1657425c2d4e77785058acbfeb7652540338a98dab8d6c73d797fa267472a952a356cd33cee76aa586f0b15f7bdb7952e5030817a8a400faf5d7f88199d5d4bb14e9ea3d5fe876fc91fea4caa8641d2125d10367368e6ad0e898a0637ba90c3bad01ccb3a37d176497d1fcf6884d38bdec082103c001a87779c81f5c1a134b8432898aa6dbca5b487efe8d9098055c7ca5e1d74f033d1bafae79f871d9a26e701073deea9110621fb466c23ab1d43d421bc9d2b2640337f5202fffad96baf56a80474eea08f53fe6cefb666527efdd634b95d891b39303d8ea4bbf1b494e77a6bcf8fe952ec3ad95f2a401643ebe78eaac2088867098a403980a9aa66cd5731904e546d5c4375916f0ae4caf2ea0f2b85d2139a1e04c0f3103cbe95ed246e5c2fd56df0107da9ed90b2f7b061141e3eb84fdde2afeaaa95e99039d876cc2292461db34883fde0ba0eff656ea81a93b378db41b627929e20fb6be03284157b4f49b3ba62de4a4595dc797d2f392e4d187666c67d7333df30fb5eb6003041d1da5d30d322e03832543a845465bf521a8b1c4c9c219e581679bf54480de037523135e467f58360f1556fdaa1862a0fd20db52e666956455a1805b030181c90390a2aea049ec967d0f1baacc109a5c1937f096c2440b3dd7b9e170738bbb303a03b6fa2b5629fe373928c9d497787b1b8b69e1359d3294d199325f1ee57da3830203a64132ba06e155fefe2f450925c7d9e3925ed6f0aa93b3bdce77436ff0a13dcc0317a1287711c12496c8caff4c99374a4b7787a49d35d17fb867bef479fec30c9e030d83e012b5ca06c4d5b798a6f520333ec4a4be8127331fe651c038eb3a76aeb703933428dc372dc6b212b653ccfaf3784cade82d14b55692b05ad53e3af5b52d7e035bcafcf808c831e229819f1bf7a7dd812ac032d4a31b6fcad1dab276c4b17add03c4ba858bb009b9a5bd477b6a649667b194cce14d8291583e4351d6c34dbb5d0d03b779bbc0725a3f5bc155061529b941c30165a9ee577a480ef468a1cd5509d3b703f8fe5f7e0ca57e7da21ae9b6d9cfda7433b7f970af26b520a65a36bf4d2dac9703f2e471acf814fb0e35e4f3e3f2287b7fd9197c260ae40962618240846e501660038f297cf12e827a63704cbc0c25a363de07b71ad5691e4c7b08bcbcd40a825df405581e03fd970384e443fdb8179cd5f7742ff3b358b1986aafd07f4c39b81d0fe004020346b82b3c62f8ab111b0f4398bcbf507c1c54c13848a4f3e07e7d47b2573d72580354d266adb42079a47f3b70152c3ef94aac8a425b31f434c3c14019b2ee2b309a0605581e03d3e165c04bbf11ef528cebce7f06b57b04febf249c8122804181c69e4007011bffffffffffffffff05581e0385fdef739bfa66eba33bcdae9db4c30f607f542dc68f3ec2cd4ac7cd000c06470151ce2013ec00035edf13801c825eff9e830686ab8142a516392ebedb879aca5a3f1a3bd74920c005581e03462ddfc2593c772a4ec6128c9172da3c487c88b5812c8b037fca8fafe0040103084840a116a5187ae885cf06d80df1fe9b307382e50366883861342ed5663c790326ced49a9e7cf512581ed12edc93eaa0e46106a0c30bec4e5fabe91c56dfe17405581d0250abdf1f50f9a3e2355b16d535059aa9277c1216aa631ae4f308291f040105581d02c44bafea4be0800c03271865dffb8d7cedabc6410a36bc03900a43dd0847049c9f169d8e0004592c1d608060405234801561001057600080fd5b50600436106101b95760003560e01c80636a627842116100f9578063ba9a7a5611610097578063d21220a711610071578063d21220a7146105da578063d505accf146105e2578063dd62ed3e14610640578063fff6cae91461067b576101b9565b8063ba9a7a5614610597578063bc25cf771461059f578063c45a0155146105d2576101b9565b80637ecebe00116100d35780637ecebe00146104d757806389afcb441461050a57806395d89b4114610556578063a9059cbb1461055e576101b9565b80636a6278421461046957806370a082311461049c5780637464fc3d146104cf576101b9565b806323b872dd116101665780633644e515116101405780633644e51514610416578063485cc9551461041e5780635909c0d5146104595780635a3d549314610461576101b9565b806323b872dd146103ad57806330adf81f146103f0578063313ce567146103f8576101b9565b8063095ea7b311610197578063095ea7b3146103155780630dfe16811461036257806318160ddd14610393576101b9565b8063022c0d9f146101be57806306fdde03146102595780630902f1ac146102d6575b600080fd5b610257600480360360808110156101d457600080fd5b81359160208101359173ffffffffffffffffffffffffffffffffffffffff604083013516919081019060808101606082013564010000000081111561021857600080fd5b82018360208201111561022a57600080fd5b8035906020019184600183028401116401000000008311171561024c57600080fd5b509092509050610683565b005b610261610d57565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561029b578181015183820152602001610283565b50505050905090810190601f1680156102c85780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6102de610d90565b604080516dffffffffffffffffffffffffffff948516815292909316602083015263ffffffff168183015290519081900360600190f35b61034e6004803603604081101561032b57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610de5565b604080519115158252519081900360200190f35b61036a610dfc565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b61039b610e18565b60408051918252519081900360200190f35b61034e600480360360608110156103c357600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060400135610e1e565b61039b610efd565b610400610f21565b6040805160ff9092168252519081900360200190f35b61039b610f26565b6102576004803603604081101561043457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020013516610f2c565b61039b611005565b61039b61100b565b61039b6004803603602081101561047f57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16611011565b61039b600480360360208110156104b257600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113cb565b61039b6113dd565b61039b600480360360208110156104ed57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113e3565b61053d6004803603602081101561052057600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113f5565b6040805192835260208301919091528051918290030190f35b610261611892565b61034e6004803603604081101561057457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81351690602001356118cb565b61039b6118d8565b610257600480360360208110156105b557600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166118de565b61036a611ad4565b61036a611af0565b610257600480360360e08110156105f857600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060408101359060608101359060ff6080820135169060a08101359060c00135611b0c565b61039b6004803603604081101561065657600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020013516611dd8565b610257611df5565b600c546001146106f457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c55841515806107075750600084115b61075c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180612b2f6025913960400191505060405180910390fd5b600080610767610d90565b5091509150816dffffffffffffffffffffffffffff168710801561079a5750806dffffffffffffffffffffffffffff1686105b6107ef576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526021815260200180612b786021913960400191505060405180910390fd5b600654600754600091829173ffffffffffffffffffffffffffffffffffffffff91821691908116908916821480159061085457508073ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff1614155b6108bf57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f556e697377617056323a20494e56414c49445f544f0000000000000000000000604482015290519081900360640190fd5b8a156108d0576108d0828a8d611fdb565b89156108e1576108e1818a8c611fdb565b86156109c3578873ffffffffffffffffffffffffffffffffffffffff166310d1e85c338d8d8c8c6040518663ffffffff1660e01b8152600401808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b1580156109aa57600080fd5b505af11580156109be573d6000803e3d6000fd5b505050505b604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff8416916370a08231916024808301926020929190829003018186803b158015610a2f57600080fd5b505afa158015610a43573d6000803e3d6000fd5b505050506040513d6020811015610a5957600080fd5b5051604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905191955073ffffffffffffffffffffffffffffffffffffffff8316916370a0823191602480820192602092909190829003018186803b158015610acb57600080fd5b505afa158015610adf573d6000803e3d6000fd5b505050506040513d6020811015610af557600080fd5b5051925060009150506dffffffffffffffffffffffffffff85168a90038311610b1f576000610b35565b89856dffffffffffffffffffffffffffff160383035b9050600089856dffffffffffffffffffffffffffff16038311610b59576000610b6f565b89856dffffffffffffffffffffffffffff160383035b90506000821180610b805750600081115b610bd5576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526024815260200180612b546024913960400191505060405180910390fd5b6000610c09610beb84600363ffffffff6121e816565b610bfd876103e863ffffffff6121e816565b9063ffffffff61226e16565b90506000610c21610beb84600363ffffffff6121e816565b9050610c59620f4240610c4d6dffffffffffffffffffffffffffff8b8116908b1663ffffffff6121e816565b9063ffffffff6121e816565b610c69838363ffffffff6121e816565b1015610cd657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f556e697377617056323a204b0000000000000000000000000000000000000000604482015290519081900360640190fd5b5050610ce4848488886122e0565b60408051838152602081018390528082018d9052606081018c9052905173ffffffffffffffffffffffffffffffffffffffff8b169133917fd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d8229181900360800190a350506001600c55505050505050505050565b6040518060400160405280600a81526020017f556e69737761702056320000000000000000000000000000000000000000000081525081565b6008546dffffffffffffffffffffffffffff808216926e0100000000000000000000000000008304909116917c0100000000000000000000000000000000000000000000000000000000900463ffffffff1690565b6000610df233848461259c565b5060015b92915050565b60065473ffffffffffffffffffffffffffffffffffffffff1681565b60005481565b73ffffffffffffffffffffffffffffffffffffffff831660009081526002602090815260408083203384529091528120547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14610ee85773ffffffffffffffffffffffffffffffffffffffff84166000908152600260209081526040808320338452909152902054610eb6908363ffffffff61226e16565b73ffffffffffffffffffffffffffffffffffffffff851660009081526002602090815260408083203384529091529020555b610ef384848461260b565b5060019392505050565b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b601281565b60035481565b60055473ffffffffffffffffffffffffffffffffffffffff163314610fb257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f556e697377617056323a20464f5242494444454e000000000000000000000000604482015290519081900360640190fd5b6006805473ffffffffffffffffffffffffffffffffffffffff9384167fffffffffffffffffffffffff00000000000000000000000000000000000000009182161790915560078054929093169116179055565b60095481565b600a5481565b6000600c5460011461108457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c81905580611094610d90565b50600654604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905193955091935060009273ffffffffffffffffffffffffffffffffffffffff909116916370a08231916024808301926020929190829003018186803b15801561110e57600080fd5b505afa158015611122573d6000803e3d6000fd5b505050506040513d602081101561113857600080fd5b5051600754604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905192935060009273ffffffffffffffffffffffffffffffffffffffff909216916370a0823191602480820192602092909190829003018186803b1580156111b157600080fd5b505afa1580156111c5573d6000803e3d6000fd5b505050506040513d60208110156111db57600080fd5b505190506000611201836dffffffffffffffffffffffffffff871663ffffffff61226e16565b90506000611225836dffffffffffffffffffffffffffff871663ffffffff61226e16565b9050600061123387876126ec565b600054909150806112705761125c6103e8610bfd611257878763ffffffff6121e816565b612878565b985061126b60006103e86128ca565b6112cd565b6112ca6dffffffffffffffffffffffffffff8916611294868463ffffffff6121e816565b8161129b57fe5b046dffffffffffffffffffffffffffff89166112bd868563ffffffff6121e816565b816112c457fe5b0461297a565b98505b60008911611326576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180612bc16028913960400191505060405180910390fd5b6113308a8a6128ca565b61133c86868a8a6122e0565b811561137e5760085461137a906dffffffffffffffffffffffffffff808216916e01000000000000000000000000000090041663ffffffff6121e816565b600b555b6040805185815260208101859052815133927f4c209b5fc8ad50758f13e2e1088ba56a560dff690a1c6fef26394f4c03821c4f928290030190a250506001600c5550949695505050505050565b60016020526000908152604090205481565b600b5481565b60046020526000908152604090205481565b600080600c5460011461146957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c81905580611479610d90565b50600654600754604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905194965092945073ffffffffffffffffffffffffffffffffffffffff9182169391169160009184916370a08231916024808301926020929190829003018186803b1580156114fb57600080fd5b505afa15801561150f573d6000803e3d6000fd5b505050506040513d602081101561152557600080fd5b5051604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905191925060009173ffffffffffffffffffffffffffffffffffffffff8516916370a08231916024808301926020929190829003018186803b15801561159957600080fd5b505afa1580156115ad573d6000803e3d6000fd5b505050506040513d60208110156115c357600080fd5b5051306000908152600160205260408120549192506115e288886126ec565b600054909150806115f9848763ffffffff6121e816565b8161160057fe5b049a5080611614848663ffffffff6121e816565b8161161b57fe5b04995060008b11801561162e575060008a115b611683576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180612b996028913960400191505060405180910390fd5b61168d3084612992565b611698878d8d611fdb565b6116a3868d8c611fdb565b604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff8916916370a08231916024808301926020929190829003018186803b15801561170f57600080fd5b505afa158015611723573d6000803e3d6000fd5b505050506040513d602081101561173957600080fd5b5051604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905191965073ffffffffffffffffffffffffffffffffffffffff8816916370a0823191602480820192602092909190829003018186803b1580156117ab57600080fd5b505afa1580156117bf573d6000803e3d6000fd5b505050506040513d60208110156117d557600080fd5b505193506117e585858b8b6122e0565b811561182757600854611823906dffffffffffffffffffffffffffff808216916e01000000000000000000000000000090041663ffffffff6121e816565b600b555b604080518c8152602081018c9052815173ffffffffffffffffffffffffffffffffffffffff8f169233927fdccd412f0b1252819cb1fd330b93224ca42612892bb3f4f789976e6d81936496929081900390910190a35050505050505050506001600c81905550915091565b6040518060400160405280600681526020017f554e492d5632000000000000000000000000000000000000000000000000000081525081565b6000610df233848461260b565b6103e881565b600c5460011461194f57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c55600654600754600854604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff9485169490931692611a2b9285928792611a26926dffffffffffffffffffffffffffff169185916370a0823191602480820192602092909190829003018186803b1580156119ee57600080fd5b505afa158015611a02573d6000803e3d6000fd5b505050506040513d6020811015611a1857600080fd5b50519063ffffffff61226e16565b611fdb565b600854604080517f70a082310000000000000000000000000000000000000000000000000000000081523060048201529051611aca9284928792611a26926e01000000000000000000000000000090046dffffffffffffffffffffffffffff169173ffffffffffffffffffffffffffffffffffffffff8616916370a0823191602480820192602092909190829003018186803b1580156119ee57600080fd5b50506001600c5550565b60055473ffffffffffffffffffffffffffffffffffffffff1681565b60075473ffffffffffffffffffffffffffffffffffffffff1681565b42841015611b7b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f556e697377617056323a20455850495245440000000000000000000000000000604482015290519081900360640190fd5b60035473ffffffffffffffffffffffffffffffffffffffff80891660008181526004602090815260408083208054600180820190925582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98186015280840196909652958d166060860152608085018c905260a085019590955260c08085018b90528151808603909101815260e0850182528051908301207f19010000000000000000000000000000000000000000000000000000000000006101008601526101028501969096526101228085019690965280518085039096018652610142840180825286519683019690962095839052610162840180825286905260ff89166101828501526101a284018890526101c28401879052519193926101e2808201937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081019281900390910190855afa158015611cdc573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff811615801590611d5757508873ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b611dc257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f556e697377617056323a20494e56414c49445f5349474e415455524500000000604482015290519081900360640190fd5b611dcd89898961259c565b505050505050505050565b600260209081526000928352604080842090915290825290205481565b600c54600114611e6657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c55600654604080517f70a082310000000000000000000000000000000000000000000000000000000081523060048201529051611fd49273ffffffffffffffffffffffffffffffffffffffff16916370a08231916024808301926020929190829003018186803b158015611edd57600080fd5b505afa158015611ef1573d6000803e3d6000fd5b505050506040513d6020811015611f0757600080fd5b5051600754604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff909216916370a0823191602480820192602092909190829003018186803b158015611f7a57600080fd5b505afa158015611f8e573d6000803e3d6000fd5b505050506040513d6020811015611fa457600080fd5b50516008546dffffffffffffffffffffffffffff808216916e0100000000000000000000000000009004166122e0565b6001600c55565b604080518082018252601981527f7472616e7366657228616464726573732c75696e743235362900000000000000602091820152815173ffffffffffffffffffffffffffffffffffffffff85811660248301526044808301869052845180840390910181526064909201845291810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb000000000000000000000000000000000000000000000000000000001781529251815160009460609489169392918291908083835b602083106120e157805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016120a4565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114612143576040519150601f19603f3d011682016040523d82523d6000602084013e612148565b606091505b5091509150818015612176575080511580612176575080806020019051602081101561217357600080fd5b50515b6121e157604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f556e697377617056323a205452414e534645525f4641494c4544000000000000604482015290519081900360640190fd5b5050505050565b60008115806122035750508082028282828161220057fe5b04145b610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f64732d6d6174682d6d756c2d6f766572666c6f77000000000000000000000000604482015290519081900360640190fd5b80820382811115610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f64732d6d6174682d7375622d756e646572666c6f770000000000000000000000604482015290519081900360640190fd5b6dffffffffffffffffffffffffffff841180159061230c57506dffffffffffffffffffffffffffff8311155b61237757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f556e697377617056323a204f564552464c4f5700000000000000000000000000604482015290519081900360640190fd5b60085463ffffffff428116917c0100000000000000000000000000000000000000000000000000000000900481168203908116158015906123c757506dffffffffffffffffffffffffffff841615155b80156123e257506dffffffffffffffffffffffffffff831615155b15612492578063ffffffff16612425856123fb86612a57565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff169063ffffffff612a7b16565b600980547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff929092169290920201905563ffffffff8116612465846123fb87612a57565b600a80547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff92909216929092020190555b600880547fffffffffffffffffffffffffffffffffffff0000000000000000000000000000166dffffffffffffffffffffffffffff888116919091177fffffffff0000000000000000000000000000ffffffffffffffffffffffffffff166e0100000000000000000000000000008883168102919091177bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167c010000000000000000000000000000000000000000000000000000000063ffffffff871602179283905560408051848416815291909304909116602082015281517f1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1929181900390910190a1505050505050565b73ffffffffffffffffffffffffffffffffffffffff808416600081815260026020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b73ffffffffffffffffffffffffffffffffffffffff8316600090815260016020526040902054612641908263ffffffff61226e16565b73ffffffffffffffffffffffffffffffffffffffff8085166000908152600160205260408082209390935590841681522054612683908263ffffffff612abc16565b73ffffffffffffffffffffffffffffffffffffffff80841660008181526001602090815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600080600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663017e7e586040518163ffffffff1660e01b815260040160206040518083038186803b15801561275757600080fd5b505afa15801561276b573d6000803e3d6000fd5b505050506040513d602081101561278157600080fd5b5051600b5473ffffffffffffffffffffffffffffffffffffffff821615801594509192509061286457801561285f5760006127d86112576dffffffffffffffffffffffffffff88811690881663ffffffff6121e816565b905060006127e583612878565b90508082111561285c576000612813612804848463ffffffff61226e16565b6000549063ffffffff6121e816565b905060006128388361282c86600563ffffffff6121e816565b9063ffffffff612abc16565b9050600081838161284557fe5b04905080156128585761285887826128ca565b5050505b50505b612870565b8015612870576000600b555b505092915050565b600060038211156128bb575080600160028204015b818110156128b5578091506002818285816128a457fe5b0401816128ad57fe5b04905061288d565b506128c5565b81156128c5575060015b919050565b6000546128dd908263ffffffff612abc16565b600090815573ffffffffffffffffffffffffffffffffffffffff8316815260016020526040902054612915908263ffffffff612abc16565b73ffffffffffffffffffffffffffffffffffffffff831660008181526001602090815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b6000818310612989578161298b565b825b9392505050565b73ffffffffffffffffffffffffffffffffffffffff82166000908152600160205260409020546129c8908263ffffffff61226e16565b73ffffffffffffffffffffffffffffffffffffffff831660009081526001602052604081209190915554612a02908263ffffffff61226e16565b600090815560408051838152905173ffffffffffffffffffffffffffffffffffffffff8516917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef919081900360200190a35050565b6dffffffffffffffffffffffffffff166e0100000000000000000000000000000290565b60006dffffffffffffffffffffffffffff82167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff841681612ab457fe5b049392505050565b80820182811015610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f64732d6d6174682d6164642d6f766572666c6f77000000000000000000000000604482015290519081900360640190fdfe556e697377617056323a20494e53554646494349454e545f4f55545055545f414d4f554e54556e697377617056323a20494e53554646494349454e545f494e5055545f414d4f554e54556e697377617056323a20494e53554646494349454e545f4c4951554944495459556e697377617056323a20494e53554646494349454e545f4c49515549444954595f4255524e4544556e697377617056323a20494e53554646494349454e545f4c49515549444954595f4d494e544544a265627a7a723158207dca18479e58487606bf70c79e44d8dee62353c9ee6d01f9a9d70885b8765f2264736f6c63430005100032032e2bc0c0ff22609eac8f10e1c8736f3e780dcb85055451e7ac674e2667ce4b570313a4031999f138fd5a2cd6ffb7ab63e1107b95a0389545c40aadc91bddd993fa00582103e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af051cb2aee8a60ca86f8bda208376042d19788005820026cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c68854c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200582002b661198733f53bb9b621ec808effd2e8a3d86db6962103738e13951e49aab0470aa8ddb6865c490219014000582002575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b582082b7b380037e2868c106004fab2c2e17609e3a9fa13840bbf296721c0dd3c0a5005820025a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a84f24bf0960cb159873685c3ddaa1a61002184400582103f6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c704101005821031f25289b5c9db29d46c3566463f71796d2e07c9a7a96a888214082f19288cd0048041f4dbd95f5b05600582002f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee358206638103b0000000000006ce4eb2514608b4f000000000000002abfbcea99faf90058200252222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f548b683c400457ef31f3c27c90acb6ab69304d1b770218480219f44505581d021ec3c811e880dc80a981a5503c30597b49fa4ab1144a8a7f423bb3010701192c1d02181c021917570384a59ac3f31976525b65ab661b72b02bf1fcd35c019ba2d4839d69d9d1f4a5a003555d3a1deea1603addefab3179dc6c0d4a9474c55a7db59a22c15f2d96be5a46039d76bb6f100ce6c89af8bf229d75e5dfef89b17dcdaf6562beae642253966b910359cdaf928aa0094243eecdd88a705298bb96fccf40245caf2827654f28102a0903e2e349c733b734f0bd6cbb14b2476a91752f85e5984909e9a9d0c3e9f346a20a03913179e69808e46ea766db4e7697baea9473ce78179d47862c65b94d8274999c03cd306511fa5054b7d3923ad15479e1c96a92f08ef36e77479e23f347f3c4d9420219ffff030960f818f714050360f6673cfc01ebfe16978b10dee5d8d1250ffa87fbc057520317faada0b2554524cbf0f158ab366854523cd3abc1048475668c620653ab83e7035e31d3f580b3388a11a9e925ee6c701a15e53c0759ff9bbbb440790a795ee291033285dac1afedb332e73b2038c86cb75d99b286cff8a15bdc0d1c428f745f1bd7036b0f57e92ea0ded986ff55bfee8906ea457f937b4d01075701114ae582653f6f0219ffff03d1012b39ca70a0069aa165e552fde8f16eade9315bf29740ec1924edda2f5d9203e714ee21efa38f19d5dd377eaca0a9bfd6e57d8403953573a3091efa67c75f4203624f37476107a5562c070d8aab9cc9e673a7c691233ee4f6209caf819feba8460219ffff0381b8e81238e4a04505742054c12b577bb7526abacd7ae5d0a452ca9e68fe00c703692a51d70221dd95c4fad9be33cd6726af0c72ace00ed5e72eb4d6af1792fe4d0366e2f37e2c5a8ec2a5064e6113d9007f112346aad335e1ba7d3160b6285d2f2c030d8a638e05c837ac5d5c91008da969fc6c102803b16fc16288b5935c7942e05303f369a7114e0e139054bb39a7da90c73af10dc967e135300b1a51ab3834f1513f037642861636360312dd872552a2f5305f921f721c78767285a87cb34dc209b26a030c48efdb98e768c8f29ad854ed8198f64ca85095f56c66188d74be5de2475ee5034db5d7a65715f046a5206f0ee013c9d4385d79f4793b68f2b31342e0f12ede6c03d6dd6ddb2d15eae9c7757764327bd92738cbfe3930c95e0926fb8b911c5edda60219ffff032d970fa441a8ceccf173b3f3583416fdf2449f0d0979b81b303ff052f05d13e60304c508eea3158972c739232daebec044ee3e6c74de4486d304fb5705f116a350039ac7b92c52f57bec422e4bd5d04cd7305dc6f5c199d06b767f7dde98260cce2003086f5ea5b03703666c4e964c11977539ee7f769dc1679e38023a236c5cea1b7c03ab9015181cf9bd4d07ea66fd55eb5fd4e66496ac9a3bf79de4b9696d29f5d71c03ff59793356b794601134356afcae4ce0eb405722c5904f5a9e97034357cd91210368194bed561141a5c1b8e690ccf34ba45ee2986ce9b7f482fab6d276a694604803e4240f626d977518478184087ae0611dfceef30d260e0b5f1549f451b0e15237038a1295b965bcb0217dc2723e69c4218f7a2a87f3aae34950fd4a272a18f95bd403f42e0c256715d1ee3a4a6918149678b09d3d3e0774f35e58b67aef0392cf880703ca95f90fbba770647f79ed35ab45489a669f692df35a5089fe8709b805f119e6032dd21f3d2a7dab53b67c1ebc0500b0839f845e97d084f7e876ead8d1c6ea1843034c0a0f68a78a39af4f96714dff89d0b7988c498313636e977901d5a999d8c84303bf068389eaf23c697e1408deb9a4ea01bd287305616a4ddbb752c1a7c637eb780369621e88944f74018f58e7814d9b6d6aa3e14e89d8790fc31c5d276c6eeb6a4a032cee9909e0c373e751b9a67e6d2c426423c2d60071ef563cb8f257124bbf8b9a03941a99a652b0da2b1442664bb924322bbd09ab4d7aafeda0cd655a25e105317403667c5365f5cb236369b4f0c5acc855da9a7ba5fad565d6bfd4b2cd7cc66cfa13030143e65770620f06dbdd4af525a0b1a64e18b4e0863566e3fc0d068bde81a11603a1062e64b6c0c1a3b115614015a0f389958eaca757a36e62e2beaf4c6074ff3e03542abb1e1fa4a79109a0d73cc3745d546501c2afea0bc907a4393a48e95f147803ba6f19341599e591e41fd0519c876484deb506094134082f0864e9c90f90cc30036ed1d7b297d52b277036171f5e79bf443c613523903d473841a7eb5bc8b1946c03ca6a57dd23e807bbbca35af47076d23a451df1debffcef0a12643b6bed8517a5038720cd405944bbed2ed9c99ac85e5a5aff3588574588e22c83a113ef88966cbf03d33019e4c7af24bce42b4f99ae30ecea23cdb837401598cdcbf363f439108e7c03bb1b72dcc996e76105a95e902b28fffa306563baa068c9a3c118c6a33eb7a03503dbe37297cf24f2f4dedf11a33e12269f0d9fa67b2b0c44b7336340f179a49b3f032433a5c47dbf5e5e31ea74c894bfe9d3cb035453a90fde1f439c7858a04c34d80372f6536a41790269753f6922540475872ab7f3bc4d672e6430221e5c5ec43493035a07e39e3ed1473b0daea069bb9a1d2adbb908d2abf49394cadf5f51bb2d762d031390d695ddf998b7577fa12fb1d6de9afc3906932ed19f47aeb3dce0df6a5d1803f83dfd71604cab3958ddb484ee3e6b8b6e52044f99d535bd84ee2321f547e8f203b49a439c70815a034488f438b88d072612c57dab54f930ca493a2c1bda93e2a903f4ae4c0c57037d3fb1a66deda8859bd075cd1b1b8a27c4db7cac5557effe70a903298138ff149e07bdf9c25f93380a723ddd55a9357ae5a973a9009232597b2a61039bc368cbf373721b248693e3151951a8782d175a9e3b7c1e1e6a4904a2302e590365fa6211e1b492da2c98f1220085e282ed72b579fa743580e6de8b70e84da626038686337b2bd55e8c5033a70fa68ec75122364e509041a1bb9bad8264b378073403fe2e601f7831716635ff577b42df3ee52e2784dc27cdf8f9d6b07a9a4e3d302403ca67e5ad9a0f7657c1cabd8a375b397a50b0f7af217e626ac0d51f44b6b1524b03e23c58c95b44b8fd12895181146a8f586b3c37ca55cd42b7c02697189044bd610380661da0f1a245ce5de68c63e8a1f0c239e8287c63234f96f3c23c70f3ddbe29037d577bac02759343ae457a5340d1f712c33b2e76ab6b7619d44dfee6f8f107cf0386f51603cb09df5a7bb925d88c0350d4d24d1c28598fd380ffe430a602f1151a035b69615a1a4a13d2a355ad8ebef639873b5dd93438f31af82cce511cda941f5403eefff2d897cb203c541aead358162b0d5a6c057941e0348da56dc83e933dd927031f50c6594f3089698e950ec35c3668c10574313715358cecafef3f17c75c7fd8035bb356ea469270bc941971fa5f3f13eebd7182c4d07b1e5936b749524c6a033903f0146a70d2698de30df9bfc84add4bf7824c3f8fc2c13bda287f68de33cb88bc035f5a1117d6968dc818545bf3caa0ec44f7d8f11c5d5bfd1e7303ed597056f19603f5c38af6f709db9cee967585e1c3475c1b2c419a7f025c8551ca0211a787993d036d18433acfe02224d8dbca55b504bac5c832f9d266ed9a338d58ebe2d0226eb8035c5b31895362a7e4b5f826c1f8c285b6ec5f307d83db0fc29849db070028a1660397dc7269c9bc8321eb76c84812b6cfd4bfc227934e113e65ba20262d933bd8c003ffb8203d5d65af0e0d0c11e1c9f688eb67ac46c262291bb5e3f499f21b319fc1031286c01bb09b3cc54808700c2d7ac300eff42defcf47f011d5c3b79e1dc0f8d0038603b9f2e5ca0befcd270cedbb962e2dfdfa5f6566a2bbcfd93fc7529f91782c03e0dc6be0773960cabdc724cec9b09fa9b9e9aff9d76df0455b2bf1d795ca29a3031d93f60f105899172f7255c030301c3af4564edd4a48577dbdc448aec7ddb0ac0605581e0302c2f650d1dabe4467d5038dd0e789abe371f9e6fada398839360fa2a007011bffffffffffffffff03767c4368cc25f0f8d30ce48e9ae71faad89a9509f0088b9a32bb32c64256cea903525e052104f94a17543f0e46570e8465a38e406fbcff6ae7447e93638dabb95f05581d02c55d89f1df9abd2eee3f6e0a8efe53f929d36bcd3a7e134f702da6fb0c03473f6f1420567ef605581d02ddb0dde0d879c2e4144fda652d366909aa742fd9ade130583bcdeaff0c0246622ab6fe13c805581d02c070fc7ed870826db358006f3c0eca572f8aac6c268ac0130805f5f20c19028b4807cb94878c0c4a3305581d02d899c0d4f975f439752f5bada431134b4990a236327a0ef46bbe37d40c0146c2f8959c742b02190d0103b2dff530257219ef8af877b1991b5006303f8ada6c2ab655e88057f2e24e6628038f59510413678d21bce67a691343a22918b9f49fab93e726d395ab19b0ff1b3205581e033fce37f1900820391700fe9d561e194a0505d7e9a2fae48a6ba017656007011bffffffffffffffff03663cad5d90e1c7c8e244b9dfa750ecdd937f6177dc94022a3f84f6cc5e01f36f05581e03c3ce4aa708f9b69e2d04abe8521021eac25dc3e71247b3f6d7a29fb2e00c01470128c3b52bf00003c21a9b4dfbf17303d1461773454a92d7a65aeeec89477b40917c544fdb7a30b305581e0379411149f46fa7df406872f14682ec1b9378577d92d9bb00b1b91df2000c014707132bc2cc801402194bd603c56988604cfe964abf785e74135cd1e1d2a2d1736ca608ccf42d8a5a663e865f03b98c164d99541c19a09eaaaa42ad4c255402961ca54727e73c95dd3f2e3c3b5e03538c5adc584217b6fdd5edd20b5471ced13546b8f7cae0a93a42ea1d741914a503377723c880b302d7a7b353447a4b8282d3a150a7fefe001dc91ff826c38e8da5031f8ff9d61c0b130ed2ae177f6d9f81906f1ae7328441c79e31bc75d2f5f30b7c039d350daceb38a0d75a940136635164832bcba6e631c015753a8a6cc0c64986c50219ffff03ffefe22c9391fe6b210ff1ea3839ff0c3573cfa695b0f2241309a6324df6e1bb0219ffff03a52ee312c27564ec5de33a92ebd25e0ac0806d9c71b2e3af0eab6283fed4d8540219ffff03169629eb79b5dee32abe97e9bdd17d7a361e12b9d47607fa1864699b4d27bc0e0219ffff0323c2256eea45e2659ade6c954eafd18a8cb38a7b6e072279869b756a70fddc6a0219ffff0354de8a7f20ea7c86963380665fcd6c91c606701f7a9717353b03820924a15884037df6def0c81558aafd4b09a5e5abf7e5cb71520ff85f4d18f733cb6440c7af54034d89ac532bed43cdd30c2e6c649bad88af20c8e5363a8378b3324795c138aa880334a29dc51fc0439b0b59dde57221b62b184cc7d81a96dc8ee72ebb30df8d89eb031f70232a64efcadb6156873cb4b48115f3f5f4cb3b0eb45ed496d84bffffa42c03e4ea3f0745592596ced9342a16166b57665bd078e426928161ebe09c17cb6e16039df94c5c71d55874cebd7d87f9fedaf39adc3ce09ca439cec77fc3cda6a45bba03c0be3be853f2ae0372aaa43bb28fbc1ddbfbbaf0a7c0e071aea06fbd6402c51f036a9b4a91548333ca179a6bcc96da6acb6a00c830f9fc54f586cb21894a55da5103c098c29465c3b8d549528d72289259706b25edd6299bf6197c6564ed2355d3e0034fa3d78fa38327dfafd5bfa12105833b15a37fe67d9c8c7612436501ea271deb035d78d22bf3533dc43fba810f32b15e1316cb8b976a31b8c1ed07c1d988962fca0324e2a710f3c13e95cc1882b0a32e55c24c5b8e7eb3cbcdba36c6dd5a46679ba303b5ece83bd6b18170337794d39a16c4f17c7de60be65e471a515028141375b9e5037b3edac7e40d418adfb726d4d7c70380d67508a40dddcb901bc68474cd39433e03dce5b7a67506788298d89b90cf942cc0aff8c26bbfed29d07b543b1be91489790368b696f4437d36244d6f07105571bc3b221977f692aa5a0b3f97191aa920c45b034d1d16c1286d9147f95d48ab693053d47ce3e095ec4af1dad3af3f9d41778e7f0322d04b8d0ca8026323d6377d1743044cd92db9461643171658ca57eb0862f8b603055020a8edabbd75812e9667884af1dbda2de45854efc6d5b988a4404adf5530037e12accbaa7f8379e0674319fb56524c5fde3506685bac6bc9b8985937b011db03a74cadab84310d49684cb8914aa35c1464e42c8081c8051f74d7291de33aaa1a039df5ba3a8dc7835e93b6cb8fe577e439e669eef9d5b3cbbddb904142f9e614f2035fe0158b1c329ab9ea1cf5387213d2a6e258aba5dec083f844216161218dcb560360291c31d3bfe2e5f5588724f50fd9af6f7e45b08bfc7a8d2acdbdaf94f20aa403d3cb20fad8435b09f179989e1bf8f9b91553423417ffca62ded425f76bd552a50361c8aaf37e1f51a3471653b082265f6d14c87b3e5a1f7cc2e869f91f078e39aa03ea4c7bb4bda02e6e154e1dc2151c06398bfc2f615f37bdedcdd6da8e9c45238c038f7186ffcd90c646d1b6805daf33d261e7322b894de7ddd5a9d09c922dee3eb503fe52f136e68e4c7581e83ad5a1998a9c148f93e89280b5ba593b04d166b6f8f703aaf1fe5995efcf21f25ac79c4647ec2e2395830221770b8553ccdd245e79646c039acdbbb39aa484a6e033d5050283fdb64848377a02afa9fd1997aab86f816c7f03672ec410bd8b35bf6c8d7263879ea2561687d0eb6ace8aed4c40b4dc6076745e05581e0316d4bd7dacd97d0c571e0b1d90bc117dad36182b78878703b2205a43a00c0344ee6b280005581e034b2d2decf95050be5986747389e5b75a9a8667f14af23b7ac1f2f78df0040104591b126080604052600436106101395760003560e01c8063751039fc116100ab578063a9059cbb1161006f578063a9059cbb1461034a578063bf474bed1461036a578063c9567bf914610380578063d34628cc14610395578063dd62ed3e146103b5578063ec1f3f63146103fb57600080fd5b8063751039fc146102e15780637d1db4a5146102f65780638da5cb5b1461030c5780638f9a55c01461033457806395d89b411461014557600080fd5b8063313ce567116100fd578063313ce5671461020a57806331c2d847146102265780633bbac5791461024857806351bc3c851461028157806370a0823114610296578063715018a6146102cc57600080fd5b806306fdde0314610145578063095ea7b3146101815780630faee56f146101b157806318160ddd146101d557806323b872dd146101ea57600080fd5b3661014057005b600080fd5b34801561015157600080fd5b506040805180820182526004815263434c415960e01b6020820152905161017891906115da565b60405180910390f35b34801561018d57600080fd5b506101a161019c366004611651565b61041b565b6040519015158152602001610178565b3480156101bd57600080fd5b506101c760115481565b604051908152602001610178565b3480156101e157600080fd5b506101c7610432565b3480156101f657600080fd5b506101a161020536600461167d565b610453565b34801561021657600080fd5b5060405160098152602001610178565b34801561023257600080fd5b506102466102413660046116d4565b6104bc565b005b34801561025457600080fd5b506101a1610263366004611799565b6001600160a01b031660009081526004602052604090205460ff1690565b34801561028d57600080fd5b50610246610551565b3480156102a257600080fd5b506101c76102b1366004611799565b6001600160a01b031660009081526001602052604090205490565b3480156102d857600080fd5b506102466105a0565b3480156102ed57600080fd5b50610246610614565b34801561030257600080fd5b506101c7600e5481565b34801561031857600080fd5b506000546040516001600160a01b039091168152602001610178565b34801561034057600080fd5b506101c7600f5481565b34801561035657600080fd5b506101a1610365366004611651565b6106c6565b34801561037657600080fd5b506101c760105481565b34801561038c57600080fd5b506102466106d3565b3480156103a157600080fd5b506102466103b03660046116d4565b610a8f565b3480156103c157600080fd5b506101c76103d03660046117b6565b6001600160a01b03918216600090815260026020908152604080832093909416825291909152205490565b34801561040757600080fd5b506102466104163660046117ef565b610b17565b6000610428338484610b5e565b5060015b92915050565b60006104406009600a611902565b61044e90633b9aca00611911565b905090565b6000610460848484610c82565b6104b284336104ad85604051806060016040528060288152602001611ab5602891396001600160a01b038a166000908152600260209081526040808320338452909152902054919061123d565b610b5e565b5060019392505050565b6000546001600160a01b031633146104ef5760405162461bcd60e51b81526004016104e690611928565b60405180910390fd5b60005b815181101561054d576000600460008484815181106105135761051361195d565b6020908102919091018101516001600160a01b03168252810191909152604001600020805460ff19169115159190911790556001016104f2565b5050565b6005546001600160a01b0316336001600160a01b03161461057157600080fd5b3060009081526001602052604090205480156105905761059081611277565b47801561054d5761054d816113f1565b6000546001600160a01b031633146105ca5760405162461bcd60e51b81526004016104e690611928565b600080546040516001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600080546001600160a01b0319169055565b6000546001600160a01b0316331461063e5760405162461bcd60e51b81526004016104e690611928565b61064a6009600a611902565b61065890633b9aca00611911565b600e556106676009600a611902565b61067590633b9aca00611911565b600f557f947f344d56e1e8c70dc492fb94c4ddddd490c016aab685f5e7e47b2e85cb44cf6106a56009600a611902565b6106b390633b9aca00611911565b60405190815260200160405180910390a1565b6000610428338484610c82565b6000546001600160a01b031633146106fd5760405162461bcd60e51b81526004016104e690611928565b601354600160a01b900460ff16156107575760405162461bcd60e51b815260206004820152601760248201527f74726164696e6720697320616c7265616479206f70656e00000000000000000060448201526064016104e6565b601280546001600160a01b031916737a250d5630b4cf539739df2c5dacb4c659f2488d9081179091556107a09030906107926009600a611902565b6104ad90633b9aca00611911565b601260009054906101000a90046001600160a01b03166001600160a01b031663c45a01556040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107f3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108179190611973565b6001600160a01b031663c9c6539630601260009054906101000a90046001600160a01b03166001600160a01b031663ad5c46486040518163ffffffff1660e01b8152600401602060405180830381865afa158015610879573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061089d9190611973565b6040516001600160e01b031960e085901b1681526001600160a01b039283166004820152911660248201526044016020604051808303816000875af11580156108ea573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061090e9190611973565b601380546001600160a01b039283166001600160a01b03199091161790556012541663f305d7194730610956816001600160a01b031660009081526001602052604090205490565b60008061096b6000546001600160a01b031690565b60405160e088901b6001600160e01b03191681526001600160a01b03958616600482015260248101949094526044840192909252606483015290911660848201524260a482015260c40160606040518083038185885af11580156109d3573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906109f89190611990565b505060135460125460405163095ea7b360e01b81526001600160a01b03918216600482015260001960248201529116915063095ea7b3906044016020604051808303816000875af1158015610a51573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a7591906119be565b506013805462ff00ff60a01b19166201000160a01b179055565b6000546001600160a01b03163314610ab95760405162461bcd60e51b81526004016104e690611928565b60005b815181101561054d57600160046000848481518110610add57610add61195d565b6020908102919091018101516001600160a01b03168252810191909152604001600020805460ff1916911515919091179055600101610abc565b6005546001600160a01b0316336001600160a01b031614610b3757600080fd5b6008548111158015610b4b57506009548111155b610b5457600080fd5b6008819055600955565b6001600160a01b038316610bc05760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b60648201526084016104e6565b6001600160a01b038216610c215760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b60648201526084016104e6565b6001600160a01b0383811660008181526002602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b6001600160a01b038316610ce65760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b60648201526084016104e6565b6001600160a01b038216610d485760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b60648201526084016104e6565b60008111610daa5760405162461bcd60e51b815260206004820152602960248201527f5472616e7366657220616d6f756e74206d7573742062652067726561746572206044820152687468616e207a65726f60b81b60648201526084016104e6565b600080546001600160a01b03858116911614801590610dd757506000546001600160a01b03848116911614155b156110fa576001600160a01b03841660009081526004602052604090205460ff16158015610e1e57506001600160a01b03831660009081526004602052604090205460ff16155b610e2757600080fd5b610e536064610e4d600a54600d5411610e4257600654610e46565b6008545b859061142b565b906114b4565b6013549091506001600160a01b038581169116148015610e8157506012546001600160a01b03848116911614155b8015610ea657506001600160a01b03831660009081526003602052604090205460ff16155b15610f8e57600e54821115610efd5760405162461bcd60e51b815260206004820152601960248201527f4578636565647320746865205f6d61785478416d6f756e742e0000000000000060448201526064016104e6565b600f5482610f20856001600160a01b031660009081526001602052604090205490565b610f2a91906119e0565b1115610f785760405162461bcd60e51b815260206004820152601a60248201527f4578636565647320746865206d617857616c6c657453697a652e00000000000060448201526064016104e6565b600d8054906000610f88836119f3565b91905055505b6013546001600160a01b038481169116148015610fb457506001600160a01b0384163014155b15610fe157610fde6064610e4d600b54600d5411610fd457600754610e46565b600954859061142b565b90505b30600090815260016020526040902054601354600160a81b900460ff1615801561101857506013546001600160a01b038581169116145b801561102d5750601354600160b01b900460ff165b801561103a575060105481115b80156110495750600c54600d54115b156110f85760155443111561105e5760006014555b6003601454106110b05760405162461bcd60e51b815260206004820152601760248201527f4f6e6c7920332073656c6c732070657220626c6f636b2100000000000000000060448201526064016104e6565b6110cd6110c8846110c3846011546114f6565b6114f6565b611277565b4780156110dd576110dd476113f1565b601480549060006110ed836119f3565b909155505043601555505b505b8015611174573060009081526001602052604090205461111a908261150b565b30600081815260016020526040908190209290925590516001600160a01b038616907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9061116b9085815260200190565b60405180910390a35b6001600160a01b038416600090815260016020526040902054611197908361156a565b6001600160a01b0385166000908152600160205260409020556111dc6111bd838361156a565b6001600160a01b0385166000908152600160205260409020549061150b565b6001600160a01b0380851660008181526001602052604090209290925585167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef611226858561156a565b60405190815260200160405180910390a350505050565b600081848411156112615760405162461bcd60e51b81526004016104e691906115da565b50600061126e8486611a0c565b95945050505050565b6013805460ff60a81b1916600160a81b17905560408051600280825260608201835260009260208301908036833701905050905030816000815181106112bf576112bf61195d565b6001600160a01b03928316602091820292909201810191909152601254604080516315ab88c960e31b81529051919093169263ad5c46489260048083019391928290030181865afa158015611318573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061133c9190611973565b8160018151811061134f5761134f61195d565b6001600160a01b0392831660209182029290920101526012546113759130911684610b5e565b60125460405163791ac94760e01b81526001600160a01b039091169063791ac947906113ae908590600090869030904290600401611a1f565b600060405180830381600087803b1580156113c857600080fd5b505af11580156113dc573d6000803e3d6000fd5b50506013805460ff60a81b1916905550505050565b6005546040516001600160a01b039091169082156108fc029083906000818181858888f1935050505015801561054d573d6000803e3d6000fd5b60008260000361143d5750600061042c565b60006114498385611911565b9050826114568583611a92565b146114ad5760405162461bcd60e51b815260206004820152602160248201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6044820152607760f81b60648201526084016104e6565b9392505050565b60006114ad83836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f0000000000008152506115ac565b600081831161150557826114ad565b50919050565b60008061151883856119e0565b9050838110156114ad5760405162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f77000000000060448201526064016104e6565b60006114ad83836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f77000081525061123d565b600081836115cd5760405162461bcd60e51b81526004016104e691906115da565b50600061126e8486611a92565b60006020808352835180602085015260005b81811015611608578581018301518582016040015282016115ec565b506000604082860101526040601f19601f8301168501019250505092915050565b6001600160a01b038116811461163e57600080fd5b50565b803561164c81611629565b919050565b6000806040838503121561166457600080fd5b823561166f81611629565b946020939093013593505050565b60008060006060848603121561169257600080fd5b833561169d81611629565b925060208401356116ad81611629565b929592945050506040919091013590565b634e487b7160e01b600052604160045260246000fd5b600060208083850312156116e757600080fd5b823567ffffffffffffffff808211156116ff57600080fd5b818501915085601f83011261171357600080fd5b813581811115611725576117256116be565b8060051b604051601f19603f8301168101818110858211171561174a5761174a6116be565b60405291825284820192508381018501918883111561176857600080fd5b938501935b8285101561178d5761177e85611641565b8452938501939285019261176d565b98975050505050505050565b6000602082840312156117ab57600080fd5b81356114ad81611629565b600080604083850312156117c957600080fd5b82356117d481611629565b915060208301356117e481611629565b809150509250929050565b60006020828403121561180157600080fd5b5035919050565b634e487b7160e01b600052601160045260246000fd5b600181815b8085111561185957816000190482111561183f5761183f611808565b8085161561184c57918102915b93841c9390800290611823565b509250929050565b6000826118705750600161042c565b8161187d5750600061042c565b8160018114611893576002811461189d576118b9565b600191505061042c565b60ff8411156118ae576118ae611808565b50506001821b61042c565b5060208310610133831016604e8410600b84101617156118dc575081810a61042c565b6118e6838361181e565b80600019048211156118fa576118fa611808565b029392505050565b60006114ad60ff841683611861565b808202811582820484141761042c5761042c611808565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b634e487b7160e01b600052603260045260246000fd5b60006020828403121561198557600080fd5b81516114ad81611629565b6000806000606084860312156119a557600080fd5b8351925060208401519150604084015190509250925092565b6000602082840312156119d057600080fd5b815180151581146114ad57600080fd5b8082018082111561042c5761042c611808565b600060018201611a0557611a05611808565b5060010190565b8181038181111561042c5761042c611808565b600060a08201878352602087602085015260a0604085015281875180845260c08601915060208901935060005b81811015611a715784516001600160a01b031683529383019391830191600101611a4c565b50506001600160a01b03969096166060850152505050608001529392505050565b600082611aaf57634e487b7160e01b600052601260045260246000fd5b50049056fe45524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e6365a2646970667358221220e85ef9e74da2b18270d1c91ede66b645fd4e18cce2d8c288dfd1bf29e20218df64736f6c6343000817003303d314e2201f63550827bf0cdf911a026e9b0dd35a2f6b042d7983f41d500c286e03afcf42739076459a23904cc6d7653ab8d5c2424c291827d6b2280df99edb949000582002193bfb8a8ac5c70cc4f5bcc93cf05c91f1701d33c7d0cd17fef09b345e0d8f41010376f05ef8fb93243667c474862883c05cf5ae53590f729fd42a29d5ac4c9c013a0369d9b379fbef4e01b3d2080f7c8e8e9f0d26a944db370902843e4359cc3a836b00582003998a60e1e675ae554eb9d43998288aa2d3bbeb74f093b78f5eaac3064f9080471c78a7679b214b00582003c941205aa8032ad63acc7460009b742213fc090c66e575c2e100ad87e19c10470154425fd7470c02188100582002e49eacbde352733e786c0a671dce3b8aadf72e9b68eff921c5041512f0f2175820fffffffffffffffffffffffffffffffffffffffffffffffffffc75045d9bcb5200582002301be158f2cae65e19a9c9e70bb5988a4d312bc95accc8748e3d5e19f0e2ca47046d61b5bb113b0058200222a9ef97d3a9b49658506a3f3bba83fe56420f692bbe4e9298b3e90e20f8d9471907a34f179dfc039716cd352f4b797457d921d9646a23e6f98723b6a53ab0491417703b25efe0bc00582002cabce7b540c95fae520e2fdf48d5a70e5b17c9e0a2ae80044ad2098884e46c5820ffffffffffffffffffffffffffffffffffffffffffffffffffea493b68062e7a03a6572a351d70ebce1425933eb911c7f3bbe62c85db8c4cc09ad943869d263fc003dde05dd943c769a0a17496b2a6697ef3fd5ad69728908d3633ab808e59e283eb00582002ba475fc33bd0f952f33744f1973eeaeec63485e8fff4760187bd9642d9c34e47013d5071b06d9d0219dcef03fc802151fd0071fc346b4fc82b419a635a5c2067d369158ad12f25869500cb23034d837f51f749756d6e8008b65f2fe70c56a7ca7b15393580ac7961b9c93e49ba03353097e71a897b3976ba6e0a1956dd21be53c87815ae6b01173a8e431e39f01c0058200254025d82f000bf5034e08b44c18adb5b631e0e795a8fbc89aeddfc2a018e154101005820021d32db26b0ef514d3aeb96bf78933b46e0a5c534d9559f1e2b24c0383ebaf65820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff032cfb0c2f14542e04ed1a5c4778f78ea17a4416159610b401d8a85c23bac0e97f00582002de8ffda797e3de9c05e8fc57b3bf0ec28a930d40b0d285d93c06501cf6a09057010001d5564d5338168dd3bafc9fcb37d264b451670b8d03058855726a4141e6411ce7d771bd04e0ace97d3c7647a04d0c006d03494cd89a036f1fece6b0a15375a7e4b85f2ad5cd8ccadcc1141f1f9f13a269c787c128b35700582002ca73392a540fe1950d294f2f14a31a1e582a928ae1c6c2dbee4372e6e07892472471ab765335a0037322c4e94923041abe6a2e423d3db076e18392f4ef7ccfc79f76c2d168963c18005820027e606b0852c084f64d8b0d39bc9b68eedc89af1ad47d6115c7baef0750ff3541010058200277db92afcba08cd86adc11425f684e852a0b67fcb6d20825ff37fe798f5d0a47090401e39b918f03f8ffcbb869a654bf93baa89618989e39f259a3b135f44b588652d91b7d894317005820022fccf1163519cc49dd925bf8a76124553ef501cb6be00ca0dd677e20c779f05820ffffffffffffffffffffffffffffffffffffffffffffffffffd6141fe2972c6a0219bfdc030fa652634736d283d246d68c56f24b97fa738f79b82cd455b5c63213fd26455b038fd9a303976d34f8958b1958dfcb7ac5bd0219758551c012cd70a5a972ee5adf00582002f67f7468ab441155363145017ccd7b7999c43b3e8757c251578bf9c9ff6f6b470913773c9750bf0058200250e6b12f97fc6f4ff69d3a9e7aedbfb25fc20d360b3006e6d4213f191fd9324704d874bff04c5a005820028a0e933ee673218feec8be9bfa6ef0e751b366df0de6ea33741babbe95310b5820fffffffffffffffffffffffffffffffffffffffffffffffffffac5d16182eae800582002fa68efb8be33d6fdba6658539618d6b7e037e7898c965052dc3736dd7bbfdd475fc7028b6a9f4203dfb06b42c95f2aac2d8057b7f94a853286db6e20f955f33d9cd1c77ed94a800d0354a7e74443ad5d8764652d19e16b1b1541f110b994870ccb80c227acd784a93b03d5e708fa215fc2d978de65263c654ddbd50fad68a3883d0af087c3405e8cc6bc00582003c74b49237f99c988c0a91e6bdc306d4df93c40f95fa24f8692b0d4edfc99a04805e26e848833dcec0058200374a746b5555b132b48865fb523a014af5828acb326222b172522c43da7cb605820ffffffffffffffffffffffffffffffffffffffffffffffffffb9020661a76b7e0219010100582003108e10bcb7c27dddfc02ed9d693a074039d026cf4ea4240b40f7d581ac8020480de0b6b3a76400000058200313c6e386e363fcf34ac713342b74445f482b86f536e9bb54b9df49b4da4860480dcfa6a6d6779d380058200352eda49633b7bce573f8e90ced5f624fb81c40bcb27970f4eb7cd9f39a19e05820ffffffffffffffffffffffffffffffffffffffffffffffffffb8fd16d69acd1f021930020357d2c137377b10b0d4cab7372ecbf084651da96132f2211bc47d3bfb622f1cbf03afcd9cb0d6eade7fee2aa56ff6937a378a4a29653f9a3ebb55bb78ccee38d1d20219f7e303407c135badaf2a22f8aaf9da359334f1876e09a0caae932686f3dee20a8ed7870305bff418893a396e4c4e7d98c317929a64317a407c780ff32c3964a3c3bfde250058200201d9688706b54d15cda98d5f7769b04166d65fd6637f266423c243e5a4818247024d22b0b21d2303eeec0bbc5981199bcbe9b6320816ac2233c649fb1a7218c571bdaffede88afa3037057e064638c7593e897c10519c5b49e31ef616fd55c519f43ee8bdf23ca3c5503c37d7c7e90d53bbb36dc81351dbf2d493d81673e9e6d94bbfb438c537bf0040200582002b5b113c3362f857a3c03606be4fbe8e1193eca42a2681838ab148addcbf5634704642f856dc1a30058200265d587a2570a145f6b3b698c79f7d0c1225355c4d56576b6e8c541a535fc775820ffffffffffffffffffffffffffffffffffffffffffffffffffb7e0350c71b49a03aca9e2ce90f6174492048ce752dafdc9d9431d9a9069cc48c05795d4ad1dace2005820029a991a3a433167b9a4cc2b7a64ba4eeed205093740df6c08b43f6ab3a37d1c5820ffffffffffffffffffffffffffffffffffffffffffffffffffebec23d1d6d16600582002715a8e73fb23ee049880a077ae86f5d350b9b77384810dff7142edc9ee664d5820ffffffffffffffffffffffffffffffffffffffffffffffffffdb40fb8f6a96d8005820030691a4b1ef5899ea41e271d5970d99438d5e69260cad78161f967c9fdfb9d05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00582003b4a454dc3493923482f07822329ed19e8244eff582cc204f8554c3620c3fd0480de0b6b3a764000000582003a6a4669ba250d26cd7a459eca9d215f8307e33aebe50379bc5a3617ec34440547a250d5630b4cf539739df2c5dacb4c659f2488d005820037ea67b79476bf871e3fadc49ce522e02a9180dccb7199785715b36ff7504d05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff021921820366ff9444fbaae00373bb481689ce569f714472f08cfba260d8ba033fb67eb7c6038575ef0604f6d6d642f3dda4494e1c80ed094c6bc3d337f760155213de33f0cb0058200201c417d962731257c2644f4fc72bcfc33a95576a151d750f9d26cee5888ea24702c7cdea3f0eaa0219bdef032603306245306e9e95f05c1033ec9fa7ebf5767d73f2fa4fe92a18b0dd244c2700582002165314e5e0eaf3eade136d72cee1f88c700abbe8ce41770bacc9424a54279e5820fffffffffffffffffffffffffffffffffffffffffffffffffff0a221a73715ec005820029d69f86befc55447495ed58448d9d143d70143055d7707e69936fca3a23f7a5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03cc86b07ad687fed0ec099fe283d0d09b9e29a170aa758f4ba6a4de5f0431a158033647c0c8e0a5133b8536e373afb3916ba9d65573446372b5f8d423103f11590b005820031ed3604650b2aeb65f74a72d2bab8f72eeedaac2dde3d554b57f413b8aab4046f8d583657ce70058200338c91d8aa6139f872500e67b257671589abdb59c61fe5fbab728b5ce3175c05820ffffffffffffffffffffffffffffffffffffffffffffffffffd58cc69491ab9600582003a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a8041010218380058200247d87da98715c3ed63046f61010fac43848bf543ced1b76aedf6a50f153b104101005820029de73f4f2a3affd41d2bb6334b4d3c02395f33513b8efb72090ec154a1039b5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff033ceef5d3cffe9831c51792d491e49ac08d3ed093512fbb40ad84df3d866a687c005820025ae2f1a686fc65f52da42bebf0636ae02902bfc01961f1d3e96aaa0dbf49b4470f59523482f14003b4517ad4d74db7e0b23d3a96fc542c7ed07c3ccb9fee240ce443c6f16a93886403e4fdafce77df788d97a16f6ef3c027471cfd7b87634a7157643adcdbfd38260500582002c8d3390450841b4be9c88c87dc9d698b506dadc7cbc90e1c7dcfd7e68412854701fe28a794f2a30219cffe00582002ebdb5d939e01b3922bdf616066d4e78e2328f90424854703bf578f373923484702fb1756aba9cb00582002a568872657d6cf6893b28fb0d9cb1f0cd04d4e0c0421174bf80d5fad48efc35820ffffffffffffffffffffffffffffffffffffffffffffffffffb9004babb3441f00582003003493ab7c00b0199c2ffe97712c2f27fb54cf5f816cc5bb760c2c25b563d0470a1118be62253a00582003d4e2c2659582808fa60c2a272b72920b10f624278260105e694dccbd15e300470ff5a428aea1b30219048000582002279ea4bbc9705e85002e16ea4a8e827319f75361985d73e4a175db464347db4c019d971e4fe0719f9d50860000582002b6990105719101dabeb77144f2a3385c8033acd3af97e9423a695e81ad1eb5420149005820021b32cf224aeb097412df8878e15269f3fd4d5aacdffa807e1084dc55f20e244710987fc355852303528fa3492e17dcab658ca7f04281b1e6759a035c203ceaa1d3665b1c2bc5b3d10058200223f9c54df335fc1b52971f0ab4f37fab3a8d5ed8f19e42ff31a617dbefec774706323e51ffbcb4005820026a41b4d5f30ba17f05b6ca0ba06c8f0f95758b480fcab4a711553983321e445820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005820022d27f3379c5bd21f1c5f287e52837c6f6a1e26502db4c38ae2db24aa1ca1bf5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff005820026966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7411a0219cfd3035004a72ba9add33ea65f3bddebd25df291c3ae0c41c9c861e8508b53f3088c4e00582002f29f424eff63b3eb8f64d802c127231e8b44129e3ea8c6f81dd190308576095820ffffffffffffffffffffffffffffffffffffffffffffffffffb184bd9a2c0a9f03121157274bb924e0068597410342ae7ea4edfc7a403018fb2eb2dda003e7643500582002692bbf9f3ee64ad30db6d40f362ff5e9d57370e47547af7040c997239e970c4101005820022dd49cfdc29b0a18041ea928a8c3326123166d51e202a1e5f38a4e788f39cf4701616b3ceff5210058200252222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f4118038d8c73d2bafd80782707cca1103323b3c8d357aa907024a4045cd371ccfa9338005820027b074fc0c7123ca3a0b740fed51125915e19d38c7b882352df2b9eea2c84bf5820ffffffffffffffffffffffffffffffffffffffffffffffffffb8fed8b4fb88a20361eb56a435e8c9abda1648b3a9a9bdaa3b56446729587b5d7f5da19a4a30a79b03b0130aa8da0e8c27af0863d015325a06e5b949295b8c7e121b0f263cc6aae3c00058200286f2b0ee929f4a217491efb5d8707c8cbf903f7d74cd367f77cdbbf6e11c444709ed0b8412e5860322d4c3db87aafeeb2edf3b63d93dec03e623754d2bd2f0df4b2e3e098ba341930312f2ecf66debdb22f78971e2308e00cd2dfccc33c465cb56fac5a7623fe1fbbc0219f7750219ffff05581e03e449b31bde037df0439b78ae02331dd2d40844839e49ef3201484542600701191b12030ba73b7f804a139e6574caec9727cf9c24aea22f7309b0fd35278d704f700dae03c7072fd42d5a6e75d519982f75051f3de632a97deac11f73cbcee54735b5b4cd01410705581e03f10fb68883dfcd964c2d4904a3aca8cbd7ba798320724f02b14ebcb5300c03462ac2b0346208021945e1034ff32c6608391be33fbe55f53c7025b71a582eec94be56ed92a439777d1816d703022bcf0a8e4a2644a6c8a56dce2ad12aacebf3260a53e04c806cbbf5912fc9630356651de917baf1e9ed0325aa485eeb2accbf5fbe7b2650b63f6155532481ff2d03f9103821b601934d482df73e1ae15b570be704e251c702a31ca7ba640fdcaf620305da9e2bfdc7f60bab1cc9470ff36c106a78bdc140a4f081ea2dfc81c90b759403111bbf32bdf2fa4dea0bbf15b2a47894616f4e4e6076ffeac3f7ef9cbab2ff0703955bb65eec57695070904627d9e21d2abb2a1e23531aee64bc2f950791398b590388213da29c4d9b44b1eaed041cca0f9392ed5bcbad8305dcfd0ee2e9f9f593b30219ffff038ec9c904d4270952fa3cb84ce079ecb69cd6d32eebcbcd00ffd190437c57cac90367c777c0d5893adbd3709a1ee34fa0df39aa49929616c9a494d6eb3faba571e203054b25c0dc6be18d19f07eb3f15c279f547a82a10552ad842064ef504361237e03d1da2bad9d8c3e4ae4f50161ef023019262726dd47a84efaf214727d3b6e9a04031fadf9d7c68a470e27a8a9464546ab43eafaa0ff3fe1dc5176c18c4f45e8213f031cbe6362e431f50e1794e2e8e5120543b33d8af786f16d7681f8ff1232cd4cf603708b1b8d171d1209a86da26a396647c9b1d93fec6783009d6ec6960fc1c2a89a030c677b49d4bdce9fe3882aa84b122dbeaef90e5e5b448b6bda5bd35527422a9d036d8241ba8f7f33c9b13c50ae19cd8b47d9278ec48bcf5ef6e4da2261bf2b93e403eab50ec55558e4897800059b38ed13848d9b166257a416da181edadf964f30230321d5b7bb9826ec9ca0d5ddc27c11421a6653b86f2e17253a04601b71331bc2410219ffff03598e6ee1c1125c6b7086fa65fa0f2e713aba867cf3b49ed451389b7e7504845903cdc25193c1aa98d6a00442dcaedbf7f7e766492e4b796f10279ba8a9147869ea0378fcb2afe7a66623f60a3e2c04ea1ed336238af0d3151e1abb94eb4e0f12388b0344ec398832716f3619ac107db0d1e297e6aede4465690a14afeb9ad66537074203edc9f8c6f16ade6b1f6bc2f08879519d59a8c5a90103ad71f856e3ab8347e1090219ffff03d7bd9ece9d45931bcf48331bf731471b6da87ab8a644fa374ce25608fc1e66ff03f772655437195900a4626ba546a7c83604e5bbaebb6f945a8ed24f7ae4476fa2034be0e71357280655400a1b513f4427954bd21f1bd287864e42eb71cd73df62a403db2c577219d228099a0630f21426d768cb7b10d309352e1f7246f152c2b8494803c66572f58383e885ee4b09235ec9587fde7dcd9168d1c3fb713f985fd4d1f748031b08d31d3f43d7d90c45e756a76f14a87f4cb75ff9ae997f91c674f9370b3517035a6ae47fd4c60efcb73c9c718ddd40c1e059870b37649c4ff65531c26c5e46800219ffff030514c9d7736cc160b7826b9ecc4272646b7b98b17e523e0950e21bd3386201d303c274a3c5c9a8ae97b7e7092b064609ddf60a40380c1d032753594e5a5ef2113a03936859c9f4de1e3adc201278a419dade170329afbe4f27d06c85d9143291670f0370510b33e2a85ad15b33ea84e5cbb7beeba921250088f5cf63257914af6129be037e2ec6026974ae7abb85fa76bf01da1305f4c42af012cb375d0906ddefe7034a03ee9aae715b49059635c1b6d59bdd0e19dcce999daf5cb3fbba24cd8c02e216120336c33965bc07409169c1da6eaa004c3243b0b5ade544b4c3cbcf35e390c5906e03bf8732a2dcaf3cb9eed5ce6de518588b722df7b63118ebacedc3acddcca9063203425014f9d3c06ca9b97fabc3e2c7fe195aa67a5abff1de230e2e49187d300a4503ac482db6a1a0e944067823679dea17e21a6b22f2c83ba2abcc26817f2e58c19f0399964d6cf151a56a43a23be84477dc72e8cac768165213f64ec65b995b92a5520308f5320b30c5e4a464d2ea439006f11cdc914e79e8cb0130367767fe31548963039f1b1fe7fa9a3fb5d7d47c800d757ea8b5ad83dbd8ee6ec4da9ac4ccdd8273a703cf84205986bd6f32a4155c35772efe433a1295e1a1446700886e608a5e2c461f030972d1609599b552167c339aa48d6ae5fda15fa5b832ea203ef723d8ad5eb61803451cf700482504e6a8d53661c6309b794d91ffbc1ee53ef04c13b47c4774cf73037e0c07ee1a9de8c5f6d963f8e696af2ddc0c10c247d53e669d4554500fd697b303a6ec326903990f6f7f4159dcd9446894bd3409961025ad530dd5ceac3d52e77a037815a1e906ad3433f8fc81741559d38ffcf91a53d077131276de3b08cba6957f03de9fdf0ed3d91173b4a4e63067566df8b9ffd958dafa64024b4524685e0c5a3e01410b05581e03a5fd656052d58ce697be9dca634eb8be7b38495f2dfb649b95a0bd9410040103562d59a51820d47f520c975e0b2bcffac644a509749a3161f481f57b6e826d210605581e03fb554f06fd3826225ba6152e356ec5bdc21317e4e2ab56e0129ea3d2a007011bffffffffffffffff05581e030185e5ff306ce55884850cd1229e1109cf4815ab48d41c6f583f606bd00c0247033a1ad2d68c5505581e038fafa06790ce29fc48d6d13e1bafe01c072357f372ff1b0dc42804ec500847028ed6103d000005581e030da26dc5b0f7683761a9c2fc03025fef3266f2e899469e214f920c94700c054778727d357361ba05581e03dded7c5b51db1d0c984295bb3ef0767297cbc222a6ba08698986221cf00c186a482337c8dff9c1f60405581e039cf1f3b7c89150dd373d4c6f39cedc666906c3fbc0215ca197b52324100c07470342dcf39088d00373146722a7504a33c86a51be4472996561f73934780d2e94c53b9ad8e1e4d50c038d94b2518d85571711e3760dd6a5ba75d870e044842f88c306fa10e90e8cbc1b0219cf9503d93c1dd782977bdde2f87077c0919b7212175bf22f89d1056e8226de58a32e3503d2d264f49ab5d9bb4eaf0b391cb99563168de9b9576169befcbc9b94c502303103ca71d5363fa100af579ed893894c0ca1dc89248ab5c0255cec482f31d0cac1830301720b28e70f64e0d152bf2702b9bd7710467651d8d203fd35d8b676e2b2bc5e03c1e188c9f6902b2ce916a96774a70e93ae5b06b6d3884171a040b9388bd4ec6d039640a812ba7bb0337f983da10bd7876bbf581086fffb609ccc23b3aee12efa4b0340183b97529cbe1c6d0f9425ffec2a19c4e4cd79a4e19adfae955e252feb3476035d631e9f478533b27356ccb27fb922396196d39f1c3c3cd3ba640eb2e65d7e76039728be4941f7771ccc66ae2112a581c6ea46ca4bec04b47d027ce7e5e1158ec30219ffff03c1db94be6e76446f21dc2a83ed376601f6f88bc6df7c4c75858dfe1a992efaa60308d5413e3a30ac721149a84a97585a713e41d9412650124efdd2719528b86d01036f2b80aa479c5ca9802ca90bf3a3bcb47e53b83f6b24dbb3963aea9ce28620b8034107b09d030b4de5850d369329692abf9e46af3eb87257e355effa031d3ecf3103f747cd819b67fe3740b882c3473066140eb78b7970b4a82647dbf994579120270366837ceceb6072918f4eaf7c2c45a7aba912ab0761cfc588e5325e8d51a8136d03c5e86de81a05b178a874e32ef8ca1cca7d6eac0b8aa842bd20fce320ab4f995f030a7c545321c56ac1d084151c3ec73d19b55d020e9cf7c1c45ac15b818149b39703dad370325e460d8108e08909f527666962fac48d60972d7de862dcb1af6bf0a90303a11a5da8539646736f1398dc8dc2555942d1c79a5bb6f271d149931a415340039cf267ab64111a30ac588bf72e45da9cbdff271259de4a4664bf524f84b1291b0364233ae43c6c06faaf3548d904fc3d4897c473a869ff656db643296841aa413a035e3cdc556ec8b1a652f45d6c09e07135826e0611d9f9d6a98db2036cc41cebd40219ffff031dc43c967546d240c14b5bbb4c589d21da42ad3a2ae350abe6df374c3bdbf7d903190010decd1351ab426935c1cf0d730f8014bd0d4f1395c02e57ca6996d65aca034402cfabb3f1cef1579165e42082fe196207f8c10f72c7dffdfbe7a6172a962a034be10ab6c150a5099d896354efbc5871bcf92cef413c67451ecba7ac8d68d94203e2f37f41b7ca8e69a12701cb9490231ed1624362851208a7b72bd509991d0028039f96962846ce4bf2cd29f1d2214bc3584697900fb75613c16cdd74cdf943166f03a6026a555dc95cb1338fe252d3dff15dfc9a1ffaf2fe179ba2cbc6d97228bad30326b35feebc1552685d57c216c8ce8e3f2211b158f2a4e46158f1decafc96f50c031747fa36f2dd6f5cb529596d52cfa0df8f38efbcf87df871fd8c9f4e20ea4426035db49085e5dfa22dfdd616ca7be9c15269e72b448df38200cfc7816d87b0e4b40348d80ff7d513259cb6f89aec5f5e783e313cde5c96d610ee8fa0147b3ce3e9b80308a3c5cbfd804c6f9c366a148813d4dd11ddaaf92587b4ce5cf43a840680e229034a992d1496d4ac9f79499bac80a745d690a6e67d3c2ad45bc323ca69118018f70219ffff033982baf2f50522e80a7b3d1c282bedfb8906071d569fac6862a0cb48286740ce034e1232fcb88f6de6be4ace419a68cfc62d90a843aa227946ea0c5a9f59e523cc034ccb6a335900fe6e7280c05c406ddd3dc9a010cf4b9577c64071d83126d030ad03d3922c5c17254d83694f713b7201237bd24e54dd36f20eb46e2943fc7a15b56003a85c6b90719992fe6057f90517584dd428fa6797ffa1c0974d5f89b0650c968003f876f581f76ceaab789825caa5662995005cc4768381e8cb0e1df061a02bb9d003e1e8401ae359ae90cffbc4cadf1e9957ae94d51e3df5c9a73ce3140dfa8b4f8c0338359ded6781f6c0bc95f38257b157ec889bedc97bba98b26fdce2ca727fcd0f0219ffff03a96bf98acae212bdc0f60e39271ec2f35c0b356e5e5c96d997d3cfdd27561ff303b85fb4d82e87f8052ca74748bdfa9facedb4a13bd75acc61effaa22e7a5477b4036c557412842e57f9c4ae9a895edf39aecff1d7d0a8c53e945e4d6167edddb1d003e0630833f7112433a49310ef4709391840e77704659a4f36de2134976b954d67033cb2c1d45bd9999a614e02b645cbb81c3b38ab31af6012b0ae34f1b4318fa2e703acb09baffacf5c7f36da9c1efee9ba368e53e2e0b5794469a4778120b4b2496f0303fe9f1a86dcf53106a51acfbd528affc36cf3e0a7eb460f1fbb2f4d7734d4dd034978492150341c1f9ff121f8819f2f080947ced925cd00c0741bcb17ec14dae00310d3838f10cc2f6efc2a5c3a24a7b51e223f299a7249dbe5d8eb0336e6ec5d2103d75af6f76cae885a23b4258cb3b2e73e2b5e6561c9e6fe4f538495901fea9dae03b6361a4a3ef4149b1fd51b991ab8a2b7f1c784107d7982c1677a95827ef0f67903a473ef48b45e2fa99b7eaf656244b107e8f7a8725f59a84a3e3e8fc3783cc36603239052cbe6a46ed6ad785e071a03bf55f0a08a4386b91c9f4277c9b4fb2e070f0383ad7d71594261016c3436561b6cc303237f411932b6c589b3cf743015e5e078035c825d694d8790eea37f34eba25baff93f5f4d01ead1b5299190a6415e60eebe035a75fc6a66f1d79053afeb3cd89e0ef67eb2dad40c610b3c6fbbf65b23fed166036148653e3a1b34d4f2ba4c0108019614116c2c81ff0baccbf6eabf413e8d2cf1035c7e1dbfbeac158146f60b6258d1cf89c9c86d78c27d306e463b8777d74159e203918d2003ca9530802ac4297d4f4f4059cc5f8eefbef5b2c3f1e1a75fb0da7ce80344c7f83de3767527b87808433cea440add1abdc21958ca3a6d8b5ad468cd47670346625c9abb4de3f10bc62e2bb79e2d4b121aace6d70d413d52088eedca8974e703df2234abb2bc67e8633ec6522e623f0cd75e0e624486b9ddc1d6bc6305a9b69a03230ffadd76935253f3c98dd6b74a21fcb6ffd603d010d71109446a4e2ff05c1303b55da95eeb8022ea9f764f6fb9511a82fe26f598c6a73ce36a3f6bd8f31e9be50355284229177eabc51491684a8f1f7d26cbbb0941a07f4fc45c095f58ea51a4ba0390bb17e624e25a0e2cead4a6788eb462122439f171438684b0f9e4137056cad50375a9a2e74df7f4250625a23044de8d3b5a0d5d46554c155839c509bb0f878f1e03dd4972a2ca580b027f6600f7f351bb097a44f3270a77aba938d377ebf95402f603f6c3ddb3f6a942c2f8e5042b7aa03cc975cd940076634f19d59f5509cb934b7e035bb4e28909d32411043ecf7cb2bea9cc537b2adea66afde6bb7a7252521f2a1d03472727fd2abdfd7b7d0522fac1482c24ab11b11a976d0abd8e68d63fc4bbb14e03207fb66c5f6affe897c7d9ed861ebd3feb852bd5f269e77184e22b571bb0128503a79825200ff898579cbdd0d42bb58df3c595311f0d6bb8a5e617d8c459b843b3031e066eaf7ecc8f473937f4023037227150234efa8adb67b9d26968f5b7fdd8d60376e70cec0559fa1a38946c7140d96bcb33bf0c0848fbb7f113211a526ce9dcdf03e5314bca4ea077809e8542a370f980459f673793f956f5273cd525f1288e4918035df371d9fa98784741ce87b2ad3ad4b506e2b8fb9e69c88f069d1849c39c2d9a039592041ba5f9e7e17a95f92c02e009c113ce73baf9f144bc0be681eb8b344ca9037662b51617aaa494a69654105b2f4339d787e2270a294a3c7720bf410c6c5b6b03657c9667cd4c4dc4c68aff1a515e6a0a35b4543bbe5aba1ad97fb53349662d950378047952d2008842519a15e756bba5f128b43977f9b70d273a12b0b33f3475c005581e0361ca3ab37370e999383754a427e9cbb499ef62199ddd8159a120c220c0040205581e03f36e6e211b104002f8aaf2d72fdc720433e5cf906298ad5c4dc9f53d900c014708d952ecf96c0005581d02904891f85119e942ba587da80b9e5cd27a7341d2c8444cf2c483786e0c19bcb0483408438a75babcc405581d021e01aaaf06ed39b3d5769854985240f2d0fe845d3d76d4ee9fa26274040e021902040365a171a68484e34a202e133a8f36edccd88f7b61c02e657522d8a6c536f820e605581e03aa683e486278b5435a79fd0ef67705225d3cf1162001ffdb849c2c7ae00c04464ff19e3f3c6805581e03cc1820b98285efea6cfe007ce81b4da4bd9e8625ff924b9c354c216f900401036f8e7b179163beb6e7be221ecc56e1b32e1862c0c8e65a8178890dee7625e62805581e03043571f01386f6a158211c34a25062042e49c70b164adc814d12914720084708664d80ba82b80219c87b03dc560377a7d58470fa47d28661a49479f0069b73d9baa4de33061f775044c9cf03f2145b18f926809d348de858949f37a13f3e0e640817b121076ec2e83cb6939303be00460d9aa041cb6b2ef05122d8fed24e7e68d232a4d40d6ff3aa86e738373b03d74f734c5b428663b2433480cc4f5bae365d9003acec404632fb916f6150930e03c2b0d0d21dbec9cacc093eebff6eb5d9caac249867952165174c7d31d79f51a40389732185edbed893a74de9e6a189ff0a1c89013144da5873bcd0b8f298fc2b730358f118652c93a1d1473c6dc99ca89f08eac2fecca378ebb496282341abb4ed6703d7a511575ecfa69f4fa822d0baf809dad9be14fcd50cac06b26e985b367a51220219ffff03d5d9d2db79ac183aefd66df21ede7ee86250b452445b6e3aa1b6a2ab442db40603e53552828f75e8bae7e8065e135c606cdbb736d04cecf6e0af1763bd4a945a57032d4cb3bf00dd0de94c0e0f3bba48175bf760d9a72b14d2d23159200f9ee6f5e7034762bdc009549f89daa7249569b2721e5c2c07112d0989ed61c1876ec61e2c6d035f333bf83956beb9154882e9ca95891bb3a2b8661e65c23be7f24446117923db038e992a6c770a20647cfb5a0fdc7a77ae10a8676188b9a18b343e9e6cdcc67e720324d713f83824eb06a5eea6b6d5df778e91d5dd9239b2982722499636a137712b030357051acc71c80c151a686aabcfcb568d87bd635b036f24d866846f25957fda0362c086af6920efff38f3eeafd0dad103bf87e94339b7a3459c2154d8fc3f85bf0219ffff03019e41c0984f181010b8aab74f88319a2ff6f699654b9c64f45971ec86c4aa950219ffff037a27355c1be40e1aa23fa2dbc6244312d852419d73cfe6c2eecaae7d503e2d790394d854bd8df4c9cb282100b7e21fd5019f7c94312e41234a7d2dff49c579acd4037bfe0c4084d843086e9282acdfcd6946d2829bd2e60686bf211583c3ac94a2e1037150db65c3ee5b5efd9c9adedb956fc315aabe293e294645bb8814c368c585ed03fbd2979986c6697b7e37289ba54c2907f108364aa55e2f4ef4b1537521fa36fb034c5e36b900a1e1c2a62d0df591a7d14c16701164dd9654755dd4fecf57635166034abe00cd3bcb2dda0dc2e9e9bba4788e2572c297363fac208914b1dbd6be2e9a033ee14d303475adcc0a49c955b8897e3acbcef6e8f570c2090758e28ff9edded60219ffff038d8c3cdd29ab08a26093442e7859a5cdc6cce1c6a315c90e0fe1297cca2c0bf90337bbdf871cd110ea11a1c69046d1af6c26c94bbadeee834c0bbe349d712e142a0219ffff03e78cbab8c801b528a16a71db904306e88d329d02acaef53a1c60fe1008f420b10328d729800f1f178643992fef5b01e8c87426c47efc733973325b14d2697c1665032ffd3afb9ad7f77130f15ec93d6493af96f42faf98103c8d7dcc9168b040e978037784ec79fdff0ecae589f2edba9d6d5d06882fafcc23c23524dfe10876a9f47703fb1cb142b2b75e310a9b7ceb775cd005af478d65afd099216dc84b1bec3ff78603bf9304b3c63dffcac293f3c423a20d754e842cccf9548cfdc8a14dba8864ebcc03fe3ec4c1da00f18de8cc0dadcb9a067a62fb116721bb40884624657cc55faaad03fd45cf5c9b54e058b05267e509f17a01959298987a8cc48ee8ee320b0f48345303549f3533c303b773c31196973479045edc2d4104617a3e40e8d4843aff033ca903a9dd42473dc2a04461505b505884a9cd7005db84b15b207a6647b09da30aa4e703840a1780a2eb1718544e577809fa319dab62516d9e3c9b22773ba59db81bbfb5039cc3c2a6f408e31e0651ebebfc7d5cc84c1cfb37362667c61b3c3ec81a55642003c7e2025fe4736de432a2b1e2870d98f0b7733cdeafdcd9e231c0efa71c856e0103a3e62675e2d61bf54b380c558127761d4f1ab76c2ad192d0a0c36c3b3c1c9a38030547398548c5c02fb10dfee8cd9dd7b52677308b6c050e22855396c76fbba685037c6f001ad55006533fed297f077c62346b004d9ffe9ebb8cc0f49e318db5499303cc30fd706a6467a3d1ebc03ba1062953e63dd50c0e506edf69bc7e09c64d6fe2033cb5d593b28382d496be9e389aa61d2fda25d5dc8ca33f15c4156898e47f7b0f03b9ac59af1d015529220489afa37a70147e1b4dd00fb36473b6d0b1ac0e76516f03d43bc0e30cfa0e89fec0b0d7011e155c34a2249dd3b6975950811feb5567081d0380dde92769abcf818ca7eb399ece5a72868e34b567030b20255f9571a8a8b9870320ab4b014b71839f151908be8ceb14489c144b063e15148c5387ed89478894b203299e87fa5695364244250d23a8d0248cbfe57ab45ef2ceda90d2022087e33f18039c1a0d0b920462f28f727e19c7e0044e5bfa560d3ca17dbcebc06608d18e8135035acfc2bac14413bd0e2e210e561df4ef00f19beff379536f3ec652ea8baa22d603993add5605371f8429f46b8272b4c67aa0a71e824db4f23b1acd248335c0982f05581d03a0c9cf1688de729324d71300dc9a0d65f9e69245464a6231dc6543900403045957886080604052600436101561001a575b3615610018575f80fd5b005b5f3560e01c806301ffc9a7146101f4578063088890dc146101ef578063150b7a02146101ea578063192128b2146101e55780631f00ca74146101e057806323a69e751461016d57806324856bc3146101db5780632f100e4a146101d65780632f40e62a146101d15780633593564c146101cc5780633d0e3ec5146101c757806349df728c146101c25780634b31e26f146101bd5780634eeca823146101b857806350431ce4146101b3578063547988f9146101ae578063791ac947146101a9578063a0136443146101a4578063b6f9de951461019f578063bb7b9c761461019a578063bc197c8114610195578063d06ca61f14610190578063d1ef92491461018b578063e81dc5c114610186578063eb92db2714610181578063f23a6e611461017c578063f2fde38b14610177578063f9da581d14610172578063fa461e331461016d5763fb3bdb410361000e576116c5565b610895565b611690565b611663565b611609565b6114f6565b61149d565b611443565b61141c565b61138d565b611373565b611314565b61126f565b6111c2565b6110ec565b611083565b61101f565b610fbc565b610ed5565b610d01565b610bf1565b610b2c565b610b14565b6109ff565b61086e565b610815565b610745565b6103ef565b346102625760203660031901126102625760043563ffffffff60e01b811680910361026257602090630271189760e51b8114908115610251575b8115610240575b506040519015158152f35b6301ffc9a760e01b1490505f610235565b630a85bd0160e11b8114915061022e565b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b6001600160401b03811161028d57604052565b610266565b606081019081106001600160401b0382111761028d57604052565b608081019081106001600160401b0382111761028d57604052565b60a081019081106001600160401b0382111761028d57604052565b90601f801991011681019081106001600160401b0382111761028d57604052565b6001600160401b03811161028d5760051b60200190565b6001600160a01b0381160361026257565b929161033782610304565b9161034560405193846102e3565b829481845260208094019160051b810192831161026257905b82821061036b5750505050565b83809183356103798161031b565b81520191019061035e565b9080601f830112156102625781602061039f9335910161032c565b90565b9060a06003198301126102625760043591602435906001600160401b038211610262576103d191600401610384565b906044356103de8161031b565b906064359060843561039f8161031b565b6104066103fb366103a2565b9294904211156116ef565b61041761041283612865565b61173b565b61043061042385611791565b516001600160a01b031690565b6001546001600160a01b03949161045091869081165b16951685146117d7565b833b15610262575f60049460405195868092630d0e30db60e41b825234905af193841561068957610500946106ff575b506001546104a490610498906001600160a01b031681565b6001600160a01b031690565b6104c66104b361042388611791565b6104bf610423896117a3565b90866128d5565b60405163a9059cbb60e01b81526001600160a01b0390911660048201523460248201526020959091869183919082905f9082906044820190565b03925af180156106895761051b915f916106d2575b50611843565b6105376104986104986104236105318951611872565b896117c3565b6040516370a0823160e01b8082526001600160a01b038516600483015292918690829060249082905afa9485156106895787915f966106ab575b5061057d918591612aab565b6105996104986104986104236105938a51611872565b8a6117c3565b6040518381526001600160a01b0385166004820152908690829060249082905afa908115610689576105d49186915f9161068e575b50612c17565b101594856105e6575b610018866118c0565b61063095509061060b610498610498610423856106058a989751611872565b906117c3565b6040519182526001600160a01b03909216600482015294859190829081906024820190565b03915afa80156106895761001893610650935f9261065c575b5050612c17565b1515905f8080806105dd565b61067b9250803d10610682575b61067381836102e3565b8101906118b1565b5f80610649565b503d610669565b611823565b6106a59150883d8a116106825761067381836102e3565b5f6105ce565b85919650916106c961057d93893d8b116106825761067381836102e3565b96915091610571565b6106f29150863d88116106f8575b6106ea81836102e3565b81019061182e565b5f610515565b503d6106e0565b8061070c6107129261027a565b80611079565b5f610480565b9181601f84011215610262578235916001600160401b038311610262576020838186019501011161026257565b346102625760803660031901126102625761076160043561031b565b61076c60243561031b565b6064356001600160401b0381116102625761078b903690600401610718565b5050604051630a85bd0160e11b8152602090f35b6060600319820112610262576004356107b78161031b565b9160243591604435906001600160401b0382116102625761039f91600401610384565b60209060206040818301928281528551809452019301915f5b828110610801575050505090565b8351855293810193928101926001016107f3565b346102625761083b61082f6108293661079f565b916119aa565b604051918291826107da565b0390f35b9060406003198301126102625760043591602435906001600160401b0382116102625761039f91600401610384565b346102625761083b61082f6108823661083f565b5f549091906001600160a01b03166119aa565b34610262576060366003190112610262576024356004356044356001600160401b038111610262576108cb903690600401610718565b925f8313938415806109c5575b6109b357826108ec9161090c94019061280f565b6001600160a01b0390811692610901836143e2565b818398929a93614430565b83339116036109a157156109935750808616908416105b156109355750610018935033916144d1565b9150916042825110155f1461096957610018935061095282614559565b61096461095f33926145f6565b612855565b614605565b919290506009548211610981576100189233916144d1565b6040516339cedf2960e11b8152600490fd5b945080841690861610610923565b6040516332b13d9160e01b8152600490fd5b60405163316cf0eb60e01b8152600490fd5b505f8213156108d8565b9181601f84011215610262578235916001600160401b038311610262576020808501948460051b01011161026257565b604080600319360112610262576001600160401b036004803582811161026257610a2c9036908301610718565b93909260243590811161026257610a4690369084016109cf565b946001956001600c5403610b03576002600c55818103610aef575f5b828110610a73576100186001600c55565b610a8f610a8182858a611abd565b356001600160f81b03191690565b610aad610aa7610aa0848689611ac9565b3691611b25565b826133b7565b159081610ae0575b50610ac1578701610a62565b8451632c4029e960e01b8152908190610adc90828901611b7f565b0390fd5b600160ff1b161590505f610ab5565b6040516001621398b960e31b031981528590fd5b6040516337affdbf60e11b81528590fd5b61083b61082f610b23366103a2565b93929092611bad565b60e036600319011261026257600435610b448161031b565b6001600160401b039060243582811161026257610b65903690600401610384565b91604435908111610262573660238201121561026257806004013592610b8a84610304565b91610b9860405193846102e3565b8483526020946024602085019160051b8301019136831161026257602401905b828210610bd65761001860c43560a43560843560643589898c611d9f565b813562ffffff81168103610262578152908601908601610bb8565b60603660031901126102625760046001600160401b03813581811161026257610c1d9036908401610718565b92909160243590811161026257610c3790369083016109cf565b936044354211610cf0576001946001600c5403610cdf576002600c55818103610ccb575f5b828110610c6d576100186001600c55565b610c7b610a81828589611abd565b610c8c610aa7610aa0848689611ac9565b159081610cbc575b50610ca0578601610c5c565b604051632c4029e960e01b8152908190610adc90828801611b7f565b600160ff1b161590505f610c94565b6040516001621398b960e31b031981528490fd5b6040516337affdbf60e11b81528490fd5b604051632dfb7c8b60e11b81528390fd5b346102625760c0366003190112610262576044356001600160401b03811161026257610d34610e009136906004016109cf565b6064359291610d428461031b565b610dfa60a43591610d528361031b565b610d604260843510156116ef565b610d6c61041284612865565b610db0610d8a610d85610d7e87611872565b8785611f31565b611f41565b600154610d9f906001600160a01b0316610498565b6001600160a01b03909116146117d7565b610df1610dc0610d858684611f19565b610dcd610d858785611f19565b90610de960043592610de2610d858a88611f22565b90886128d5565b9033906140f5565b3093369161032c565b90612aab565b600154610e1790610498906001600160a01b031681565b6040516370a0823160e01b81523060048201529091602082602481865afa918215610689575f92610eb4575b5060243582101580610eab575b610e59906118c0565b823b1561026257604051632e1a7d4d60e01b815260048101839052925f908490602490829084905af19283156106895761001893610e98575b5061376c565b8061070c610ea59261027a565b5f610e92565b50811515610e50565b610ece91925060203d6020116106825761067381836102e3565b905f610e43565b346102625760208060031936011261026257600435610ef38161031b565b610efb6141d1565b6040516370a0823160e01b81523060048201526001600160a01b0391909116908281602481855afa908115610689575f928492610f6f928591610f95575b50610f45811515611f4b565b60405163a9059cbb60e01b8152336004820152602481019190915293849283919082906044820190565b03925af1801561068957610f7f57005b8161001892903d106106f8576106ea81836102e3565b610fac9150843d86116106825761067381836102e3565b5f610f39565b8015150361026257565b60c036600319011261026257600435610fd481610fb2565b602435906001600160401b03821161026257610ff76100189236906004016109cf565b916064359061100582610fb2565b60a435936110128561031b565b6084359360443592611f8f565b60c0366003190112610262576044356001600160401b0381116102625761082f61105061083b923690600401610384565b6064359061105d8261031b565b60a4359161106a8361031b565b6084359160243560043561234b565b5f91031261026257565b34610262575f3660031901126102625761109b6141d1565b4780156110b1575f808080933382f11561068957005b60405162461bcd60e51b81526020600482015260136024820152724e6f7468696e6720746f20776974686472617760681b6044820152606490fd5b6080366003190112610262576001600160401b0360048035828111610262576111189036908301610718565b926024359081116102625761113090369084016109cf565b9290606435936044354211610cf05761114b85341015611e99565b6001956001600c5403610cdf576002600c55808203610ccb575f5b818110611180576100188761117b6001600c55565b614044565b61118e610a81828489611abd565b61119f610aa7610aa0848789611ac9565b1590816111b3575b50610ca0578701611166565b600160ff1b161590505f6111a7565b346102625760a0366003190112610262576044356001600160401b038111610262576111f5610e009136906004016109cf565b60643592916112038461031b565b610dfa42608435101591611216836116ef565b610db060018060a01b0361122d815f5416956116ef565b61123961041286612865565b61124f610d8561124888611872565b8886611f31565b600154909190611267906001600160a01b0316610498565b9116146117d7565b60a03660031901126102625760043561128781610fb2565b602435906001600160401b038211610262576112aa6100189236906004016109cf565b91606435906112b882610fb2565b5f546001600160a01b0316936084359360443592611f8f565b9060806003198301126102625760043591602435906001600160401b0382116102625761130091600401610384565b9060443561130d8161031b565b9060643590565b61131d366112d1565b9291924211159061132d826116ef565b5f546001600160a01b039390841692611345906116ef565b61135161041284612865565b61045061136061042387611791565b60015486906001600160a01b0316610446565b346102625761083b61082f6113873661079f565b916123d3565b346102625760a0366003190112610262576113a960043561031b565b6113b460243561031b565b6001600160401b03604435818111610262576113d49036906004016109cf565b5050606435818111610262576113ee9036906004016109cf565b505060843590811161026257611408903690600401610718565b505060405163bc197c8160e01b8152602090f35b346102625761083b61082f6114303661083f565b5f549091906001600160a01b03166123d3565b60c0366003190112610262576004356001600160401b038111610262576114716100189136906004016109cf565b906044359161147f83610fb2565b60a4359261148c8461031b565b6084359260643592602435916124d1565b60a0366003190112610262576004356001600160401b038111610262576114cb6100189136906004016109cf565b90604435916114d983610fb2565b5f546001600160a01b0316926084359260643592602435916124d1565b34610262576020366003190112610262576004356001600160401b0380821691828103610262575f805160206157338339815191528054928460ff8560401c169182156115fc575b50506115ea577fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2936115e5936001600160401b0319161790556115a55f805160206157338339815191526801000000000000000068ff000000000000000019825416179055565b6115ad61264d565b5f80516020615733833981519152805468ff0000000000000000191690556040516001600160401b0390911681529081906020820190565b0390a1005b60405163f92ee8a960e01b8152600490fd5b851610159050845f61153e565b346102625760a03660031901126102625761162560043561031b565b61163060243561031b565b6084356001600160401b0381116102625761164f903690600401610718565b505060405163f23a6e6160e01b8152602090f35b34610262576020366003190112610262576100186004356116838161031b565b61168b6141d1565b61278b565b5f366003190112610262576116a36141d1565b5f34156116bc575b5f8080809334904190f11561068957005b506108fc6116ab565b6100186116d1366112d1565b916116de428410156116ef565b5f546001600160a01b031693611bad565b156116f657565b60405162461bcd60e51b815260206004820152601860248201527f556e69737761705632526f757465723a204558504952454400000000000000006044820152606490fd5b1561174257565b60405162461bcd60e51b8152602060048201526013602482015272556e737570706f7274656420466163746f727960681b6044820152606490fd5b634e487b7160e01b5f52603260045260245ffd5b80511561179e5760200190565b61177d565b80516001101561179e5760400190565b80516002101561179e5760600190565b805182101561179e5760209160051b010190565b156117de57565b60405162461bcd60e51b815260206004820152601d60248201527f556e69737761705632526f757465723a20494e56414c49445f504154480000006044820152606490fd5b6040513d5f823e3d90fd5b90816020910312610262575161039f81610fb2565b1561184a57565b634e487b7160e01b5f52600160045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b5f1981019190821161188057565b61185e565b60011981019190821161188057565b9061271091820391821161188057565b9190820391821161188057565b90816020910312610262575190565b156118c757565b60405162461bcd60e51b815260206004820152602b60248201527f556e69737761705632526f757465723a20494e53554646494349454e545f4f5560448201526a1514155517d05353d5539560aa1b6064820152608490fd5b1561192757565b60405162461bcd60e51b815260206004820152601e60248201527f556e697377617056324c6962726172793a20494e56414c49445f5041544800006044820152606490fd5b9061197682610304565b61198360405191826102e3565b8281528092611994601f1991610304565b0190602036910137565b8015611880575f190190565b929190926119bc600283511015611920565b6119c6825161196c565b936119da6119d48651611872565b866117c3565b526119e481612c24565b906119ef8351611872565b805b6119fb5750505050565b80611a29611a15610423611a0f8795611872565b886117c3565b611a2261042384896117c3565b9085612d0d565b50909391905f9083611a9757505090611a7f91611a9194611a6c611a58610423611a5288611872565b8c6117c3565b611a65610423888d6117c3565b90896128d5565b915b611a78868d6117c3565b5188612f41565b611a8b61053183611872565b5261199e565b806119f1565b809194959350611ab3575b50611a919392611a7f928792611a6e565b9550611a91611aa2565b9082101561179e570190565b919081101561179e5760051b81013590601e19813603018212156102625701908135916001600160401b038311610262576020018236038113610262579190565b6001600160401b03811161028d57601f01601f191660200190565b929192611b3182611b0a565b91611b3f60405193846102e3565b829481845281830111610262578281602093845f960137010152565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b60609181526040602082015281518060408301528060808484015e5f828201840152601f01601f1916010190565b92611bba904211156116ef565b611bc661041285612865565b611bf881611bd661042382611791565b6001546001600160a01b039081169691611bf2911687146117d7565b866119aa565b93611c0e611c0586611791565b51341015611d43565b611c1785611791565b5193803b15610262575f90600460405180978193630d0e30db60e41b83525af193841561068957611cc394611d30575b50600154602090611c6290610498906001600160a01b031681565b611c84611c7161042386611791565b611c7d610423876117a3565b90856128d5565b90611c8e88611791565b5160405163a9059cbb60e01b81526001600160a01b0390931660048401526024830152909586919082905f9082906044820190565b03925af190811561068957611ce38692611ce8965f91611d175750611843565b613617565b611cf181611791565b513411611cfb5790565b61039f611d11611d0a83611791565b51346118a4565b3361376c565b6106f2915060203d6020116106f8576106ea81836102e3565b8061070c611d3d9261027a565b5f611c47565b15611d4a57565b60405162461bcd60e51b815260206004820152602760248201527f556e69737761705632526f757465723a204558434553534956455f494e50555460448201526617d05353d5539560ca1b6064820152608490fd5b91909395944211611e87576001600c5403611e75576002600c55611dc585341015611e99565b8351815190600182018092116118805703611e405783611dff93611e1897611ded88346118a4565b91611df883306137e3565b30956139d8565b611e0833613df5565b6003815114611e24575b50614044565b611e226001600c55565b565b611e3a90611e3561042333926117a3565b613f2c565b5f611e12565b60405162461bcd60e51b815260206004820152600d60248201526c1a5b9d985b1a59081a5b9c1d5d609a1b6044820152606490fd5b6040516337affdbf60e11b8152600490fd5b604051632dfb7c8b60e11b8152600490fd5b15611ea057565b60405162461bcd60e51b815260206004820152602160248201527f5469702063616e277420626520626967676572207468616e2074782076616c756044820152606560f81b6064820152608490fd5b906001820180921161188057565b906002820180921161188057565b906064820180921161188057565b901561179e5790565b906001101561179e5760200190565b919081101561179e5760051b0190565b3561039f8161031b565b15611f5257565b60405162461bcd60e51b81526020600482015260156024820152744e6f20746f6b656e7320746f20776974686472617760581b6044820152606490fd5b9293949091611fa085341015611e99565b8434039234841161188057611fb691369161032c565b9215611fdd575093611fd791611e2295611fcf42611f0b565b913391612160565b506140a0565b9490916064420180421161188057611ff7904211156116ef565b61200361041283612865565b6120358161201361042382611791565b6001546001600160a01b03908116999161202f91168a146117d7565b846119aa565b9161204b8461204385611791565b511115611d43565b61205483611791565b5196803b15610262575f906004604051809a8193630d0e30db60e41b83525af1968715610689576120ed9761214d575b5060015460209061209f90610498906001600160a01b031681565b6120ae611c7161042386611791565b906120b886611791565b5160405163a9059cbb60e01b81526001600160a01b0390931660048401526024830152909889919082905f9082906044820190565b03925af19182156106895761210f61211793611e22995f91611d175750611843565b833392613617565b61212081611791565b51821161212f575b50506140a0565b6121469161213f611d1192611791565b51906118a4565b5f80612128565b8061070c61215a9261027a565b5f612084565b93919092612170904211156116ef565b61217c61041283612865565b6121ae8361218c61042382611791565b6001546001600160a01b0390811697916121a8911688146117d7565b846123d3565b936121bc6119d48651611872565b506121db6121d36121cd8751611872565b876117c3565b5115156118c0565b6121e485611791565b5190803b15610262575f90600460405180948193630d0e30db60e41b83525af1801561068957612338575b5060015461222790610498906001600160a01b031681565b90612237611c7161042386611791565b9361224186611791565b5160405163a9059cbb60e01b81526001600160a01b039690961660048701526024860152602094928590849060449082905f905af1801561068957610498610423836122a8866122b2968c6122df9b611ce3610498998f9d8e5f9261231b575b5050611843565b6106058151611872565b6040516370a0823160e01b81526001600160a01b0390921660048301529092839190829081906024820190565b03915afa9081156106895761039f925f926122fe575b505015156118c0565b6123149250803d106106825761067381836102e3565b5f806122f5565b6123319250803d106106f8576106ea81836102e3565b5f8e6122a1565b8061070c6123459261027a565b5f61220f565b909492919361235c904211156116ef565b61236861041284612865565b6123988461237861042382611791565b6001546001600160a01b0394916123929186908116610446565b856123d3565b946123a66121cd8751611872565b511015806123b8575b6121db906118c0565b506121db6123c96121cd8751611872565b51151590506123af565b929190926123e5600283511015611920565b6123ef825161196c565b936123f985611791565b5261240381612c24565b915f5b6124108251611872565b8110156124cb578061243f6124296104238794866117c3565b6124386104236121cd85611eef565b9086612d0d565b50909391905f90836124a75750509061249491600194612481612465610423878a6117c3565b61247a61042361247489611eef565b8b6117c3565b908a6128d5565b915b61248d868d6117c3565b51896142bf565b6124a061053183611eef565b5201612406565b8091949593506124c2575b5060019392612494928892612483565b965060016124b2565b50505050565b969594939291906124e482341015611e99565b81340390348211611880576124fa36828b61032c565b94612509600287511015611920565b612513865161196c565b95816125226105318951611872565b5261252c89612c24565b906125378151611872565b90818315925b612576575050505050612570611e2298998361256661255f6105938a51611872565b5198611791565b511192369161032c565b9161421c565b806125e285858f948e6125f4966125a9612595610423611a5286611872565b6125a2610423868d6117c3565b9083612d0d565b50509390928d856125ba8d51611872565b84149182612642575b5050612629575b5f956125fa575b906125db916117c3565b5190612f41565b611a8b6125ee83611872565b8d6117c3565b8061253d565b9450806126208b611c7d6104238461261a6104236119d46125db99611872565b936117c3565b959091506125d1565b61263285611872565b61263c83836117c3565b526125ca565b10159050858f6125c3565b73bdeb498e872e36f899f237fd1b93673ed6c14474330361275a57612670615060565b612678615060565b6126813361278b565b612689615060565b6001600160601b0360a01b735c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f815f5416175f5573c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2808260015416176001556126d7615060565b6e22d473030f116ddee9f6b43ac78ba3826003541617600355816002541617600255731f98431c8ad98523631ae4a59f267346ea31f9848160045416176004557fe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b546005556006541660065561274a614379565b612752614389565b611e226143d3565b60405162461bcd60e51b81526020600482015260096024820152682737ba1027bbb732b960b91b6044820152606490fd5b6001600160a01b039081169081156127f7577f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981168417909155167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a3565b604051631e4fbdf760e01b81525f6004820152602490fd5b91906040838203126102625782356001600160401b03811161026257830181601f8201121561026257602091818361284993359101611b25565b92013561039f8161031b565b600160ff1b8114611880575f0390565b60018060a01b0316735c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f81149081156128cc575b81156128c3575b81156128ba575b81156128b1575b816128aa575090565b9050151590565b801591506128a1565b8015915061289a565b80159150612893565b8015915061288c565b916128df9161471a565b6128ea839293612c24565b6129a65761039f9261293c61294a612907610498610498876147e1565b93604051928391602083019586906029926001600160601b0319809260601b16835260601b1660148201525f60288201520190565b03601f1981018352826102e3565b51902090916043916055936040519260388401526f5af43d82803e903d91602b57fd5bf3ff60248401526014830152733d602d80600a3d3981f3363d3d373d3d3d363d73825260588201526037600c8201206078820152012090565b906129e3612a4b61039f9461049894604051938491602083019384906028926001600160601b0319809260601b16835260601b1660148201520190565b03926129f7601f19948581018352826102e3565b51902091612a04846147e1565b6040516001600160f81b03196020820190815260609690961b6bffffffffffffffffffffffff191660218201526035810194909452605584015260759081018352826102e3565b5190206001600160a01b031690565b604051602081018181106001600160401b0382111761028d576040525f8152905f368137565b909260809261039f95948352602083015260018060a01b031660408201528160608201520190611b5b565b9092915f5b612aba8551611872565b811015612c1057612ace61042382876117c3565b90612ade610423611a0f83611eef565b91612ae9838261471a565b5092612af68183886128d5565b8094612b0383858a612d0d565b50604080516370a0823160e01b81526001600160a01b03968716600480830191909152919891969491851693909260209290918385602481895afa918215610689578f612b608f9483908b99612b66995f9261065c575050612c17565b906142bf565b931603612c08578a5f92945b612b7c8251611885565b881015612bff57612b96610423612b9d936106058b611efd565b908b6128d5565b965b1691612ba9612a5a565b90833b1561026257612bd25f9692879351998a978896879563022c0d9f60e01b87528601612a80565b03925af191821561068957600192612bec575b5001612ab0565b8061070c612bf99261027a565b5f612be5565b50508796612b9f565b8a5f94612b72565b5050509050565b9081039081116118805790565b5f906001600160a01b0316735c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8103612c525750506126f290565b61039f57505f90565b51906001600160701b038216820361026257565b9081606091031261026257612c8381612c5b565b916040612c9260208401612c5b565b92015163ffffffff811681036102625790565b519061ffff8216820361026257565b919082608091031261026257612cc982612c5b565b91612cd660208201612c5b565b9161039f6060612ce860408501612ca5565b9301612ca5565b8115612cf9570490565b634e487b7160e01b5f52601260045260245ffd5b9091612d19818461471a565b5091612d2481612c24565b60018103612dfd5750612d42610498610498600494876080956128d5565b604051630240bc6b60e21b815292839182905afa908115610689575f935f80925f94612dbb575b50600a612d9691612d8e8261ffff80936001600160701b038091169b16971604611894565b951604611894565b92935b6001600160a01b03918216911603612db5579291905b90919293565b91612daf565b9050612d969550600a9350612de991925060803d608011612df6575b612de181836102e3565b810190612cb4565b9296509193919291612d69565b503d612dd7565b91612e1461049861049860609388600499966128d5565b604051630240bc6b60e21b815295869182905afa8015610689575f945f91612e4d575b506001600160701b038091169416908293612d99565b9050612e7291945060603d606011612e7c575b612e6a81836102e3565b810190612c6f565b509390935f612e37565b503d612e60565b15612e8a57565b60405162461bcd60e51b815260206004820152602c60248201527f556e697377617056324c6962726172793a20494e53554646494349454e545f4f60448201526b155514155517d05353d5539560a21b6064820152608490fd5b15612eeb57565b60405162461bcd60e51b815260206004820152602860248201527f556e697377617056324c6962726172793a20494e53554646494349454e545f4c604482015267495155494449545960c01b6064820152608490fd5b909394612f4f851515612e83565b82151580613023575b612f6190612ee4565b80613005575060405163cc56b2c560e01b81526001600160a01b0395861660048201525f60248201529460209186916044918391165afa90811561068957612fd684612fd0612fcb612fe197612fc5612fdb9761039f9b5f91612fe6575b50611894565b95614888565b61482d565b94612c17565b614888565b90612cef565b6148c4565b612fff915060203d6020116106825761067381836102e3565b5f612fbf565b905061039f9450612fd684612fd0612fcb612fe197612fdb96614888565b50831515612f58565b5190611e228261031b565b9080601f830112156102625781519060209161305281610304565b9361306060405195866102e3565b81855260208086019260051b82010192831161026257602001905b828210613089575050505090565b83809183516130978161031b565b81520191019061307b565b9080601f83011215610262578151906020916130bd81610304565b936130cb60405195866102e3565b81855260208086019260051b82010192831161026257602001905b8282106130f4575050505090565b815181529083019083016130e6565b919060a083820312610262578251926020810151926040820151926001600160401b0393848111610262578161313a918501613037565b936060840151908111610262576080916131559185016130a2565b92015161039f8161031b565b608081830312610262578051926020820151926001600160401b03938481116102625781613190918501613037565b936040840151908111610262576060916131559185016130a2565b919082604091031261026257602082516131c48161031b565b92015190565b519065ffffffffffff8216820361026257565b81601f82011215610262578051906131f482611b0a565b9261320260405194856102e3565b8284526020838301011161026257815f9260208093018386015e8301015290565b91909180830360e081126102625760c081126102625760806040519161324883610292565b1261026257604051613259816102ad565b82516132648161031b565b815260208301516132748161031b565b6020820152613285604084016131ca565b6040820152613296606084016131ca565b606082015281526132a96080830161302c565b602082015260a082015160408201529260c08201516001600160401b0381116102625761039f92016131dd565b604061039f94936101009360018060a01b038091168452815181815116602086015281602082015116848601526060848201519165ffffffffffff80931682880152015116608085015260208201511660a0840152015160c08201528160e08201520190611b5b565b908160609103126102625780516133558161031b565b91604060208301516131c48161031b565b919060a08382031261026257825161337d8161031b565b9260208101519260408201519260608301516001600160401b038111610262576080916133ab9185016131dd565b92015161039f81610fb2565b600192919060f81c601f16601081106133ce575050565b60088110156134a5578061341e57506133f381602080611e2294518301019101613366565b909290156134145761340f33945b6001600160a01b0316614d5f565b614f0c565b61340f3094613401565b60018103613467575061343d81602080611e2294518301019101613366565b9092901561345d5761345833946001600160a01b0316614d5f565b614d83565b6134583094613401565b600414613472575b50565b61348881602080611e229451830101910161333f565b91906001600160a01b039061349e908216614d5f565b9116613faf565b600a81036135235750806020806134c193518301019101613223565b6003549091906134d9906001600160a01b0316610498565b91823b1561026257613505925f92836040518096819582946302b67b5760e41b845233600485016132d6565b03925af18015610689576135165750565b8061070c611e229261027a565b600b810361355a575061355561354582602080611e22955183010191016131ab565b91906001600160a01b0316614d5f565b6137e3565b600c8103613581575061357c61354582602080611e22955183010191016131ab565b613eb4565b600d81036135b357506135a081602080611e2294518301019101613161565b6001600160a01b03169290919034614c2c565b600e81036135e557506135d28160208061346f94518301019101613161565b6001600160a01b03169290919034614ae6565b600f146135ef5750565b61360581602080611e2294518301019101613103565b6001600160a01b031693909290614965565b90919392935f5b6136288251611872565b8110156137355761363c61042382846117c3565b9061365261042361364c83611eef565b856117c3565b9161365d838261471a565b509061366b611a0f84611eef565b51916001600160a01b03828116911603613725576136be6104986104985f94965b6136968951611885565b87101561371e576136b66136af610423611a528a611efd565b828c6128d5565b945b8a6128d5565b6136c6612a5a565b94813b15610262575f80946136f16040519889968795869463022c0d9f60e01b865260048601612a80565b03925af19182156106895760019261370b575b500161361e565b8061070c6137189261027a565b5f613704565b8c946136b8565b6136be6104986104985f9661368c565b505050509050565b3d15613767573d9061374e82611b0a565b9161375c60405193846102e3565b82523d5f602084013e565b606090565b5f918291613778612a5a565b91602083519301915af161378a61373d565b501561379257565b60405162461bcd60e51b815260206004820152602360248201527f5472616e7366657248656c7065723a204554485f5452414e534645525f46414960448201526213115160ea1b6064820152608490fd5b90600160ff1b81036138cb575047905b816137fc575050565b600254613811906001600160a01b0316610498565b803b15610262575f8391600460405180968193630d0e30db60e41b83525af19182156106895761388f936020936138b8575b50600254613859906001600160a01b0316610498565b60405163a9059cbb60e01b81526001600160a01b03909216600483015260248201929092529283919082905f9082906044820190565b03925af18015610689576138a05750565b61346f9060203d6020116106f8576106ea81836102e3565b8061070c6138c59261027a565b5f613843565b90478211156137f3575b604051631a84bc4160e21b8152600490fd5b9360429592916001600160601b03199485809260601b16875262ffffff60e81b809460e81b16601488015260601b16601786015260e81b16602b84015260601b16602e8201520190565b6001600160a01b039081165f19019190821161188057565b6001600160a01b039081166001019190821161188057565b90613979602091949394604084526040840190611b5b565b6001600160a01b03909416910152565b9190826040910312610262576020825192015190565b6001600160a01b039182168152911515602083015260408201929092529116606082015260a06080820181905261039f92910190611b5b565b93959491926139e690600955565b613a636139fb6104986104986104238a611791565b91613a32613a0b6104238a611791565b613a176104238b6117a3565b613a2c613a2388611791565b5162ffffff1690565b91614430565b604080516370a0823160e01b81526001600160a01b0390921660048301529360209384918391829081906024820190565b03915afa801561068957600a915f91613dd8575b50048110613b70575b613a97575b50505050505050611e22600854600955565b613b139660018451145f14613b1f57613b0d9281613ad2610423613acc613a23613ac661042361293c986117a3565b99611791565b92611791565b915195869485019192602b936001600160601b0319809360601b16845262ffffff60e81b9060e81b16601484015260601b1660178201520190565b91614e1b565b5f808080808080613a85565b613b0d9281613b3361042361293c946117b3565b95613b40613a23826117a3565b613b64610423613b5e613a23613b58610423886117a3565b95611791565b94611791565b935197889687016138e7565b8351600103613d7f57878784613b8861042384611791565b613be3613b97613a238a611791565b9161293c613ba7610423886117a3565b85519485938b85019192602b936001600160601b0319809360601b16845262ffffff60e81b9060e81b16601484015260601b1660178201520190565b613c31610498610498613bf8610423886117a3565b613c076104986104238a611791565b6001600160a01b0390911610968b613a2c613a23613acc610423613c2b8187611791565b956117a3565b845f8c613c94613c4361095f8b6145f6565b95848414613d5a57600a54613c78908d9061293c90613c6a906001600160a01b0316613949565b9b5b8b519485938401613961565b8751630251596160e31b8152988997889687956004870161399f565b03925af191825f925f94613d25575b50613ceb57505050848103613a8057825162461bcd60e51b8152602060048201526012602482015271151bdbc81b5d58da081c995c5d595cdd195960721b6044820152606490fd5b15613d165750613cfa90612855565b03613d05575f613a80565b8151636a70124760e11b8152600490fd5b613d209150612855565b613cfa565b909350613d49919250863d8811613d53575b613d4181836102e3565b810190613989565b919091925f613ca3565b503d613d37565b600b54613c78908d9061293c90613d79906001600160a01b0316613931565b9b613c6c565b878784613d8e61042384611791565b613dd3613d9d613a238a611791565b9161293c613dad610423886117a3565b613db9613a238d6117a3565b613dc56104238a6117b3565b9187519687958d87016138e7565b613be3565b613def9150843d86116106825761067381836102e3565b5f613a77565b600254613e0a906001600160a01b0316610498565b6040516370a0823160e01b81523060048201529091602082602481865afa918215610689575f92613e93575b5081613e4157505050565b823b1561026257604051632e1a7d4d60e01b815260048101839052925f908490602490829084905af192831561068957611e2293613e80575b50614fa7565b8061070c613e8d9261027a565b5f613e7a565b613ead91925060203d6020116106825761067381836102e3565b905f613e36565b600254909190613ecc906001600160a01b0316610498565b6040516370a0823160e01b815230600482015290929091602083602481875afa928315610689575f93613f0b575b5082106138d55781613e4157505050565b613f2591935060203d6020116106825761067381836102e3565b915f613efa565b6001600160a01b031680613f4e57504780613f45575050565b611e2291614fa7565b6040516370a0823160e01b81523060048201529091602082602481865afa918215610689575f92613f8e575b5081613f8557505050565b611e2292614ff1565b613fa891925060203d6020116106825761067381836102e3565b905f613f7a565b9091906001600160a01b031680613fd25750479081106138d55780613f45575050565b6040516370a0823160e01b815230600482015290929091602083602481875afa928315610689575f93614023575b5082106140115781613f8557505050565b604051630ceb95c760e31b8152600490fd5b61403d91935060203d6020116106825761067381836102e3565b915f614000565b80614078575b504780158015614058575050565b5f8080938193829061406f575b3390f11561068957565b506108fc614065565b5f8091614083612a5a565b90602082519201904161c350f15061409961373d565b505f61404a565b90816140b6575b50504780158015614058575050565b5f806140e6936140c4612a5a565b90602082519201904161c350f16140d961373d565b5081156140ed5750611843565b5f806140a7565b90505f610515565b90915f80949381946040519160208301946323b872dd60e01b865260018060a01b038092166024850152166044830152606482015260648152614137816102c8565b51925af161414361373d565b816141a2575b501561415157565b60405162461bcd60e51b8152602060048201526024808201527f5472616e7366657248656c7065723a205452414e534645525f46524f4d5f46416044820152631253115160e21b6064820152608490fd5b80518015925082156141b7575b50505f614149565b6141ca925060208091830101910161182e565b5f806141af565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b0316330361420457565b60405163118cdaa760e01b8152336004820152602490fd5b9294909593955f14614244575091611e229591611fd79361423c42611f0b565b92339261234b565b959192506064420180421161188057611ff7904211156116ef565b1561426657565b60405162461bcd60e51b815260206004820152602b60248201527f556e697377617056324c6962726172793a20494e53554646494349454e545f4960448201526a1394155517d05353d5539560aa1b6064820152608490fd5b93909291936142cf84151561425f565b84151580614370575b6142e190612ee4565b856143585760405163cc56b2c560e01b81526001600160a01b0392831660048201525f6024820152955060209186916044918391165afa9182156106895761434d61434661435392614340612fdb9661039f995f91612fe65750611894565b90614888565b9283614888565b9361482d565b614915565b505061435361434d61434661039f96612fdb95614888565b508215156142d8565b614381615060565b612710600755565b614391615060565b5f198060085560095573fffd8963efd1fc6a506488495d951d5263988d266001600160601b0360a01b6401000276a381600a541617600a55600b541617600b55565b6143db615060565b6001600c55565b9081516143ef818461508e565b926017821061441e57602b6017820151921061440c57602b015191565b60405163a78aa27f60e01b8152600490fd5b604051636c84b51f60e11b8152600490fd5b6001600160a01b039291838116848316116144cb575b62ffffff846004541693856040519381602086019616865216604084015216606082015260608152614477816102ad565b5190206005546040516001600160f81b03196020820190815260609490941b6bffffffffffffffffffffffff19166021820152603581019290925260558201526144c4816075810161293c565b5190201690565b90614446565b6001600160a01b0393929184163081036144f05750611e22935061509d565b848492941161454757846003541693843b15610262575f94868692816084966040519a8b998a98631b63c28b60e11b8a5260048a01521660248801521660448601521660648401525af18015610689576135165750565b60405163c4bd89a960e01b8152600490fd5b8051601619808201929190818411611880578360088301106145e457601782106145e457818351106145d257601782146145c057601f8416801560051b0183019182010160178201915b8181106145b05750505052565b82518152602092830192016145a3565b60405163664a531d60e11b8152600490fd5b604051633b99b53d60e01b8152600490fd5b6040516323d5783d60e11b8152600490fd5b600160ff1b8110156102625790565b61049892936104985f60409461468c61463c6146208a6143e2565b6001600160a01b03808416908316109b8c989093909290614430565b948484146146c457600a546146709061465d906001600160a01b0316613949565b9a5b61293c8a5193849260208401613961565b8751630251596160e31b8152998a97889687956004870161399f565b03925af18015610689575f925f916146a357509192565b90506146bf91925060403d604011613d5357613d4181836102e3565b919092565b600b54614670906146dd906001600160a01b0316613931565b9a61465f565b61049892936104985f60409461468c61463c6146fe8a6143e2565b6001600160a01b03808316908416109b8c989093909290614430565b90916001600160a01b039182841683821680821461478e57101561478957925b9183161561474457565b60405162461bcd60e51b815260206004820152601e60248201527f556e697377617056324c6962726172793a205a45524f5f4144445245535300006044820152606490fd5b61473a565b60405162461bcd60e51b815260206004820152602560248201527f556e697377617056324c6962726172793a204944454e544943414c5f41444452604482015264455353455360d81b6064820152608490fd5b5f906001600160a01b0316735c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f8103612c525750507f96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f90565b9061271091828102928184048114821517156118805783040361484c57565b60405162461bcd60e51b815260206004820152601460248201527364732d6d6174682d6d756c2d6f766572666c6f7760601b6044820152606490fd5b5f9291801591821561489e575b50501561484c57565b80820294509150811582850482141715611880576148bc9084612cef565b145f80614895565b9060018201918281116118805782106148d957565b60405162461bcd60e51b815260206004820152601460248201527364732d6d6174682d6164642d6f766572666c6f7760601b6044820152606490fd5b91908201918281116118805782106148d957565b1561493057565b60405162461bcd60e51b815260206004820152600d60248201526c139bdd081cdd5c1c1bdc9d1959609a1b6044820152606490fd5b909392916149c1816149e4946149bc60018060a01b0361498a81600654161515614929565b61499a6104236119d48651611872565b6002549091906149b490610498906001600160a01b031681565b911614614a5d565b615186565b6149dd836149d161042385611791565b610de961042385611791565b3092615436565b506002546149fa906001600160a01b0316610498565b6040516370a0823160e01b815230600482015290602090829060249082905afa801561068957611e2293614a37925f92614a3c575b501115614a9a565b613df5565b614a5691925060203d6020116106825761067381836102e3565b905f614a2f565b15614a6457565b60405162461bcd60e51b815260206004820152600e60248201526d496e76616c696420496e7075747360901b6044820152606490fd5b15614aa157565b60405162461bcd60e51b815260206004820152601a60248201527f494e53554646494349454e545f4f55545055545f414d4f554e540000000000006044820152606490fd5b92919360018060a01b0390614b0082600654161515614929565b85511561179e57614b3986614b86956149bc614b7f95602084015116614b3361049861049860025460018060a01b031690565b14614a5d565b95614b45848289615599565b96614b5b87614b538a611791565b511115614baa565b614b7a614b6a61042383611791565b614b738a611791565b51906137e3565b6152c3565b1015614bef565b614b8f82611791565b518111614b9a575090565b611d1161039f9161213f84611791565b15614bb157565b60405162461bcd60e51b8152602060048201526016602482015275115610d154d4d2559157d25394155517d05353d5539560521b6044820152606490fd5b15614bf657565b60405162461bcd60e51b815260206004820152600e60248201526d1253959053125117d3d55514155560921b6044820152606490fd5b93909282614c59916149bc60018060a01b03614c4d81600654161515614929565b61499a61042385611791565b90614c6e6104986104986104238551876117c3565b6040516370a0823160e01b8082526001600160a01b0384166004830152602095919392918685602481865afa95861561068957614cf29988965f98614d36575b50918183614cc6614ccb956135556104238b97611791565b615436565b506040519081526001600160a01b0390921660048301529095869190829081906024820190565b03915afa801561068957611e2294614d12935f92614d19575b50506118a4565b1015614a9a565b614d2f9250803d106106825761067381836102e3565b5f80614d0b565b614ccb9391985091614d558793893d8b116106825761067381836102e3565b9891935091614cae565b6001600160a01b03811660018103614d775750503390565b60020361039f57503090565b614d999391949260095561096461095f866145f6565b90919015614dca5750614dab90612855565b03614db857600854600955565b604051636a70124760e11b8152600490fd5b614dd49150612855565b614dab565b15614de057565b60405162461bcd60e51b8152602060048201526013602482015272151bdbd7d31a5d1d1b1957d49958d95a5d9959606a1b6044820152606490fd5b909192614e2f61049861049886518761508e565b6040516370a0823160e01b815230600482015290602090829060249082905afa91821561068957614e9f92614e91925f91614eed575b50939291905b614e7a604288511015956145f6565b8515614ee757305b614e8b89615704565b916146e3565b90919015614ee05750612855565b9115614ebf57614e91614e9f913090614eb787614559565b929190614e6b565b50611e229250908110159081614ed6575b50614dd9565b905015155f614ed0565b9050612855565b84614e82565b614f06915060203d6020116106825761067381836102e3565b5f614e65565b9390919293600160ff1b8314614f34575b90614e91614e9f91614e7a604288511015956145f6565b9150614f4761049861049886518761508e565b6040516370a0823160e01b815230600482015290602090829060249082905afa91821561068957614e9f92614e91925f91614f88575b509391509150614f1d565b614fa1915060203d6020116106825761067381836102e3565b5f614f7d565b5f80809381935af115614fb657565b60405162461bcd60e51b815260206004820152601360248201527211551217d514905394d1915497d19052531151606a1b6044820152606490fd5b5f91826044926020956040519363a9059cbb60e01b8552600485015260248401525af13d15601f3d1160015f51141617161561502957565b60405162461bcd60e51b815260206004820152600f60248201526e1514905394d1915497d19052531151608a1b6044820152606490fd5b60ff5f805160206157338339815191525460401c161561507c57565b604051631afcd79f60e31b8152600490fd5b9060141161440c576014015190565b9091906001600160a01b0316806150b85750611e2291614fa7565b600160ff1b82146150ce575b91611e2292614ff1565b6040516370a0823160e01b815230600482015292909150602083602481855afa801561068957611e22935f91615109575b50919092506150c4565b615122915060203d6020116106825761067381836102e3565b5f6150ff565b9081608091031261026257606060405191615142836102ad565b61514b81612ca5565b8352602081015161515b8161031b565b6020840152604081015161516e81610fb2565b6040840152015161517e81610fb2565b606082015290565b9091615192825161196c565b6151a161049861042386611791565b6006545f9591906151ba906001600160a01b0316610498565b905b83518710156152a55761522690966151dd61049861042360018401876117c3565b80986151e9838a6117c3565b5160405163704037bd60e01b81526001600160a01b0392831660048201529290911660248301526044820152916080908190849081906064820190565b0381875afa801561068957610498602061525892600196615271955f92615278575b505001516001600160a01b031690565b61526283886117c3565b6001600160a01b039091169052565b01956151bc565b6152979250803d1061529e575b61528f81836102e3565b810190615128565b5f80615248565b503d615285565b50505092509050565b90816020910312610262575161039f8161031b565b9092916152cf84611791565b505f805b835182101561542f57506152ea61042382856117c3565b600182016152fb61042382896117c3565b90855181145f1461541d5750835b60408051633684184360e21b81526001600160a01b03946004946020949293918716919085858881865afa9081156106895788615383995f93899885916153f0575b50945163029e02cd60e51b815294169116149682018781526001600160a01b0394909416602085015290968793849291839160400190565b03925af18015610689576001936153af925f926153d3575b5050906001600160801b0382169160801c90565b906001600160801b03925f146153ca575016915b01906152d3565b905016916153c3565b6153e99250803d106106825761067381836102e3565b5f8061539b565b6154109150893d8b11615416575b61540881836102e3565b8101906152ae565b5f61534b565b503d6153fe565b61042361542a91876117c3565b615309565b9450505050565b939290919361544485611791565b505f905b835182101561542f575061545f61042382856117c3565b6001820161547061042382896117c3565b90855181145f146155465750835b60408051633684184360e21b81526001600160a01b03946004946020949293918716919085858881865afa90811561068957886154f7995f93899885916153f05750945163029e02cd60e51b815294169116149682018781526001600160a01b0394909416602085015290968793849291839160400190565b03925af1801561068957600193615522925f926153d3575050906001600160801b0382169160801c90565b906001600160801b03925f1461553d575016915b0190615448565b90501691615536565b61042361555391876117c3565b61547e565b51906001600160801b038216820361026257565b908160609103126102625761558081615558565b9161039f604061559260208501615558565b9301615558565b909291926155a7815161196c565b936155b38351866117c3565b528151805b6155c157505050565b6155d66104236155d083611872565b846117c3565b906155ec610498610498610423611a0f85611872565b916156076155fa83896117c3565b516001600160801b031690565b604080516305e8746d60e01b815290946020939260049285818581865afa9081156106895761567d965f926156e7575b505060018060a01b03975195869485938493630abcd78360e41b85528b60609c8d9a169116149184019092916020906001600160801b0360408401951683521515910152565b03915afa908115610689576156b2935f926156b8575b5050611a8b6001600160801b036156a984611872565b921691886117c3565b806155b8565b6156d79250803d106156e0575b6156cf81836102e3565b81019061556c565b50505f80615693565b503d6156c5565b6156fd9250803d106154165761540881836102e3565b5f80615637565b90602b8251106145d257602b60405192600b810151600b8501520151602b830152602b82526060820160405256fef0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00a2646970667358221220acaf04ce472be82b53e96d88f0fb25b098e6c53619924b6a8555e62e55080e5d64736f6c6343000819003300582103175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9054fffd8963efd1fc6a506488495d951d5263988d2600582103e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af05820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0058210366cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c68804227100058210365a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a804501000276a300582103f6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c704101005821033f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee305820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0219b44105581d03639c64228440f4bd2ae46d5b506813ab36fd9137777c62afe5d42f1007011957880219040205581d02876caffad5ef4c51b4d7ef6ea839513ae3eeee534ef47110ec28049504050219400805581e03f47119f7136b6a1def201d85e900331cd238747bc8c56c3c6e6463e7f0040105581e03311fc32da51f90af9ee044ac2e8c9c108c54017da6dd8b9e59106bc2a0040103ce33220d5c7f0d09d75ceff76c05863c5e7d6e801c70dfe7d5d45d4c44e806540306b487d15c028b6df56c3ebb9b7086965eba3a240857a647faece2ff13269f2b05581e03f3dd9015331d033b99d51b3f932aa7d589333cffd3d5c3281757b40b9007011bffffffffffffffff05581e03498ad85465a6d9a87a248ecba19ca612a0bc6e9eaae5da1c26f895fdc00c014709865bab2c88db0219a980039e26cdfb2a9ef661c97ac577b5207d516c184ae68b63335b6d3aaea2a72fecb50304e047bddcc986b3e4762c0b8f8a30b1c2c1bd53d7c959886b27d01c14c5957903ca665c98bdf67c1125f0bedd8d8fd52675bde6f425d81e4d230df5613f775ca703d49702deef497ce5e14f1897dec381593ecd0ee7550f73dc9e69d7eff813644d03457919256d8fd435855f2f60a1b6d5c0c602cb85b7dbeb1bb5793b9001e5b6fc03fd9b6e85a3740c518b284ce1c741f50380f55d7e834f652157495486cfe2ba4003cbe85ca4262ccf78f464d42fe901642b5bb184d1d7f88e69d137178ab9e69dbd03ab9c6524b81a15e0a09ceade91a4d7906800fe4651a203dd36398a44bbfd362d03c6485d885a74cceb4aa4805afe93b9ec4fac2e7dec21ea6618174c100e3dd62003af9497edc61cb8bbf853ac85e79dbdd55239cb7dac9440159d685b462b2ee58303adf802caed32458eecb623c177f75a4684ddafae4f24940323d04b7898dddf7a030881ae0052e0b541d7ee6705433d1a3a943692cc1fb773f54261e6fe25c2b27803725840d94797b3d2448b9513cd102ef6ba6d97a9d6c346311f39dbd5e4aeb45e030e007fd342e7c666da57b5a55e785a48b3e7f54346c807bf4131d18963bd5b700219ffff03dbb2743e1bf3bc70ec684efe001f85c977d856e3f6a407d871f1ad87b3427b7103a6dd7d23742fc177717871d75110b6bc48a4c5007a65ef9a978a6f7ea4e7bd810353c8e4f984c85f15cc08a12ff9dff4ff696c9049e3e3a31283a10c361c6c650c03820e7d036d5c74cc931cdf9bdc4662679fee6bcc9c36a9ec949edfbe708735bd03c2b111442e029426523cd89ead01bfcadf50af753cdef5b369fff72116a7289a0315ec50a03cba12c11b8283b52bc0a3382c94d90ef45268c9109cc46096683094032f7cfc2cfa8cbcec6764b6361d8cb03f92f4d385ad5075e338bf0cc0d767e33403ec9f544fd94a9f85057cdb333a5ef60bba2c00741b236392b85879ef7dce8a000377df763b2775ab1a67a1c2cce7abc97ead79f630416f12af3966fc1af62bc51f0390820b770c5afe59f8ab4997a135077d606718156a330b96b938c8e1bddad9c60219ffff0351d06600fc48aa1cd8ccf461876591ed92572f5964e3a794906cd5a5a736211903346824faa70f9045d88a7a340f4e1fdab02df5564c33dc3cbee2fff33a198d2a03b4920daaccf096345811a281e9d8f1ceddc1f72c59fadfada053dad0eb43ffcf038838b0e1e40bdbb5a40ac9330ff369f3ba3ce1da08f02fc5e8c1a9c3a06ad95c03be603f93cb67235b0ce40ed9e09d63516e5fa4c84529b552aa1c3baeacbfa08b03cb23c6b8674d6201f953d7f57495d4ac36d5711d55b06588d43f57e5c97290c2031d340b453d99e40ede024baffab1b3b32d672e8cfafd6a401aa5e872cbbbed4d0219ffff031cb713d5b98e1fd9c1c7d0457ed03cff2dd309134fc30bc8cc69e9f393084d9d0385be28acd8e5a9ac187b3f46965870433b710cf2fd00e78f4cfdf0e4886e3fa2030f2904bcfcd2e5b65894e512aab57a9633ff6715aa0233c6be0a20f5a70e846a03209df946dae28f680b7fe35f69b0a206f873dc893ce7818063c24e8d39625ee5033808177eb7739f2a0d707a72341461c975b76bbc7ac89b4b3993a940482aceb6033f09cbe6400a6156d36256fcaf08a3c450fed265a9aa9c252cc651e5398497ea0351dcde93059a13be750adcfaeb86176c0cd7a1cf4f00de3b0b067b938c3131550219ffff036f4ba5041ec34e5ad20f27343109bdab7e03181a84f94d4f44b4b66a1177bdc803578a82a14b10cc29aafba9da1ef8327d17b608a8d510c169a1bff286ab632b710382da591b4c27bdc67233a56e852f47d5c0d4adeb12dd559102e6aa521bc6691803d1eccfc39d03774334ee2c11cbddf526ecad8efa1ece517f490e30685df431e3037e886dcd44962845861636005fe9774ebe38db969fe11b802e56dbcb22f921f003f851851f31bd96d86410a5b5fbc002d49c8557d0b4171cb5b2a3d74540d05453035b494d9fa637b4c8ea2d0244da4c4ef964a3ce23faf4aeb344ead56fe6872e450331df86174d19d4f7a64b9feea449f19c426a4076b9e72652ac4ef8be5923b5d40325b24749d38072352205cad7c630332903e58e756168fe462f7cd0f10d1e6ec8037abc4bc4286b5bfe55d54a4cac58aa665ff4c70a8566a22d13dc3e3bf200689203f00096d9fd850394917bc6dea78f91d7601842300ecbaf870d2ea548c8364b0603d8c4aa053640ac090fe91eb500c8d979ff8658c78d5659f52df9ba2d52d742d8034b0771f7765d87278dc4243edae494339a3211ba0d6f11452a95bce27499038003b4771969c58afb00b26c2fd23d3a04e22404ea4d643ff920dd5852aefd35286903038958194ff4630a736dd175b0c460e04e30410a1d6b947062c500b390fdad4e036b002fa1a4ecc0c112680eb6d11f538f5f02dcedcdf8a1fa01b8357dda386e77033e3f07336025fd4faedd4d09468c2281ad5810c403517ec9c30ab7c8bbbf75d803fcad8c671df6139a9b2af47a7ad69f047853c1e7a77a4990fdbe9eb53c7f776703ce158ad80e548c8b6f1d7846737ea80f113648bd73f24c173b4ec7dea3fbf4ea03e5b8b5b1176af1d8f6f0348c563c6ba6e4a4cd1bd905aeef0febfee5e1b7463f0328d401030f733cc3ec4c649749033a6d406faa768de36df982bb0e2b6383c5830398655926d7ec1914d9e02e5fd3fb4436eae06ed55d9d5bda4600a5a3c5819d5f03e4da7a497d1a5d6f2824d5de0783a0600453332912f016e2a60c1afed965ccbe03cfad1eb775484093d3914942ed25151ef7f4e67a0261e3f465f339412d38ca900311628d4726a3f4be18ddd5e6d05949603b84a606dafd51f00a1a9facc14c2065033543d40fe32c46898ef35a743f3eccdeab1dc211a23d30349e922350e738f11803ff4cfd8df78f09b601b5032fafdf54f3a6010c03c4024e180874a24fe63a5aef0308e646de1b8558bc5846be79f3c190dc2f593cdb3161f4f3a28784955db6771d035c6720265557f1d838e27be1d70a2f950b60baaf30b6da4eba698d03c34b224803a3a2fe05c989e1ca1062377d9e3f4d56c452a9e9aa2e88e16710b5355fdcbbcb0326258930d31c13a1ed017f9b332840e789d87f6209b3585a784297e86c55d12d037515a29bf2dc47f757037ba92bdaedb6f85aeea0a5e9d0ebaa7afbbf17ad938b03842e936ef2394185320732470844ce9546c1935c32f29bab348a71b5b8fbf0b003193cbc5748bddbb8a3a364da1b75ac7d0e75dc1438ab8e14ecfd834fe263c4f403ffd76237e5d0435f54c7d362aac2a7be8f4933100b377b4205dd07771ed98578032ee739d643abf9b49d0ddcde51b77162eb24bd02f4d8b617230f3ab71c3913eb032c5dc9bf165128100881a37ad4f837cf2c145d9a98c29c4be1391bfece277f2e0353b4fde41e241e84b23a7072a31be33cd65252095bf7518f752ea6e606ce3a880391656aa50dac0aa20b0dcc02e5e99ee0bcfe24c38a618478526cecab21720da903836fc9c38c897e40706b47461eba74da50399967facda16b47e1e7ec51fca68803b8b6b9c1e9f10008e1d55ae7a37244c2557a929428431c7e737ac3cdc232fc8a03361df2be3bb3ebe5a894fc6d1632b0e468ce16a7de2e9999bde2e99a054e462d05581e03305b479751ece2c9cc059886acbc618492e01d5438e8b490a6502322c00c1830480449f29074f6a3ab05581e0370f02829d676e57d162950596ed9ab0ffa4802c4cb4e65f13a73ec35b0040603cd43772fe60435cccc44d0b0152be3399b3cbb95ae3dc6b4658d5951e95fa3e8031e71c9a529b6857d946cd4e6ec6ea3b9c22022dc8be98c1eae00747edec6499503b236bd75c9c21bddb287d6a6925dc0d658aed590da7301e4e11ad7e815d9f6f4045920a26080604052600436106101635760003560e01c80637ecac20b116100d1578063b9b4aacd1161008a578063e98d8c3a11610064578063e98d8c3a1461040e578063ed83cd0b1461042e578063fa461e331461044e578063ff04e9b41461046e57600080fd5b8063b9b4aacd146103ae578063c7137f5e146103ce578063e1477062146103ee57600080fd5b80637ecac20b14610313578063824a811d146103335780638d690bab1461033b578063999895db1461035b578063a00000001461037b578063b8443aa71461038e57600080fd5b80632de6ca25116101235780632de6ca251461025b5780633c0000001461027b5780633ccfd60b1461028e5780634b849261146102a357806353b03a83146102d35780635ca7ab59146102f357600080fd5b8060021461016f5780606914610184578062f750e2146101975780630b93a9de146101db57806315e2b6cd1461020b5780631fce50961461022b57600080fd5b3661016a57005b600080fd5b61018261017d366004611926565b61048e565b005b61018261019236600461198d565b610538565b3480156101a357600080fd5b506101c76101b23660046119ea565b60046020526000908152604090205460ff1681565b604051901515815260200160405180910390f35b3480156101e757600080fd5b506101c76101f63660046119ea565b60026020526000908152604090205460ff1681565b34801561021757600080fd5b506101826102263660046119ea565b6105cd565b34801561023757600080fd5b506101c76102463660046119ea565b60036020526000908152604090205460ff1681565b34801561026757600080fd5b50610182610276366004611a0e565b610618565b610182610289366004611a27565b6106aa565b34801561029a57600080fd5b5061018261073f565b3480156102af57600080fd5b506101c76102be3660046119ea565b60016020526000908152604090205460ff1681565b3480156102df57600080fd5b506101826102ee3660046119ea565b6107df565b3480156102ff57600080fd5b5061018261030e366004611ace565b61082a565b34801561031f57600080fd5b5061018261032e3660046119ea565b6109c5565b610182610a10565b34801561034757600080fd5b506101826103563660046119ea565b610a7a565b34801561036757600080fd5b506101826103763660046119ea565b610acc565b61018261038936600461198d565b610b17565b34801561039a57600080fd5b506101826103a93660046119ea565b610bae565b3480156103ba57600080fd5b506101826103c93660046119ea565b610be1565b3480156103da57600080fd5b506101826103e9366004611b3a565b610c14565b3480156103fa57600080fd5b50610182610409366004611b66565b610d6f565b34801561041a57600080fd5b506101826104293660046119ea565b610e54565b34801561043a57600080fd5b506101826104493660046119ea565b610ea6565b34801561045a57600080fd5b50610182610469366004611ba7565b610ed9565b34801561047a57600080fd5b50610182610489366004611c27565b610ee7565b3443146104b65760405162461bcd60e51b81526004016104ad90611cc1565b60405180910390fd5b33600090815260016020526040902054606086901c9060ff166104eb5760405162461bcd60e51b81526004016104ad90611cdc565b6001600160a01b03811660009081526003602052604090205460ff166105235760405162461bcd60e51b81526004016104ad90611cf7565b6105308686868686610fe2565b505050505050565b3443146105575760405162461bcd60e51b81526004016104ad90611cc1565b33600090815260016020526040902054859060ff166105885760405162461bcd60e51b81526004016104ad90611cdc565b6001600160a01b03811660009081526003602052604090205460ff166105c05760405162461bcd60e51b81526004016104ad90611cf7565b6105308686868686611151565b6000546001600160a01b031633146105f75760405162461bcd60e51b81526004016104ad90611d12565b6001600160a01b03166000908152600260205260409020805460ff19169055565b3360009081526002602052604090205460ff166106475760405162461bcd60e51b81526004016104ad90611d2d565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b031663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b15801561069657600080fd5b505af1158015610530573d6000803e3d6000fd5b3443146106c95760405162461bcd60e51b81526004016104ad90611cc1565b33600090815260016020526040902054859060ff166106fa5760405162461bcd60e51b81526004016104ad90611cdc565b6001600160a01b03811660009081526003602052604090205460ff166107325760405162461bcd60e51b81526004016104ad90611cf7565b6105308686868686611399565b6000546001600160a01b031633146107695760405162461bcd60e51b81526004016104ad90611d12565b600080546040516001600160a01b039091169047908381818185875af1925050503d80600081146107b6576040519150601f19603f3d011682016040523d82523d6000602084013e6107bb565b606091505b50509050806107dc5760405162461bcd60e51b81526004016104ad90611d48565b50565b6000546001600160a01b031633146108095760405162461bcd60e51b81526004016104ad90611d12565b6001600160a01b03166000908152600160205260409020805460ff19169055565b6000546001600160a01b031633146108545760405162461bcd60e51b81526004016104ad90611d12565b82811461086057600080fd5b6000805b8281101561089a5783838281811061087e5761087e611d63565b90506020020135826108909190611d8f565b9150600101610864565b50604051632e1a7d4d60e01b81526004810182905273c02aaa39b223fe8d0a0e5c4f27ead9083c756cc290632e1a7d4d90602401600060405180830381600087803b1580156108e857600080fd5b505af11580156108fc573d6000803e3d6000fd5b5050505060005b8281101561053057600086868381811061091f5761091f611d63565b905060200201602081019061093491906119ea565b6001600160a01b031685858481811061094f5761094f611d63565b9050602002013560405160006040518083038185875af1925050503d8060008114610996576040519150601f19603f3d011682016040523d82523d6000602084013e61099b565b606091505b50509050806109bc5760405162461bcd60e51b81526004016104ad90611d48565b50600101610903565b6000546001600160a01b031633146109ef5760405162461bcd60e51b81526004016104ad90611d12565b6001600160a01b03166000908152600360205260409020805460ff19169055565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b031663d0e30db0346040518263ffffffff1660e01b81526004016000604051808303818588803b158015610a5f57600080fd5b505af1158015610a73573d6000803e3d6000fd5b5050505050565b6000546001600160a01b03163314610aa45760405162461bcd60e51b81526004016104ad90611d12565b6107dc816001600160a01b03166000908152600460205260409020805460ff19166001179055565b6000546001600160a01b03163314610af65760405162461bcd60e51b81526004016104ad90611d12565b6001600160a01b03166000908152600460205260409020805460ff19169055565b344314610b365760405162461bcd60e51b81526004016104ad90611cc1565b33600090815260016020526040902054859060ff16610b675760405162461bcd60e51b81526004016104ad90611cdc565b6001600160a01b03811660009081526003602052604090205460ff16610b9f5760405162461bcd60e51b81526004016104ad90611cf7565b61053060058787878787611492565b6000546001600160a01b03163314610bd85760405162461bcd60e51b81526004016104ad90611d12565b6107dc81611675565b6000546001600160a01b03163314610c0b5760405162461bcd60e51b81526004016104ad90611d12565b6107dc81611699565b33600090815260026020526040902054829060ff16610c455760405162461bcd60e51b81526004016104ad90611d2d565b6001600160a01b03811660009081526004602052604090205460ff16610c915760405162461bcd60e51b81526020600482015260016024820152601d60fa1b60448201526064016104ad565b604051632e1a7d4d60e01b81526004810183905273c02aaa39b223fe8d0a0e5c4f27ead9083c756cc290632e1a7d4d90602401600060405180830381600087803b158015610cde57600080fd5b505af1158015610cf2573d6000803e3d6000fd5b505050506000836001600160a01b03168360405160006040518083038185875af1925050503d8060008114610d43576040519150601f19603f3d011682016040523d82523d6000602084013e610d48565b606091505b5050905080610d695760405162461bcd60e51b81526004016104ad90611d48565b50505050565b33600090815260026020526040902054839060ff16610da05760405162461bcd60e51b81526004016104ad90611d2d565b6001600160a01b03811660009081526004602052604090205460ff16610dec5760405162461bcd60e51b81526020600482015260016024820152601d60fa1b60448201526064016104ad565b60405163a9059cbb60e01b81526001600160a01b0385811660048301526024820184905284169063a9059cbb90604401600060405180830381600087803b158015610e3657600080fd5b505af1158015610e4a573d6000803e3d6000fd5b5050505050505050565b6000546001600160a01b03163314610e7e5760405162461bcd60e51b81526004016104ad90611d12565b6107dc816001600160a01b03166000908152600260205260409020805460ff19166001179055565b6000546001600160a01b03163314610ed05760405162461bcd60e51b81526004016104ad90611d12565b6107dc816116e8565b610d6960058585858561170f565b6000546001600160a01b03163314610f115760405162461bcd60e51b81526004016104ad90611d12565b60005b85811015610f5357610f4b878783818110610f3157610f31611d63565b9050602002016020810190610f4691906119ea565b611699565b600101610f14565b5060005b83811015610f9657610f8e858583818110610f7457610f74611d63565b9050602002016020810190610f8991906119ea565b611675565b600101610f57565b5060005b81811015610fd957610fd1838383818110610fb757610fb7611d63565b9050602002016020810190610fcc91906119ea565b6116e8565b600101610f9a565b50505050505050565b6001600160a01b03841663095ea7b373ba12222222228d8ba445958a75a0704d566bf2c8611011856001611d8f565b6040516001600160e01b031960e085901b1681526001600160a01b0390921660048301526024820152604401600060405180830381600087803b15801561105757600080fd5b505af115801561106b573d6000803e3d6000fd5b50506040805160c081018252888152600060208083018290526001600160a01b03808b1684860152891660608085019190915260808085018a90528551808401875284815260a086015285519081018652308082529281018490528086019290925281019190915291516352bbbe2960e01b815273ba12222222228d8ba445958a75a0704d566bf2c894506352bbbe29935061110e929086904290600401611dee565b6020604051808303816000875af115801561112d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105309190611ecc565b600080869050600080826001600160a01b0316630902f1ac6040518163ffffffff1660e01b8152600401606060405180830381865afa158015611198573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111bc9190611efc565b509150915087156111eb576111e487826001600160701b0316846001600160701b03166118c2565b935061120b565b61120887836001600160701b0316836001600160701b03166118c2565b93505b8584101561123f5760405162461bcd60e51b81526020600482015260016024820152603760f91b60448201526064016104ad565b60405163a9059cbb60e01b81526001600160a01b038a811660048301526024820189905286919082169063a9059cbb90604401600060405180830381600087803b15801561128c57600080fd5b505af11580156112a0573d6000803e3d6000fd5b50505050881561131e57604080516020810182526000808252915163022c0d9f60e01b81526001600160a01b0387169263022c0d9f926112e7928a92913091600401611f4c565b600060405180830381600087803b15801561130157600080fd5b505af1158015611315573d6000803e3d6000fd5b5050505061138d565b604080516020810182526000808252915163022c0d9f60e01b81526001600160a01b0387169263022c0d9f9261135a928a913091600401611f4c565b600060405180830381600087803b15801561137457600080fd5b505af1158015611388573d6000803e3d6000fd5b505050505b50505050505050505050565b6001600160a01b03841663095ea7b3866113b4866001611d8f565b6040516001600160e01b031960e085901b1681526001600160a01b0390921660048301526024820152604401600060405180830381600087803b1580156113fa57600080fd5b505af115801561140e573d6000803e3d6000fd5b5050604051638201aa3f60e01b81526001600160a01b03878116600483015260248201879052858116604483015260648201859052600019608483015288169250638201aa3f915060a401600060405180830381600087803b15801561147357600080fd5b505af1158015611487573d6000803e3d6000fd5b505050505050505050565b6001860180546001600160a01b0319166001600160a01b0387169081179091558387558590600090819063128acb08308989816114e35773fffd8963efd1fc6a506488495d951d5263988d256114ea565b6401000276a45b604080516001600160a01b038c1660208201528e15158183015281518082038301815260608201928390526001600160e01b031960e089901b16909252611538959493929190606401611f83565b60408051808303816000875af1158015611556573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061157a9190611fbe565b60018b015491935091506001600160a01b031630146115bf5760405162461bcd60e51b81526020600482015260016024820152600f60fb1b60448201526064016104ad565b8854156115f25760405162461bcd60e51b81526020600482015260016024820152607960f81b60448201526064016104ad565b8615611639578461160282611fe2565b12156116345760405162461bcd60e51b81526020600482015260016024820152606d60f81b60448201526064016104ad565b611487565b8461164383611fe2565b12156114875760405162461bcd60e51b81526020600482015260016024820152604d60f81b60448201526064016104ad565b6001600160a01b03166000908152600360205260409020805460ff19166001179055565b60005460405163095ea7b360e01b81526001600160a01b03918216600482015260001960248201529082169063095ea7b390604401600060405180830381600087803b158015610a5f57600080fd5b6001600160a01b03166000908152600160208190526040909120805460ff19169091179055565b60018501546001600160a01b031633146117515760405162461bcd60e51b81526020600482015260036024820152623bba3360e91b60448201526064016104ad565b6001850180546001600160a01b0319163017905560008061177483850185611ffe565b91509150801561181e57865486146117b25760405162461bcd60e51b81526020600482015260016024820152606f60f81b60448201526064016104ad565b6000875560405163a9059cbb60e01b81523360048201526024810187905282906001600160a01b0382169063a9059cbb90604401600060405180830381600087803b15801561180057600080fd5b505af1158015611814573d6000803e3d6000fd5b5050505050610fd9565b865485146118525760405162461bcd60e51b81526020600482015260016024820152606f60f81b60448201526064016104ad565b6000875560405163a9059cbb60e01b81523360048201526024810186905282906001600160a01b0382169063a9059cbb90604401600060405180830381600087803b1580156118a057600080fd5b505af11580156118b4573d6000803e3d6000fd5b505050505050505050505050565b6000806118d1856103e5612033565b905060006118df8483612033565b90506000826118f0876103e8612033565b6118fa9190611d8f565b9050611906818361204a565b979650505050505050565b6001600160a01b03811681146107dc57600080fd5b600080600080600060a0868803121561193e57600080fd5b85359450602086013561195081611911565b9350604086013561196081611911565b94979396509394606081013594506080013592915050565b8035801515811461198857600080fd5b919050565b600080600080600060a086880312156119a557600080fd5b85356119b081611911565b94506119be60208701611978565b9350604086013592506060860135915060808601356119dc81611911565b809150509295509295909350565b6000602082840312156119fc57600080fd5b8135611a0781611911565b9392505050565b600060208284031215611a2057600080fd5b5035919050565b600080600080600060a08688031215611a3f57600080fd5b8535611a4a81611911565b94506020860135611a5a81611911565b9350604086013592506060860135611a7181611911565b949793965091946080013592915050565b60008083601f840112611a9457600080fd5b50813567ffffffffffffffff811115611aac57600080fd5b6020830191508360208260051b8501011115611ac757600080fd5b9250929050565b60008060008060408587031215611ae457600080fd5b843567ffffffffffffffff80821115611afc57600080fd5b611b0888838901611a82565b90965094506020870135915080821115611b2157600080fd5b50611b2e87828801611a82565b95989497509550505050565b60008060408385031215611b4d57600080fd5b8235611b5881611911565b946020939093013593505050565b600080600060608486031215611b7b57600080fd5b8335611b8681611911565b92506020840135611b9681611911565b929592945050506040919091013590565b60008060008060608587031215611bbd57600080fd5b8435935060208501359250604085013567ffffffffffffffff80821115611be357600080fd5b818701915087601f830112611bf757600080fd5b813581811115611c0657600080fd5b886020828501011115611c1857600080fd5b95989497505060200194505050565b60008060008060008060608789031215611c4057600080fd5b863567ffffffffffffffff80821115611c5857600080fd5b611c648a838b01611a82565b90985096506020890135915080821115611c7d57600080fd5b611c898a838b01611a82565b90965094506040890135915080821115611ca257600080fd5b50611caf89828a01611a82565b979a9699509497509295939492505050565b6020808252600190820152603160f91b604082015260600190565b6020808252600190820152603f60f81b604082015260600190565b6020808252600190820152606160f81b604082015260600190565b6020808252600190820152602160f81b604082015260600190565b6020808252600190820152603b60f81b604082015260600190565b6020808252600190820152603360f91b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b80820180821115611da257611da2611d79565b92915050565b6000815180845260005b81811015611dce57602081850181015186830182015201611db2565b506000602082860101526020601f19601f83011685010191505092915050565b60e08152845160e08201526000602086015160028110611e1e57634e487b7160e01b600052602160045260246000fd5b61010083015260408601516001600160a01b03166101208301526060860151611e536101408401826001600160a01b03169052565b50608086015161016083015260a086015160c0610180840152611e7a6101a0840182611da8565b915050611eba602083018680516001600160a01b039081168352602080830151151590840152604080830151909116908301526060908101511515910152565b60a082019390935260c0015292915050565b600060208284031215611ede57600080fd5b5051919050565b80516001600160701b038116811461198857600080fd5b600080600060608486031215611f1157600080fd5b611f1a84611ee5565b9250611f2860208501611ee5565b9150604084015163ffffffff81168114611f4157600080fd5b809150509250925092565b84815283602082015260018060a01b0383166040820152608060608201526000611f796080830184611da8565b9695505050505050565b6001600160a01b0386811682528515156020830152604082018590528316606082015260a06080820181905260009061190690830184611da8565b60008060408385031215611fd157600080fd5b505080516020909101519092909150565b6000600160ff1b8201611ff757611ff7611d79565b5060000390565b6000806040838503121561201157600080fd5b823561201c81611911565b915061202a60208401611978565b90509250929050565b8082028115828204841417611da257611da2611d79565b60008261206757634e487b7160e01b600052601260045260246000fd5b50049056fea2646970667358221220ef166859aad688c15a88e7b99652e092c91187ac153848039b87fb8c8e2ecfc164736f6c63430008180033005820026004cfce3b37ce513505e385c405a0515ee2f102a9e81b6ee5d7917596f3df4101031fc2ceb0c5a6717734fbdf09556818a7c300f926f50b0d34516f89f6fb8b174100582002a6dcd8587eef6abc082ae820eaf38e7fb33bb7f97add8191795a4c6b03f5d34101005820020e12aa90c5cf8a7ac579a1f2ee089db91c28b07224f1874f5ff480367a4235410100582002cc33c39bd122732ec380f9d24244a3448640b3c45c025f4b51d453d2db48e441010219326003f112aa86d947066596cdaf666e6cc4b939536e3d050c114c5b291864246016b103c1ae8d2b41def7f89cd1fe0f66830adcd5f3177e32acb65dd802c02f7f940a9903eece07e57ceb3321dd0c23f26582069c7748279ba48f4cfbdf3cc5af9b119cc503618b138891c164ca992f56a9cfe0c2fc4f1badb2cd4f9f616d5818a1d852c91a03fd352e447b7b7c66c3751a5aa2818e8f3f71f1d9d44fe7c99ab9c3857e1285b1005820021f2fae2bc1ccdd9dc943ff851fe8f85b2eea0899206cca942a2aa37b6651eb4101030be603ef75e68a5639064ec61f827a0256ddc6d5ee71ece143c0dbc6a1820dc20058200247d6771a3ec964564328eb0da7eb6220a120cb587f3a3bba9546fc97152bb0410100582003cdfb8b1afd2d22a55e76d3848c15db11fde0e7bf5d02600d7fdf983d7a7ed0410100582003517f7a2c9dff9056bcf0bae4fa15190b64d93b3c3b93241fe115a65698ab504101021830021910e003b92aa65ab50ce38cde2f5b8dca3fc97560b799691106b2b22c9dd05714fd25ba0058200202eeb219247b01c8162aa97ca9dad7fe28fe9def6ea389cb95b204325054d7410100582002421fc8d3399966045baaf47840fd4c9f889317118e2854843b51fb97ddbfd641010355e7e561b544a8f8312f83e92090b8af73510e4183f802b04fd09512adb2c6da005820026152fb80a5ed863adbf060e4d5adb4fb9f3f98aa726e5bdcf56461fd496cd3410103c0f2e1f0bc31085dc36008763b89e5466628f3e0be4e9d6c69c539dd249a47b3005820027cc3c3fd447f826218757cb48b4d1040a96e311003892e111ee5c53cf5a61841010219e2280332bdf4218a4603811ebadacd2833dc301d3850f3551cdf8e75d85ba7e23e05b003724ef44f4c56ede21026656715d5bfa79131a5b797fd65e0980196f68590ff8d03434da0408135d605bf70377c51ad2e76d34f746ac17442b0571d6b628abd68ac035d97b561ece55f9ef1609eace62f2941e2f92aa416246a4b57a5f1924b4c5dc403c45402b1843bee9c58e1836226c72152427d97e690d84773afeb222656944b2f03924e180db1059bafdbd296a7ebf839e54b302f7008e09c2f959fc32eb1b1f44600582002007d7329d500a1815ec60ae0ae8e7ad6796315fe2cc01046c31d6ea88683074101005820026d05a2b6acaedd4a24cfeaa8b351889e42ea768bd2396a4469c98e278d49b94101005820025318c4196a78ed62dd2a2d34ea5b6dc281c0e40ecf64137ec13ece332d6ca7410100582002c9938aa8c4853e9c4548a522f8409d0e629279fceb9a0676b98472cae7fa6341010058200252222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f549def7cde171841a9f0724124ca0b01a622d749e400582002355fbf3a861b29216af7c8b04d84b9a3aeb037dd215373a85a067ac488de9e410103f8b1af8c17be110ac6c57e4e44e458f84e13e8f2a6f8dc9ac6de43eda3626041005820023eecbf72238d4044508e4efbdc3e136491cae256a5fe6152f3785e02ff5e77410102190b760219ffff05581e03461caee77f54e513b75a4f22cac1f7ee05fb0864b0338411bd59100ed00f0146016a7e824a4f1920a20382d260f1773676c4984396308ac5564ae6774151a959dfdb2208dfbeb4c0232203be329ec0855c67a5c1d2e01b58817a72ad206aeb046eaa96ce891bdae415b8f803d406fa363d0ff4124598dfeefee08adda4c560e48f1ca64a236dd9630d79d7d102196e5c039913fe4d9543dc01c7e0be3ed08f41b6c8d4cf2df2d8ec9af3c056883d7c2c5d03b26a30d352d51e27bdd2e33ea52a809839b7180d2e77e2c4c686436af736200c03aa01603d0139f29720acb399eb6bd30498d5429240f8ba44df63689e7bb1b0ee0315e8a6fa84bfd2acb8eaaea996aa4a46f5de253a0ad609c7f0277ecd5b7d9ec9030db1b444fb36bd1b36ecb2c5be61f74fde9ba73348a2932cd59d6b5fa51af9c503e524d098d04850bd610cf78c96af74626f752666103b0ff686a912722a2821810219ffff034ab005e656dd7ee732bdda28ed82dc2fbf913b11ee168f711f3910a5e101ea850374468738d14c619e33326b3d60427f61e70e73df25c1c85f497c3b0e643c7487034e6b33247da26c37b793e07ab5e073144ff3aa78b59cc322177c1347ccd9011b0219ffff03d6151fe3141aab3766abfd8fb64f188228ab3a01860f94e5f3dae386759b05dd03b83b3078b341fc98d7e941955281275ee677b8ffce66db59a26316465ec016ab03d14ea7cbe8b9aea666c03cf632ae8fb5c6388563ee195c218fe16d32843f76fc03bd60b8c124f03a06a8d176bd2f5422afa15aaa90490c7c49148352d3e64046c10219ffff032b27f0606aea732db89223c448ef50051f7bcdacb533eff5931dfee79f4601af0384412a5b06bc923b7204cd68d0af8f2598fc1e6c90b33d58acdd25d3c747308b039dc97041e8fd84aa338de1fe436aec129d3aa5c0ec11f244775451e3896f3a770378f173b8a4d30a29eee9203ce1e1e54a6106dd9ed5ed4c17c2eae9b68dbd752a030306203e35608f1ae9e70be93c153097e81fa74bc30b2d4572a5300aa33c528b03d519e78944c791a6b7290e3615220d6180f5f3b4f988e74af50a6819b304ad0f03f13c094428bb853ec68e8805360b7bb054a9ef83e884875f09713245a2b9925c03f3adc317e7a0528ad864ef7449ddca3c709b121225f5bdb318043a76596b21fa03e998862be9e44d9272eb6ee0cd0245f6b290a0e68fec23df273ecf18aeecc2600219ffff03653cf990b312b48d3129a8e667459075cae0de8e23543a2a8f04af38a9cbd117038c69f7638729c2b2c4fd1579bb7c450a2e945b05ed09c4d5249e7709e66d92df033fa9f17da0dac9876f9aff16e95dfe253d755448e4b4f133f41349d4827853bb031fb9c175a369a789c26791dd78840a981848810e0d606590e26318e2dba6c38603e92dc4c06f0c8a2f57c0e636b1aef6c3c6b95ce37d2b022597e6d2c29865ea910366417b85fbf96957da56d9a89c5a56a5350800ae61a58765beacb5e7bfb074900219ffff03fcc036779558111dad829df80bdbf8e7ed86971f307eddb5faef9aa2001af1b603fdd49d71c61e97c8cc0eef9254598a4cbd34a908f3375712a90abfb6eaeb30a7037e392fad3f87ac3072039da5cbea2b99f2d10a01aa4d46cb1c81bd8c9bf77994037dab82c751f31533cec6a7f680f545f8bd290d1e62f1b78e3652f2c16c68c43d034413df840cbf29eab30fdd6c5925cf38775cc308be825933f0a4457e5bd8190003a6a403f1d17d616dc28a8fc1653934563fa05637e92170eaf48b3517f636cf4003dc70f577ad37b64c94b215e6cacc6bfc9b76d2069d31c06542739b67f639cf16032e7471c5bc98fbe799ca31f6e82685707e5c6dbd89de69dc16e102adfc152be8036d0931f6981b42fc72d928e9237be1f2817d88cd821dbfe1a67cb0c10f65b0eb03a605521d3ea657b84908918a62052fbb844c539ac5e1904c08746befea6ab877032b0aec36736378b182af8aa6cd22a57d90d9cda61259009045b3ce03a21f2b6f035f096515ad4d65439ea7972903d400af2ed81d8e9a03256ea82db5b186b18d5003cb4db9c3f9db9defe8097d397457551863ac85b7e57c1b2c6fe2f00f0f4761ef03b890b1d684bb97a7977117ba6e78bf90133399ca98a1469720c020d428a8fc61037838d9c0c5980c413be285b7a88c47d58965a4bdf13d6df39c793b8572f2804a0322d287c04885e4ae3b0c002c66fab5cab6c761c8e77f1edd85a19d7db20bd9890351d6789efc868d8d91a24e3199915266b865b02c9ccdaafbdb5adf131bfba228030009af2241f7a17fa0125b532b7e72bd234ab52ab142b16e48b6b4c2518908a3033d51ef1aae5244f85b1fd2d604d2384ffaa7f389f195ab60d86d7dcd0899ab57035b03faa8d3677bfe305dabae8a83dcf83d98ef31f57547fe27a7a500e862283003478aa63c14b12d7af2a5ea2cfbb0af6ebdb0717093bb82001ee5b17314506a7703469d53d574fdf8216720ecf1c26bc457b9f2eaac36c422333e6da374a5be39470316b4eab8973ba9c94d090c4fd40db16d269e32c4b02bfcf1318dcca6ff9c47560316c32aea618f4a35b9bacd455d0a305b960eb28b9795389ee85cd5ea9d18772f03f702531b8b946381db1496a22f719d224543c603ff44234a20f4095e2f95e2c703467c473c8c47f5d82542447d79eb8e60381bc8da41a35cc685cafb4c5e754243034938d492e7906f4db03687b5f21832b35d4df44bd658ed3b0d7ab61cac0bf8fd03a97b9a724ff5b1a1e90032b29559e5da78e98ae90a150b138bdb6685491caaef03a2d5d8f07c83ca3edb8ad79711009dff87c3369ef21c8744c7068c993a48300103aab9764f7d79b8bf52560f5511668cdee2004377a1ea8426701cfa57f05b712803f8d2c48807f0302e324a2ff68d95c5c068789c6ffa8cd022af4bc8704b162e72034fef6d2e108904e11c75633ac94c56ea2c34abd415af6321c9ddd9a79bfb4d1003a2b2260c7dcbc720ed260c92c78e6fd942cd64c323268de67352a5f8de28476b03b03f7536efeaec7d03c85344bd05b42863ffb786acfaedf0e8590a0ecefc952003b611868c281d92a921c9163f1f46adac9e6145f2fa830f494821d676aa9964ef0378803bbbd997283c5c1fc15dc2c5e4dbbd79bac491d993e1621c5358fc169bc603cde17225753c3b37029c26c020f00192de4316e059214e960daff2f16f8653b7037712d6452e2663dbf635f9e788c40977fa07dc920b9dcca7815d5c628bc3bd8e03900975d26c0b3d9a55e84b5100d675acff2b4bcc96fd7cededa6ae810a9043b905581e0331b9077f63e20374836787589d20ab56d308768baf6feb1b2dcb34d7600c024605774fea440003282c5a650368e9b0e7d24d5cdf495dbddf26f77f85fdd6edcaed272af7b35db9035fffd5d88254ce0e08c2302c2c557e343e6cef958b526ab8128e1a20f7ee041605581e0385609eb13c8028dfa4852fb063cfb8e9ae6dd4d43778d540f79800b0600c0b471136f25b00215c05581e030857a6b3d0e9d021a7e24cd58fd0180f851308386471fe7c0fa1131df00c0147177cfd930cba0005581e03f7b7f46b544bcdb050ae1cb9e4843c0c52f99f99213fffdd72c2bc1920040105581e03bded25abb9d9062e93b4b938e4342e381cdd0a9b160c1b6756e896b0200c0f472e0ee90acbfe0003941a28b98d7e7eb2c9d4860a46b4a1f0e3d8d7f5b8e7d66380aaa00245738eb20605581e037a692a51bd2764381b710aa6ee14f3b10a03c47a86d246d05dc0f4ef1007011bffffffffffffffff033fe61504a88f3baa02a7be9ed4728a11da5d0f028d0b73db886f87a9c482ec0105581e0364c13f862dadef046f86fd496b9b1360e13a3a65342770f96cbc2acce0040405581d02a7c06f685821c187259b57fa7d198c295f55a8fb9fb0f44704f3780f0c014704137d1495f02405581d02301e91dad0a99625ab7bac95e9e45e2e6c0681478c0559064f4789400c190ba34802c110f3cc0dbcac05581d025a3ac47fb477a04756ae6d09e974e9e149c86df42e72defac9deab540c034502540be40005581d022dad32b67f3b3ce215ea703599b5d2589505a10e3b92c0c4009de3c50c024701a14eb204aeb802191422036087c726410bace6cb756c82f9b320bf010d071292d2b80cce2bc0aee01728b505581e03ea2ccf104aa3c3bb8a661bb9bd6968b012c3094ac820522c26671449f00c01472e732851988bc00219fff503050c9d1709afb49f78147db39e560425f0f72da7f569698702d0d5a581548ae403c179e7694d3be61fa1f9319dcbf263c998adb8f36b519e1839e79862431ce20d037db25c6d5f953628d47d48c24582e72045d247fe69feab537e1a7d843fdca3b40383cf2c90e85255a4e09fbd9a658d3ec16407a2dfd86ff3746da792b8f6590b7603c23cc926ef7fa26faaf9688523547afdeb8893c5fb0f9d8d89e36b1f0caf29f103fb4a689725f146d33ab055975bf0c61e04e055ec367ee0cfe6e379be40c303a9034514fb4047115ea4be94dcff5c8631ef7bf69378de573168456d8d33a835410d03966ab5264ab6b1ee79f1ed13fd1932330e34dbb18c6046756e2aa0ba2b9e67be03528f9c0eb3ccabd7c5cab313c3d06216a88621f5d6cd8a3a2d697940ca53e79d038ff13f1dda9496f7520e2f12323102141a64c2df069e80548454033cdba2e8fa0219ffff034f7ed5868b4ee23d06f4589c1cf34ca1de570161402012a2a43a4929236b1bed038c462403accfc96a7dfae94395ca85df4ccdce971f5e400afb30679f71c7bf0503f032d0e0ef52bee499f897361f94d8c6c7a846a4cb90275c6e79c8447bc5d484032fba07432177953683b07cd48bdfc75c40619773ea628e2ab245260ca408f2fc03f635269f46f53f6c9f16d08a9011fa081b1bb8c6357cd6eeac1716c40895f5a00219ffff03be49b0a81eb9e4dfe5a6ee51c833400888b4d5aa20b4234ca3f3089192fe978d038cda71049b37ec7e57b9a3b6284d704309f284ec3ce61efc5c41edb161203f7a03b047f3417a84face987743331e0ac2c103c5874895c681a41da8edeeb8d36bb803f53e6adde8d814671ace27379317467e2b7bd7d978c5165387fb115d34e371f6034b6ee7d46f63af3adbeb98b2ab40ec52885cc22d8b1f180c1f1e37e8d7f7526d03d435ba4db19baa93bc2aa261861cb69de4302d2d8ae457df6de4b11938c8faaf0219ffff0314a43490ce217930ade1a451851a8309c9bb06327e03a532e968e6b8d252de7803e1d9bcf8dce50d1689556db5b80a475cfa4cf0612de71b16879e5486df480f9b03bc5f36bf257796f5abaa7058281a3b4f2befe46c05d83cefeba121b8d226ff69036ace07466894ed823acd5dcbb63ef8e697e33d512f3088bfd30e1406bb0c37580363ccacb83d51f763d50cdf67c5873146e208e269a66a4c33e9a59c64aee17ba403848ee16e47f93e687feb50e503df1df655e9667a38004b252c57ee2a28bc81e0031590fc8ff97576fae28a5df42747196765da859d1b5113024f27ba2b04ed963d03046651a3fdae9bf24c346ee2491878a1662d39f37c9a5131866e3a06c7ad851b0369b35143e0f448a43832ca39d2f700e63d22ee0fad299aeeb10d0e4d3b7943c403175f10d40bca2dacfc856d038d1be1af8c4b220f37be556e38856f252e2044db0219ffff0324c6aeba386774d1e8989c95fe3a60742c9fb86190dae4d47ac1544a70b43d3f03b1008494b77f7fcc99181392b436873fb9f07aabf3b4b3bd128fc4309c554d4e0375fbcbfd8e098ffb654c8e4b666ba80cf32d4795fd82ce91dd4cffc4716802f40306b9fff347dfab6cb1084ede3b2a700e9d00b1dcef9bd9fbc4903ec2fa7697af039b6d58df5642346489ea38635f9151ddd4c6dabd0557d19141d9e9a474081f67032d21d533193aedd2a1c01adfc3e399d49270fe20140fe4d3a2a1bf465efd56d403c14018960d14de84efa601675e3c2a0dbdb72c3e1fb92057c5b6e2d6548cd0320385c0c288c0a94c653707d7527cec75ed8cc11f134b350c948af794f8568d2dd0030f7c5648780a7124770847e449c0598caeb5ad572a7856a0c2bb95d275cfb2540396e4316287fc1f3cb7f8d7f7ebaa9728ff7c3c6b5ebfbbf166d41240fbad772c035a3e040b77814795da5ae702b278345bcc58e7d3b6c741ad0b0d0331c46bc131033969b52a1610f1fb5413da159cd22d4c3efea54cfead34b98bc5fec53e03553d033c4936346aa09ac8f98b55000b9b26e1da30e616c6f732571230f20e35d4e0f6036c77bd63a45e913f9234b8181f7a75eab2958453c5a323526620a91646247a2e030c5918294d7821de3a74259acdbe6a6ce26749d520173960fe395374d4c7e899034fcf99c4a7818ebaa24ee9b03e46034624c46d6a7f3e273cd6b548db09bc3bbe0364bf4cc4fbd2dc79c85377c5679c9e8e24cd4abdd5807241715510f967c49fea03cd1efb4e0b1e8d04c2201c552a9cf11f7ef2f68d211a61be4b62efce5711155f037c05a1c086ebeff7d183efc5aa604f8b652127afa0b484d66f399f286b4c912e03e732b3d9645534f9a841df82be813fb28bb5a1b31d2ec72340b37100cbeb784603382909c11c72d4be673a9fd382b3636970f02c29c40badd44308656a4e449bf2035831e6c55de3bb602dfa63a06052f0718e648df163f1bdeab689dacd766e20090368c77dd73eae27c406283eac6e4c539e59079e4c80c83f2b7251167ba0adef0e030bed0e3b7ee979159bb15671267fd5b4a8f081b250f46b90efb68b5c2c861f3d032ccf6367990e901e7bf3ad92b8e47785ab37eef984d2674a40d880d2b92d16c7031fff886985a52e320df2fc94e77707b276e915252a16a4a3cffdaefd8ccf799e03124400534e8615dd46750904ff389d07ef4a078ea9b9b27b5be7bdd8dab34c8803a455fda28dfaccecf3eb7d6c5dbeca72b0bf19c6248f9bb38feaa2ccf2bfa4c603738d99b87e044cfc305c3926e803efe71a1fa29a632bc762f53d6ea60fe8b5f403f8b127665029ea44bd5e1001ea4bc0da6610f6cb16071f7fdd077d2c0ea09f4a034f808bef87afee94999253ff211b2937e2c0015819a663358b4a851e81727fe7038cad1aca5edfdb2c23831ec6945f4bd90847a32150544a8a3b5fab8266e52b4303db1edd88da31b9173b3a01a4bcb70736f1fb7d3d7c4ff994859c9853af8d13ad03368a3deceddbf642196ed53eccf27173a264a7676dd248d202620423db656dba03dcaddf110217ee48bfb8eeff146af24219a51ac93ca67f522482e2a3564b460103f4f3faad37bea3e205267b20d5a04787281af8a7fb40e7e25abd16b1fb673d980351b04acdc59d6d2676cb376513e6ef4e0e457fb7667841912288d0e20a2a80b303cf603aa4de6a946d150a191044c72b182176fb4021c6b2d18a383855a8f0234e031b5526d099906496a3a33d60306caefad770cd03a0881fe26d7f1a0de66b43ac035e59a8277f34b835258b96b4508bec0ef3922515e4142f0490770b3b2175569f03293dce157508c58129da9fdebc65200c1c1d983a8107e0f00d93b0cfe25af3e005581e03e61bc331fa2bcfdfbcbaaffe43953dd29f5dae150f901483658d263be00c044707b05cbe758c7d05581e03e98f02ec287a7bc81664c3bd3547c9c43fc30dd780deb035cc572ed0200c044683c5a2e4fd4005581e0301b4c5ac518015a0bce436357c6ffae80bb8291afc971ebe35544f24400c024501aa2450d005581e03fe83da1ccaef9d9bd78ce53543b6368d6df3947d410bf2d34eb594c300040205581e03ec48afe9bed2ccb5532d8a32fc0cbb2e1ba61c7556de5f51e397c4be200c01463d1ec5ffb1f0032df39c0062882ba634fb24c89e7f9871ecef10a6e6a6e79a336f6ed463d5c94203576d247b864a9c9c193245be5262a86aceeb51240a250d7b4354c293d83a678d05581e0397fc5d38badeb2417fc4af69d36e4965c61f33bb84f646b7d58a9da68007011bffffffffffffffff033fb0fa6f5661fde6378c4db73cf7f1243b922e4b55a3435735c010e477f1c24405581e037a436184a5fc4ce62ad4b2daaf8a45e35f2947e33aeb11416da640405004060219e48e03026455994a7764223a128fa757f29d61c41ac8e28bbab1f879c66e232fddb035036c4372246d91297c5eca3d8d1d450153f9522df23de517957182e29d8416b60c039c41360797c1aa7097a0a9af04c1f817bb5c454d0f772e8ebe2fd352ca7e0d8003ba5c25d3ffc47ad414f2443245ed620a381ce9d9f0d1ee70505a1db59120de1403c0b160d239ce0c71812917d0798c6964edb234491c2f32eb7574c8d7a16270d1034424e96e0a1731300a2559039dea0424a1e764dd8d7d15d03aeaad0ad4f3c4510219ffff031607ad0a1ba305ef701797730c019c3b185c0dbb0f482f10386db3446a8f7a540312287bc29f2baed4b1fc930f52489532b4fec922a36b4a91a91f7783402c380a037ec73b2e9bcef8facbe2637ff6111e1ef07153186e00f47d729f0690dc004b4503055d30bc306d96ab0cb9aedaff495f7afb4730446c7a1f96f785c05004135f200398418a681397427f906b8e034f61b8c96f509d367affb82a452f268302c94de103d152c22b71f6c3039c0923a13d58ba41db780be6dbee23526315a0a7cf0431f1037d07fa5723febf58b7b064f66fb51ccd33542f1733fd5095655fdb1cb52ed61003d757e18731ae954d17ba2b37aef6316225c955be31a5610b87e9454be425325803753b37ff3abfbe6254417acdccdbc7792897f9a69ca3519f33db8e1f0a06f71d035d0f2edb7053f1faccf65af86e531d22c3cc2949b8deb3160c91914b639d5bcb03a103f6ad9e92cdc19712b1a2c2520868d48838c6ae9841b5b6598facc92964630219ffff03d8b89977962e191ae00a471cfc8d04ab27004105aabea3f20a3514118c6362a40342228b2ce4d7af8913be27e5d33f74ac1156e79ce6b215261a0f340150aed5990219ffff034f61ea35d70737235a7524be89070d8a1b4312a463edd647598925fad5dd27ef0367356218f3ac808868159402784456d665ec6a3aa0a41585abfbb65d98f699280338604a343bc9aecc1935981e153cfe89177e98d6a52749241e3474fb37d3062d0379e57d3018566ef607f70739871a70e22f250c7073514ba45ebf0b623e2150310219ffff03ff41096042448d60e51cf90c3f4d32c484f2075dcf75997b73cb5132fa14cee303d55330cfd7fa4095c67d97d6e226d5f746dfa699344068ff850c9cd661a7237e0388185dc33be1752e598f422b3ca36b88f72978624ac5d3c52bca8f1aa5df907b0331efd165c257b035e9d310972a721a568efbe1497c9d6cadc31cdc141092b0dd033175ca26ae0b78e24f241d6d59350f393f513d386f76caa4dc01eec584c9683703c72b5eb847b4637591afae256fd03a1e652300ee869b73b9a744d5260fda709b03ee694228acfb8ca4132d1854640a984b7526b6dc1d8d1d2f7388559769b83fd403772906b1418f536fd3438952d8a507c574f872fecd6527c4d4f4a544f111f742039c81737ae5a38baeb1726a1555e57d6c70cdd7ff0277bba013842f992fe3d414031ad3fe9c07099f58f9840ce63f5cb56a2131a661ce47e279620513c287bccf980353da96fa6b3cf8379071046658960fe9f2e5583b7371aa00bc0b74a6e78a92a1037146604f75eae7c004c649e88055dbdaad5f11c9d75a1c070b80cdd59069fb9b03ea5c7eca43d22257270df496f2de21a4c2ecaab6fd1cfb16d9dfa4d47900295903a009fcb0a43b723b5e57d134fcf69fea087ca706f9821a47c2d9b4078e848a6a0385cd59308c0ba41b68662aa4c4ca5dc43ccecbfe34a0bb758961f960b6c08f76032277366223dc4bd673248b35fdbffb6d962fccfde4417a1b5ee1c25988bc06f803edf621f6da6f85e3543611cb792e072dec8da2d3136176b5ae6477665df090f80320c0a600fde72ac1545c1dcea40ee003c6ce25bbfbe5a425ca100ddac6bb8fc50326c500f83376c53ec000ce4b6de712524fc6cd3f212eef3691502404f29858cd03fffe27ae5abbc45140db8c52c20fa03e4240a35c6c91f37d38279ba9a3127a160302f261fe22ba2bda212b7d6ce8d7a5a6c1e8821417a12a758c3f6aa13317d2e903468dc4cd595e8792cf5d3f757e548396f5d871043b32bbfa37c8abe3143ef6dc03163eab7b6ba3a1f8eba01c69bbbb81c0d5534ba8feab7c1bb11d201e06f7d53b034f5a5f6706dc853cb3ae2279729e0d7e24dda128a77358144e4c0fd3e5d60e980605581e033dadb7653588841a2087764475e0b140094c5573ad02852325c0ee44f007011bffffffffffffffff0399bec80f747a7f4d8c90241f2b4687e87d876ab2a1e786184b774bc9a3b314bc036260dd7e446b60f4c765ee6fa242dd88c0bb4d8ea47dddc956c771e85948b10c05581e03676cdbe01ce54cbf6c6a6c5c2dd9bc2972134e73cf653233f83a9523800405038d6fe41665cfb4d58918bbf3a81688da1ca3f67e529dc7f4cc040b0f75102fa60301337e4413f01c345b614ccdf8788466933f1a0def558c37a19ce0bfd6101d4605581e033431dd3c678cf6bb4c09f0cf8292eb4f7d82052f7363f6f952caf1ea600c0147011f667bbfc00005581d0258edc64491e8dd578d51f1c192eae6265a18ba613588a2056b86e4ed0c02470207288b40948205581d0268f24ae2fad81ed2dca702123aed6f0af6a60f88dbb12d3d35ae93630c02470804bac3420ffe05581d02e390288ece92f3c6fbb890464690c53d3e01772dd97046a8daff4f900c04478034e7a51a2a2804592c1d608060405234801561001057600080fd5b50600436106101b95760003560e01c80636a627842116100f9578063ba9a7a5611610097578063d21220a711610071578063d21220a7146105da578063d505accf146105e2578063dd62ed3e14610640578063fff6cae91461067b576101b9565b8063ba9a7a5614610597578063bc25cf771461059f578063c45a0155146105d2576101b9565b80637ecebe00116100d35780637ecebe00146104d757806389afcb441461050a57806395d89b4114610556578063a9059cbb1461055e576101b9565b80636a6278421461046957806370a082311461049c5780637464fc3d146104cf576101b9565b806323b872dd116101665780633644e515116101405780633644e51514610416578063485cc9551461041e5780635909c0d5146104595780635a3d549314610461576101b9565b806323b872dd146103ad57806330adf81f146103f0578063313ce567146103f8576101b9565b8063095ea7b311610197578063095ea7b3146103155780630dfe16811461036257806318160ddd14610393576101b9565b8063022c0d9f146101be57806306fdde03146102595780630902f1ac146102d6575b600080fd5b610257600480360360808110156101d457600080fd5b81359160208101359173ffffffffffffffffffffffffffffffffffffffff604083013516919081019060808101606082013564010000000081111561021857600080fd5b82018360208201111561022a57600080fd5b8035906020019184600183028401116401000000008311171561024c57600080fd5b509092509050610683565b005b610261610d57565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561029b578181015183820152602001610283565b50505050905090810190601f1680156102c85780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6102de610d90565b604080516dffffffffffffffffffffffffffff948516815292909316602083015263ffffffff168183015290519081900360600190f35b61034e6004803603604081101561032b57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610de5565b604080519115158252519081900360200190f35b61036a610dfc565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b61039b610e18565b60408051918252519081900360200190f35b61034e600480360360608110156103c357600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060400135610e1e565b61039b610efd565b610400610f21565b6040805160ff9092168252519081900360200190f35b61039b610f26565b6102576004803603604081101561043457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020013516610f2c565b61039b611005565b61039b61100b565b61039b6004803603602081101561047f57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16611011565b61039b600480360360208110156104b257600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113cb565b61039b6113dd565b61039b600480360360208110156104ed57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113e3565b61053d6004803603602081101561052057600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113f5565b6040805192835260208301919091528051918290030190f35b610261611892565b61034e6004803603604081101561057457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81351690602001356118cb565b61039b6118d8565b610257600480360360208110156105b557600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166118de565b61036a611ad4565b61036a611af0565b610257600480360360e08110156105f857600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060408101359060608101359060ff6080820135169060a08101359060c00135611b0c565b61039b6004803603604081101561065657600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020013516611dd8565b610257611df5565b600c546001146106f457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c55841515806107075750600084115b61075c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180612b2f6025913960400191505060405180910390fd5b600080610767610d90565b5091509150816dffffffffffffffffffffffffffff168710801561079a5750806dffffffffffffffffffffffffffff1686105b6107ef576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526021815260200180612b786021913960400191505060405180910390fd5b600654600754600091829173ffffffffffffffffffffffffffffffffffffffff91821691908116908916821480159061085457508073ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff1614155b6108bf57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f556e697377617056323a20494e56414c49445f544f0000000000000000000000604482015290519081900360640190fd5b8a156108d0576108d0828a8d611fdb565b89156108e1576108e1818a8c611fdb565b86156109c3578873ffffffffffffffffffffffffffffffffffffffff166310d1e85c338d8d8c8c6040518663ffffffff1660e01b8152600401808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b1580156109aa57600080fd5b505af11580156109be573d6000803e3d6000fd5b505050505b604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff8416916370a08231916024808301926020929190829003018186803b158015610a2f57600080fd5b505afa158015610a43573d6000803e3d6000fd5b505050506040513d6020811015610a5957600080fd5b5051604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905191955073ffffffffffffffffffffffffffffffffffffffff8316916370a0823191602480820192602092909190829003018186803b158015610acb57600080fd5b505afa158015610adf573d6000803e3d6000fd5b505050506040513d6020811015610af557600080fd5b5051925060009150506dffffffffffffffffffffffffffff85168a90038311610b1f576000610b35565b89856dffffffffffffffffffffffffffff160383035b9050600089856dffffffffffffffffffffffffffff16038311610b59576000610b6f565b89856dffffffffffffffffffffffffffff160383035b90506000821180610b805750600081115b610bd5576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526024815260200180612b546024913960400191505060405180910390fd5b6000610c09610beb84600363ffffffff6121e816565b610bfd876103e863ffffffff6121e816565b9063ffffffff61226e16565b90506000610c21610beb84600363ffffffff6121e816565b9050610c59620f4240610c4d6dffffffffffffffffffffffffffff8b8116908b1663ffffffff6121e816565b9063ffffffff6121e816565b610c69838363ffffffff6121e816565b1015610cd657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f556e697377617056323a204b0000000000000000000000000000000000000000604482015290519081900360640190fd5b5050610ce4848488886122e0565b60408051838152602081018390528082018d9052606081018c9052905173ffffffffffffffffffffffffffffffffffffffff8b169133917fd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d8229181900360800190a350506001600c55505050505050505050565b6040518060400160405280600a81526020017f556e69737761702056320000000000000000000000000000000000000000000081525081565b6008546dffffffffffffffffffffffffffff808216926e0100000000000000000000000000008304909116917c0100000000000000000000000000000000000000000000000000000000900463ffffffff1690565b6000610df233848461259c565b5060015b92915050565b60065473ffffffffffffffffffffffffffffffffffffffff1681565b60005481565b73ffffffffffffffffffffffffffffffffffffffff831660009081526002602090815260408083203384529091528120547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14610ee85773ffffffffffffffffffffffffffffffffffffffff84166000908152600260209081526040808320338452909152902054610eb6908363ffffffff61226e16565b73ffffffffffffffffffffffffffffffffffffffff851660009081526002602090815260408083203384529091529020555b610ef384848461260b565b5060019392505050565b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b601281565b60035481565b60055473ffffffffffffffffffffffffffffffffffffffff163314610fb257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f556e697377617056323a20464f5242494444454e000000000000000000000000604482015290519081900360640190fd5b6006805473ffffffffffffffffffffffffffffffffffffffff9384167fffffffffffffffffffffffff00000000000000000000000000000000000000009182161790915560078054929093169116179055565b60095481565b600a5481565b6000600c5460011461108457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c81905580611094610d90565b50600654604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905193955091935060009273ffffffffffffffffffffffffffffffffffffffff909116916370a08231916024808301926020929190829003018186803b15801561110e57600080fd5b505afa158015611122573d6000803e3d6000fd5b505050506040513d602081101561113857600080fd5b5051600754604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905192935060009273ffffffffffffffffffffffffffffffffffffffff909216916370a0823191602480820192602092909190829003018186803b1580156111b157600080fd5b505afa1580156111c5573d6000803e3d6000fd5b505050506040513d60208110156111db57600080fd5b505190506000611201836dffffffffffffffffffffffffffff871663ffffffff61226e16565b90506000611225836dffffffffffffffffffffffffffff871663ffffffff61226e16565b9050600061123387876126ec565b600054909150806112705761125c6103e8610bfd611257878763ffffffff6121e816565b612878565b985061126b60006103e86128ca565b6112cd565b6112ca6dffffffffffffffffffffffffffff8916611294868463ffffffff6121e816565b8161129b57fe5b046dffffffffffffffffffffffffffff89166112bd868563ffffffff6121e816565b816112c457fe5b0461297a565b98505b60008911611326576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180612bc16028913960400191505060405180910390fd5b6113308a8a6128ca565b61133c86868a8a6122e0565b811561137e5760085461137a906dffffffffffffffffffffffffffff808216916e01000000000000000000000000000090041663ffffffff6121e816565b600b555b6040805185815260208101859052815133927f4c209b5fc8ad50758f13e2e1088ba56a560dff690a1c6fef26394f4c03821c4f928290030190a250506001600c5550949695505050505050565b60016020526000908152604090205481565b600b5481565b60046020526000908152604090205481565b600080600c5460011461146957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c81905580611479610d90565b50600654600754604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905194965092945073ffffffffffffffffffffffffffffffffffffffff9182169391169160009184916370a08231916024808301926020929190829003018186803b1580156114fb57600080fd5b505afa15801561150f573d6000803e3d6000fd5b505050506040513d602081101561152557600080fd5b5051604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905191925060009173ffffffffffffffffffffffffffffffffffffffff8516916370a08231916024808301926020929190829003018186803b15801561159957600080fd5b505afa1580156115ad573d6000803e3d6000fd5b505050506040513d60208110156115c357600080fd5b5051306000908152600160205260408120549192506115e288886126ec565b600054909150806115f9848763ffffffff6121e816565b8161160057fe5b049a5080611614848663ffffffff6121e816565b8161161b57fe5b04995060008b11801561162e575060008a115b611683576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180612b996028913960400191505060405180910390fd5b61168d3084612992565b611698878d8d611fdb565b6116a3868d8c611fdb565b604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff8916916370a08231916024808301926020929190829003018186803b15801561170f57600080fd5b505afa158015611723573d6000803e3d6000fd5b505050506040513d602081101561173957600080fd5b5051604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905191965073ffffffffffffffffffffffffffffffffffffffff8816916370a0823191602480820192602092909190829003018186803b1580156117ab57600080fd5b505afa1580156117bf573d6000803e3d6000fd5b505050506040513d60208110156117d557600080fd5b505193506117e585858b8b6122e0565b811561182757600854611823906dffffffffffffffffffffffffffff808216916e01000000000000000000000000000090041663ffffffff6121e816565b600b555b604080518c8152602081018c9052815173ffffffffffffffffffffffffffffffffffffffff8f169233927fdccd412f0b1252819cb1fd330b93224ca42612892bb3f4f789976e6d81936496929081900390910190a35050505050505050506001600c81905550915091565b6040518060400160405280600681526020017f554e492d5632000000000000000000000000000000000000000000000000000081525081565b6000610df233848461260b565b6103e881565b600c5460011461194f57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c55600654600754600854604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff9485169490931692611a2b9285928792611a26926dffffffffffffffffffffffffffff169185916370a0823191602480820192602092909190829003018186803b1580156119ee57600080fd5b505afa158015611a02573d6000803e3d6000fd5b505050506040513d6020811015611a1857600080fd5b50519063ffffffff61226e16565b611fdb565b600854604080517f70a082310000000000000000000000000000000000000000000000000000000081523060048201529051611aca9284928792611a26926e01000000000000000000000000000090046dffffffffffffffffffffffffffff169173ffffffffffffffffffffffffffffffffffffffff8616916370a0823191602480820192602092909190829003018186803b1580156119ee57600080fd5b50506001600c5550565b60055473ffffffffffffffffffffffffffffffffffffffff1681565b60075473ffffffffffffffffffffffffffffffffffffffff1681565b42841015611b7b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f556e697377617056323a20455850495245440000000000000000000000000000604482015290519081900360640190fd5b60035473ffffffffffffffffffffffffffffffffffffffff80891660008181526004602090815260408083208054600180820190925582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98186015280840196909652958d166060860152608085018c905260a085019590955260c08085018b90528151808603909101815260e0850182528051908301207f19010000000000000000000000000000000000000000000000000000000000006101008601526101028501969096526101228085019690965280518085039096018652610142840180825286519683019690962095839052610162840180825286905260ff89166101828501526101a284018890526101c28401879052519193926101e2808201937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081019281900390910190855afa158015611cdc573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff811615801590611d5757508873ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b611dc257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f556e697377617056323a20494e56414c49445f5349474e415455524500000000604482015290519081900360640190fd5b611dcd89898961259c565b505050505050505050565b600260209081526000928352604080842090915290825290205481565b600c54600114611e6657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f556e697377617056323a204c4f434b4544000000000000000000000000000000604482015290519081900360640190fd5b6000600c55600654604080517f70a082310000000000000000000000000000000000000000000000000000000081523060048201529051611fd49273ffffffffffffffffffffffffffffffffffffffff16916370a08231916024808301926020929190829003018186803b158015611edd57600080fd5b505afa158015611ef1573d6000803e3d6000fd5b505050506040513d6020811015611f0757600080fd5b5051600754604080517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152905173ffffffffffffffffffffffffffffffffffffffff909216916370a0823191602480820192602092909190829003018186803b158015611f7a57600080fd5b505afa158015611f8e573d6000803e3d6000fd5b505050506040513d6020811015611fa457600080fd5b50516008546dffffffffffffffffffffffffffff808216916e0100000000000000000000000000009004166122e0565b6001600c55565b604080518082018252601981527f7472616e7366657228616464726573732c75696e743235362900000000000000602091820152815173ffffffffffffffffffffffffffffffffffffffff85811660248301526044808301869052845180840390910181526064909201845291810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb000000000000000000000000000000000000000000000000000000001781529251815160009460609489169392918291908083835b602083106120e157805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016120a4565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114612143576040519150601f19603f3d011682016040523d82523d6000602084013e612148565b606091505b5091509150818015612176575080511580612176575080806020019051602081101561217357600080fd5b50515b6121e157604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f556e697377617056323a205452414e534645525f4641494c4544000000000000604482015290519081900360640190fd5b5050505050565b60008115806122035750508082028282828161220057fe5b04145b610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f64732d6d6174682d6d756c2d6f766572666c6f77000000000000000000000000604482015290519081900360640190fd5b80820382811115610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f64732d6d6174682d7375622d756e646572666c6f770000000000000000000000604482015290519081900360640190fd5b6dffffffffffffffffffffffffffff841180159061230c57506dffffffffffffffffffffffffffff8311155b61237757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f556e697377617056323a204f564552464c4f5700000000000000000000000000604482015290519081900360640190fd5b60085463ffffffff428116917c0100000000000000000000000000000000000000000000000000000000900481168203908116158015906123c757506dffffffffffffffffffffffffffff841615155b80156123e257506dffffffffffffffffffffffffffff831615155b15612492578063ffffffff16612425856123fb86612a57565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff169063ffffffff612a7b16565b600980547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff929092169290920201905563ffffffff8116612465846123fb87612a57565b600a80547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff92909216929092020190555b600880547fffffffffffffffffffffffffffffffffffff0000000000000000000000000000166dffffffffffffffffffffffffffff888116919091177fffffffff0000000000000000000000000000ffffffffffffffffffffffffffff166e0100000000000000000000000000008883168102919091177bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167c010000000000000000000000000000000000000000000000000000000063ffffffff871602179283905560408051848416815291909304909116602082015281517f1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1929181900390910190a1505050505050565b73ffffffffffffffffffffffffffffffffffffffff808416600081815260026020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b73ffffffffffffffffffffffffffffffffffffffff8316600090815260016020526040902054612641908263ffffffff61226e16565b73ffffffffffffffffffffffffffffffffffffffff8085166000908152600160205260408082209390935590841681522054612683908263ffffffff612abc16565b73ffffffffffffffffffffffffffffffffffffffff80841660008181526001602090815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600080600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663017e7e586040518163ffffffff1660e01b815260040160206040518083038186803b15801561275757600080fd5b505afa15801561276b573d6000803e3d6000fd5b505050506040513d602081101561278157600080fd5b5051600b5473ffffffffffffffffffffffffffffffffffffffff821615801594509192509061286457801561285f5760006127d86112576dffffffffffffffffffffffffffff88811690881663ffffffff6121e816565b905060006127e583612878565b90508082111561285c576000612813612804848463ffffffff61226e16565b6000549063ffffffff6121e816565b905060006128388361282c86600563ffffffff6121e816565b9063ffffffff612abc16565b9050600081838161284557fe5b04905080156128585761285887826128ca565b5050505b50505b612870565b8015612870576000600b555b505092915050565b600060038211156128bb575080600160028204015b818110156128b5578091506002818285816128a457fe5b0401816128ad57fe5b04905061288d565b506128c5565b81156128c5575060015b919050565b6000546128dd908263ffffffff612abc16565b600090815573ffffffffffffffffffffffffffffffffffffffff8316815260016020526040902054612915908263ffffffff612abc16565b73ffffffffffffffffffffffffffffffffffffffff831660008181526001602090815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b6000818310612989578161298b565b825b9392505050565b73ffffffffffffffffffffffffffffffffffffffff82166000908152600160205260409020546129c8908263ffffffff61226e16565b73ffffffffffffffffffffffffffffffffffffffff831660009081526001602052604081209190915554612a02908263ffffffff61226e16565b600090815560408051838152905173ffffffffffffffffffffffffffffffffffffffff8516917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef919081900360200190a35050565b6dffffffffffffffffffffffffffff166e0100000000000000000000000000000290565b60006dffffffffffffffffffffffffffff82167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff841681612ab457fe5b049392505050565b80820182811015610df657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f64732d6d6174682d6164642d6f766572666c6f77000000000000000000000000604482015290519081900360640190fdfe556e697377617056323a20494e53554646494349454e545f4f55545055545f414d4f554e54556e697377617056323a20494e53554646494349454e545f494e5055545f414d4f554e54556e697377617056323a20494e53554646494349454e545f4c4951554944495459556e697377617056323a20494e53554646494349454e545f4c49515549444954595f4255524e4544556e697377617056323a20494e53554646494349454e545f4c49515549444954595f4d494e544544a265627a7a723158207dca18479e58487606bf70c79e44d8dee62353c9ee6d01f9a9d70885b8765f2264736f6c634300051000320384b2573251bfe2e94ba79c0b98c656bf64322ed350b0879a0ee59d5ec30128200058210390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5630480c192f1954503f3600582103e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af050010374865282d34b9409d4255f0b455c005820026cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c68854f1001ad1ee7743e0f6715f50cdaeb1397d5cb4fa00582002b661198733f53bb9b621ec808effd2e8a3d86db6962103738e13951e49aab0471ef8ca7e487b790219014000582002575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5820eb94db55691873fd8f4d937239039c663e5f6abd98c065fcfe316a94084248f7005820025a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a850d579019250d11fd5122a90a86965d7bc02184400582103f6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c704101005821031f25289b5c9db29d46c3566463f71796d2e07c9a7a96a888214082f19288cd00480bfa364ed607bfd500582002f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee35820663811bb00000000000005e26e848833dcec0000000000001a0ce48769dbdff200582002aa68fd0b9ce5597a6b852e7cf6a582dad59ebc65cf9a27520b304170361e9c5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0058200252222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f54c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20218680219f44505581d02a2f0a757c767f2f1f00959b54bb0afc795dbe5a347ff3575e897fb610701192c1d0219c01203217db0427a9a7df644a99dd99bf1ac77d3b430bc74730438ccd55a2a8d7588aa0219da66034d6198727d3f428a3cb210a1b6a8881848ef6b9991490fe3d0fa63f5aac8e468039575845ef29c5c4f5c59d52ccf6a166fbfa745b5ea4f3eff8f35a36592a5a854030bdbc2e6c85207aa334c5439c72db9811d5a27cb403d501e259b7b8211d0482c03fd8f29530748b96c609c2963458b556bce3969a75b37fc96b3ec2f9fe724dd5a03db69853cb602c5c1d6f733e14ca347c37aafb0b625672e219254fd1540b79ec80373b63d0fb6ed7ffde952aa3122f60eaa97e27e71b3b6eb02a094c1f0483e9480033d90703c6ebb918568ef318aa3f6e37ca739916c5219f9cdbd18b22e4d61dc7b03fb32801dbc0d7c9f6fdaccec9071ffde5f68694c6e2bb2869b15b48eaca0968f03a144dfc4f3b1e2fee3dc40999a2217d2da947a61f860d7c9949e19a62afcb8660219ffff035b7997205673de73c19fa9f04c68d5ee94ab57f998c0451aac528c46f1e8566a038a5aea6593fe8e5eabf7d70e5e2af861358713fe6ddbc6a80a05fae6aa571076036766860aed3876af5e53c0d11777eaa443d3639f23258cbbb2013d7ca8a27bdd0376557c3d8ec75e27a501c5a50f204a29391196c5d9dfd264f017315ecbe590830385e066bb492ded45b73a7e0052b0065170d19946f4049f34a6e5646a5eb39b5c0385d59bbaea0f6912608a5ad65f23e8922d4bebebd6dc89f3c395c9b7604187070330b3be6521dcd9f23aa6ccdaee41133c186e71dbd3d8f623bf9108cea020c9fc03b88e987b529e4542dc89df56a59a3e793bf14327347262732745e977a8607f830219ffff03bae8e9a8c6b14ade72932393307cf9f4faa48e3f0d52da0ad02899ebc22993b7030015fd996455b178383a9b571118ddfc72a6bfec4c68748637b22d68ba3d1dfd0306372ec8ab675c393e78f1640f859fb02b1e8553dfd0d9fe86cddf0542b5e939032927ceda284800ad13f2671e078252d3a6067898d2692612555e723693ca7c010364630014871611d84e932a7cd483c2226cf50cb23ddd5c9cdef68b1adcfae9ca03cddd35c2520efd78f95aa0efb1bcfe359ff3d6f2926a220673f33fcb20724b9503c2050126de32364fd49b91c7f710a981853f1b19cfe9a398e16dd63ef616a14c03e4eb94a23ff2beae29a2b3b2cda59f58b2c8081a012d542a2dbc8446474192150390fd2713d204ec02c657eda837d9b903649ca7c21d0bea883560c44a5840cf4f03bad1715db45f58203a1a422cfa3a825aec95f336e5f629ceff505a53b96139980351e45346ddebcd8c95122999b00c0bb21bc3555e46e27df61a54e6556eeb9d0403c32070cbdb4ff193f3ee4659b55f3b2ac1a0ca6e52412ab75d426ce0051fbc990219ffff03a4036d7fde0eea3dd44ad9b1c511f36c5301a5071fd6cee7858bc1f95da35b45030a2fd5e9f19dde84be4f4f8cfead9fd18996d6e0fa39c61eca67049dfd4ec449030e5ea0ca316e81e91bb649fdc161477629da3ecb7e0c762c88dacdc4e332da25038bf68ee483ab94728260a8b47e9ea31366c2e060ed0a35f8253a79e197800910035f3e6ab6e55998003d7d8aa47f2e59965c07e4aecce263a74862460ce3f92fd90365a409f32023f587e6ac8ae13f17021a5b23c8fb39e237ba9cfc763d537f726f03e8e73209210e16df3f423bed3182ea79da5163ee8afc1977c1561bdd1fde9ffc032cb6b42a4667a84d4e42197f5166acb157f1d8edbdfb221d66b68d6016dde21503cde372eb381408caeb10376665bc157307f24506c2491f20ea4c89a7989233e60398cd574d16a61cf4e468e83e89d979ad0c391ea8d422733dc61c173c4f094cd50219ffff0219ffff0219ffff" + } + }, + "txn_info": [ + { + "traces": { + "0x46affe1b4f3fc41581fd20fbaf055daeab80a8b5": { + "code_usage": { + "read": "0xfa39a7947e23c58a7ac305011e8a8ea07439af13ba7588c779761c91858a1127" + } + }, + "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { + "balance": "0x5d9d8b208ea1b6cc" + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "0x28dd64a7658410c285153", + "storage_read": [ + "0x6e11b2ad3f4a126fa523dbca759473ee7a6cebd4efb81c3015eabee05d9bea1f", + "0x3532f2f644d8a7080f8db547c7b002c7f5c206478ebcc15036b3c74f89a6943b" + ], + "storage_written": { + "0x6e11b2ad3f4a126fa523dbca759473ee7a6cebd4efb81c3015eabee05d9bea1f": "0x0", + "0x3532f2f644d8a7080f8db547c7b002c7f5c206478ebcc15036b3c74f89a6943b": "0x10ec3dc1f89c0802" + }, + "code_usage": { + "read": "0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23" + } + }, + "0xd95fc8448cea732810bfa312a3ebaaafa2c6a3cb": { + "storage_read": [ + "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x000000000000000000000000000000000000000000000000000000000000000c", + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000007", + "0x0000000000000000000000000000000000000000000000000000000000000009", + "0x000000000000000000000000000000000000000000000000000000000000000a" + ], + "storage_written": { + "0x000000000000000000000000000000000000000000000000000000000000000c": "0x1", + "0x000000000000000000000000000000000000000000000000000000000000000a": "0xd8a5da9077d5c5ec8b806a3ac3c8", + "0x0000000000000000000000000000000000000000000000000000000000000008": "0x663811c700000000000010ec3dc1f89c0802000000000000012384455dc21a01", + "0x0000000000000000000000000000000000000000000000000000000000000009": "0xaa28037adae5827cce516b9dd7dfd8" + }, + "code_usage": { + "read": "0x5b83bdbcc56b2e630f2807bbadd2b0c21619108066b92a58de081261089e9ce5" + } + }, + "0x71f755898886f79efa73334450f05a824faeae02": { + "balance": "0x190705b0d47776e3", + "nonce": "0x8f" + }, + "0x80a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e": { + "storage_read": [ + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "code_usage": { + "read": "0x0e42165348c9fef8f8381bd60d5276087423604d3f51cabec442610b09b1f5ae" + } + }, + "0x3339474491b13ac638f163d634fbbc722cd27916": { + "storage_read": [ + "0x000000000000000000000000000000000000000000000000000000000000000e", + "0x0000000000000000000000000000000000000000000000000000000000000014", + "0xa959202a47a24885beeb7de07cfd01f9ec6e5a393f1d515f40a6ff4cc8322d41", + "0x1ddb94459ffc40c2677b8a9322a30bbcd8865572dffd5aba3e2181b5ee3bbf20", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x9bacabca323d73597c4482cd7d164b0ba76e976ed396b897fece57202fe17e3e", + "0xb30d0c34c10c130ac92a1de37e54ac7bdb90a70d224fdd92cba743574dc18743", + "0x0000000000000000000000000000000000000000000000000000000000000013", + "0x666a1242978ca79d65d7cbfd5a9f5216d93c4785e10f7e5374786925fe8ccf53", + "0x000000000000000000000000000000000000000000000000000000000000000b", + "0x0000000000000000000000000000000000000000000000000000000000000009", + "0xbb900bdeb39d5848d51385674f626e4a5f449ab5742c2f63f7ae5f91877fb474", + "0x000000000000000000000000000000000000000000000000000000000000000f", + "0x0000000000000000000000000000000000000000000000000000000000000010", + "0x0000000000000000000000000000000000000000000000000000000000000006" + ], + "storage_written": { + "0x1ddb94459ffc40c2677b8a9322a30bbcd8865572dffd5aba3e2181b5ee3bbf20": "0x6e948b35fe5ff", + "0x666a1242978ca79d65d7cbfd5a9f5216d93c4785e10f7e5374786925fe8ccf53": "0x12384455dc21a01", + "0x000000000000000000000000000000000000000000000000000000000000000e": "0x9" + }, + "code_usage": { + "read": "0x3db32b9d0f5a762bfcd05f5d0cbafe91fca8b67a0e2834e13c8bb0531a8125a6" + } + } + }, + "meta": { + "byte_code": "0x02f9017b01818e850c570bd200850dc778ba13830549a19480a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e87649ff6f7003174b90104088890dc000000000000000000000000000000000000000000000000000011b15df428b300000000000000000000000000000000000000000000000000000000000000a000000000000000000000000071f755898886f79efa73334450f05a824faeae0200000000000000000000000000000000000000000000000000000000663811c80000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000003339474491b13ac638f163d634fbbc722cd27916c001a03efde0059a9e20313ee5a466273604b3e79d199a60b2c44e753f5f2fe56eb987a040eca86c1d87c685a3024af4a0224dab92758dc1990b8f8ecb6040f88498ab1e", + "new_txn_trie_node_byte": "0x02f9017b01818e850c570bd200850dc778ba13830549a19480a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e87649ff6f7003174b90104088890dc000000000000000000000000000000000000000000000000000011b15df428b300000000000000000000000000000000000000000000000000000000000000a000000000000000000000000071f755898886f79efa73334450f05a824faeae0200000000000000000000000000000000000000000000000000000000663811c80000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000003339474491b13ac638f163d634fbbc722cd27916c001a03efde0059a9e20313ee5a466273604b3e79d199a60b2c44e753f5f2fe56eb987a040eca86c1d87c685a3024af4a0224dab92758dc1990b8f8ecb6040f88498ab1e", + "new_receipt_trie_node_byte": "0xb9043e02f9043a01830316aab9010000200000000000000000000080000000000000000000000000000000000001000800000000000000000000000000000002000000080000000000000000000000000000000000000000000008000000200000000100000000000000008000000000000000000000000000400000000000000000000000000100000010000080000000000000000000000000000000008100000001000008080000004000000000000400000000000000000008000000000000000000000000000008000000000000000002000000000000200000000000000000000000001000000000000000000040200000000000000000000000000000000000000000400000000000000000f9032ff87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a0e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109ca000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9ea000000000000000000000000000000000000000000000000000649ff6f7003174f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9ea0000000000000000000000000d95fc8448cea732810bfa312a3ebaaafa2c6a3cba000000000000000000000000000000000000000000000000000649ff6f7003174f89b943339474491b13ac638f163d634fbbc722cd27916f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000d95fc8448cea732810bfa312a3ebaaafa2c6a3cba000000000000000000000000071f755898886f79efa73334450f05a824faeae02a00000000000000000000000000000000000000000000000000006e948b35fe5fff87994d95fc8448cea732810bfa312a3ebaaafa2c6a3cbe1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b840000000000000000000000000000000000000000000000000012384455dc21a0100000000000000000000000000000000000000000000000010ec3dc1f89c0802f8fc94d95fc8448cea732810bfa312a3ebaaafa2c6a3cbf863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9ea000000000000000000000000071f755898886f79efa73334450f05a824faeae02b880000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000649ff6f70031740000000000000000000000000000000000000000000000000006e948b35fe5ff0000000000000000000000000000000000000000000000000000000000000000", + "gas_used": 202410 + } + }, + { + "traces": { + "0x80a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e": { + "storage_read": [ + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "code_usage": { + "read": "0x0e42165348c9fef8f8381bd60d5276087423604d3f51cabec442610b09b1f5ae" + } + }, + "0x46affe1b4f3fc41581fd20fbaf055daeab80a8b5": { + "code_usage": { + "read": "0xfa39a7947e23c58a7ac305011e8a8ea07439af13ba7588c779761c91858a1127" + } + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "0x28dd64adaf838032882c7", + "storage_read": [ + "0x6e11b2ad3f4a126fa523dbca759473ee7a6cebd4efb81c3015eabee05d9bea1f", + "0x3532f2f644d8a7080f8db547c7b002c7f5c206478ebcc15036b3c74f89a6943b" + ], + "storage_written": { + "0x3532f2f644d8a7080f8db547c7b002c7f5c206478ebcc15036b3c74f89a6943b": "0x1150ddb8ef9c3976", + "0x6e11b2ad3f4a126fa523dbca759473ee7a6cebd4efb81c3015eabee05d9bea1f": "0x0" + }, + "code_usage": { + "read": "0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23" + } + }, + "0xa890e6b83ac95c7de4ffd4549d4cd282b407b495": { + "balance": "0x22b2b5d53ed342d2", + "nonce": "0x6b" + }, + "0x3339474491b13ac638f163d634fbbc722cd27916": { + "storage_read": [ + "0x2f896bfc9c9e71c6886136cc9d4cdf6a0dad4fb0087b5adb73d7f2333184fdb9", + "0x9bacabca323d73597c4482cd7d164b0ba76e976ed396b897fece57202fe17e3e", + "0x000000000000000000000000000000000000000000000000000000000000000b", + "0x21ef1d37618698fd5eab9f9249a314a31323bb90142688cae6c8c2ab595693dc", + "0xa959202a47a24885beeb7de07cfd01f9ec6e5a393f1d515f40a6ff4cc8322d41", + "0xe02dfcb26e74ceff6d1d546e4ebc866a3afeeb82714de1afc3c7783c51bf5ba8", + "0x0000000000000000000000000000000000000000000000000000000000000013", + "0x0000000000000000000000000000000000000000000000000000000000000010", + "0x666a1242978ca79d65d7cbfd5a9f5216d93c4785e10f7e5374786925fe8ccf53", + "0x000000000000000000000000000000000000000000000000000000000000000e", + "0x0000000000000000000000000000000000000000000000000000000000000014", + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000009", + "0x000000000000000000000000000000000000000000000000000000000000000f" + ], + "storage_written": { + "0x666a1242978ca79d65d7cbfd5a9f5216d93c4785e10f7e5374786925fe8ccf53": "0x11ceb324cf27726", + "0x2f896bfc9c9e71c6886136cc9d4cdf6a0dad4fb0087b5adb73d7f2333184fdb9": "0x6991310cfa2db", + "0x000000000000000000000000000000000000000000000000000000000000000e": "0xa" + }, + "code_usage": { + "read": "0x3db32b9d0f5a762bfcd05f5d0cbafe91fca8b67a0e2834e13c8bb0531a8125a6" + } + }, + "0xd95fc8448cea732810bfa312a3ebaaafa2c6a3cb": { + "storage_read": [ + "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x000000000000000000000000000000000000000000000000000000000000000c", + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ], + "storage_written": { + "0x000000000000000000000000000000000000000000000000000000000000000c": "0x1", + "0x0000000000000000000000000000000000000000000000000000000000000008": "0x663811c70000000000001150ddb8ef9c3976000000000000011ceb324cf27726" + }, + "code_usage": { + "read": "0x5b83bdbcc56b2e630f2807bbadd2b0c21619108066b92a58de081261089e9ce5" + } + }, + "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { + "balance": "0x5dbb414e52781acc" + } + }, + "meta": { + "byte_code": "0x02f9017a016a850c570bd200850dc778ba13830549a19480a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e87649ff6f7003174b90104088890dc000000000000000000000000000000000000000000000000000011b15df428b300000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000a890e6b83ac95c7de4ffd4549d4cd282b407b49500000000000000000000000000000000000000000000000000000000663811c80000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000003339474491b13ac638f163d634fbbc722cd27916c001a0a884ab9e5c1057e2e741e5ad4c6335f655a0dd248b3a6b7d7d0eb450ac2cff8ea07befa7f3480ddc589444e0d4498e0fd68fa765747676bafd9a128c6dcd91d5a1", + "new_txn_trie_node_byte": "0x02f9017a016a850c570bd200850dc778ba13830549a19480a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e87649ff6f7003174b90104088890dc000000000000000000000000000000000000000000000000000011b15df428b300000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000a890e6b83ac95c7de4ffd4549d4cd282b407b49500000000000000000000000000000000000000000000000000000000663811c80000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000003339474491b13ac638f163d634fbbc722cd27916c001a0a884ab9e5c1057e2e741e5ad4c6335f655a0dd248b3a6b7d7d0eb450ac2cff8ea07befa7f3480ddc589444e0d4498e0fd68fa765747676bafd9a128c6dcd91d5a1", + "new_receipt_trie_node_byte": "0xb9043e02f9043a0183057f0cb9010000200000000000000000000080000000000000000000000080000000000001000800000000000000000000000000000002000000080000000000000000000000000000000000000000000008000000200000000100000000000000008000000000000000000000000000400000000000000000000000000100000010000080000000000000000000000000000000008100000001000008080000004000400000000000000000000000000008000000000000000000000000000000000000000000000002000000000000200000000000000000000000001000000000000000000040200000000000000000000000000000000000000000400000000000000000f9032ff87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a0e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109ca000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9ea000000000000000000000000000000000000000000000000000649ff6f7003174f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9ea0000000000000000000000000d95fc8448cea732810bfa312a3ebaaafa2c6a3cba000000000000000000000000000000000000000000000000000649ff6f7003174f89b943339474491b13ac638f163d634fbbc722cd27916f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000d95fc8448cea732810bfa312a3ebaaafa2c6a3cba0000000000000000000000000a890e6b83ac95c7de4ffd4549d4cd282b407b495a00000000000000000000000000000000000000000000000000006991310cfa2dbf87994d95fc8448cea732810bfa312a3ebaaafa2c6a3cbe1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b840000000000000000000000000000000000000000000000000011ceb324cf277260000000000000000000000000000000000000000000000001150ddb8ef9c3976f8fc94d95fc8448cea732810bfa312a3ebaaafa2c6a3cbf863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9ea0000000000000000000000000a890e6b83ac95c7de4ffd4549d4cd282b407b495b880000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000649ff6f70031740000000000000000000000000000000000000000000000000006991310cfa2db0000000000000000000000000000000000000000000000000000000000000000", + "gas_used": 157794 + } + }, + { + "traces": { + "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { + "balance": "0x5dd52155a6ae57cc" + }, + "0xd95fc8448cea732810bfa312a3ebaaafa2c6a3cb": { + "storage_read": [ + "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x000000000000000000000000000000000000000000000000000000000000000c", + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ], + "storage_written": { + "0x0000000000000000000000000000000000000000000000000000000000000008": "0x663811c700000000000011c16aded0b6a2f30000000000000115e265a04ab726", + "0x000000000000000000000000000000000000000000000000000000000000000c": "0x1" + }, + "code_usage": { + "read": "0x5b83bdbcc56b2e630f2807bbadd2b0c21619108066b92a58de081261089e9ce5" + } + }, + "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": { + "code_usage": { + "read": "0xa324bc7db3d091b6f1a2d526e48a9c7039e03b3cc35f7d44b15ac7a1544c11d2" + } + }, + "0xdee90fc88ef3aceace9a22d2b2921906b7244552": { + "balance": "0x8a9d9dbb9e5f0d3", + "nonce": "0xa19" + }, + "0x3339474491b13ac638f163d634fbbc722cd27916": { + "storage_read": [ + "0x0000000000000000000000000000000000000000000000000000000000000013", + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x08f55d8b98c4bc7e11f2e039154ef30538af6b9937232ca209892c45871b15fb", + "0x000000000000000000000000000000000000000000000000000000000000000b", + "0x000000000000000000000000000000000000000000000000000000000000000e", + "0x0000000000000000000000000000000000000000000000000000000000000009", + "0x0000000000000000000000000000000000000000000000000000000000000014", + "0x9bacabca323d73597c4482cd7d164b0ba76e976ed396b897fece57202fe17e3e", + "0x0de215f63bfff4fe7883d192cea3721d7ad4060ea680c272f62f5c5faf4d8402", + "0x000000000000000000000000000000000000000000000000000000000000000f", + "0xad854c85d5b9e7470859152ad53c772f27f17f8dee52d4d3ff9c85f2b46f27b1", + "0xa959202a47a24885beeb7de07cfd01f9ec6e5a393f1d515f40a6ff4cc8322d41", + "0x666a1242978ca79d65d7cbfd5a9f5216d93c4785e10f7e5374786925fe8ccf53", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000010" + ], + "storage_written": { + "0x666a1242978ca79d65d7cbfd5a9f5216d93c4785e10f7e5374786925fe8ccf53": "0x115e265a04ab726", + "0xad854c85d5b9e7470859152ad53c772f27f17f8dee52d4d3ff9c85f2b46f27b1": "0x708ccaca7c000", + "0x000000000000000000000000000000000000000000000000000000000000000e": "0xb" + }, + "code_usage": { + "read": "0x3db32b9d0f5a762bfcd05f5d0cbafe91fca8b67a0e2834e13c8bb0531a8125a6" + } + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "0x28dd64b4b855de442ec44", + "storage_read": [ + "0xfb19a963956c9cb662dd3ae48988c4b90766df71ea130109840abe0a1b23dba8", + "0x3532f2f644d8a7080f8db547c7b002c7f5c206478ebcc15036b3c74f89a6943b" + ], + "storage_written": { + "0x3532f2f644d8a7080f8db547c7b002c7f5c206478ebcc15036b3c74f89a6943b": "0x11c16aded0b6a2f3", + "0xfb19a963956c9cb662dd3ae48988c4b90766df71ea130109840abe0a1b23dba8": "0x0" + }, + "code_usage": { + "read": "0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23" + } + } + }, + "meta": { + "byte_code": "0x02f9015c01820a18850b4ad345008515c508c70083035b60947a250d5630b4cf539739df2c5dacb4c659f2488d8801cd918d39380020b8e4fb3bdb41000000000000000000000000000000000000000000000000000708ccaca7c0000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000dee90fc88ef3aceace9a22d2b2921906b724455200000000000000000000000000000000000000000000000000000000663811fc0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000003339474491b13ac638f163d634fbbc722cd27916c001a0d244c53d24ffc3b2fcdfaf692d82593c0bb8cf3369b2a94c91c2c0249c4510a7a0751ca6879dce7d240443728d6e5756fa78be470b7ebd94cf984a1cb94f9f2c30", + "new_txn_trie_node_byte": "0x02f9015c01820a18850b4ad345008515c508c70083035b60947a250d5630b4cf539739df2c5dacb4c659f2488d8801cd918d39380020b8e4fb3bdb41000000000000000000000000000000000000000000000000000708ccaca7c0000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000dee90fc88ef3aceace9a22d2b2921906b724455200000000000000000000000000000000000000000000000000000000663811fc0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000003339474491b13ac638f163d634fbbc722cd27916c001a0d244c53d24ffc3b2fcdfaf692d82593c0bb8cf3369b2a94c91c2c0249c4510a7a0751ca6879dce7d240443728d6e5756fa78be470b7ebd94cf984a1cb94f9f2c30", + "new_receipt_trie_node_byte": "0xb9043e02f9043a018307c9a5b9010000200000000000000000000080000000000000000000000000010000000001000800000000000000000000000000000002000000080000000000000000000000000000000000000000000008000000200000000000000000000000008000000000000000020000000000000000000000000000000000000100000010000080000000000000000000004000000000008100000001000008080000004000000000000000000000000000000008000000000000000000000000000000000000000000000002000000000000200000000200000000000000001000000000000020000000200000000000000000000000000000000000000000400008000000000000f9032ff87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a0e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109ca00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da000000000000000000000000000000000000000000000000000708d25e11a697df89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da0000000000000000000000000d95fc8448cea732810bfa312a3ebaaafa2c6a3cba000000000000000000000000000000000000000000000000000708d25e11a697df89b943339474491b13ac638f163d634fbbc722cd27916f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000d95fc8448cea732810bfa312a3ebaaafa2c6a3cba0000000000000000000000000dee90fc88ef3aceace9a22d2b2921906b7244552a0000000000000000000000000000000000000000000000000000708ccaca7c000f87994d95fc8448cea732810bfa312a3ebaaafa2c6a3cbe1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b8400000000000000000000000000000000000000000000000000115e265a04ab72600000000000000000000000000000000000000000000000011c16aded0b6a2f3f8fc94d95fc8448cea732810bfa312a3ebaaafa2c6a3cbf863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da0000000000000000000000000dee90fc88ef3aceace9a22d2b2921906b7244552b880000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000708d25e11a697d000000000000000000000000000000000000000000000000000708ccaca7c0000000000000000000000000000000000000000000000000000000000000000000", + "gas_used": 150169 + } + }, + { + "traces": { + "0xd95fc8448cea732810bfa312a3ebaaafa2c6a3cb": { + "storage_read": [ + "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x000000000000000000000000000000000000000000000000000000000000000c", + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ], + "storage_written": { + "0x000000000000000000000000000000000000000000000000000000000000000c": "0x1", + "0x0000000000000000000000000000000000000000000000000000000000000008": "0x663811c70000000000001237d2e8e9b68a47000000000000010ed998f3a2f726" + }, + "code_usage": { + "read": "0x5b83bdbcc56b2e630f2807bbadd2b0c21619108066b92a58de081261089e9ce5" + } + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "0x28dd64bc1ed67fd42d398", + "storage_read": [ + "0xfb19a963956c9cb662dd3ae48988c4b90766df71ea130109840abe0a1b23dba8", + "0x3532f2f644d8a7080f8db547c7b002c7f5c206478ebcc15036b3c74f89a6943b" + ], + "storage_written": { + "0x3532f2f644d8a7080f8db547c7b002c7f5c206478ebcc15036b3c74f89a6943b": "0x1237d2e8e9b68a47", + "0xfb19a963956c9cb662dd3ae48988c4b90766df71ea130109840abe0a1b23dba8": "0x0" + }, + "code_usage": { + "read": "0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23" + } + }, + "0xfe6cf59162f319fb2c536f63a6121eb88dc85a34": { + "balance": "0x73a223146e589e8", + "nonce": "0x28c" + }, + "0x3339474491b13ac638f163d634fbbc722cd27916": { + "storage_read": [ + "0x0000000000000000000000000000000000000000000000000000000000000010", + "0x9bacabca323d73597c4482cd7d164b0ba76e976ed396b897fece57202fe17e3e", + "0x932f5dddf61dcab2b7f81a97907cab7ab3afa53fb02eaa509ecf46d078a590ad", + "0x0000000000000000000000000000000000000000000000000000000000000009", + "0x0000000000000000000000000000000000000000000000000000000000000014", + "0x7e5308f712c73bb1d1fdf32173d487dce249d64f2aed871db408acd1809c3c8e", + "0x666a1242978ca79d65d7cbfd5a9f5216d93c4785e10f7e5374786925fe8ccf53", + "0xa959202a47a24885beeb7de07cfd01f9ec6e5a393f1d515f40a6ff4cc8322d41", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000000000b", + "0x000000000000000000000000000000000000000000000000000000000000000e", + "0x0000000000000000000000000000000000000000000000000000000000000013", + "0x2f2974bd4e0f611fb6e95d73ca9161f61dd52ac4d1e50f1933fbd0faaa911374", + "0x000000000000000000000000000000000000000000000000000000000000000f", + "0x0000000000000000000000000000000000000000000000000000000000000006" + ], + "storage_written": { + "0x7e5308f712c73bb1d1fdf32173d487dce249d64f2aed871db408acd1809c3c8e": "0x708ccaca7c000", + "0x666a1242978ca79d65d7cbfd5a9f5216d93c4785e10f7e5374786925fe8ccf53": "0x10ed998f3a2f726", + "0x000000000000000000000000000000000000000000000000000000000000000e": "0xc" + }, + "code_usage": { + "read": "0x3db32b9d0f5a762bfcd05f5d0cbafe91fca8b67a0e2834e13c8bb0531a8125a6" + } + }, + "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { + "balance": "0x5ded909a5428d1cc" + }, + "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": { + "code_usage": { + "read": "0xa324bc7db3d091b6f1a2d526e48a9c7039e03b3cc35f7d44b15ac7a1544c11d2" + } + } + }, + "meta": { + "byte_code": "0x02f9015c0182028b850aa9e48a008515c508c70083035b60947a250d5630b4cf539739df2c5dacb4c659f2488d880102a336dba60000b8e4fb3bdb41000000000000000000000000000000000000000000000000000708ccaca7c0000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000fe6cf59162f319fb2c536f63a6121eb88dc85a3400000000000000000000000000000000000000000000000000000000663811fc0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000003339474491b13ac638f163d634fbbc722cd27916c080a0b66d674b17cb04d44900455636b496131fdae9b8e4c81f790854bda351130e83a00db241292520a7c135d381b0fd18788c9490aff4b756d397453a6454da4e0a3c", + "new_txn_trie_node_byte": "0x02f9015c0182028b850aa9e48a008515c508c70083035b60947a250d5630b4cf539739df2c5dacb4c659f2488d880102a336dba60000b8e4fb3bdb41000000000000000000000000000000000000000000000000000708ccaca7c0000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000fe6cf59162f319fb2c536f63a6121eb88dc85a3400000000000000000000000000000000000000000000000000000000663811fc0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000003339474491b13ac638f163d634fbbc722cd27916c080a0b66d674b17cb04d44900455636b496131fdae9b8e4c81f790854bda351130e83a00db241292520a7c135d381b0fd18788c9490aff4b756d397453a6454da4e0a3c", + "new_receipt_trie_node_byte": "0xb9043e02f9043a01830a143eb9010000200000000000000000000080100004000000000000000000010000000001000800000000000000000000000000000002000000080000000000000000000000000000000000000000000008000000200000000000000000000000008000000000001000000000000000000000000000000000000000000100000010000080000000000000000000004000000000008100000001000008080000004000000000000000000000000000000008000000000000000000000000000000000000000000000002000000000000200000000000000000000000001000000000000020000000200000000000000000000000000000000000000000400000000000000000f9032ff87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a0e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109ca00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da00000000000000000000000000000000000000000000000000076680a18ffe754f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da0000000000000000000000000d95fc8448cea732810bfa312a3ebaaafa2c6a3cba00000000000000000000000000000000000000000000000000076680a18ffe754f89b943339474491b13ac638f163d634fbbc722cd27916f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000d95fc8448cea732810bfa312a3ebaaafa2c6a3cba0000000000000000000000000fe6cf59162f319fb2c536f63a6121eb88dc85a34a0000000000000000000000000000000000000000000000000000708ccaca7c000f87994d95fc8448cea732810bfa312a3ebaaafa2c6a3cbe1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b840000000000000000000000000000000000000000000000000010ed998f3a2f7260000000000000000000000000000000000000000000000001237d2e8e9b68a47f8fc94d95fc8448cea732810bfa312a3ebaaafa2c6a3cbf863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da0000000000000000000000000fe6cf59162f319fb2c536f63a6121eb88dc85a34b88000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000076680a18ffe754000000000000000000000000000000000000000000000000000708ccaca7c0000000000000000000000000000000000000000000000000000000000000000000", + "gas_used": 150169 + } + }, + { + "traces": { + "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { + "balance": "0x5def439c4b5641cc" + }, + "0x80a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e": { + "storage_read": [ + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "code_usage": { + "read": "0x0e42165348c9fef8f8381bd60d5276087423604d3f51cabec442610b09b1f5ae" + } + }, + "0x46affe1b4f3fc41581fd20fbaf055daeab80a8b5": { + "code_usage": { + "read": "0xfa39a7947e23c58a7ac305011e8a8ea07439af13ba7588c779761c91858a1127" + } + }, + "0x4fadefe2a5eb1bf9e1d3dc4a9fc85daa5c5c660b": { + "storage_read": [ + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000007", + "0x0000000000000000000000000000000000000000000000000000000000000009", + "0x000000000000000000000000000000000000000000000000000000000000000a", + "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x000000000000000000000000000000000000000000000000000000000000000c" + ], + "storage_written": { + "0x0000000000000000000000000000000000000000000000000000000000000009": "0xcf1ba905921432e18b85621f8cf12637a0", + "0x000000000000000000000000000000000000000000000000000000000000000a": "0x255a7ee758811e1b4264e80e0c47e8", + "0x0000000000000000000000000000000000000000000000000000000000000008": "0x663811c700000000000068a4699375205d36000000000000002c7dc20fc759b8", + "0x000000000000000000000000000000000000000000000000000000000000000c": "0x1" + }, + "code_usage": { + "read": "0x5b83bdbcc56b2e630f2807bbadd2b0c21619108066b92a58de081261089e9ce5" + } + }, + "0x8b683c400457ef31f3c27c90acb6ab69304d1b77": { + "storage_read": [ + "0x000000000000000000000000000000000000000000000000000000000000000c", + "0x000000000000000000000000000000000000000000000000000000000000000a", + "0x83e1437aab4333b6030f364dc9caee9c0c897f2db6337f02ce233d75b273b1fa", + "0x0000000000000000000000000000000000000000000000000000000000000011", + "0x675bf37819e10c16f976a50d9f656ba9812a6d5a65c8e7d60705c35921eb77be", + "0x000000000000000000000000000000000000000000000000000000000000000b", + "0x0000000000000000000000000000000000000000000000000000000000000009", + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000014", + "0xfb1d4442566123dd7c0fe5cd59628ac4451e1b9577fd6e20474cca2443e1b531", + "0xddc732c826b289315f15077a83d3fe3859af931e5164604edd58e7607cacb5f5", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000000000e" + ], + "storage_written": { + "0xddc732c826b289315f15077a83d3fe3859af931e5164604edd58e7607cacb5f5": "0xfffffffffffffffffffffffffffffffffffffffffffffffffffe2a8152211587", + "0x83e1437aab4333b6030f364dc9caee9c0c897f2db6337f02ce233d75b273b1fa": "0xa26c347416cf", + "0x675bf37819e10c16f976a50d9f656ba9812a6d5a65c8e7d60705c35921eb77be": "0x2c7dc20fc759b8", + "0xfb1d4442566123dd7c0fe5cd59628ac4451e1b9577fd6e20474cca2443e1b531": "0x1" + }, + "code_usage": { + "read": "0xe7ade98613d60bcf57bf6f63bdc0ba142056f211f922f48e2e16a049c0ba5048" + } + }, + "0xc58e4d309c4f5c88afe355c260ee9a1a699ed1dd": { + "balance": "0x6fd1b56dbabfd0d", + "nonce": "0xba4" + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "0x28dd647816bd65e02a57f", + "storage_read": [ + "0x76cd8662b2bfb43bcc04aa9280535bff21a0556a979e6d40014a513d17cd257e", + "0x6e11b2ad3f4a126fa523dbca759473ee7a6cebd4efb81c3015eabee05d9bea1f" + ], + "storage_written": { + "0x6e11b2ad3f4a126fa523dbca759473ee7a6cebd4efb81c3015eabee05d9bea1f": "0x0", + "0x76cd8662b2bfb43bcc04aa9280535bff21a0556a979e6d40014a513d17cd257e": "0x68a4699375205d36" + }, + "code_usage": { + "read": "0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23" + } + } + }, + "meta": { + "byte_code": "0x02f9019401820ba384b2d05e008502233d46138304dc2f9480a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e80b901243d0e3ec50000000000000000000000000000000000000000000000000001d57eaddeea78000000000000000000000000000000000000000000000000023bb9178b0192be00000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000c58e4d309c4f5c88afe355c260ee9a1a699ed1dd00000000000000000000000000000000000000000000000000000000663811c80000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f00000000000000000000000000000000000000000000000000000000000000020000000000000000000000008b683c400457ef31f3c27c90acb6ab69304d1b77000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2c080a0dd70145d23e041328d8242858ca9815cbb03bf408dd7d81e30532932497838fca0496bf86926d61a533fa5824c9d21271b8b1597b48014282168a48575f8cf417c", + "new_txn_trie_node_byte": "0x02f9019401820ba384b2d05e008502233d46138304dc2f9480a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e80b901243d0e3ec50000000000000000000000000000000000000000000000000001d57eaddeea78000000000000000000000000000000000000000000000000023bb9178b0192be00000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000c58e4d309c4f5c88afe355c260ee9a1a699ed1dd00000000000000000000000000000000000000000000000000000000663811c80000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f00000000000000000000000000000000000000000000000000000000000000020000000000000000000000008b683c400457ef31f3c27c90acb6ab69304d1b77000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2c080a0dd70145d23e041328d8242858ca9815cbb03bf408dd7d81e30532932497838fca0496bf86926d61a533fa5824c9d21271b8b1597b48014282168a48575f8cf417c", + "new_receipt_trie_node_byte": "0xb9057802f9057401830c8306b9010000200000000000000000000080000000000000000000000000000000000100000000000000000008000000000000000002000000080080001000000000200000000000000000000000000008000000200000100100400000000000000000000000000020000000000000400000000000000000000000040000000010000000000000000000080000000000000000000000000000000000090000004000000000020000000000000000000000000000000000000000000000000000000080000020000002000000000000000000000800000000000000001000000042000000000050200000000810000000000000000000000000000000000000000000000000f90469f89b948b683c400457ef31f3c27c90acb6ab69304d1b77f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000c58e4d309c4f5c88afe355c260ee9a1a699ed1dda00000000000000000000000008b683c400457ef31f3c27c90acb6ab69304d1b77a00000000000000000000000000000000000000000000000000000177988b18bb9f89b948b683c400457ef31f3c27c90acb6ab69304d1b77f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000c58e4d309c4f5c88afe355c260ee9a1a699ed1dda00000000000000000000000004fadefe2a5eb1bf9e1d3dc4a9fc85daa5c5c660ba00000000000000000000000000000000000000000000000000001be05252d5ebff89b948b683c400457ef31f3c27c90acb6ab69304d1b77f863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a0000000000000000000000000c58e4d309c4f5c88afe355c260ee9a1a699ed1dda000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9ea0fffffffffffffffffffffffffffffffffffffffffffffffffffe2a8152211587f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000004fadefe2a5eb1bf9e1d3dc4a9fc85daa5c5c660ba000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9ea0000000000000000000000000000000000000000000000000044081919f402e19f879944fadefe2a5eb1bf9e1d3dc4a9fc85daa5c5c660be1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b840000000000000000000000000000000000000000000000000002c7dc20fc759b800000000000000000000000000000000000000000000000068a4699375205d36f8fc944fadefe2a5eb1bf9e1d3dc4a9fc85daa5c5c660bf863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9ea000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9eb8800000000000000000000000000000000000000000000000000001be05252d5ebf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044081919f402e19f87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a07fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65a000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9ea0000000000000000000000000000000000000000000000000044081919f402e19", + "gas_used": 159432 + } + }, + { + "traces": { + "0x46affe1b4f3fc41581fd20fbaf055daeab80a8b5": { + "code_usage": { + "read": "0xfa39a7947e23c58a7ac305011e8a8ea07439af13ba7588c779761c91858a1127" + } + }, + "0x80a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e": { + "storage_read": [ + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "code_usage": { + "read": "0x0e42165348c9fef8f8381bd60d5276087423604d3f51cabec442610b09b1f5ae" + } + }, + "0x8693ca3483daf299aad023eae75221436d6c11a0": { + "balance": "0x8521330e10f264f", + "nonce": "0x162d" + }, + "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { + "balance": "0x5df1064f497363cc" + }, + "0xd5564d5338168dd3bafc9fcb37d264b451670b8d": { + "storage_read": [ + "0x000000000000000000000000000000000000000000000000000000000000000c", + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000007", + "0x0000000000000000000000000000000000000000000000000000000000000009", + "0x000000000000000000000000000000000000000000000000000000000000000a", + "0x0000000000000000000000000000000000000000000000000000000000000008" + ], + "storage_written": { + "0x0000000000000000000000000000000000000000000000000000000000000009": "0x1062a74ebba592da735f3bf72a0ee64", + "0x000000000000000000000000000000000000000000000000000000000000000a": "0xd5ae2126c2d0a7ceefb92f3f99b89aac", + "0x0000000000000000000000000000000000000000000000000000000000000008": "0x663811c70000000000000551c685d5c01ae60000000000001cd36f7824efdff2", + "0x000000000000000000000000000000000000000000000000000000000000000c": "0x1" + }, + "code_usage": { + "read": "0x5b83bdbcc56b2e630f2807bbadd2b0c21619108066b92a58de081261089e9ce5" + } + }, + "0xf1001ad1ee7743e0f6715f50cdaeb1397d5cb4fa": { + "storage_read": [ + "0xba6fabe7c1b33f9faa35c25ff04fe055f58509edaa961f522e7d05ea5b41d704", + "0x000000000000000000000000000000000000000000000000000000000000000a", + "0x0d6f85cf373209cd6fd4c2738b51bed4e72c574868b2d1789fc613f6de974283", + "0x0000000000000000000000000000000000000000000000000000000000000012", + "0x000000000000000000000000000000000000000000000000000000000000000e", + "0x42839f559ed51fc754e4cff021a3d0fb278fa5e9d01911adb8c98803f7d2c47e", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000013", + "0x32bf1e57b2f3d6c52d6033622253e59c320f4f31dd907f1da3ac1c2fc891a4b6", + "0x8b364a1dd167da933f7abc9cb2ca26d32b828ba52ee73f18dd43ac7813dfdc93", + "0x000000000000000000000000000000000000000000000000000000000000000d", + "0x0000000000000000000000000000000000000000000000000000000000000008", + "0xcaab888c23d12df622bf5e4d33f8b1b243f92f6f0e47f8ff75a68c6247893ae9", + "0x000000000000000000000000000000000000000000000000000000000000000f" + ], + "storage_written": { + "0x42839f559ed51fc754e4cff021a3d0fb278fa5e9d01911adb8c98803f7d2c47e": "0x90a7feb273c206", + "0x0d6f85cf373209cd6fd4c2738b51bed4e72c574868b2d1789fc613f6de974283": "0x551c685d5c01ae6", + "0x000000000000000000000000000000000000000000000000000000000000000d": "0x14a" + }, + "code_usage": { + "read": "0x7d443041f5f212485af211c85b7fb6fcf1550f959a66bf997e2c4951782dc525" + } + }, + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "balance": "0x28dd64a47f6c71916a57f", + "storage_read": [ + "0x6e11b2ad3f4a126fa523dbca759473ee7a6cebd4efb81c3015eabee05d9bea1f", + "0x793b794d76a0a454c7b10e10121915a5212de57e0d7d94cceacd9abd22330ccc" + ], + "storage_written": { + "0x6e11b2ad3f4a126fa523dbca759473ee7a6cebd4efb81c3015eabee05d9bea1f": "0x0", + "0x793b794d76a0a454c7b10e10121915a5212de57e0d7d94cceacd9abd22330ccc": "0x1cd36f7824efdff2" + }, + "code_usage": { + "read": "0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23" + } + } + }, + "meta": { + "byte_code": "0x02f9017c0182162c84b2d05e008502233d4613830461c69480a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e8802c68af0bb140000b90104088890dc000000000000000000000000000000000000000000000000007af53217af31b800000000000000000000000000000000000000000000000000000000000000a00000000000000000000000008693ca3483daf299aad023eae75221436d6c11a000000000000000000000000000000000000000000000000000000000663811c80000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000f1001ad1ee7743e0f6715f50cdaeb1397d5cb4fac001a02c9a9ab0d7eb2503a5ebbfe1a66c2f08a9170e1da5b14cdb142fce831522a43aa0360ecd3be1f224118862ddb78787cf31ad7600994b6c7a4a947ae3a071619a8b", + "new_txn_trie_node_byte": "0x02f9017c0182162c84b2d05e008502233d4613830461c69480a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e8802c68af0bb140000b90104088890dc000000000000000000000000000000000000000000000000007af53217af31b800000000000000000000000000000000000000000000000000000000000000a00000000000000000000000008693ca3483daf299aad023eae75221436d6c11a000000000000000000000000000000000000000000000000000000000663811c80000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000f1001ad1ee7743e0f6715f50cdaeb1397d5cb4fac001a02c9a9ab0d7eb2503a5ebbfe1a66c2f08a9170e1da5b14cdb142fce831522a43aa0360ecd3be1f224118862ddb78787cf31ad7600994b6c7a4a947ae3a071619a8b", + "new_receipt_trie_node_byte": "0xb9043e02f9043a01830f0845b9010000200000000000000000000080000000400000000008000000000000000000000000800000000000000000000008000002000000080000000000000000000000000000000000000000000008000000200000000100000000000000008000000000000000000000000000400008000000000000000000000000000010000000000000000000000000000000000000040040000001000000080800004000000000000100000000000000000000000008000000000001000000000000000000000000000002000000000000000000000000000000000000001000000000000000000040200000000000800000000000000000000000000000400000000000000000f9032ff87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a0e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109ca000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9ea000000000000000000000000000000000000000000000000002c68af0bb140000f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9ea0000000000000000000000000d5564d5338168dd3bafc9fcb37d264b451670b8da000000000000000000000000000000000000000000000000002c68af0bb140000f89b94f1001ad1ee7743e0f6715f50cdaeb1397d5cb4faf863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000d5564d5338168dd3bafc9fcb37d264b451670b8da00000000000000000000000008693ca3483daf299aad023eae75221436d6c11a0a00000000000000000000000000000000000000000000000000090a7feb273c206f87994d5564d5338168dd3bafc9fcb37d264b451670b8de1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b8400000000000000000000000000000000000000000000000001cd36f7824efdff20000000000000000000000000000000000000000000000000551c685d5c01ae6f8fc94d5564d5338168dd3bafc9fcb37d264b451670b8df863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a000000000000000000000000080a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9ea00000000000000000000000008693ca3483daf299aad023eae75221436d6c11a0b88000000000000000000000000000000000000000000000000002c68af0bb140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090a7feb273c206", + "gas_used": 165183 + } + }, + { + "traces": { + "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { + "balance": "0x5df12eb9f766c554" + }, + "0x17032783b5f29c8a68e0fafba4db021f248409e4": { + "balance": "0x0", + "nonce": "0x1" + }, + "0xb27b03580c193d324931acb143f6a1f3477e702e": { + "balance": "0x1257cf59bdce9000" + } + }, + "meta": { + "byte_code": "0xf86c808501a13b860082520894b27b03580c193d324931acb143f6a1f3477e702e8801ff118a94dfd0008026a06b6a8c636721916ee32404201135745eeb2bfad873e662f5a59c331cb6647a95a0452ae97ab3fb5bd8e4cc7890b28876a662430c40bf643239303e0c86770c96bf", + "new_txn_trie_node_byte": "0xf86c808501a13b860082520894b27b03580c193d324931acb143f6a1f3477e702e8801ff118a94dfd0008026a06b6a8c636721916ee32404201135745eeb2bfad873e662f5a59c331cb6647a95a0452ae97ab3fb5bd8e4cc7890b28876a662430c40bf643239303e0c86770c96bf", + "new_receipt_trie_node_byte": "0xf9010901830f5a4db9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", + "gas_used": 21000 + } + }, + { + "traces": { + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { + "storage_read": [ + "0x390f6178407c9b8e95802b8659e6df8e34c1e3d4f8d6a49e6132bbcdd937b63a", + "0xbedcb5df52f588f61ff32320302f7849d95752554baca3aee643459d2813fab3" + ], + "storage_written": { + "0xbedcb5df52f588f61ff32320302f7849d95752554baca3aee643459d2813fab3": "0x1497511bb6b1f16f2", + "0x390f6178407c9b8e95802b8659e6df8e34c1e3d4f8d6a49e6132bbcdd937b63a": "0x4d5baffcde6306e76a7" + }, + "code_usage": { + "read": "0xd0a06b12ac47863b5c7be4185c2deaad1c61557033f56c7d4ea74429cbb25e23" + } + }, + "0x43506849d7c04f9138d1a2050bbf3a0c054402dd": { + "code_usage": { + "read": "0xcdfb7d322961af3acae7a8f7ee8b69c205b36f576cc5b077f170c7eb8ecbe3ea" + } + }, + "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { + "balance": "0x5df1aadbe3850878" + }, + "0x93793bd1f3e35a0efd098c30e486a860a0ef7551": { + "balance": "0x34051d7a04031aac", + "nonce": "0xbcb1" + }, + "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640": { + "storage_read": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000004", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x00000000000000000000000000000000000000000000000000000000000002b2", + "0xda4f87b75ea46b0bcb01699726cbe92f07b59ae026875105518c234a010fe63a", + "0xda4f87b75ea46b0bcb01699726cbe92f07b59ae026875105518c234a010fe639", + "0xad9dde667637e023f1ffee9137c14ca72fd0a457bea73c3e79e176283357a6c3", + "0x0000000000000000000000000000000000000000000000000000000000000002", + "0xda4f87b75ea46b0bcb01699726cbe92f07b59ae026875105518c234a010fe63b", + "0xda4f87b75ea46b0bcb01699726cbe92f07b59ae026875105518c234a010fe63c", + "0x00000000000000000000000000000000000000000000000000000000000002b3" + ], + "storage_written": { + "0xda4f87b75ea46b0bcb01699726cbe92f07b59ae026875105518c234a010fe63c": "0x1000ed33000000000000000000020ce00fa821636b2d952f000002c5cb2601c", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x8e9b198ababa2f7fe6a21112ad04", + "0x00000000000000000000000000000000000000000000000000000000000002b3": "0x10000000000000001ec39445981969ea7d3b54b3200112eb4a4fef7663811c7", + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x10002d302d302ab02fceb00000000000045c6a16d265bb9fcae89d55c6498", + "0xda4f87b75ea46b0bcb01699726cbe92f07b59ae026875105518c234a010fe63b": "0x22d49e52321c6f90afad5a369abbdcbfa6", + "0xda4f87b75ea46b0bcb01699726cbe92f07b59ae026875105518c234a010fe63a": "0x1c24849ab98c7905f71913f3039", + "0x0000000000000000000000000000000000000000000000000000000000000004": "0x9336b8b0a431b7d8" + }, + "code_usage": { + "read": "0xa981b66c747a3d9fa29d7e200d5faaa2826960523d0e5a0df8148e8868c480b4" + } + }, + "0x9def7cde171841a9f0724124ca0b01a622d749e4": { + "balance": "0x16a7fb085b7", + "storage_read": [ + "0x5f5a09b54e538f9aa6a9bd36e4e1a5370b6d3b889be76cfa9f33bf6e1d909fe7", + "0x390f6178407c9b8e95802b8659e6df8e34c1e3d4f8d6a49e6132bbcdd937b63a", + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000005" + ], + "storage_written": { + "0x0000000000000000000000000000000000000000000000000000000000000005": "0x0", + "0x0000000000000000000000000000000000000000000000000000000000000006": "0x9def7cde171841a9f0724124ca0b01a622d749e4" + }, + "code_usage": { + "read": "0xa85d7ec4ffa81da8f494f1e81b70a200d3f75a414e3a803943ecd868aff78ab5" + } + }, + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { + "storage_read": [ + "0x10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b", + "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", + "0x1f21a62c4538bacf2aabeca410f0fe63151869f172e03c0e00357ba26a341eff", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0xe6695488b0655a1b1fdfee55f87e57d940b86639032942a6c8ac0cc373a938f8" + ], + "storage_written": { + "0x1f21a62c4538bacf2aabeca410f0fe63151869f172e03c0e00357ba26a341eff": "0x49b731ca328c", + "0xe6695488b0655a1b1fdfee55f87e57d940b86639032942a6c8ac0cc373a938f8": "0xd6adb5d897" + }, + "code_usage": { + "read": "0xd80d4b7c890cb9d6a4893e6b52bc34b56b25335cb13716e0d1d31383e6b41505" + } + } + }, + "meta": { + "byte_code": "0x02f901170182bcb08434fd4acd850158174adc83061a80949def7cde171841a9f0724124ca0b01a622d749e484012e3b68b8a4a000000000000000000000000000000088e6a0c2ddd26feeb64f039a2c41296fcb3f56400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000099f388994000000000000000000000000000000000000000000000000b6ea0676d933c915000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c001a0269fa6d550e1ce6f76884a87645bd2a462c303dca06423f2f0638a572109f0f7a0776dab3e59d703ff24cc45be1b856a73f66facbd0c5d6c41ecc940ca7e02416f", + "new_txn_trie_node_byte": "0x02f901170182bcb08434fd4acd850158174adc83061a80949def7cde171841a9f0724124ca0b01a622d749e484012e3b68b8a4a000000000000000000000000000000088e6a0c2ddd26feeb64f039a2c41296fcb3f56400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000099f388994000000000000000000000000000000000000000000000000b6ea0676d933c915000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c001a0269fa6d550e1ce6f76884a87645bd2a462c303dca06423f2f0638a572109f0f7a0776dab3e59d703ff24cc45be1b856a73f66facbd0c5d6c41ecc940ca7e02416f", + "new_receipt_trie_node_byte": "0xb9036802f90364018311b201b9010000000000010000000000000000000000000000000000000000000000040000000000000000000000000008400000000002000000080020000000000000800000000000000000000808000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000800000000000000000000000000000000000000000000010000000000000000000000000000000000200000000000000000000000000000000000002000000008000000000002000000000000000000000000000000000000000000000000000000000000200000000000000010000000000000000000800000000000000000000000f90259f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa000000000000000000000000088e6a0c2ddd26feeb64f039a2c41296fcb3f5640a00000000000000000000000009def7cde171841a9f0724124ca0b01a622d749e4a0000000000000000000000000000000000000000000000000b6ea0676d933c915f89b94a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000009def7cde171841a9f0724124ca0b01a622d749e4a000000000000000000000000088e6a0c2ddd26feeb64f039a2c41296fcb3f5640a0000000000000000000000000000000000000000000000000000000099f388994f9011c9488e6a0c2ddd26feeb64f039a2c41296fcb3f5640f863a0c42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67a00000000000000000000000009def7cde171841a9f0724124ca0b01a622d749e4a00000000000000000000000009def7cde171841a9f0724124ca0b01a622d749e4b8a0000000000000000000000000000000000000000000000000000000099f388994ffffffffffffffffffffffffffffffffffffffffffffffff4915f98926cc36eb00000000000000000000000000000000000045c6a16d265bb9fcae89d55c64980000000000000000000000000000000000000000000000009336b8b0a431b7d8000000000000000000000000000000000000000000000000000000000002fceb", + "gas_used": 153524 + } + }, + { + "traces": { + "0xd9db270c1b5e3bd161e8c8503c55ceabee709552": { + "code_usage": { + "read": "0xbba688fbdb21ad2bb58bc320638b43d94e7d100f6f3ebaab0a4e4de6304b1c2e" + } + }, + "0x617c8de5bde54ffbb8d92716cc947858ca38f582": { + "balance": "0x2863bce4f1dad386ab", + "storage_read": [ + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "code_usage": { + "read": "0xb89c1b3bdf2cf8827818646bce9a8f6e372885f8c55e5c07acbd307cb133b000" + } + }, + "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { + "balance": "0x5d848c31830be0ba", + "nonce": "0xe5ad8" + } + }, + "meta": { + "byte_code": "0x02f87101830e5ad7808501231a000f826ac194617c8de5bde54ffbb8d92716cc947858ca38f582876ca54625d8e66f80c001a0e0c1666544ae41c9fecaba65277c51af8755838a43c6e5bdc15c83970af18461a0405b701cfa1a3b22fc547c21955d55210f7fdd87b576466a2cf30cb27e9fb442", + "new_txn_trie_node_byte": "0x02f87101830e5ad7808501231a000f826ac194617c8de5bde54ffbb8d92716cc947858ca38f582876ca54625d8e66f80c001a0e0c1666544ae41c9fecaba65277c51af8755838a43c6e5bdc15c83970af18461a0405b701cfa1a3b22fc547c21955d55210f7fdd87b576466a2cf30cb27e9fb442", + "new_receipt_trie_node_byte": "0xb9018a02f901860183121cc2b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000002000000000000000000000000000000000000000000040000000000000000000000000000000001000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000800000000000000000000000000000000000000000f87cf87a94617c8de5bde54ffbb8d92716cc947858ca38f582f842a03d0ce9bfc3ed7d6862dbb28b2dea94561fe714a1b4d019aa8af39730d1ad7c3da000000000000000000000000095222290dd7278aa3ddd389cc1e1d165cc4bafe5a0000000000000000000000000000000000000000000000000006ca54625d8e66f", + "gas_used": 27329 + } + } + ] + }, + "other_data": { + "b_data": { + "b_meta": { + "block_beneficiary": "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5", + "block_timestamp": "0x663811c7", + "block_number": "0x12e3b68", + "block_difficulty": "0x0", + "block_random": "0x253d73a8035e2de53f82869fbdd8a700c96604da0485d0a02b05eebc53f615e3", + "block_gaslimit": "0x1c9c380", + "block_chain_id": "0x1", + "block_base_fee": "0x1231a000f", + "block_gas_used": "0x121cc2", + "block_blob_gas_used": "0x0", + "block_excess_blob_gas": "0x0", + "parent_beacon_block_root": "0xb2fdced42678407cdb560649f70178a2a0e4095543930548bf164b7b936eff36", + "block_bloom": [ + "0x20000001000000000000008010000440000000000800008001000004010100", + "0x8008000000000080000084000080000020000000800a0005000000000a00000", + "0x80a0000080000002000001001004000000000000080000400", + "0x102002000000000040000800000100000000000004110000001000088000", + "0x80000004000000000048140000001010008090800004000400000", + "0x205000000002000000000480000080000000000010000000020080000880000", + "0x20000002000000000000200000000a0000000000000040100000004200002000", + "0x50200000000810800010800000000000000000800000400008000000000000" + ] + }, + "b_hashes": { + "prev_hashes": [ + "0x684d6da309f7c8a0471b18617c4458fea2ebfec17d4f0a5e678f1adc7d5e9741", + "0xe9f1efd5c5d0eeda512ab8cfaa72a1e9289c1b7a1283080532c8bf75b78289ea", + "0xf1f4005a05d16db3af62e164efecbf1b244dfbdd4ecaa7787db627be3dc65b6c", + "0xb4a6b84696480f6864d1662abbdf9bf74d8c58d6831f0d7ebfb89c9527fd1a06", + "0x8aa706c69b4efb973104f4e3a32e95d0961cee806ac934c0b8d0f315a498fc1b", + "0x444a9b5d65aad376d8aad458454c8ad90e7ea3846e9a5bc88ce3838d25159a11", + "0x1be1d7933a44de4622ab864125ee3235446ec8090ae5ad96c5f4828ecefa7ce2", + "0x36608be1e2611f8248650a94ed0758bc3c884feb9c0182cef5b7a63b7cc13779", + "0xa018694b69aeead7c9707f8c4cc7087744b18f2697f5c0e8a72149f86472c3c4", + "0x42b9f09fc79a85c6dbc70e2580c57750a85e32aa34a83eef14f300d348d80549", + "0x037a1779222ab6b0c5bc9bf100ee568481169909eddd00b72d91cf43bd3b9450", + "0xecd72414a715469a622e9efd685e27bc0441d39bc3d1b900f9754ec88f9801dc", + "0x8e052b282793944721886849be4cf06bb9e2775bbecc017e62dda26fb0f167a2", + "0x57aa3978290d109b379f9622cb7bd3004b31e97e1d6c5fc9aef813b26a12417a", + "0xeb29dd370b5de7729819c006f19d3914245e72d3256b9c995bfc821c9244a782", + "0x62c4a05472424c89303241223787d704f5315bb16b5944adb8d950cc16d75ec6", + "0x10a0e4f10d854bb43c754b7cb0d027189f85db84c7da3f7533d1bf739bb89226", + "0x6ca125813a1af466dd481a16c6f008fe7301984f9caae0a16930609a1ceb43c0", + "0x18408b483e1a38b76caa9dc51e6c7dd759983b35d486954d2c50c9b90fffad01", + "0x5b1c83b40fda62cb008946b4884c1b95b1768c7a9a8a1e7dc20b34a4d8c403c7", + "0x06bee782c257495049d7a554a608a8f3acce1a09e6f844a8ddc9f723d93f83ce", + "0x72ea26252912b77f3b227e529337cf14b0971ea20e4f186ea969746a424def3f", + "0xb0b4482691ab89cbcfc2afdfc38fe972122598ef6db45c03684dc8c3ce63c4d2", + "0xcf7024250378017427243922eed81ee9088d32bf4867417e260cc11c71777519", + "0xe49ea52092a071f276c7b306d0f8d40c038ce49b3f8a45e03040c59e96c0354c", + "0xe4ef8d83b4e6e621c0b573d7209a364d87c763b840b572148b2006c6d346667e", + "0x5a06ccbd9311095490a4efbc9b358c85b5f3a1317841a83e7c36257e3827d16a", + "0x8dfbe7aa136575b5e3ed1659e1eee974f48d427717965748faec651425946931", + "0xab7ab2503be5139555349596c5059bc7fe1baa4f16222717f18023d7c8beceec", + "0x35c18135a8a4fb0c195df12feb2bdde3ae41fd2a5e68de71b2b8a8668cf01f50", + "0x265ac74569927d3827fe9a690f8b1dcd909e9400253da565681b0b6c3254b1be", + "0x5150ceb40dcc6656b079be8c4b5d0fdd0ec4c03dc56a07ded8bb4958f59f4cbe", + "0x131dce8de367b3afccb61ee1111ecbcc26b6c8cc011858def61749257822475b", + "0xcba6e3e9371fc2428e0d64458c1ea38247bc2402c35512699acd9e98c791bf4a", + "0x11d1ff0fba9724e35b61aa790e030eb87dfa4ae5f3eb32c562dfe73301a0e538", + "0x177c10d79e45f97825d1aee005daac2f5b878dae2afbeb077d701031eb8f5fdc", + "0x369df7c07465d4ce309973349ff656ec13dcdfed6bb6003a0e276257d2c804de", + "0xef7ceb8248b175f4f2ebd3ab1d3e9f716b7066d771728afc13ce26582001864f", + "0x612c72dc848b6752e0c59a08a69aad3195c94cf15b2be1ea04b8de949bbd8a62", + "0xc7810d75f85d4affc277425e3cd8016fa5fe5fbcebbc2f313f78dc733627c764", + "0x58f26bc1fc04a3d64c035bba57ccf0a11bd85643ca25a12c2872a379807fb223", + "0x06672d0ef05ad36ad7cbe4a6b0f8973d9fcf2f4948f3faad186eceab128f033b", + "0xcc7cf48db08ffbd8b3424b5d657f50f805cb7669da9f94b9ce708bcfe6ff60ab", + "0xf7afed969af520af3135ab06f591b98a657e6ed456cae4a8cf9ff74692a89ec6", + "0x2b5a83bdb1420eaaa036b871302aa8bd6f0591353932b9716ae6f5d9fa4960f7", + "0x17da50dc5a57ddfc357fd76dd2c6d844ab86f00c7c169f672160d94d29e0cfbd", + "0x901fcc4fd29455ab7e86debd1c0e590be42ed9a32eaca646491db2c0b28e1de9", + "0x0a46152ca0428fee39b130485849e10aee553f1d20af050610786b0c435076a5", + "0x3cda416406d979ad59f6bc3e924b619413a0431cecd4305a0cc0a5d7f72b1abb", + "0x669e1c9b20f56963eedff200b3287a89ecbff1d729d72150e9df36f0500f40f4", + "0x47e065cbecfdf242a8348899b50d34ab1136fcf1286219cb5865168112de1af8", + "0x01692e1363a33e509152e33cf5683b8ec0b778fd056ba37ee9b14e78dbcdc23d", + "0xc3b4f5f75a81c39f9f89c67c8d3c6e22dabfe55ec84e400f71095471478b22ce", + "0xf800724be14e70f9d21d7298e5cd85accfb1b5993b9641dd7d18f84e3fdf139f", + "0xc78c8f4aef3560226da9e906f818326914099cf7281311269fb3f185dad4026d", + "0x2d1d7a03d4eb6a453acbc98e7de60b9964c2dbabe8f364c64ce09713954816d6", + "0x319eddfeb21e1b4ac4eec6e0ab207f96bfd39fce8ad357412244a7a204cd15de", + "0x9267223f50a0670bad1af619be27538967dc3ffb4187427b8718dda635b0646f", + "0xf4fdc676ba397e28ef91298691083e12cfada760c6212d42d9d8bfc203b2e9c4", + "0xbbca762d5f9b9afece0c0c4b1b6d39c696101f0d380a059d42de63ee58a7b4a7", + "0x0caf0d6547aad36aef5058f772589929611795961e4c11ff6e4f0054350f124d", + "0x6e765d61eb9936842a77969a46b3ec1e8e63f8217c9a38dc1ae03fc1373e244e", + "0x074ad7e8b9a67df3be4ee365ca4d0ab0f21ac9681b206d8cd2e81d63999ab540", + "0xabe2d66e6ca7b273d15bdcc9569b73d69a7ce40ac4c7e0bca15233a0b466c48d", + "0xa559298ce71269e041845b239a2192380f19a0df3e986c9f7b88c52dfbb68b72", + "0x7a4474570b63e6e68f692ef9a2018faac14f83b2d5fe714dc8c28ac55ac5905e", + "0x6ad9f2d6af5853591ca84cf8c8641c40ea800ed17d0a93293df66964801d03bc", + "0xb2d224381f1b031be102dbb9d37bff9401c970648af6099de354dba9c6ffdc9e", + "0x920acddab483a67476079786ac02506ac2cb329acf707110cf4fc08be0946bce", + "0xbf74a03ed288773ab3d8ad7d6c7e45702d3679aa685d44d8b4ed31d25df0ee64", + "0x22fd1691c5b5c5d7e0f25a25045131cebb54e96418ac33f6b033dae571f0e1d8", + "0x584f88b1cc9ca5825aa734b30c1fb88947f7b29fa5d389f9d375c52c89be0095", + "0x1dd49abeeb34f3a389166da6f7efa1d7949b4bdee5d013696b53e1d3e0b9a1a5", + "0xceff63f333df8bf4dea68dd443b195b5d53234d4f3f14316949c2de4b2e51c86", + "0x1ee213255d77233e366e55a248c9c524fa15f218a439cad211c52d08bc09845b", + "0xc66fff1c5b7c88b856f8ece31c2f1323b54633a83674d87dfffe4a039adc5440", + "0x6968799fc7f27cd33e698332e67b85305583909934aa9ba5d2d503c722b808d3", + "0x489aba839fa83f28484a016bfea5657d9fee72f0bea757f0ac9a525bc9d4e0c4", + "0x8011b25fa321e68cbffce47bb81ec56e944834e9319323b6d99242b1decc258d", + "0x034004ec7113ffbaabdcf03e27f06cfc4b51fca60f79d9f1c78689eddde9ff0f", + "0xf1ca1b925a4666b8156d842c61b4d8d5a8dadd1493c1deeebcec06607f1c57c3", + "0x1c336bfc7570cb6baea4f653a5ae9ba9e6b2966d9662d544a50dafa3f80dc8a5", + "0x994795c6a5db487bc99d50a5c47ca4b0a31f0c14e266c75699ed998e418b0273", + "0xa3e8dc9d9b954be9e365913c0274dca50600b157ef2a66b480f464fc60ac2582", + "0x7cb8907a3c885ffafd56d399ffbdfc77647d589029466556ef8f0a875768cac8", + "0x685efa6d0c4ef05758e0d4cc60d8bc30c5b4d09aa595b293802bff4a14130d6d", + "0xd53ee0bac8d4f6afc15d9187d8959e047551b286607e98abc71af4a276e5533d", + "0x9c94b7cad6eed497f33a8cc13c23be945cfe59b5fc88ebf6e5a78b63139eb416", + "0x746593942f1a4e89157f04ebe3b5d19178964d6b358d03422340314b431a30eb", + "0x8f00f2b21786fef3908e708b2f9a9646dedf89570e52487c359d6237a10b2332", + "0xaccfffd17acfa905ea0882f1329d9419e5c6f8a914ce79ea8bde3168fc93bc43", + "0x382e627dc9457a8b97ead189afe4209b1301ef73409198ba1d03296eb0573c10", + "0x663c5109a960127c5f4461650b9ac0ec98b441af6a5c813ac394a4a54174863b", + "0x19bdaf621cb61a7e39ed537392459a8fc6fe593e80ac043b0541e3aa49db0083", + "0x258b4555385f82af59c3297d6809bdee140a627a72108fa2ccd7a467b7f9cf77", + "0xd2f5ec45869a2227af4789029b17d79e0f7465a3f8491ef8263f65fe9ee67863", + "0xb7429f2486335edee7b97c68bf21746516eb3edc2421b0af31cc0981edc71274", + "0x8532734773de15bd7612ef289467d82ecdb38f97f721842329c032baad97797e", + "0xfa196478a8d98d9e5a29d49e5dae3a0e8af0bd81bb1d171cdd0793511de497ee", + "0xabfa52d45f2b64ba8da63779cb8c0da104c6e4085a94a12e4c01e888d0eff5b0", + "0x79af7c7f2df5704e1ea35e336dcb50d6e1c4644beb770c4b737f92279b7cea4a", + "0x8bc16add47e2bffc84ae2cafaefd162c81d9377c9628e0e7947f05424374341b", + "0xcc2ff38b0b787f201208bec3c7ba38ae0a9ecf9c6ff3bb4bb91f07c5260b9f0d", + "0xe6bd50d7a43e80dc310283c8d28b6057de35dab665fc389ccf2934ef522a923e", + "0x976b800be89a4615209eeafe260f290b961f8f35f243ae15caaf16dfb2e8afdc", + "0x61a02bc6d0e17ea710151c4fe838cf5bdfe39ee7c341dc8c819e3090e8995218", + "0xaf4ee6ee6f0c42d3890e4924e9643e91b32c5aceec89f065f19c45abcc154afa", + "0x0f7199378a78513d389e381668ab838bc87df917e5e754834ad1f5b4ba46a6af", + "0x66cfabbb497ae08a4886bdd98e29ec79fb1f3faf1d1ec0a70f216512003f77c7", + "0x50897dda2b2a63b1cc8b51dffe4639b10e5914b650b91be064539c56782c2669", + "0x65f708a418a87d3ba9358abcb3000aa267c384be3e817691e59f6e2abfb7e383", + "0xc0b3bc8a33b5990a67385736bf5f3bf1f93d7f5d88b61f8e587da89741e58a78", + "0x3aa019da243d763fc2d4aedd82a42dd62088970f41df5b4701cddbc95ec80d1b", + "0x563be0cef26a96fe96d2458a459c16a1cfd98ff30de359be1ef0fc8148dcacd9", + "0x53d17e0d0919d7d07e8aece7825a7a898b4b0b4d0fce1514aeca0ff467fe359e", + "0xb37bb8db446e1a68ddb111e983aeae19443e6a8320d8c067b96b7e0e1a99872f", + "0x711a0ef39ec7f508ef2f5518d2d2b572d9af583cdd0c5b8d1b0aad720887ff8f", + "0x8f6223a484539e961de50a72916b5c37eacd50fc874cc9cb57cce7f3a7fbc768", + "0x78adee7ef19e71b90686be4672ade5996a705ab3676ecf56aa12c5fc7e31232a", + "0x66a7b68e630b5c23fe3a8c1ebaf2f71d65cce803acece13e7f6e04164b391170", + "0x49998c072fd731b40d077d1a8b908f79732ffe4ec4e2da0fbf213fd862d0c4b3", + "0x3e5c71e123e0b9ca51c5ed7b564993541002f89bbf50899c493aeae139bbfa73", + "0xce955633621fa117e4f462ac09a82e957eb8875e9157f5a4519db7dd9f3b3db8", + "0xccf8c2816119cf226e69262ee6189843884fbb9193c83f43ad112593c579a97b", + "0x9b38788917cf5046229a4763fc45d527b56734f07c93c9975192e1053590676d", + "0x3f5312d1efed17763afe2c2b773a7587614aba32c00f88ffc0d698494894ffc1", + "0xaab17205746a6a5daa4eb3d0466ec46e0c8d04b3c623b6fe14cc2fd82645d040", + "0x31c1f4e27e27c122a6a14ab18c6fd88362bc2b429d235845039bad13ff9ed249", + "0x5a6375471b27ee726cb0ba9e003429feeb43e2bd7f4e3fc96efe82f8f33684ba", + "0x6f5d262d1320c05cb93ca7d3d992a25ae9a6fe11b9651cabaedd420744253fe9", + "0x067b51b1902fa5927c5e23fba472434f29b062f91e0a8f6f03d5e4b4181cd769", + "0x9abcf7ba8d6a8803ac56d28d05cb9953df3fe3ea131bfcb1458f13ab5ab002f1", + "0x4898c61d3980d568e8d9869f9640be08595bba2097d3670d8a9f4cb531c83cd4", + "0xbec84066edad2a85a68054ce63fa0b03a8bdefc2c2921e09bf39e231e90393d9", + "0xe7531552af7303e818ae3ee063ff0e744cf71cea8169ccccdf303dad279ae6bf", + "0x25e0066d1dd0caf08b7affa7efe80264d46531196e281472a2b980e75697f6ee", + "0x6d5382548b060f3a60ae44a588df9338105f4e81f2de512aa36e6bf097292b75", + "0xadc1f0377233d6a5b656285306196cc7d8812f6513c2a9e92129ad261b7bba64", + "0x48a56c398dd7f128b06cf43f48cf8101c73cad5089622805af90db35a3a2426e", + "0x796ba441d23ff8350f8dd21c95df5bff4be0f3d873aceb329870c9d93e4b4f6a", + "0xaae31869bdfe85b02cfc3730b048b9e8e867879eed841bc020f272e893a4c0e1", + "0xeda5bdda98493d39a7204389c01006b9f116d3b568b0a4b58760d1d50f10c43b", + "0xc69cdb8b8d9b86fd9189c4dfbaaa542ff2eb9ee1f966d74e0936eb61162a9440", + "0xe138f6ab0383a803777aacf019c008232d49e3938cb80755d91f94b8c1df9157", + "0x05c166f7b3e46160fb1f4ac911ed3d4c787d422417b0a3548eeb3ec05c000899", + "0x5715789eb35a14b9f2594183f5e1b2636286ab0eedfc6f865af792d3329e3932", + "0x06fdc5cdb25a18fdaace2028efcb17c70dc6dccb6836d67e6c4db9d19e4b30a8", + "0x436ca87dbaf5000546a1158bba2679e093644214021c55bf7a18938bcbc8c26b", + "0x06894a55e5217c96c84691b662bb5882090290e3d2b8fb815d289bd43f401053", + "0x26c1f7ee90e8bd920401242821aa78bd2e76cd8acfb5fe42e8bc26215b52db55", + "0x4d17f9ff3941f0445607712ebebe2503ee90c31256324860d6e671a31dfc8553", + "0x7020b4039cb2fed36bd801861b72ffb52ea3692fb0de287cdc4555647691de3e", + "0xe142905c102278068d91f1305da98fd3aed0f0bcdcb79ae84f737b74e542687d", + "0x0b60248cf3eaff8acbdef8407bae6ea515c4f7277aa50e7bf0f3a13dc9838d64", + "0xd41fdec7606d96e22c817f96dbf17e5e13bb4e78212650e91b7400902e778bce", + "0xcb6628fc75d77664c234f1d54c51a7cdf15cc8c3b7ad42249755f5e5dab163ad", + "0x3fed5b830f088274b00699b5099deca1c39c0c184fc5c8d1759127430b799c68", + "0x6c81efc467bdadf6699b8b50d87b2b3f073b1d298080fa487edec7678bbcdd7e", + "0x0a5681394def8105618c8c229de40baed1deb5ecd36ac9f5f7d9f82f7dd8d951", + "0xad9fc5133f35f8c368921d614a6fd24e959ea43b5e3ed28ad744d776312c912a", + "0xb2c1ee02dd75e9ffb9d569d3c94152e51c9a92e119f9569a556413fe9283669d", + "0xb7587fe5feead89ea8bc2beded08ce896657010c6b7d3e1da556a028dd60aa48", + "0xffb8497fce9f9951f155f22cb21c88a8346cce183cadfcd36a797909b8399335", + "0x27227cf2b8fe94edb031f9f4b120e877776b7e71ae9f1a7b691027418768a512", + "0x8cafc271f1243f94fa188ce39e1ba9739d80f86bdcf7f0c4010906a0d7090669", + "0x68f1869e7e8f5764068759080c8b0ff0286d37a8c29586fa85ac9fff73c4d6f5", + "0x347203b050cb378a6b9eeb3550a773ed54f50bd58173d4e11e3775f17d66f159", + "0x55b8e9795919367fabbffa613d1be39b3ccf550e3d950255579d3d7d71836f4e", + "0x637439c6d5433a874591bcb7da0ce781f4c25a874f62c818315c5830a1aba7ae", + "0x2de673eb74141f4ffbdaf7ebdda72bfcea0feaf9a142d063f3076c830515fc4b", + "0x6db7840198ae6647f0396b0cffb0f0e3a5f19311b75f6165470bed13a5ec491d", + "0xe20f94a3cdb5b9583962ba1f91574d92d3d8ec2ffc342352c818cbfb1817520a", + "0x63e84cd9b5d9feb635bffccfdde923321b2228637b15f4c3140efdbefab2db2b", + "0x5dfb69849e6108ac90ec5a1dad1c02272e2749d63d7bdaf23e5f64a47b29d4f6", + "0x4a0eebd96c61d60db69c4b5ad5db67b5fb12b5ad82b97806123278e11f8e9ea7", + "0xb3b11a0d126c5a8b4e17ed69992814241a1b878eebc7881f746afbbf314f3948", + "0x8a43a231547f106b5327611db49e34d6e4ef973dab099027812efb441e377e68", + "0xe2d8e65eceab5214cc111762bf93b3f28bc4df80537772391b90c4b0f64dc79a", + "0x37fd13e02c213bf88b8c936ae156da419085ec4784501c856068e4e5701690d0", + "0x43ff3058e2be0dc50c23e8dd30d2222626d8f33055bba0e54bc039438439844f", + "0x218808f95657312a6048a0ef5ab932797271a031a17f0c9ea3c4ce800b223602", + "0x176aa1cc1879ea31d77f16e7f676357a071a6251a73a4fe102ea9e5a4a225d01", + "0xfaa14244b79614b82d7032d9268ea13c99da4784866e3bcb8261ba64eb4e3061", + "0x6c59f62d885febe6a62a95bc4d4193371717f6e371a8d0f99afc8d0c453eba2c", + "0xceac1b9b4fe4ecc2abb129a577ad3418ae76bacaee0795df90b72efcb6593f80", + "0x5f7a826a06c7edbab0eb5684a9774ffaf820340e296a6852384d4379e28c77f2", + "0x144fc77d47ab1ba0d034429cce70860e427f22e30a43405a8f428c0da78aa7e5", + "0x9c998912ed306b9a8e384c0e8afdca281557d1808e68ae2ebbf0ed2344b3a9b2", + "0x7ff6f8bb85a0dc93aed009e3a967fff69c97952b18bf1042ec4cc4b89d3569e2", + "0xee6783172418d7984b42267c1612bcddb7d38e23375793000c092b1205eb2b81", + "0x50ef73d80aa69de4640d47bf47012ad9bc6fe971d38f32200fe6df4ea0ed8fcc", + "0xcb90a6a4c4189787842e3330fab531daf96f240c9bb77b9ef4a9a7bc5a7dceac", + "0x891884d140c8ada4d7af76bf1f1e4e7085fcdaab482d3f093ee9ef4c3e2b5955", + "0xcacfbc3c46f96bcbd1c93915883c9af2b32078c383f65eebfccf8e2736f331ce", + "0x352b23629786a33d536df0957c32984c19e1b13d338a1dd0cffeef6a0c9c063f", + "0x6d7cad0c54976b20f02db2ba8926a59d51c95be9e5dac6d3a7f5f13c06e3c207", + "0x0a7eb23ff876af6025848afd5be18ea20b29d98bc58f659eac3ff66127199613", + "0x5933e5991f50c1693833f19f0d9059ca1d5daba6fe8d37620f397bee7ff02ea8", + "0x9c20dd8216a07e1952ac1ca48277c615d1a0fc0d4d3833df76c5a2de231dd7d4", + "0x4821c85f5615995b87747f6ef3022532677d4e86fdf010412dc2a3df7bef94c0", + "0xae021e27142fc325181ef3521e9cc29883d845f96a971f3dd4bb9eed8ae1aa92", + "0x78d29fda96832395048b73d9e023958767457069a1718ec5e27740798eeadb74", + "0x25724cc81bf397890f2bdd8f35a3756b3386f333ed55fc7e51bbc2f1fd2bf6a2", + "0x824762d03b42d475150a9fda08ff9285f62f2c0bf6401d7375d602bda1f3bea6", + "0x9333ebdf48f884bef84e204c826dc6677ea35bb496ae9738e5e4e0f47df8988b", + "0x70b1e57107c22010c865f93f7f733c043e56a38faa8ec3781ee7036ed0e309d6", + "0x1aa6351752aa54bb2130bd05beaedd3512fb362e69fd4a44663fd698e013ce71", + "0x1b656e01797575847ba4c952c0c056d1470d690eb65f6515def1d25db71b414e", + "0xeac3bf46591d9d4f20321cd484fdea8273ac34a5253266f1df57fdd983bce73b", + "0xc376b24cb0a5528dc24776b2fb7b065cf69ae303d0a643a980e5531e2ff0dee4", + "0x0c5d2c7315c9ea69050ad21982e373768d0a3526db617f33da18e6bd1b63c373", + "0xeb06cdc3f738bb8a8669b37cae56d51bf3ec47ed7fd0f93825a42f1007909042", + "0xcc349f38dcd62fd9c5fbd781a080fe1571aa415ad5907eb370844f54e2dc820c", + "0x7e7b0e1c580a5745ac78bc733d960d43f34f16d7f124dcb6d7fba5403fe4c006", + "0x1519a24ed04d8572dc9b6b050b3856fcf11fcf5a1dce1dbc22bf15fd2ac1d156", + "0x086764bcad0624385326103f4960c64e4fe8426883e81145713d118d16a8303d", + "0x621f91b175ad126f9b503e1bedb24e0752b88eec86c8e7efd30435a1ab9a4a52", + "0x56f59bce52ed19a2791690f7917aca8bd2ae2f412eae82dde2ed0d89c1645f5b", + "0x8560130fe131d33f84e58cd33e4157b5a3ccc0f8a0952bc8dbc02984e2b0a68a", + "0xf66c3c244b672ef8df24cdd6f81021342d8d354f5b5e88ef55f7a791cc39a1bb", + "0x6b7f194a9a191bea25a5f988bfcde8461a661ff0cd084042b716a34cb3d6bf54", + "0x32f1c5949c9448345947a59d5c5104d1bc90f7b702fdca4f521dcc4b5c0df0d5", + "0x3f8391d7ed4f166d44d21a9f6357608855320ca5fb50e89894635ad48e0e6604", + "0xd6390cb566f7a9ff98913fc33ede46108b29759a3f819a42eff90766e6f6435d", + "0xd06f278cceb875552dec0ac684f30e906b9e9e26c81891803c0e332ac92eec21", + "0x47fc7b6ab4869079bc2059199df9ef6db2b1d9c38fa5dc73f482a4ee367d0560", + "0xd0016d3ad15aa07661db1756fdfd7d586e245fc0f15269c65370feac1485b406", + "0x5adb9f0abcf8622847c66176355b549742614156c4fa62b54dd853ae1d4e8ed3", + "0x3bad795c74f5e233fe973cf09b6a7f680bbb62fe8788feb2cc4949285d1bc916", + "0x42dc8204f8822efa30913242c0c7b41bcd1707e790cc4d609e67412cff634e64", + "0x66e8171b0ddf974391efb526e2b7ccebd85f137094225e2627b8c7f7149e8e37", + "0xcf186ed9125bca4d3bf513d3c324d72290b55b334052e43effc9c01880108dca", + "0xaf95f87155fd1bdeb3c85f003a4b232950f08abd700e5f198dff81e7fe28a90b", + "0xf18e69bdaf8bb5c5e43959d6d7e9ca57b391ff18112197b3615dafbce0c96eba", + "0xc6ab386f906a7f4bfed294e143d87b0e3a33a2270cd36642605e162bf9b13dfe", + "0xcfda661e48ad92890b2d8598d5375baffd31657ab88917966338caf29729f848", + "0x69decb9aa1175f5783c608242cc366907b07917cffbedaa434786aa2cc1f25fe", + "0x80f9b7b74df1d29ba5d29559255f20096c7419f8fb5f25a9a52c4f6bda0bdb06", + "0x89d751062d8782376d3c187aa726620d694aea101ed9562fbbae5586b2a5ddd9", + "0x554a7df6055887e4e3f23dabf665219d49c49aec890467ffc39394de053f8be9", + "0xe6a6717093a4d6d61ae2548f18c023b916fea722fe301b234c2065968eba8896", + "0xfbe85151ebf2b992e6db11d6e5c0957fa11c3ffc924cfc7912101c5a1c8b7e3a", + "0x21b315e2a4fd2cacd402fa338518800ca7d215dbb63ccdb83c044a0bf63146eb", + "0x39065ef91631dfc82f937b0f834e674d37326d6e8a23410d27cdef7c4d52e5a2", + "0x6b720dc652a51931dd7a8dc86b7cd30c12185a39db1cbc6dcc7323d8355627c7", + "0xc6bfd1c8c7222bf522882ff5c3c86c7b6f17196b79afd7d39b700fc3526b6bcb", + "0x5725adf9c722d78ef5e43192465eeca06b4f8753ed584f9eb5eb3dbf0ed9b129", + "0x92c5c278bb28520f283325dbd8a2773644a13cbba491933ba9be7a3c6169daba", + "0xcfa791094b86177ae57ccc0be478cbf8c6c09b79b698af50bcacd239997cf329", + "0x43acd4f1ae62e9e5c1c87284abc8f7d0d0aef0f70cd934a788ab566573e2f5a8", + "0xef4b796bda5a978f28ab810df3f2ee6ba0d646bb35ecc59d4a8aa1a11011d8e5", + "0xd804abc861a77340c0556e21650cd1f8e1e4a3fa4d83ab19862c83fbe0ab6b9f", + "0x633167a2ee64b2820d9557bd0bc540255f9379ef0802e7385c70c90bc7f3c57c", + "0x2c32447d713a9859b680e9da9f634341c159c0ae1f94f35d47f6ef384f7e69b6", + "0xbf5087bda3e2f18942e922e2e0aa27e88b23d65f33009049dfde50400fa1c7ad", + "0x27c8003b8d5ed3a0dfb4313cf8e969478176df308cb8ff7590f86323469046e2" + ], + "cur_hash": "0x0b4c5b498320e5858e7445a5af10658ffee39c9a39de22c81452de5421f63464" + }, + "withdrawals": [ + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x11a0834" + ], + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x11a27fc" + ], + [ + "0xc9234d5606e02a0acdb7682fe36adb588cae60d8", + "0x11b88e3" + ], + [ + "0xc9234d5606e02a0acdb7682fe36adb588cae60d8", + "0x11be16e" + ], + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x11a1416" + ], + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x11a2dc0" + ], + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x11a42a8" + ], + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x119940e" + ], + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x1199000" + ], + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x11a046e" + ], + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x119d364" + ], + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x3bc7b17" + ], + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x1192ee6" + ], + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x119bcc9" + ], + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x118f663" + ], + [ + "0xa8c62111e4652b07110a0fc81816303c42632f64", + "0x119b5ce" + ] + ] + }, + "checkpoint_state_trie_root": "0xbbd66174555d27c88e285ff4797de401470d8d2486d15513ab36e491e864bca2" + } + } +] \ No newline at end of file diff --git a/zero_bin/tools/artifacts/witness_b2_b7.json b/zero_bin/tools/artifacts/witness_b2_b7.json deleted file mode 100644 index edb9d173a..000000000 --- a/zero_bin/tools/artifacts/witness_b2_b7.json +++ /dev/null @@ -1,2414 +0,0 @@ -[ - { - "block_trace": { - "trie_pre_images": { - "combined": { - "compact": "0x0005582002601462093b5945d1676df093446790fd31b20e7b12a2e8e5e09d068109616b084a021e19e0c9bab240000005582002b64061d1b10621ed3cea3432c7e961244197663de5d2b7b25c29e63bec606d0847038d7ea4c680000218480558200268288056310c82aa4c01a7e12a10f8111a0560e72b700555479031b86c357d0841010558200239fa8ab811ddec4c30c62c575a346979cb7339d3e6b1b446aa9cec770bb1ca0847038d7ea4c68000055820022c9421b06b5fb4ed4f3f4b868f487d536ba02ebe1a6347c6a1312167dc000a0847038d7ea4c6800002194210055820021df1fa259221d02aa4956eb0d35ace318ca24c0a33a64c1af96cf67cf245b6084101055820021703c5eda8644a64cec152c58f5aacec93d72fb0bfa705f0473f9043a8357c0841010558200228a39461658094f425f190222a515c7902808b31fd90ea063e831b49d744390847038d7ea4c680000219808405582103b70e80538acdabd6137353b0f9d8d149f4dba91e8be2e7946e409bfdbe685b900841010558210389802d6ed1a28b049e9d4fe5334c5902fd9bc00c42821c82f82ee2da10be90800841010558200256274a27dd7524955417c11ecd917251cc7c4c8310f4c7e4bd3c304d3d9a790c064a021e19cb147d4182dc00055820023ab0970b73895b8c9959bae685c3a19f45eb5ad89d42b52a340ec4ac204d190841010219102005582103876da518a393dbd067dc72abfa08d475ed6447fca96d92ec3f9e7eba503ca6100841010558210352688a8f926c816ca1e079067caba944f158e764817b83fc43594370ca9cf6200841010558200296cdcb823ae5bcb55a33b2a1a22c03bb69870a0270cfef4e7ea22125e9aa090847038d7ea4c680000558200290b239ba3aaf993e443ae14aeffc44cf8d9931a79baed9fa141d0e4506e13108410102184205582103bd0026f7e234624f2bd4ca2c50d2f731cd19f206a89b29fa0c50b5b4e6bf13300847038d7ea4c680000219e573" - } - }, - "code_db": null, - "txn_info": [ - { - "traces": { - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e19c778536cbd49b0", - "nonce": "0x7" - }, - "0x14dc11386c1eee1dff28b4823f68e8de3b5a2747": { - "balance": "0x71afd498d0000" - } - }, - "meta": { - "byte_code": "0x02f86f820539068084684ee1808252089414dc11386c1eee1dff28b4823f68e8de3b5a274787038d7ea4c6800080c001a092dbe3ecfa549833bd3d19749cfe5cf8f52d97453161a1b7e54183844940748fa07a2b355a1badc2ac05a2114373c908041cd3a41d6e5fbffaeaa28564739f9c37", - "new_txn_trie_node_byte": "0x02f86f820539068084684ee1808252089414dc11386c1eee1dff28b4823f68e8de3b5a274787038d7ea4c6800080c001a092dbe3ecfa549833bd3d19749cfe5cf8f52d97453161a1b7e54183844940748fa07a2b355a1badc2ac05a2114373c908041cd3a41d6e5fbffaeaa28564739f9c37", - "new_receipt_trie_node_byte": "0xb9010c02f9010801825208b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e19c3dc2997f7b760", - "nonce": "0x8" - }, - "0x92d3267215ec56542b985473e73c8417403b15ac": { - "balance": "0x71afd498d0000" - } - }, - "meta": { - "byte_code": "0x02f86f820539078084684ee1808252089492d3267215ec56542b985473e73c8417403b15ac87038d7ea4c6800080c080a0bc63624565de404628556d7eb9417d09b42a2d7ebe157b4f793228c1853eb264a07d864356f782d8550850cefd9443685949ca9a798b3525f18372b95631a1b3af", - "new_txn_trie_node_byte": "0x02f86f820539078084684ee1808252089492d3267215ec56542b985473e73c8417403b15ac87038d7ea4c6800080c080a0bc63624565de404628556d7eb9417d09b42a2d7ebe157b4f793228c1853eb264a07d864356f782d8550850cefd9443685949ca9a798b3525f18372b95631a1b3af", - "new_receipt_trie_node_byte": "0xb9010c02f901080182a410b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e19c03fffc3322510", - "nonce": "0x9" - }, - "0x882145b1f9764372125861727d7be616c84010ef": { - "balance": "0x71afd498d0000" - } - }, - "meta": { - "byte_code": "0x02f86f820539088084684ee18082520894882145b1f9764372125861727d7be616c84010ef87038d7ea4c6800080c080a02713ccd51a6f9a0dffe183d16f74298378766ec9ad7144271c71d2585e33759aa0536b5efaacae718f1b88022075479e2a4a15538062feb46eed9195b24c9a02d7", - "new_txn_trie_node_byte": "0x02f86f820539088084684ee18082520894882145b1f9764372125861727d7be616c84010ef87038d7ea4c6800080c080a02713ccd51a6f9a0dffe183d16f74298378766ec9ad7144271c71d2585e33759aa0536b5efaacae718f1b88022075479e2a4a15538062feb46eed9195b24c9a02d7", - "new_receipt_trie_node_byte": "0xb9010c02f901080182f618b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e19bca3d5ee6c92c0", - "nonce": "0xa" - }, - "0x2c80179883217370f777e76c067eea91d8283c5c": { - "balance": "0x71afd498d0000" - } - }, - "meta": { - "byte_code": "0x02f86f820539098084684ee180825208942c80179883217370f777e76c067eea91d8283c5c87038d7ea4c6800080c080a06fa2057d82f93db7270054b4d14a87ae9b637a55d1807554558df6d594426a3da01a305e8690961c69049c91241736e8f42a69331fc858be99f0c685d51459efcf", - "new_txn_trie_node_byte": "0x02f86f820539098084684ee180825208942c80179883217370f777e76c067eea91d8283c5c87038d7ea4c6800080c080a06fa2057d82f93db7270054b4d14a87ae9b637a55d1807554558df6d594426a3da01a305e8690961c69049c91241736e8f42a69331fc858be99f0c685d51459efcf", - "new_receipt_trie_node_byte": "0xb9010d02f901090183014820b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e19b907ac19a70070", - "nonce": "0xb" - }, - "0x9e0823da9f7f3a0b22dd2798e6af7b39be37f0da": { - "balance": "0x71afd498d0000" - } - }, - "meta": { - "byte_code": "0x02f86f8205390a8084684ee180825208949e0823da9f7f3a0b22dd2798e6af7b39be37f0da87038d7ea4c6800080c080a030aa6c020193e75936be747c0e8da2f9beebc266de42f6a3ebb3afe6247733bfa05220d355e4ec287f06ae0e40b23bb4a86dfb54c645b5a345d80fd9c097d5bccc", - "new_txn_trie_node_byte": "0x02f86f8205390a8084684ee180825208949e0823da9f7f3a0b22dd2798e6af7b39be37f0da87038d7ea4c6800080c080a030aa6c020193e75936be747c0e8da2f9beebc266de42f6a3ebb3afe6247733bfa05220d355e4ec287f06ae0e40b23bb4a86dfb54c645b5a345d80fd9c097d5bccc", - "new_receipt_trie_node_byte": "0xb9010d02f901090183019a28b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e19b56b8244e16e20", - "nonce": "0xc" - }, - "0x7972eef40a371cbfd84c7d709507cc300c6d06a5": { - "balance": "0x71afd498d0000" - } - }, - "meta": { - "byte_code": "0x02f86f8205390b8084684ee180825208947972eef40a371cbfd84c7d709507cc300c6d06a587038d7ea4c6800080c001a0350f184d3a2dfd842fe7e95131933ab980f9ce1bf2d81fc37582df17d3c97883a03d91d78fb680003308495c1c1f40055c0e5530cd235cba33371ef0815bbabc2e", - "new_txn_trie_node_byte": "0x02f86f8205390b8084684ee180825208947972eef40a371cbfd84c7d709507cc300c6d06a587038d7ea4c6800080c001a0350f184d3a2dfd842fe7e95131933ab980f9ce1bf2d81fc37582df17d3c97883a03d91d78fb680003308495c1c1f40055c0e5530cd235cba33371ef0815bbabc2e", - "new_receipt_trie_node_byte": "0xb9010d02f90109018301ec30b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - } - ] - }, - "other_data": { - "b_data": { - "b_meta": { - "block_beneficiary": "0x67b1d87101671b127f5f8714789c7192f7ad340e", - "block_timestamp": "0x666a9696", - "block_number": "0x2", - "block_difficulty": "0x2", - "block_random": "0x0000000000000000000000000000000000000000000000000000000000000000", - "block_gaslimit": "0xafd1a5", - "block_chain_id": "0x539", - "block_base_fee": "0x2dc70bca", - "block_gas_used": "0x1ec30", - "block_bloom": [ - "0x0", - "0x0", - "0x0", - "0x0", - "0x0", - "0x0", - "0x0", - "0x0" - ] - }, - "b_hashes": { - "prev_hashes": [ - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0xc493151b4991dd1bf459952509bba9bc3fb2b706299e18e36e6b78781835065d", - "0xecc163d4e19061c77f9bc84afaaa6b98961e33435f80f9ebd6f751716f172b9c" - ], - "cur_hash": "0x52ed2bdc1acae1b2ad7890c4084e6d0db5a85b79d6ffa3f7a7efc04e3428b5f4" - }, - "withdrawals": [] - }, - "checkpoint_state_trie_root": "0x0fd5324836befac89fcd430abb81f2a274130af0dfa1a89babac76c574b58c40" - } - }, - { - "block_trace": { - "trie_pre_images": { - "combined": { - "compact": "0x0005582002601462093b5945d1676df093446790fd31b20e7b12a2e8e5e09d068109616b084a021e19e0c9bab240000005582002b64061d1b10621ed3cea3432c7e961244197663de5d2b7b25c29e63bec606d0847071afd498d00000218480558200268288056310c82aa4c01a7e12a10f8111a0560e72b700555479031b86c357d0841010558200239fa8ab811ddec4c30c62c575a346979cb7339d3e6b1b446aa9cec770bb1ca0847071afd498d0000055820022c9421b06b5fb4ed4f3f4b868f487d536ba02ebe1a6347c6a1312167dc000a0847071afd498d000002194210055820021df1fa259221d02aa4956eb0d35ace318ca24c0a33a64c1af96cf67cf245b6084101055820021703c5eda8644a64cec152c58f5aacec93d72fb0bfa705f0473f9043a8357c0841010558200228a39461658094f425f190222a515c7902808b31fd90ea063e831b49d744390847071afd498d00000219808405582103b70e80538acdabd6137353b0f9d8d149f4dba91e8be2e7946e409bfdbe685b900841010558210389802d6ed1a28b049e9d4fe5334c5902fd9bc00c42821c82f82ee2da10be90800841010558200256274a27dd7524955417c11ecd917251cc7c4c8310f4c7e4bd3c304d3d9a790c0c4a021e19b56b8244e16e20055820023ab0970b73895b8c9959bae685c3a19f45eb5ad89d42b52a340ec4ac204d190841010219102005582103876da518a393dbd067dc72abfa08d475ed6447fca96d92ec3f9e7eba503ca6100841010558210352688a8f926c816ca1e079067caba944f158e764817b83fc43594370ca9cf6200841010558200296cdcb823ae5bcb55a33b2a1a22c03bb69870a0270cfef4e7ea22125e9aa090847071afd498d00000558200290b239ba3aaf993e443ae14aeffc44cf8d9931a79baed9fa141d0e4506e13108410102184205582103bd0026f7e234624f2bd4ca2c50d2f731cd19f206a89b29fa0c50b5b4e6bf13300847071afd498d00000219e573" - } - }, - "code_db": null, - "txn_info": [ - { - "traces": { - "0x14dc11386c1eee1dff28b4823f68e8de3b5a2747": { - "balance": "0xaa87bee538000" - }, - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e19b1d123920a4700", - "nonce": "0xd" - } - }, - "meta": { - "byte_code": "0x02f86f8205390c80845b8e17948252089414dc11386c1eee1dff28b4823f68e8de3b5a274787038d7ea4c6800080c080a00a58699f9b733fbcedc646a376f2ce3147de686a82577f52345dcb07eb452636a036ccf13d2c1d7f3b3d73a35e1de002d5b8b9b3a16b1a76befb3dc652e3848a96", - "new_txn_trie_node_byte": "0x02f86f8205390c80845b8e17948252089414dc11386c1eee1dff28b4823f68e8de3b5a274787038d7ea4c6800080c080a00a58699f9b733fbcedc646a376f2ce3147de686a82577f52345dcb07eb452636a036ccf13d2c1d7f3b3d73a35e1de002d5b8b9b3a16b1a76befb3dc652e3848a96", - "new_receipt_trie_node_byte": "0xb9010c02f9010801825208b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x92d3267215ec56542b985473e73c8417403b15ac": { - "balance": "0xaa87bee538000" - }, - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e19ae36c4df331fe0", - "nonce": "0xe" - } - }, - "meta": { - "byte_code": "0x02f86f8205390d80845b8e17948252089492d3267215ec56542b985473e73c8417403b15ac87038d7ea4c6800080c080a004d21abe2e5c2b58d08dca7efdd4f1f5f9b612cbaac0559cb994dbab4af065e1a038c90f192cb6cd6b7932e55e816307ffefd43eac31daa12eb220f343f0c87ae2", - "new_txn_trie_node_byte": "0x02f86f8205390d80845b8e17948252089492d3267215ec56542b985473e73c8417403b15ac87038d7ea4c6800080c080a004d21abe2e5c2b58d08dca7efdd4f1f5f9b612cbaac0559cb994dbab4af065e1a038c90f192cb6cd6b7932e55e816307ffefd43eac31daa12eb220f343f0c87ae2", - "new_receipt_trie_node_byte": "0xb9010c02f901080182a410b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e19aa9c662c5bf8c0", - "nonce": "0xf" - }, - "0x882145b1f9764372125861727d7be616c84010ef": { - "balance": "0xaa87bee538000" - } - }, - "meta": { - "byte_code": "0x02f86f8205390e80845b8e179482520894882145b1f9764372125861727d7be616c84010ef87038d7ea4c6800080c001a0c82df3fcaf2dc8704585465d951353092c85497dae88c0af95349b000ebec19da026311c7ed4f3580bdbb6ff90d66143ed9268f3fa849c22b0bf2697a87c27a042", - "new_txn_trie_node_byte": "0x02f86f8205390e80845b8e179482520894882145b1f9764372125861727d7be616c84010ef87038d7ea4c6800080c001a0c82df3fcaf2dc8704585465d951353092c85497dae88c0af95349b000ebec19da026311c7ed4f3580bdbb6ff90d66143ed9268f3fa849c22b0bf2697a87c27a042", - "new_receipt_trie_node_byte": "0xb9010c02f901080182f618b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x2c80179883217370f777e76c067eea91d8283c5c": { - "balance": "0xaa87bee538000" - }, - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e19a702077984d1a0", - "nonce": "0x10" - } - }, - "meta": { - "byte_code": "0x02f86f8205390f80845b8e1794825208942c80179883217370f777e76c067eea91d8283c5c87038d7ea4c6800080c001a024b550b0378ff482258f0cbded8acca28dd596cfa345b72eb820aef81547fbb5a0370acb5c8df0856109b044786515f7912fb126cae4749dd21ed842f2de9e15fe", - "new_txn_trie_node_byte": "0x02f86f8205390f80845b8e1794825208942c80179883217370f777e76c067eea91d8283c5c87038d7ea4c6800080c001a024b550b0378ff482258f0cbded8acca28dd596cfa345b72eb820aef81547fbb5a0370acb5c8df0856109b044786515f7912fb126cae4749dd21ed842f2de9e15fe", - "new_receipt_trie_node_byte": "0xb9010d02f901090183014820b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x9e0823da9f7f3a0b22dd2798e6af7b39be37f0da": { - "balance": "0xaa87bee538000" - }, - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e19a367a8c6adaa80", - "nonce": "0x11" - } - }, - "meta": { - "byte_code": "0x02f86f8205391080845b8e1794825208949e0823da9f7f3a0b22dd2798e6af7b39be37f0da87038d7ea4c6800080c080a090a32c6b8c9465c41196acf9e693b08e354ab4b42a3f04d80031882dfa5201f5a066d70a3e893fccefdb8c64a099543a08c19d38b2456c5e39a25cc66ada38f319", - "new_txn_trie_node_byte": "0x02f86f8205391080845b8e1794825208949e0823da9f7f3a0b22dd2798e6af7b39be37f0da87038d7ea4c6800080c080a090a32c6b8c9465c41196acf9e693b08e354ab4b42a3f04d80031882dfa5201f5a066d70a3e893fccefdb8c64a099543a08c19d38b2456c5e39a25cc66ada38f319", - "new_receipt_trie_node_byte": "0xb9010d02f901090183019a28b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x7972eef40a371cbfd84c7d709507cc300c6d06a5": { - "balance": "0xaa87bee538000" - }, - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e199fcd4a13d68360", - "nonce": "0x12" - } - }, - "meta": { - "byte_code": "0x02f86f8205391180845b8e1794825208947972eef40a371cbfd84c7d709507cc300c6d06a587038d7ea4c6800080c001a0cbd26a2d7e25345c0dc186ec91039a0d5cf958b0cffa9334b85c18949870c4cea02016b7f4800b0cc7f6c383e14a3bf7a1c1079c2a7a9158c7149f7bbc7147f079", - "new_txn_trie_node_byte": "0x02f86f8205391180845b8e1794825208947972eef40a371cbfd84c7d709507cc300c6d06a587038d7ea4c6800080c001a0cbd26a2d7e25345c0dc186ec91039a0d5cf958b0cffa9334b85c18949870c4cea02016b7f4800b0cc7f6c383e14a3bf7a1c1079c2a7a9158c7149f7bbc7147f079", - "new_receipt_trie_node_byte": "0xb9010d02f90109018301ec30b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - } - ] - }, - "other_data": { - "b_data": { - "b_meta": { - "block_beneficiary": "0x67b1d87101671b127f5f8714789c7192f7ad340e", - "block_timestamp": "0x666a96b7", - "block_number": "0x3", - "block_difficulty": "0x2", - "block_random": "0x0000000000000000000000000000000000000000000000000000000000000000", - "block_gaslimit": "0xaffd98", - "block_chain_id": "0x539", - "block_base_fee": "0x282e33e4", - "block_gas_used": "0x1ec30", - "block_bloom": [ - "0x0", - "0x0", - "0x0", - "0x0", - "0x0", - "0x0", - "0x0", - "0x0" - ] - }, - "b_hashes": { - "prev_hashes": [ - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0xc493151b4991dd1bf459952509bba9bc3fb2b706299e18e36e6b78781835065d", - "0xecc163d4e19061c77f9bc84afaaa6b98961e33435f80f9ebd6f751716f172b9c", - "0x52ed2bdc1acae1b2ad7890c4084e6d0db5a85b79d6ffa3f7a7efc04e3428b5f4" - ], - "cur_hash": "0x43e9e6398559ccdd4238d08a0b79ef57f50dddb2b75dd7c00e132718b54414c6" - }, - "withdrawals": [] - }, - "checkpoint_state_trie_root": "0x0fd5324836befac89fcd430abb81f2a274130af0dfa1a89babac76c574b58c40" - } - }, - { - "block_trace": { - "trie_pre_images": { - "combined": { - "compact": "0x0005582002601462093b5945d1676df093446790fd31b20e7b12a2e8e5e09d068109616b084a021e19e0c9bab240000005582002b64061d1b10621ed3cea3432c7e961244197663de5d2b7b25c29e63bec606d08470aa87bee5380000218480558200268288056310c82aa4c01a7e12a10f8111a0560e72b700555479031b86c357d0841010558200239fa8ab811ddec4c30c62c575a346979cb7339d3e6b1b446aa9cec770bb1ca08470aa87bee538000055820022c9421b06b5fb4ed4f3f4b868f487d536ba02ebe1a6347c6a1312167dc000a08470aa87bee53800002194210055820021df1fa259221d02aa4956eb0d35ace318ca24c0a33a64c1af96cf67cf245b6084101055820021703c5eda8644a64cec152c58f5aacec93d72fb0bfa705f0473f9043a8357c0841010558200228a39461658094f425f190222a515c7902808b31fd90ea063e831b49d7443908470aa87bee5380000219808405582103b70e80538acdabd6137353b0f9d8d149f4dba91e8be2e7946e409bfdbe685b900841010558210389802d6ed1a28b049e9d4fe5334c5902fd9bc00c42821c82f82ee2da10be90800841010558200256274a27dd7524955417c11ecd917251cc7c4c8310f4c7e4bd3c304d3d9a790c124a021e199fcd4a13d68360055820023ab0970b73895b8c9959bae685c3a19f45eb5ad89d42b52a340ec4ac204d190841010219102005582103876da518a393dbd067dc72abfa08d475ed6447fca96d92ec3f9e7eba503ca6100841010558210352688a8f926c816ca1e079067caba944f158e764817b83fc43594370ca9cf6200841010558200296cdcb823ae5bcb55a33b2a1a22c03bb69870a0270cfef4e7ea22125e9aa0908470aa87bee5380000558200290b239ba3aaf993e443ae14aeffc44cf8d9931a79baed9fa141d0e4506e13108410102184205582103bd0026f7e234624f2bd4ca2c50d2f731cd19f206a89b29fa0c50b5b4e6bf133008470aa87bee5380000219e573" - } - }, - "code_db": null, - "txn_info": [ - { - "traces": { - "0x14dc11386c1eee1dff28b4823f68e8de3b5a2747": { - "balance": "0xe35fa931a0000" - }, - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e199c347e6242cbe8", - "nonce": "0x13" - } - }, - "meta": { - "byte_code": "0x02f86f820539128084505c67c88252089414dc11386c1eee1dff28b4823f68e8de3b5a274787038d7ea4c6800080c001a07623906391b43777272fd81f61128a53d5a774e2d8869706d55db74949f63c8aa06d5354f430ac0d64bea7ca1846bfc99818b70e56a2bbf11e43b563fbf92e6130", - "new_txn_trie_node_byte": "0x02f86f820539128084505c67c88252089414dc11386c1eee1dff28b4823f68e8de3b5a274787038d7ea4c6800080c001a07623906391b43777272fd81f61128a53d5a774e2d8869706d55db74949f63c8aa06d5354f430ac0d64bea7ca1846bfc99818b70e56a2bbf11e43b563fbf92e6130", - "new_receipt_trie_node_byte": "0xb9010c02f9010801825208b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x92d3267215ec56542b985473e73c8417403b15ac": { - "balance": "0xe35fa931a0000" - }, - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e19989bb2b0af1470", - "nonce": "0x14" - } - }, - "meta": { - "byte_code": "0x02f86f820539138084505c67c88252089492d3267215ec56542b985473e73c8417403b15ac87038d7ea4c6800080c001a093768ed5baa342a66908d6acf28633218c96d295fd1cd9c8fe22080f56202ed4a03464d2a6c818e77e004e66624d7d722aa98579b0fc821f1d9101b76f1f6f3e86", - "new_txn_trie_node_byte": "0x02f86f820539138084505c67c88252089492d3267215ec56542b985473e73c8417403b15ac87038d7ea4c6800080c001a093768ed5baa342a66908d6acf28633218c96d295fd1cd9c8fe22080f56202ed4a03464d2a6c818e77e004e66624d7d722aa98579b0fc821f1d9101b76f1f6f3e86", - "new_receipt_trie_node_byte": "0xb9010c02f901080182a410b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e199502e6ff1b5cf8", - "nonce": "0x15" - }, - "0x882145b1f9764372125861727d7be616c84010ef": { - "balance": "0xe35fa931a0000" - } - }, - "meta": { - "byte_code": "0x02f86f820539148084505c67c882520894882145b1f9764372125861727d7be616c84010ef87038d7ea4c6800080c080a05770c4bafb679d29918cd6320af2384452c3ba35ae27dd5614a226ba002db4c5a02bbbc7405851bc0a7e84ecb188262dcb2cc7e2128418b13f60c485f7e656e372", - "new_txn_trie_node_byte": "0x02f86f820539148084505c67c882520894882145b1f9764372125861727d7be616c84010ef87038d7ea4c6800080c080a05770c4bafb679d29918cd6320af2384452c3ba35ae27dd5614a226ba002db4c5a02bbbc7405851bc0a7e84ecb188262dcb2cc7e2128418b13f60c485f7e656e372", - "new_receipt_trie_node_byte": "0xb9010c02f901080182f618b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e19916a1b4d87a580", - "nonce": "0x16" - }, - "0x2c80179883217370f777e76c067eea91d8283c5c": { - "balance": "0xe35fa931a0000" - } - }, - "meta": { - "byte_code": "0x02f86f820539158084505c67c8825208942c80179883217370f777e76c067eea91d8283c5c87038d7ea4c6800080c001a0ba3727de78f7a49d17825ff5983dfeab11572736d4b22f1806e29d605e24c686a025c36e4810754fb42b2837e9fb7621f254c5a241709dae666e4adf00cf1e3e64", - "new_txn_trie_node_byte": "0x02f86f820539158084505c67c8825208942c80179883217370f777e76c067eea91d8283c5c87038d7ea4c6800080c001a0ba3727de78f7a49d17825ff5983dfeab11572736d4b22f1806e29d605e24c686a025c36e4810754fb42b2837e9fb7621f254c5a241709dae666e4adf00cf1e3e64", - "new_receipt_trie_node_byte": "0xb9010d02f901090183014820b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e198dd14f9bf3ee08", - "nonce": "0x17" - }, - "0x9e0823da9f7f3a0b22dd2798e6af7b39be37f0da": { - "balance": "0xe35fa931a0000" - } - }, - "meta": { - "byte_code": "0x02f86f820539168084505c67c8825208949e0823da9f7f3a0b22dd2798e6af7b39be37f0da87038d7ea4c6800080c080a05481ba0ae3a286406d94d84fd70248a3d3a5daff8ec2375f40a9fb25c06b19b5a074c8d65317262b7b6ce89b9eb37438a82ea4ca9b9fcb76082ccd8252bc2b9351", - "new_txn_trie_node_byte": "0x02f86f820539168084505c67c8825208949e0823da9f7f3a0b22dd2798e6af7b39be37f0da87038d7ea4c6800080c080a05481ba0ae3a286406d94d84fd70248a3d3a5daff8ec2375f40a9fb25c06b19b5a074c8d65317262b7b6ce89b9eb37438a82ea4ca9b9fcb76082ccd8252bc2b9351", - "new_receipt_trie_node_byte": "0xb9010d02f901090183019a28b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x7972eef40a371cbfd84c7d709507cc300c6d06a5": { - "balance": "0xe35fa931a0000" - }, - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e198a3883ea603690", - "nonce": "0x18" - } - }, - "meta": { - "byte_code": "0x02f86f820539178084505c67c8825208947972eef40a371cbfd84c7d709507cc300c6d06a587038d7ea4c6800080c001a03ab3b30088fffac6c69a35924b4f9750a3208f64a1db817490c1564bce6ea159a04bdf3f5a24c1b46a9a7a067557706f3edb8f7aabb9c728ae85ebc3c93ec3ae3d", - "new_txn_trie_node_byte": "0x02f86f820539178084505c67c8825208947972eef40a371cbfd84c7d709507cc300c6d06a587038d7ea4c6800080c001a03ab3b30088fffac6c69a35924b4f9750a3208f64a1db817490c1564bce6ea159a04bdf3f5a24c1b46a9a7a067557706f3edb8f7aabb9c728ae85ebc3c93ec3ae3d", - "new_receipt_trie_node_byte": "0xb9010d02f90109018301ec30b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - } - ] - }, - "other_data": { - "b_data": { - "b_meta": { - "block_beneficiary": "0x67b1d87101671b127f5f8714789c7192f7ad340e", - "block_timestamp": "0x666a96bd", - "block_number": "0x4", - "block_difficulty": "0x2", - "block_random": "0x0000000000000000000000000000000000000000000000000000000000000000", - "block_gaslimit": "0xb02996", - "block_chain_id": "0x539", - "block_base_fee": "0x2344852f", - "block_gas_used": "0x1ec30", - "block_bloom": [ - "0x0", - "0x0", - "0x0", - "0x0", - "0x0", - "0x0", - "0x0", - "0x0" - ] - }, - "b_hashes": { - "prev_hashes": [ - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0xc493151b4991dd1bf459952509bba9bc3fb2b706299e18e36e6b78781835065d", - "0xecc163d4e19061c77f9bc84afaaa6b98961e33435f80f9ebd6f751716f172b9c", - "0x52ed2bdc1acae1b2ad7890c4084e6d0db5a85b79d6ffa3f7a7efc04e3428b5f4", - "0x43e9e6398559ccdd4238d08a0b79ef57f50dddb2b75dd7c00e132718b54414c6" - ], - "cur_hash": "0x7b32edfa990c8aa0dc2afc2c0a23e1c6e1ede6b03de689a444163011231d90ce" - }, - "withdrawals": [] - }, - "checkpoint_state_trie_root": "0x0fd5324836befac89fcd430abb81f2a274130af0dfa1a89babac76c574b58c40" - } - }, - { - "block_trace": { - "trie_pre_images": { - "combined": { - "compact": "0x0005582002601462093b5945d1676df093446790fd31b20e7b12a2e8e5e09d068109616b084a021e19e0c9bab240000005582002b64061d1b10621ed3cea3432c7e961244197663de5d2b7b25c29e63bec606d08470e35fa931a00000218480558200268288056310c82aa4c01a7e12a10f8111a0560e72b700555479031b86c357d0841010558200239fa8ab811ddec4c30c62c575a346979cb7339d3e6b1b446aa9cec770bb1ca08470e35fa931a0000055820022c9421b06b5fb4ed4f3f4b868f487d536ba02ebe1a6347c6a1312167dc000a08470e35fa931a000002194210055820021df1fa259221d02aa4956eb0d35ace318ca24c0a33a64c1af96cf67cf245b6084101055820021703c5eda8644a64cec152c58f5aacec93d72fb0bfa705f0473f9043a8357c0841010558200228a39461658094f425f190222a515c7902808b31fd90ea063e831b49d7443908470e35fa931a00000219808405582103b70e80538acdabd6137353b0f9d8d149f4dba91e8be2e7946e409bfdbe685b900841010558210389802d6ed1a28b049e9d4fe5334c5902fd9bc00c42821c82f82ee2da10be90800841010558200256274a27dd7524955417c11ecd917251cc7c4c8310f4c7e4bd3c304d3d9a790c18184a021e198a3883ea603690055820023ab0970b73895b8c9959bae685c3a19f45eb5ad89d42b52a340ec4ac204d190841010219102005582103876da518a393dbd067dc72abfa08d475ed6447fca96d92ec3f9e7eba503ca6100841010558210352688a8f926c816ca1e079067caba944f158e764817b83fc43594370ca9cf6200841010558200296cdcb823ae5bcb55a33b2a1a22c03bb69870a0270cfef4e7ea22125e9aa0908470e35fa931a00000558200290b239ba3aaf993e443ae14aeffc44cf8d9931a79baed9fa141d0e4506e13108410102184205582103bd0026f7e234624f2bd4ca2c50d2f731cd19f206a89b29fa0c50b5b4e6bf133008470e35fa931a00000219e573" - } - }, - "code_db": null, - "txn_info": [ - { - "traces": { - "0x14dc11386c1eee1dff28b4823f68e8de3b5a2747": { - "balance": "0x11c37937e08000" - }, - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e1986a119f5a569f8", - "nonce": "0x19" - } - }, - "meta": { - "byte_code": "0x02f86f82053918808446890a5e8252089414dc11386c1eee1dff28b4823f68e8de3b5a274787038d7ea4c6800080c080a01ee4e0b9e3a36b9365dd66ef1739747aaa9ea180b99ef919745bb0c26eca9a0aa0529429ae74c290510e991704260aae40bbac5a8baa974bf7e8c2efedcc676c54", - "new_txn_trie_node_byte": "0x02f86f82053918808446890a5e8252089414dc11386c1eee1dff28b4823f68e8de3b5a274787038d7ea4c6800080c080a01ee4e0b9e3a36b9365dd66ef1739747aaa9ea180b99ef919745bb0c26eca9a0aa0529429ae74c290510e991704260aae40bbac5a8baa974bf7e8c2efedcc676c54", - "new_receipt_trie_node_byte": "0xb9010c02f9010801825208b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e198309b000ea9d60", - "nonce": "0x1a" - }, - "0x92d3267215ec56542b985473e73c8417403b15ac": { - "balance": "0x11c37937e08000" - } - }, - "meta": { - "byte_code": "0x02f86f82053919808446890a5e8252089492d3267215ec56542b985473e73c8417403b15ac87038d7ea4c6800080c080a09b213fe9a7ab176112945662b93d7a93a5f84f3ef6909e32d14608b12a1d5a71a0787b1244a63828be22c2d446d5861784bf0e2f2ff896142a3d8812da89f0cc24", - "new_txn_trie_node_byte": "0x02f86f82053919808446890a5e8252089492d3267215ec56542b985473e73c8417403b15ac87038d7ea4c6800080c080a09b213fe9a7ab176112945662b93d7a93a5f84f3ef6909e32d14608b12a1d5a71a0787b1244a63828be22c2d446d5861784bf0e2f2ff896142a3d8812da89f0cc24", - "new_receipt_trie_node_byte": "0xb9010c02f901080182a410b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e197f72460c2fd0c8", - "nonce": "0x1b" - }, - "0x882145b1f9764372125861727d7be616c84010ef": { - "balance": "0x11c37937e08000" - } - }, - "meta": { - "byte_code": "0x02f86f8205391a808446890a5e82520894882145b1f9764372125861727d7be616c84010ef87038d7ea4c6800080c080a0339da690b2886d5946051623746e59d1d39fe2bcd68779ad10dd1a6b79a5bf82a05240a5c320af3a5ef10125dd21a21fd661a3a9e9c5325c653aa3aea71ba61a47", - "new_txn_trie_node_byte": "0x02f86f8205391a808446890a5e82520894882145b1f9764372125861727d7be616c84010ef87038d7ea4c6800080c080a0339da690b2886d5946051623746e59d1d39fe2bcd68779ad10dd1a6b79a5bf82a05240a5c320af3a5ef10125dd21a21fd661a3a9e9c5325c653aa3aea71ba61a47", - "new_receipt_trie_node_byte": "0xb9010c02f901080182f618b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x2c80179883217370f777e76c067eea91d8283c5c": { - "balance": "0x11c37937e08000" - }, - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e197bdadc17750430", - "nonce": "0x1c" - } - }, - "meta": { - "byte_code": "0x02f86f8205391b808446890a5e825208942c80179883217370f777e76c067eea91d8283c5c87038d7ea4c6800080c080a00583425445c2d0f1deb7a25ce7419319fec040a2a2f42964f50212b14a8786e2a027750f5f7bd58f4d5bda04fac460d8fd1655dcc3aed93c4c24e194d1bb52af1a", - "new_txn_trie_node_byte": "0x02f86f8205391b808446890a5e825208942c80179883217370f777e76c067eea91d8283c5c87038d7ea4c6800080c080a00583425445c2d0f1deb7a25ce7419319fec040a2a2f42964f50212b14a8786e2a027750f5f7bd58f4d5bda04fac460d8fd1655dcc3aed93c4c24e194d1bb52af1a", - "new_receipt_trie_node_byte": "0xb9010d02f901090183014820b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e1978437222ba3798", - "nonce": "0x1d" - }, - "0x9e0823da9f7f3a0b22dd2798e6af7b39be37f0da": { - "balance": "0x11c37937e08000" - } - }, - "meta": { - "byte_code": "0x02f86f8205391c808446890a5e825208949e0823da9f7f3a0b22dd2798e6af7b39be37f0da87038d7ea4c6800080c080a0adff5b10cb25534d49ee5ad0f20ef089c520faec0f16cfbac65066b422c42ff4a04421d46de38a004848f62c524e2f3ceb4d02a989b6b3331c149190b9631a60bb", - "new_txn_trie_node_byte": "0x02f86f8205391c808446890a5e825208949e0823da9f7f3a0b22dd2798e6af7b39be37f0da87038d7ea4c6800080c080a0adff5b10cb25534d49ee5ad0f20ef089c520faec0f16cfbac65066b422c42ff4a04421d46de38a004848f62c524e2f3ceb4d02a989b6b3331c149190b9631a60bb", - "new_receipt_trie_node_byte": "0xb9010d02f901090183019a28b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e1974ac082dff6b00", - "nonce": "0x1e" - }, - "0x7972eef40a371cbfd84c7d709507cc300c6d06a5": { - "balance": "0x11c37937e08000" - } - }, - "meta": { - "byte_code": "0x02f86f8205391d808446890a5e825208947972eef40a371cbfd84c7d709507cc300c6d06a587038d7ea4c6800080c080a0607e1c02503e06143020ef6d58073e0346750c2d1a290b979570bd9e3be19829a002a062d70894169ba2e49894eb602dc5324ecab8e6a0453288dd1b9462a3bb45", - "new_txn_trie_node_byte": "0x02f86f8205391d808446890a5e825208947972eef40a371cbfd84c7d709507cc300c6d06a587038d7ea4c6800080c080a0607e1c02503e06143020ef6d58073e0346750c2d1a290b979570bd9e3be19829a002a062d70894169ba2e49894eb602dc5324ecab8e6a0453288dd1b9462a3bb45", - "new_receipt_trie_node_byte": "0xb9010d02f90109018301ec30b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - } - ] - }, - "other_data": { - "b_data": { - "b_meta": { - "block_beneficiary": "0x67b1d87101671b127f5f8714789c7192f7ad340e", - "block_timestamp": "0x666a96c2", - "block_number": "0x5", - "block_difficulty": "0x2", - "block_random": "0x0000000000000000000000000000000000000000000000000000000000000000", - "block_gaslimit": "0xb0559f", - "block_chain_id": "0x539", - "block_base_fee": "0x1ef496d3", - "block_gas_used": "0x1ec30", - "block_bloom": [ - "0x0", - "0x0", - "0x0", - "0x0", - "0x0", - "0x0", - "0x0", - "0x0" - ] - }, - "b_hashes": { - "prev_hashes": [ - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0xc493151b4991dd1bf459952509bba9bc3fb2b706299e18e36e6b78781835065d", - "0xecc163d4e19061c77f9bc84afaaa6b98961e33435f80f9ebd6f751716f172b9c", - "0x52ed2bdc1acae1b2ad7890c4084e6d0db5a85b79d6ffa3f7a7efc04e3428b5f4", - "0x43e9e6398559ccdd4238d08a0b79ef57f50dddb2b75dd7c00e132718b54414c6", - "0x7b32edfa990c8aa0dc2afc2c0a23e1c6e1ede6b03de689a444163011231d90ce" - ], - "cur_hash": "0x540299ad479ada4fd07dc1f9506ab7cf71719c3d372d288d2ce43906a2437f59" - }, - "withdrawals": [] - }, - "checkpoint_state_trie_root": "0x0fd5324836befac89fcd430abb81f2a274130af0dfa1a89babac76c574b58c40" - } - }, - { - "block_trace": { - "trie_pre_images": { - "combined": { - "compact": "0x0005582002601462093b5945d1676df093446790fd31b20e7b12a2e8e5e09d068109616b084a021e19e0c9bab240000005582002b64061d1b10621ed3cea3432c7e961244197663de5d2b7b25c29e63bec606d084711c37937e080000218480558200268288056310c82aa4c01a7e12a10f8111a0560e72b700555479031b86c357d0841010558200239fa8ab811ddec4c30c62c575a346979cb7339d3e6b1b446aa9cec770bb1ca084711c37937e08000055820022c9421b06b5fb4ed4f3f4b868f487d536ba02ebe1a6347c6a1312167dc000a084711c37937e0800002194210055820021df1fa259221d02aa4956eb0d35ace318ca24c0a33a64c1af96cf67cf245b6084101055820021703c5eda8644a64cec152c58f5aacec93d72fb0bfa705f0473f9043a8357c0841010558200228a39461658094f425f190222a515c7902808b31fd90ea063e831b49d74439084711c37937e080000219808405582103b70e80538acdabd6137353b0f9d8d149f4dba91e8be2e7946e409bfdbe685b900841010558210389802d6ed1a28b049e9d4fe5334c5902fd9bc00c42821c82f82ee2da10be90800841010558200256274a27dd7524955417c11ecd917251cc7c4c8310f4c7e4bd3c304d3d9a790c181e4a021e1974ac082dff6b00055820023ab0970b73895b8c9959bae685c3a19f45eb5ad89d42b52a340ec4ac204d190841010219102005582103876da518a393dbd067dc72abfa08d475ed6447fca96d92ec3f9e7eba503ca6100841010558210352688a8f926c816ca1e079067caba944f158e764817b83fc43594370ca9cf6200841010558200296cdcb823ae5bcb55a33b2a1a22c03bb69870a0270cfef4e7ea22125e9aa09084711c37937e080000558200290b239ba3aaf993e443ae14aeffc44cf8d9931a79baed9fa141d0e4506e13108410102184205582103bd0026f7e234624f2bd4ca2c50d2f731cd19f206a89b29fa0c50b5b4e6bf1330084711c37937e080000219e573" - } - }, - "code_db": null, - "txn_info": [ - { - "traces": { - "0x14dc11386c1eee1dff28b4823f68e8de3b5a2747": { - "balance": "0x1550f7dca70000" - }, - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e197115d4b751ecc0", - "nonce": "0x1f" - } - }, - "meta": { - "byte_code": "0x02f86f8205391e80843de92da68252089414dc11386c1eee1dff28b4823f68e8de3b5a274787038d7ea4c6800080c001a0f097340ff786fe4936c8dcc2358abfb35b9888fd97da23f7de5966e9564e2766a0625ba834320c99f672daa3b53b965f7af9407485ce49ac450d169198a8972735", - "new_txn_trie_node_byte": "0x02f86f8205391e80843de92da68252089414dc11386c1eee1dff28b4823f68e8de3b5a274787038d7ea4c6800080c001a0f097340ff786fe4936c8dcc2358abfb35b9888fd97da23f7de5966e9564e2766a0625ba834320c99f672daa3b53b965f7af9407485ce49ac450d169198a8972735", - "new_receipt_trie_node_byte": "0xb9010c02f9010801825208b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x92d3267215ec56542b985473e73c8417403b15ac": { - "balance": "0x1550f7dca70000" - }, - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e196d7fa140a46e80", - "nonce": "0x20" - } - }, - "meta": { - "byte_code": "0x02f86f8205391f80843de92da68252089492d3267215ec56542b985473e73c8417403b15ac87038d7ea4c6800080c080a02fce8a1c632efd8fea240326f04a7f9b01c84573282adf8595343ccb848286fba05a550cbb3851f59d214878d202ed9ee8eb12c54e38e20d0ee4826b0f25c601ac", - "new_txn_trie_node_byte": "0x02f86f8205391f80843de92da68252089492d3267215ec56542b985473e73c8417403b15ac87038d7ea4c6800080c080a02fce8a1c632efd8fea240326f04a7f9b01c84573282adf8595343ccb848286fba05a550cbb3851f59d214878d202ed9ee8eb12c54e38e20d0ee4826b0f25c601ac", - "new_receipt_trie_node_byte": "0xb9010c02f901080182a410b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e1969e96dc9f6f040", - "nonce": "0x21" - }, - "0x882145b1f9764372125861727d7be616c84010ef": { - "balance": "0x1550f7dca70000" - } - }, - "meta": { - "byte_code": "0x02f86f8205392080843de92da682520894882145b1f9764372125861727d7be616c84010ef87038d7ea4c6800080c001a0a78cb955a97f7a93de2244bb33b7dcd34fd042883ce0875b4fee5bf8b106d7afa032b00b2c6907982ccca6ed1fb4f5e4c3a27bd1529b85e125f086999317070665", - "new_txn_trie_node_byte": "0x02f86f8205392080843de92da682520894882145b1f9764372125861727d7be616c84010ef87038d7ea4c6800080c001a0a78cb955a97f7a93de2244bb33b7dcd34fd042883ce0875b4fee5bf8b106d7afa032b00b2c6907982ccca6ed1fb4f5e4c3a27bd1529b85e125f086999317070665", - "new_receipt_trie_node_byte": "0xb9010c02f901080182f618b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e1966533a53497200", - "nonce": "0x22" - }, - "0x2c80179883217370f777e76c067eea91d8283c5c": { - "balance": "0x1550f7dca70000" - } - }, - "meta": { - "byte_code": "0x02f86f8205392180843de92da6825208942c80179883217370f777e76c067eea91d8283c5c87038d7ea4c6800080c080a00d844acefc2d7e85b87efeb6b2dafe7e19cd48d3f22e6e4c7761bd2567b41b20a07056ce0b20e483e22384c69350893ebd00379f2a148486e17ac274d7bdba33a4", - "new_txn_trie_node_byte": "0x02f86f8205392180843de92da6825208942c80179883217370f777e76c067eea91d8283c5c87038d7ea4c6800080c080a00d844acefc2d7e85b87efeb6b2dafe7e19cd48d3f22e6e4c7761bd2567b41b20a07056ce0b20e483e22384c69350893ebd00379f2a148486e17ac274d7bdba33a4", - "new_receipt_trie_node_byte": "0xb9010d02f901090183014820b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x9e0823da9f7f3a0b22dd2798e6af7b39be37f0da": { - "balance": "0x1550f7dca70000" - }, - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e1962bd06dc9bf3c0", - "nonce": "0x23" - } - }, - "meta": { - "byte_code": "0x02f86f8205392280843de92da6825208949e0823da9f7f3a0b22dd2798e6af7b39be37f0da87038d7ea4c6800080c080a00dd87a2c722138e8fe9e9242d6fea71c9456343457a58133867b3eb7b480506aa03f36067e682f89ef2723779b856f416d8fff548f99af0705bdb6ba32943dd4cd", - "new_txn_trie_node_byte": "0x02f86f8205392280843de92da6825208949e0823da9f7f3a0b22dd2798e6af7b39be37f0da87038d7ea4c6800080c080a00dd87a2c722138e8fe9e9242d6fea71c9456343457a58133867b3eb7b480506aa03f36067e682f89ef2723779b856f416d8fff548f99af0705bdb6ba32943dd4cd", - "new_receipt_trie_node_byte": "0xb9010d02f901090183019a28b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x7972eef40a371cbfd84c7d709507cc300c6d06a5": { - "balance": "0x1550f7dca70000" - }, - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e195f26d365ee7580", - "nonce": "0x24" - } - }, - "meta": { - "byte_code": "0x02f86f8205392380843de92da6825208947972eef40a371cbfd84c7d709507cc300c6d06a587038d7ea4c6800080c001a0aaac3878bcb884b0cf46d1535fccb8421113d41045daed772338937500dcc57aa0593599f728a330be6d7bec338f95670bfc7021794ab83c0da9fe981b6e7248ed", - "new_txn_trie_node_byte": "0x02f86f8205392380843de92da6825208947972eef40a371cbfd84c7d709507cc300c6d06a587038d7ea4c6800080c001a0aaac3878bcb884b0cf46d1535fccb8421113d41045daed772338937500dcc57aa0593599f728a330be6d7bec338f95670bfc7021794ab83c0da9fe981b6e7248ed", - "new_receipt_trie_node_byte": "0xb9010d02f90109018301ec30b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - } - ] - }, - "other_data": { - "b_data": { - "b_meta": { - "block_beneficiary": "0x67b1d87101671b127f5f8714789c7192f7ad340e", - "block_timestamp": "0x666a96c8", - "block_number": "0x6", - "block_difficulty": "0x2", - "block_random": "0x0000000000000000000000000000000000000000000000000000000000000000", - "block_gaslimit": "0xb081b3", - "block_chain_id": "0x539", - "block_base_fee": "0x1b2b9dc8", - "block_gas_used": "0x1ec30", - "block_bloom": [ - "0x0", - "0x0", - "0x0", - "0x0", - "0x0", - "0x0", - "0x0", - "0x0" - ] - }, - "b_hashes": { - "prev_hashes": [ - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0xc493151b4991dd1bf459952509bba9bc3fb2b706299e18e36e6b78781835065d", - "0xecc163d4e19061c77f9bc84afaaa6b98961e33435f80f9ebd6f751716f172b9c", - "0x52ed2bdc1acae1b2ad7890c4084e6d0db5a85b79d6ffa3f7a7efc04e3428b5f4", - "0x43e9e6398559ccdd4238d08a0b79ef57f50dddb2b75dd7c00e132718b54414c6", - "0x7b32edfa990c8aa0dc2afc2c0a23e1c6e1ede6b03de689a444163011231d90ce", - "0x540299ad479ada4fd07dc1f9506ab7cf71719c3d372d288d2ce43906a2437f59" - ], - "cur_hash": "0x8f4e8872fece2779107919faea4dd350dbdef0b579084080c12de204614b01e4" - }, - "withdrawals": [] - }, - "checkpoint_state_trie_root": "0x0fd5324836befac89fcd430abb81f2a274130af0dfa1a89babac76c574b58c40" - } - }, - { - "block_trace": { - "trie_pre_images": { - "combined": { - "compact": "0x0005582002601462093b5945d1676df093446790fd31b20e7b12a2e8e5e09d068109616b084a021e19e0c9bab240000005582002b64061d1b10621ed3cea3432c7e961244197663de5d2b7b25c29e63bec606d08471550f7dca700000218480558200268288056310c82aa4c01a7e12a10f8111a0560e72b700555479031b86c357d0841010558200239fa8ab811ddec4c30c62c575a346979cb7339d3e6b1b446aa9cec770bb1ca08471550f7dca70000055820022c9421b06b5fb4ed4f3f4b868f487d536ba02ebe1a6347c6a1312167dc000a08471550f7dca7000002194210055820021df1fa259221d02aa4956eb0d35ace318ca24c0a33a64c1af96cf67cf245b6084101055820021703c5eda8644a64cec152c58f5aacec93d72fb0bfa705f0473f9043a8357c0841010558200228a39461658094f425f190222a515c7902808b31fd90ea063e831b49d7443908471550f7dca700000219808405582103b70e80538acdabd6137353b0f9d8d149f4dba91e8be2e7946e409bfdbe685b900841010558210389802d6ed1a28b049e9d4fe5334c5902fd9bc00c42821c82f82ee2da10be90800841010558200256274a27dd7524955417c11ecd917251cc7c4c8310f4c7e4bd3c304d3d9a790c18244a021e195f26d365ee7580055820023ab0970b73895b8c9959bae685c3a19f45eb5ad89d42b52a340ec4ac204d190841010219102005582103876da518a393dbd067dc72abfa08d475ed6447fca96d92ec3f9e7eba503ca6100841010558210352688a8f926c816ca1e079067caba944f158e764817b83fc43594370ca9cf6200841010558200296cdcb823ae5bcb55a33b2a1a22c03bb69870a0270cfef4e7ea22125e9aa0908471550f7dca700000558200290b239ba3aaf993e443ae14aeffc44cf8d9931a79baed9fa141d0e4506e13108410102184205582103bd0026f7e234624f2bd4ca2c50d2f731cd19f206a89b29fa0c50b5b4e6bf133008471550f7dca700000219e573" - } - }, - "code_db": null, - "txn_info": [ - { - "traces": { - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e195b91b077ba65a0", - "nonce": "0x25" - }, - "0x14dc11386c1eee1dff28b4823f68e8de3b5a2747": { - "balance": "0x18de76816d8000" - } - }, - "meta": { - "byte_code": "0x02f86f82053924808436573b908252089414dc11386c1eee1dff28b4823f68e8de3b5a274787038d7ea4c6800080c080a01f5434680e70fe67c289666daa62f021d77e75e014d772b76f7b66e96c2e35aca06ded5910833c78b37dcac16427030b2768c6d5457c79febb841c6a4f483c6138", - "new_txn_trie_node_byte": "0x02f86f82053924808436573b908252089414dc11386c1eee1dff28b4823f68e8de3b5a274787038d7ea4c6800080c080a01f5434680e70fe67c289666daa62f021d77e75e014d772b76f7b66e96c2e35aca06ded5910833c78b37dcac16427030b2768c6d5457c79febb841c6a4f483c6138", - "new_receipt_trie_node_byte": "0xb9010c02f9010801825208b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e1957fc8d898655c0", - "nonce": "0x26" - }, - "0x92d3267215ec56542b985473e73c8417403b15ac": { - "balance": "0x18de76816d8000" - } - }, - "meta": { - "byte_code": "0x02f86f82053925808436573b908252089492d3267215ec56542b985473e73c8417403b15ac87038d7ea4c6800080c001a0b58e8699319e3529c578ac9bb0b571f8f257df0943de4a17ce4cdab7cced1f40a078b718333c2cb89fbd67a2888fe72befb64685533c83f44980bc9e3a42feeb84", - "new_txn_trie_node_byte": "0x02f86f82053925808436573b908252089492d3267215ec56542b985473e73c8417403b15ac87038d7ea4c6800080c001a0b58e8699319e3529c578ac9bb0b571f8f257df0943de4a17ce4cdab7cced1f40a078b718333c2cb89fbd67a2888fe72befb64685533c83f44980bc9e3a42feeb84", - "new_receipt_trie_node_byte": "0xb9010c02f901080182a410b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e1954676a9b5245e0", - "nonce": "0x27" - }, - "0x882145b1f9764372125861727d7be616c84010ef": { - "balance": "0x18de76816d8000" - } - }, - "meta": { - "byte_code": "0x02f86f82053926808436573b9082520894882145b1f9764372125861727d7be616c84010ef87038d7ea4c6800080c080a011779b6c3d9c596d24125c4800472caaf666d22056a30156a3b52170c8f23b79a00e0129e08c7917f08356dc870976275a2de21d6dc89b36f53ffd74f9a8518d90", - "new_txn_trie_node_byte": "0x02f86f82053926808436573b9082520894882145b1f9764372125861727d7be616c84010ef87038d7ea4c6800080c080a011779b6c3d9c596d24125c4800472caaf666d22056a30156a3b52170c8f23b79a00e0129e08c7917f08356dc870976275a2de21d6dc89b36f53ffd74f9a8518d90", - "new_receipt_trie_node_byte": "0xb9010c02f901080182f618b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e1950d247ad1e3600", - "nonce": "0x28" - }, - "0x2c80179883217370f777e76c067eea91d8283c5c": { - "balance": "0x18de76816d8000" - } - }, - "meta": { - "byte_code": "0x02f86f82053927808436573b90825208942c80179883217370f777e76c067eea91d8283c5c87038d7ea4c6800080c001a077839a39ca68239775657fa67c028891424835ed0fcf9b84149382e9c65e018fa03c4aeea69f7a0c1b04ff13288702290155396075ea13ae982fab97441efbf594", - "new_txn_trie_node_byte": "0x02f86f82053927808436573b90825208942c80179883217370f777e76c067eea91d8283c5c87038d7ea4c6800080c001a077839a39ca68239775657fa67c028891424835ed0fcf9b84149382e9c65e018fa03c4aeea69f7a0c1b04ff13288702290155396075ea13ae982fab97441efbf594", - "new_receipt_trie_node_byte": "0xb9010d02f901090183014820b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x9e0823da9f7f3a0b22dd2798e6af7b39be37f0da": { - "balance": "0x18de76816d8000" - }, - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e194d3d24beea2620", - "nonce": "0x29" - } - }, - "meta": { - "byte_code": "0x02f86f82053928808436573b90825208949e0823da9f7f3a0b22dd2798e6af7b39be37f0da87038d7ea4c6800080c001a01d26b7cc6ff90bf7e28d5228add30e6231f68165ffe980d34b70a6ad313ab99fa00a03a2a9e24513a9e763948758937c31f223ed8f41fe345fb3bc439797b0ce89", - "new_txn_trie_node_byte": "0x02f86f82053928808436573b90825208949e0823da9f7f3a0b22dd2798e6af7b39be37f0da87038d7ea4c6800080c001a01d26b7cc6ff90bf7e28d5228add30e6231f68165ffe980d34b70a6ad313ab99fa00a03a2a9e24513a9e763948758937c31f223ed8f41fe345fb3bc439797b0ce89", - "new_receipt_trie_node_byte": "0xb9010d02f901090183019a28b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - }, - { - "traces": { - "0x67b1d87101671b127f5f8714789c7192f7ad340e": { - "balance": "0x21e1949a801d0b61640", - "nonce": "0x2a" - }, - "0x7972eef40a371cbfd84c7d709507cc300c6d06a5": { - "balance": "0x18de76816d8000" - } - }, - "meta": { - "byte_code": "0x02f86f82053929808436573b90825208947972eef40a371cbfd84c7d709507cc300c6d06a587038d7ea4c6800080c001a0c88544f777b3d7f6a069479f5a792b080efb42f90b1875e28a04b59105fc0931a017e6cf1abe23b0cfa3cf83947ae3ce26f69753aeb0a51695e3dc75c748fdf164", - "new_txn_trie_node_byte": "0x02f86f82053929808436573b90825208947972eef40a371cbfd84c7d709507cc300c6d06a587038d7ea4c6800080c001a0c88544f777b3d7f6a069479f5a792b080efb42f90b1875e28a04b59105fc0931a017e6cf1abe23b0cfa3cf83947ae3ce26f69753aeb0a51695e3dc75c748fdf164", - "new_receipt_trie_node_byte": "0xb9010d02f90109018301ec30b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", - "gas_used": 21000 - } - } - ] - }, - "other_data": { - "b_data": { - "b_meta": { - "block_beneficiary": "0x67b1d87101671b127f5f8714789c7192f7ad340e", - "block_timestamp": "0x666a96cd", - "block_number": "0x7", - "block_difficulty": "0x2", - "block_random": "0x0000000000000000000000000000000000000000000000000000000000000000", - "block_gaslimit": "0xb0add2", - "block_chain_id": "0x539", - "block_base_fee": "0x17d91afc", - "block_gas_used": "0x1ec30", - "block_bloom": [ - "0x0", - "0x0", - "0x0", - "0x0", - "0x0", - "0x0", - "0x0", - "0x0" - ] - }, - "b_hashes": { - "prev_hashes": [ - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0xc493151b4991dd1bf459952509bba9bc3fb2b706299e18e36e6b78781835065d", - "0xecc163d4e19061c77f9bc84afaaa6b98961e33435f80f9ebd6f751716f172b9c", - "0x52ed2bdc1acae1b2ad7890c4084e6d0db5a85b79d6ffa3f7a7efc04e3428b5f4", - "0x43e9e6398559ccdd4238d08a0b79ef57f50dddb2b75dd7c00e132718b54414c6", - "0x7b32edfa990c8aa0dc2afc2c0a23e1c6e1ede6b03de689a444163011231d90ce", - "0x540299ad479ada4fd07dc1f9506ab7cf71719c3d372d288d2ce43906a2437f59", - "0x8f4e8872fece2779107919faea4dd350dbdef0b579084080c12de204614b01e4" - ], - "cur_hash": "0x9fe97326da9f39550fd361614c13bc56b648fa5bd30ed7d296920b9928c44138" - }, - "withdrawals": [] - }, - "checkpoint_state_trie_root": "0x0fd5324836befac89fcd430abb81f2a274130af0dfa1a89babac76c574b58c40" - } - } -] diff --git a/zero_bin/tools/artifacts/witness_b3_b6.json b/zero_bin/tools/artifacts/witness_b3_b6.json new file mode 100644 index 000000000..0edc3f9ed --- /dev/null +++ b/zero_bin/tools/artifacts/witness_b3_b6.json @@ -0,0 +1,1295 @@ +[ + { + "block_trace": { + "trie_pre_images": { + "combined": { + "compact": "0x010397e4ae58e8d997dd2b19ed09ad9a66d1c0fd304d074e4a7bfb3f1fcc512a3e6303e3f965eb951555f4f5b98ab8ff7a6e33813bf9ac6830c1f8e5806e5563d580580338e34f9e0e4830343ba24f5fcf0eba28d79cb86397adfb16a0169ee7f018003603d71a215e4d3ec0f32cf2962c736400ffd957cd5659f11839f25e747932246d9103d2e57f615a47508c6e60935353428b9fc1cc75677a3eb8f5f73d61dd0aaff5f5055820021d99c6bec3aecbec962be74b25448511983b013c0876f6f4e6029773dfad61084101034d78166b48044fdc28ed22d2fd39c8df6f8aaa04cb71d3a17286856f6893ff83036c029a231254fadb724d63be769f75eedd66362df034a3e663252b49d062a66603556a482068355939c95a3412bdb21213a301483edb1b64402fb66ac9f3583599055820022a47fc6863b89a6b51890ef3c1550d560886c027141d2058ba1e2d4c66d99a031bffffffffffffffff05582002b3096c912adc7674ca92b68ddb3b0494e9b988fbef1bed7938e8fac4c2df7a0841010458613373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff01550000582103f385fda393b99de9d37a10f60e1713e15d430bfca19f20f5997568cc361b0f7058202985b5a3204327f0c9e564f4dbd496221cf00899f09a84cd4c3db217476571a2005821039fe5675746c1bdfc5be63cc73266f9a1bea6676dfe021cfac5e6b88653bd4ae0446655365b005821034e140195790fdd41923099a781a48299e52be2ea0d82c572d9cde32398cea9d05820ff93003a1ba7e7b0314bc0ea0d462b103e74d94cce26e8bd473a3190dfa05b3100582103027d6c60c17ddd9b4aacf97d79e98b3b80d8c5dead4d0bf0bc14a0e581d21350446655364f0219b20005582002d65eaa92c6bc4c13a5ec45527f0c18ea8932588728769ec7aecfe6d9f32e420701186105582002401b4e97e67c4a6d8095979b4c1ecb9eaf5726fc8a12798975a7653e98087208410105582002c8bb92227ba91a2657f29c5498f5c5b07ae36369c8d9283444f477930c79be0841010391d9c76bfbc066e84f0b415c737ab8c477498701d920526db41690050cfade9905582002a5ac70a18399817ccbe8bd4d9c78115136cc61699d51af87608af520226c32084101055820029bd268aedc8dbb36db0a4664e17b83abcfd87e404901eee7bd5704a95064ee08410105582002a06a67e7fee9c938155c50e6cec9da70b66679e777da6f02149c6674cd910508410103d9240a9d2d5851d05a97ff3305334dfdb0101e1e321fc279d2bb3cad6afa8fc802197ffd0362837e6c846ab7d74ae300efbcf8b54fc9b7c587525c315cfffe71ba9172195503bc8f79f6ffa56f0331be2cef2091206ad44f58cbba0b2a0a3af67d140a20bbf203724f631a141f6a338cc83620dba6cbc53e49035312204f3847a68e2d852fd67a03b76eeb79901a6c64dd10ac149bd51778546491f0b64982a0425da73a9c20de5d035750dfde66a7a051d429386138dc80cc40f94d0212ebbd6f3dd7950b994b7e6903be88e4724326382a8b56e2328eeef0ad51f18d5bae0e84296afe14c4028c4af9035c131dd29dd0d65f1862c89aea9402aae410dfc48668d4952ae18c9fc9730856031796617427e67ed10cdf8a72b02689a700ba71eb93186a1b120c9ad0b0e56eae03ebc052143e57028286ef115b03a8a7736efa26c165fcfffc807960b2bf91e1c003bffc43dfea9c9a304f9ae5154c60adff35f4c9a6e722a59d60cca4d97c4038bb0390358d83779973801b7a3f4468c4f9475905a3eb4a5e1d54aa00d46599596d2905582002e23690e890cdec6eb19b06f5d31d422b5c17bd4c435afb2a2532180011453f0841010314389ec4a809554bd6c0ed11ecbc90f9fc419990a636e3533ecdee11a8b6f51e0378dd85c2fc0d0ceffc589922117c800868eb53fc692f74c423072dad5252b76e05582002295c5aa043a60d279854f7424e21250d5c1e1f9959549e90bc926d70e97068084101055820025b734df35ab6d576607e01c42f21d275cd9c0f86fbb3100241a14ba0c7672f08410103cf8063193541030762db90dd2f67697c531e949dabe33f8d608c3a6bb352b0eb055820038320edc6a9c30d249d75af74a19ade7f69c56e5e0d3a9adb17fee1b0a4bef008410105582003f18b96a1c12507c9a95b7f48c3e012651793a196ce2b81aaa6cb185dc03b60084101021910100303c5801bf244c28c5c430853d6797ee54f7e1a7c384f51d8c9a1dc4cd96cb74f03a5acd0ecfd7dcecd1f7360af116e7dd25617a0205b5f8b369be3ffc784c4f5a903bf90e746ea86172da995d366c29e417cc158c6689eab8f65860b5734a438719e05582002a6cdd9768090f90960cf9bad47705ca3b19a54c8ec8186e2987ffdd3a8435b08410103823a38527672ae42d480369a80b0b9c2ede964cb93668c0487e1341607c5d5ee02195dff035740575999fde2ddd421a9f5907dfc4be5fc3f5f96d65240ae3b3b596d38465f05582002b693085beb21b0878c7e5fab7519ce0d6105354cf6744dbfb73e3745e929e4084101055820027f470df15c88b9beaf3fad72890a64f8851d01ece546b0c9fb3bdf92041a35084101055820029cb4b751333c144710961e20611eb00c783b77e8cb532c64307202fb109699084c033b2e3c9fd0803ce8000000035f4ca08c32e4a3997d0e94d8946e3d361ec5d0225d9cd85c6de8d572bb0a99c903faecf459527318d12fee2db8d85c54fc244d0207ba336a01e397593b801ae61f055820039bbcf08818ec3329e5769dff3fbdad66e6312a961acb32c1b203edf70aeba0084101031c13664b9096979e0a6a58919940b248687992af952308581eff9d0e8c14119a0219400805582002facf5483fdda2fc98d08bcce1a68d5d308d3ad88ee21b50004fd694ba6f805084101055820026a58207750197f48cb90864096850259845c2c8e90c74433325c0b144bf8bb08410105582002fa0eae268038cfa984647a1d0635beb86eda9fb7b500688f3189520cfa9ee50841010219fb610219ffff" + } + }, + "txn_info": [] + }, + "other_data": { + "b_data": { + "b_meta": { + "block_beneficiary": "0x8943545177806ed17b9f23f0a21ee5948ecaa776", + "block_timestamp": "0x66553667", + "block_number": "0x3", + "block_difficulty": "0x0", + "block_random": "0xdb36fd4f088f574f897e0d64913f92974ee32215c4e379c78fc470cb2ca439c9", + "block_gaslimit": "0x17e969d", + "block_chain_id": "0x301824", + "block_base_fee": "0x283a7441", + "block_gas_used": "0x0", + "block_blob_gas_used": "0x0", + "block_excess_blob_gas": "0x0", + "parent_beacon_block_root": "0x98864e82e4469d33ea8be37f21c494cd3aa44e745ea2b4645ef58747ad6d5586", + "block_bloom": [ + "0x0", + "0x0", + "0x0", + "0x0", + "0x0", + "0x0", + "0x0", + "0x0" + ] + }, + "b_hashes": { + "prev_hashes": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xbd491c81fa88681710feabc6b0502b779985169c92af323fffd790752961145f", + "0xd009e8d31d1f79c2e09bc9d0eb97d0802f0b0b0421d8299a01e43f7d61e1619d", + "0x7ee1f9d1d3f5a50d5c766847ffa644493f7dfe40c15eca0842fffc9966d56fae" + ], + "cur_hash": "0xaeb0a1814c75f5a92d4eccf2b57aff945682dd682c9aa35721af4f5d837af1cf" + }, + "withdrawals": [] + }, + "checkpoint_state_trie_root": "0xfa446f2a9bf579b0ea805b88e3ee2acf601573f6ff48baaa8880915613aec508" + } + }, + { + "block_trace": { + "trie_pre_images": { + "combined": { + "compact": "0x010397e4ae58e8d997dd2b19ed09ad9a66d1c0fd304d074e4a7bfb3f1fcc512a3e6303e3f965eb951555f4f5b98ab8ff7a6e33813bf9ac6830c1f8e5806e5563d580580338e34f9e0e4830343ba24f5fcf0eba28d79cb86397adfb16a0169ee7f018003605582003d2dbe83a6ba7fd75737c8d7453d984e7938ba7ae113d3da2ad7433061157b0084101055820039c0a91ba30d346a55890b1b07287d8aae35baa8c4068ef8f1de66084aca750084101055820032dee2834b372effea65d7c74ab9f3ac60ef271e846721aa8759a346a3b55400c154c033b2e3c9fceb8d1c7e75a050219020303d2e57f615a47508c6e60935353428b9fc1cc75677a3eb8f5f73d61dd0aaff5f5055820021d99c6bec3aecbec962be74b25448511983b013c0876f6f4e6029773dfad61084101034d78166b48044fdc28ed22d2fd39c8df6f8aaa04cb71d3a17286856f6893ff83036c029a231254fadb724d63be769f75eedd66362df034a3e663252b49d062a66603556a482068355939c95a3412bdb21213a301483edb1b64402fb66ac9f3583599055820022a47fc6863b89a6b51890ef3c1550d560886c027141d2058ba1e2d4c66d99a031bffffffffffffffff05582002b3096c912adc7674ca92b68ddb3b0494e9b988fbef1bed7938e8fac4c2df7a0841010458613373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff01550000582103843cb8b9216f632c66c9bb34b91132b078d41610e2afc92539cedd1fb0c25450582098864e82e4469d33ea8be37f21c494cd3aa44e745ea2b4645ef58747ad6d558600582103f385fda393b99de9d37a10f60e1713e15d430bfca19f20f5997568cc361b0f7058202985b5a3204327f0c9e564f4dbd496221cf00899f09a84cd4c3db217476571a2005821039fe5675746c1bdfc5be63cc73266f9a1bea6676dfe021cfac5e6b88653bd4ae0446655365b03125365b35dc4086ec9792ef0ad57e78ab63d3d5078b1d61e22f734a7f908b12700582103027d6c60c17ddd9b4aacf97d79e98b3b80d8c5dead4d0bf0bc14a0e581d21350446655364f0219b20405582002d65eaa92c6bc4c13a5ec45527f0c18ea8932588728769ec7aecfe6d9f32e420701186105582002401b4e97e67c4a6d8095979b4c1ecb9eaf5726fc8a12798975a7653e98087208410105582002c8bb92227ba91a2657f29c5498f5c5b07ae36369c8d9283444f477930c79be0841010391d9c76bfbc066e84f0b415c737ab8c477498701d920526db41690050cfade9905582002a5ac70a18399817ccbe8bd4d9c78115136cc61699d51af87608af520226c32084101055820029bd268aedc8dbb36db0a4664e17b83abcfd87e404901eee7bd5704a95064ee08410105582002a06a67e7fee9c938155c50e6cec9da70b66679e777da6f02149c6674cd910508410103d9240a9d2d5851d05a97ff3305334dfdb0101e1e321fc279d2bb3cad6afa8fc802197ffd0362837e6c846ab7d74ae300efbcf8b54fc9b7c587525c315cfffe71ba9172195503bc8f79f6ffa56f0331be2cef2091206ad44f58cbba0b2a0a3af67d140a20bbf203724f631a141f6a338cc83620dba6cbc53e49035312204f3847a68e2d852fd67a03b76eeb79901a6c64dd10ac149bd51778546491f0b64982a0425da73a9c20de5d035750dfde66a7a051d429386138dc80cc40f94d0212ebbd6f3dd7950b994b7e6903be88e4724326382a8b56e2328eeef0ad51f18d5bae0e84296afe14c4028c4af9035c131dd29dd0d65f1862c89aea9402aae410dfc48668d4952ae18c9fc9730856031796617427e67ed10cdf8a72b02689a700ba71eb93186a1b120c9ad0b0e56eae03ebc052143e57028286ef115b03a8a7736efa26c165fcfffc807960b2bf91e1c003bffc43dfea9c9a304f9ae5154c60adff35f4c9a6e722a59d60cca4d97c4038bb032c0386eb4a4b8c2a523e98b0b17664b6d1bc406f81d74407fb24ed6ddc5dd2fd035740575999fde2ddd421a9f5907dfc4be5fc3f5f96d65240ae3b3b596d38465f05582002b693085beb21b0878c7e5fab7519ce0d6105354cf6744dbfb73e3745e929e4084101055820027f470df15c88b9beaf3fad72890a64f8851d01ece546b0c9fb3bdf92041a35084101055820029cb4b751333c144710961e20611eb00c783b77e8cb532c64307202fb109699084c033b2e3c9fd0803ce8000000035f4ca08c32e4a3997d0e94d8946e3d361ec5d0225d9cd85c6de8d572bb0a99c903faecf459527318d12fee2db8d85c54fc244d0207ba336a01e397593b801ae61f055820039bbcf08818ec3329e5769dff3fbdad66e6312a961acb32c1b203edf70aeba0084101031c13664b9096979e0a6a58919940b248687992af952308581eff9d0e8c14119a0219400805582002facf5483fdda2fc98d08bcce1a68d5d308d3ad88ee21b50004fd694ba6f805084101055820026a58207750197f48cb90864096850259845c2c8e90c74433325c0b144bf8bb08410105582002fa0eae268038cfa984647a1d0635beb86eda9fb7b500688f3189520cfa9ee50841010219fb610219ffff" + } + }, + "txn_info": [ + { + "traces": { + "0x8943545177806ed17b9f23f0a21ee5948ecaa776": { + "balance": "0x33b2e3c9fcead3d4c56658d", + "nonce": "0x16" + } + }, + "meta": { + "byte_code": "0xf8891584693d4ca88254de948943545177806ed17b9f23f0a21ee5948ecaa77680a3646174613a2c7b226d7367223a2246696e697368656420636f6d6d6f6e202d2031227d8360306ca0d9cec3b90279e705e556df1e81d112122dffc624c62f1db63b68df60f197d876a07ca0bc46178062fa88c2c040a8cd5c20617edec09ab1d5148a60eeb0b660f902", + "new_txn_trie_node_byte": "0xf8891584693d4ca88254de948943545177806ed17b9f23f0a21ee5948ecaa77680a3646174613a2c7b226d7367223a2246696e697368656420636f6d6d6f6e202d2031227d8360306ca0d9cec3b90279e705e556df1e81d112122dffc624c62f1db63b68df60f197d876a07ca0bc46178062fa88c2c040a8cd5c20617edec09ab1d5148a60eeb0b660f902", + "new_receipt_trie_node_byte": "0xf9010801825438b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", + "gas_used": 21560 + } + } + ] + }, + "other_data": { + "b_data": { + "b_meta": { + "block_beneficiary": "0x8943545177806ed17b9f23f0a21ee5948ecaa776", + "block_timestamp": "0x66553673", + "block_number": "0x4", + "block_difficulty": "0x0", + "block_random": "0x3dd9e550652f142c97c1fe8f913faf5e1818ef9ce3b5b591b14a1c7a77cc2058", + "block_gaslimit": "0x17ef641", + "block_chain_id": "0x301824", + "block_base_fee": "0x233325b9", + "block_gas_used": "0x5438", + "block_blob_gas_used": "0x0", + "block_excess_blob_gas": "0x0", + "parent_beacon_block_root": "0xb7cb32a5e970c4d5e365dbe77c18496f6e821850ae9f4faa3660a2a08c7e56ed", + "block_bloom": [ + "0x0", + "0x0", + "0x0", + "0x0", + "0x0", + "0x0", + "0x0", + "0x0" + ] + }, + "b_hashes": { + "prev_hashes": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xbd491c81fa88681710feabc6b0502b779985169c92af323fffd790752961145f", + "0xd009e8d31d1f79c2e09bc9d0eb97d0802f0b0b0421d8299a01e43f7d61e1619d", + "0x7ee1f9d1d3f5a50d5c766847ffa644493f7dfe40c15eca0842fffc9966d56fae", + "0xaeb0a1814c75f5a92d4eccf2b57aff945682dd682c9aa35721af4f5d837af1cf" + ], + "cur_hash": "0xede379e8b2d9fecba28ac5a82da2f8c981edc9b5820e6ff4dd4816cf1aea3a69" + }, + "withdrawals": [] + }, + "checkpoint_state_trie_root": "0xfa446f2a9bf579b0ea805b88e3ee2acf601573f6ff48baaa8880915613aec508" + } + }, + { + "block_trace": { + "trie_pre_images": { + "combined": { + "compact": "0x010397e4ae58e8d997dd2b19ed09ad9a66d1c0fd304d074e4a7bfb3f1fcc512a3e6303e3f965eb951555f4f5b98ab8ff7a6e33813bf9ac6830c1f8e5806e5563d580580338e34f9e0e4830343ba24f5fcf0eba28d79cb86397adfb16a0169ee7f018003605582003d2dbe83a6ba7fd75737c8d7453d984e7938ba7ae113d3da2ad7433061157b0084101055820039c0a91ba30d346a55890b1b07287d8aae35baa8c4068ef8f1de66084aca750084101055820032dee2834b372effea65d7c74ab9f3ac60ef271e846721aa8759a346a3b55400c164c033b2e3c9fcead3d4c56658d0219020303d2e57f615a47508c6e60935353428b9fc1cc75677a3eb8f5f73d61dd0aaff5f5055820021d99c6bec3aecbec962be74b25448511983b013c0876f6f4e6029773dfad61084101034d78166b48044fdc28ed22d2fd39c8df6f8aaa04cb71d3a17286856f6893ff83036c029a231254fadb724d63be769f75eedd66362df034a3e663252b49d062a66603556a482068355939c95a3412bdb21213a301483edb1b64402fb66ac9f3583599055820022a47fc6863b89a6b51890ef3c1550d560886c027141d2058ba1e2d4c66d99a031bffffffffffffffff05582002b3096c912adc7674ca92b68ddb3b0494e9b988fbef1bed7938e8fac4c2df7a0841010458613373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff01550000582103843cb8b9216f632c66c9bb34b91132b078d41610e2afc92539cedd1fb0c25450582098864e82e4469d33ea8be37f21c494cd3aa44e745ea2b4645ef58747ad6d558600582103ad33d9c91e1cd299659fe3c90caee16cded224abc810f6552a91d09028c367b05820b7cb32a5e970c4d5e365dbe77c18496f6e821850ae9f4faa3660a2a08c7e56ed00582103f385fda393b99de9d37a10f60e1713e15d430bfca19f20f5997568cc361b0f7058202985b5a3204327f0c9e564f4dbd496221cf00899f09a84cd4c3db217476571a2005821034ac8ca477a537f652c7565346c91b91bb13a157b5fd28ad0664af7fed3466c504466553673005821039fe5675746c1bdfc5be63cc73266f9a1bea6676dfe021cfac5e6b88653bd4ae0446655365b03125365b35dc4086ec9792ef0ad57e78ab63d3d5078b1d61e22f734a7f908b12700582103027d6c60c17ddd9b4aacf97d79e98b3b80d8c5dead4d0bf0bc14a0e581d21350446655364f0219b68405582002d65eaa92c6bc4c13a5ec45527f0c18ea8932588728769ec7aecfe6d9f32e420701186105582002401b4e97e67c4a6d8095979b4c1ecb9eaf5726fc8a12798975a7653e98087208410105582002c8bb92227ba91a2657f29c5498f5c5b07ae36369c8d9283444f477930c79be0841010391d9c76bfbc066e84f0b415c737ab8c477498701d920526db41690050cfade9905582002a5ac70a18399817ccbe8bd4d9c78115136cc61699d51af87608af520226c32084101055820029bd268aedc8dbb36db0a4664e17b83abcfd87e404901eee7bd5704a95064ee08410105582002a06a67e7fee9c938155c50e6cec9da70b66679e777da6f02149c6674cd910508410103d9240a9d2d5851d05a97ff3305334dfdb0101e1e321fc279d2bb3cad6afa8fc802197ffd0362837e6c846ab7d74ae300efbcf8b54fc9b7c587525c315cfffe71ba9172195503bc8f79f6ffa56f0331be2cef2091206ad44f58cbba0b2a0a3af67d140a20bbf203724f631a141f6a338cc83620dba6cbc53e49035312204f3847a68e2d852fd67a03b76eeb79901a6c64dd10ac149bd51778546491f0b64982a0425da73a9c20de5d035750dfde66a7a051d429386138dc80cc40f94d0212ebbd6f3dd7950b994b7e6903be88e4724326382a8b56e2328eeef0ad51f18d5bae0e84296afe14c4028c4af9035c131dd29dd0d65f1862c89aea9402aae410dfc48668d4952ae18c9fc9730856031796617427e67ed10cdf8a72b02689a700ba71eb93186a1b120c9ad0b0e56eae03ebc052143e57028286ef115b03a8a7736efa26c165fcfffc807960b2bf91e1c003bffc43dfea9c9a304f9ae5154c60adff35f4c9a6e722a59d60cca4d97c4038bb032c0386eb4a4b8c2a523e98b0b17664b6d1bc406f81d74407fb24ed6ddc5dd2fd035740575999fde2ddd421a9f5907dfc4be5fc3f5f96d65240ae3b3b596d38465f05582002b693085beb21b0878c7e5fab7519ce0d6105354cf6744dbfb73e3745e929e4084101055820027f470df15c88b9beaf3fad72890a64f8851d01ece546b0c9fb3bdf92041a35084101055820029cb4b751333c144710961e20611eb00c783b77e8cb532c64307202fb109699084c033b2e3c9fd0803ce8000000035f4ca08c32e4a3997d0e94d8946e3d361ec5d0225d9cd85c6de8d572bb0a99c903faecf459527318d12fee2db8d85c54fc244d0207ba336a01e397593b801ae61f055820039bbcf08818ec3329e5769dff3fbdad66e6312a961acb32c1b203edf70aeba0084101031c13664b9096979e0a6a58919940b248687992af952308581eff9d0e8c14119a0219400805582002facf5483fdda2fc98d08bcce1a68d5d308d3ad88ee21b50004fd694ba6f805084101055820026a58207750197f48cb90864096850259845c2c8e90c74433325c0b144bf8bb08410105582002fa0eae268038cfa984647a1d0635beb86eda9fb7b500688f3189520cfa9ee50841010219fb610219ffff" + } + }, + "txn_info": [ + { + "traces": { + "0x8943545177806ed17b9f23f0a21ee5948ecaa776": { + "balance": "0x33b2e3c9fcea31abd3bb57d", + "nonce": "0x17" + } + }, + "meta": { + "byte_code": "0xf88916845ecdefb98254de948943545177806ed17b9f23f0a21ee5948ecaa77680a3646174613a2c7b226d7367223a2246696e697368656420636f6d6d6f6e202d2032227d8360306ca00798b214fc83c700c32f77f88883450fc113756160353084300b6ddfe55b5d25a043f37de26b0018318c792b0e4333ef5c1e0ff6949502f447095503e46d91da6f", + "new_txn_trie_node_byte": "0xf88916845ecdefb98254de948943545177806ed17b9f23f0a21ee5948ecaa77680a3646174613a2c7b226d7367223a2246696e697368656420636f6d6d6f6e202d2032227d8360306ca00798b214fc83c700c32f77f88883450fc113756160353084300b6ddfe55b5d25a043f37de26b0018318c792b0e4333ef5c1e0ff6949502f447095503e46d91da6f", + "new_receipt_trie_node_byte": "0xf9010801825438b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", + "gas_used": 21560 + } + } + ] + }, + "other_data": { + "b_data": { + "b_meta": { + "block_beneficiary": "0x8943545177806ed17b9f23f0a21ee5948ecaa776", + "block_timestamp": "0x6655367f", + "block_number": "0x5", + "block_difficulty": "0x0", + "block_random": "0x115ebb1564d5339531f1b793139aa20e227fb843873e3ab93419424e48ddfb21", + "block_gaslimit": "0x17f55fd", + "block_chain_id": "0x301824", + "block_base_fee": "0x1eceb06e", + "block_gas_used": "0x5438", + "block_blob_gas_used": "0x0", + "block_excess_blob_gas": "0x0", + "parent_beacon_block_root": "0x48d8f073d9b46c873fe6af7836b1fdb981c4740c0600c88b30522da14496e641", + "block_bloom": [ + "0x0", + "0x0", + "0x0", + "0x0", + "0x0", + "0x0", + "0x0", + "0x0" + ] + }, + "b_hashes": { + "prev_hashes": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xbd491c81fa88681710feabc6b0502b779985169c92af323fffd790752961145f", + "0xd009e8d31d1f79c2e09bc9d0eb97d0802f0b0b0421d8299a01e43f7d61e1619d", + "0x7ee1f9d1d3f5a50d5c766847ffa644493f7dfe40c15eca0842fffc9966d56fae", + "0xaeb0a1814c75f5a92d4eccf2b57aff945682dd682c9aa35721af4f5d837af1cf", + "0xede379e8b2d9fecba28ac5a82da2f8c981edc9b5820e6ff4dd4816cf1aea3a69" + ], + "cur_hash": "0xc21c5ebbc8a026a4256cb0dd606673ab7ce1bb8116659989e29e2602439763c2" + }, + "withdrawals": [] + }, + "checkpoint_state_trie_root": "0xfa446f2a9bf579b0ea805b88e3ee2acf601573f6ff48baaa8880915613aec508" + } + }, + { + "block_trace": { + "trie_pre_images": { + "combined": { + "compact": "0x010397e4ae58e8d997dd2b19ed09ad9a66d1c0fd304d074e4a7bfb3f1fcc512a3e6303e3f965eb951555f4f5b98ab8ff7a6e33813bf9ac6830c1f8e5806e5563d580580338e34f9e0e4830343ba24f5fcf0eba28d79cb86397adfb16a0169ee7f018003605582003d2dbe83a6ba7fd75737c8d7453d984e7938ba7ae113d3da2ad7433061157b0084101055820039c0a91ba30d346a55890b1b07287d8aae35baa8c4068ef8f1de66084aca750084101055820032dee2834b372effea65d7c74ab9f3ac60ef271e846721aa8759a346a3b55400c174c033b2e3c9fcea31abd3bb57d0219020303d2e57f615a47508c6e60935353428b9fc1cc75677a3eb8f5f73d61dd0aaff5f5055820021d99c6bec3aecbec962be74b25448511983b013c0876f6f4e6029773dfad61084101034d78166b48044fdc28ed22d2fd39c8df6f8aaa04cb71d3a17286856f6893ff83036c029a231254fadb724d63be769f75eedd66362df034a3e663252b49d062a66603556a482068355939c95a3412bdb21213a301483edb1b64402fb66ac9f3583599055820022a47fc6863b89a6b51890ef3c1550d560886c027141d2058ba1e2d4c66d99a031bffffffffffffffff05582002b3096c912adc7674ca92b68ddb3b0494e9b988fbef1bed7938e8fac4c2df7a0841010458613373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff01550000582103843cb8b9216f632c66c9bb34b91132b078d41610e2afc92539cedd1fb0c25450582098864e82e4469d33ea8be37f21c494cd3aa44e745ea2b4645ef58747ad6d558600582103d46ebd314b93c885dc64adacaa9ffedc748aa513e9bf12449804a4df7919d890582048d8f073d9b46c873fe6af7836b1fdb981c4740c0600c88b30522da14496e64100582103ad33d9c91e1cd299659fe3c90caee16cded224abc810f6552a91d09028c367b05820b7cb32a5e970c4d5e365dbe77c18496f6e821850ae9f4faa3660a2a08c7e56ed00582103f385fda393b99de9d37a10f60e1713e15d430bfca19f20f5997568cc361b0f7058202985b5a3204327f0c9e564f4dbd496221cf00899f09a84cd4c3db217476571a2005821034ac8ca477a537f652c7565346c91b91bb13a157b5fd28ad0664af7fed3466c504466553673005821039fe5675746c1bdfc5be63cc73266f9a1bea6676dfe021cfac5e6b88653bd4ae0446655365b03125365b35dc4086ec9792ef0ad57e78ab63d3d5078b1d61e22f734a7f908b1270373183fec9b0b90e2127d401665f0bda9daccc5d1313ed7cc1481af960934dbd90219b6c405582002d65eaa92c6bc4c13a5ec45527f0c18ea8932588728769ec7aecfe6d9f32e420701186105582002401b4e97e67c4a6d8095979b4c1ecb9eaf5726fc8a12798975a7653e98087208410105582002c8bb92227ba91a2657f29c5498f5c5b07ae36369c8d9283444f477930c79be0841010391d9c76bfbc066e84f0b415c737ab8c477498701d920526db41690050cfade9905582002a5ac70a18399817ccbe8bd4d9c78115136cc61699d51af87608af520226c32084101055820029bd268aedc8dbb36db0a4664e17b83abcfd87e404901eee7bd5704a95064ee08410105582002a06a67e7fee9c938155c50e6cec9da70b66679e777da6f02149c6674cd910508410103d9240a9d2d5851d05a97ff3305334dfdb0101e1e321fc279d2bb3cad6afa8fc802197ffd0362837e6c846ab7d74ae300efbcf8b54fc9b7c587525c315cfffe71ba9172195503bc8f79f6ffa56f0331be2cef2091206ad44f58cbba0b2a0a3af67d140a20bbf203724f631a141f6a338cc83620dba6cbc53e49035312204f3847a68e2d852fd67a03b76eeb79901a6c64dd10ac149bd51778546491f0b64982a0425da73a9c20de5d035750dfde66a7a051d429386138dc80cc40f94d0212ebbd6f3dd7950b994b7e6903be88e4724326382a8b56e2328eeef0ad51f18d5bae0e84296afe14c4028c4af9035c131dd29dd0d65f1862c89aea9402aae410dfc48668d4952ae18c9fc973085603042725662c701c4b9e20f7a6ca8eed26fbb75c57a661a1881feac698c2077201055820024d9fdc657d712208d6e7c2cfbf62f7ad7ba1bfff2e63e6caee2b0bf5bad0fd084101055820025939867994cd8e5a18f341628420fa8ab88b2f5d05ffe51291654922f5454f084101055820021ba6d576dbde0542d30a9264e2c71be270e3eb4f5910567a8c1c0619a2e5ec084101055820021089e529c1ff6c4d6583ecfca7ea6419bf39e0d418686bb7a24424c670b332084101055820023f6bc81ecfaf0cf4c3dcd9527e3c0f6910383acf964ed14b194fa660d30b10084101055820038c273af675612bc8ee68c7a419b671e678e63fb9ed62646358d70adb82434008410105582003c8b1cc055dd94a0f598f5ce4f8e121a8e3e4ac752b027ba3e2e99ed10c7b8008410102199000031696e2d680544beff6f9cf8e7d00e608f79ffcd716dc58a5cb82609f8e07cb9e05582002af72fc0534a4a5a94eb1f64cb1bd6c425acba3249fb8441e7411e9be9ec1f30841010558200264e3e72bf85be5684a53d65672caf037bf80d71a339ee85e8c2b719fff575e084c033b2e3c9fd0803ce80000000558200262e8d8d0eb3d7432758c244131f1649874367cc52a4fa4fd0609def9e57389084101055820020cb591a67822e88dcebb8991bca5b7305b5858d65bc2b5e6605e481a65058808410105582002dec651652e0aa17e056de1790554aeca5fa248cecc4131d189dacd26c4ca5508410103be44445c6367f50e0d747e8d65c9d72e142226e0ab24a88620acec0edc556aa702197fbf03ebc052143e57028286ef115b03a8a7736efa26c165fcfffc807960b2bf91e1c003bffc43dfea9c9a304f9ae5154c60adff35f4c9a6e722a59d60cca4d97c4038bb032c0386eb4a4b8c2a523e98b0b17664b6d1bc406f81d74407fb24ed6ddc5dd2fd035740575999fde2ddd421a9f5907dfc4be5fc3f5f96d65240ae3b3b596d38465f05582002b693085beb21b0878c7e5fab7519ce0d6105354cf6744dbfb73e3745e929e4084101055820027f470df15c88b9beaf3fad72890a64f8851d01ece546b0c9fb3bdf92041a35084101055820029cb4b751333c144710961e20611eb00c783b77e8cb532c64307202fb109699084c033b2e3c9fd0803ce8000000035f4ca08c32e4a3997d0e94d8946e3d361ec5d0225d9cd85c6de8d572bb0a99c903faecf459527318d12fee2db8d85c54fc244d0207ba336a01e397593b801ae61f055820039bbcf08818ec3329e5769dff3fbdad66e6312a961acb32c1b203edf70aeba0084101031c13664b9096979e0a6a58919940b248687992af952308581eff9d0e8c14119a0219400805582002facf5483fdda2fc98d08bcce1a68d5d308d3ad88ee21b50004fd694ba6f805084101055820026a58207750197f48cb90864096850259845c2c8e90c74433325c0b144bf8bb08410105582002fa0eae268038cfa984647a1d0635beb86eda9fb7b500688f3189520cfa9ee50841010219fb610219ffff" + } + }, + "txn_info": [ + { + "traces": { + "0x2a3365c575a5fc8fd2842b82d29f8035e7f71cec": { + "nonce": "0x1", + "code_usage": { + "write": "0x60005b60010180405060405a1163000000025700" + } + }, + "0x8943545177806ed17b9f23f0a21ee5948ecaa776": { + "balance": "0x33b2e3c9fce8b776d2f7e79", + "nonce": "0x18" + } + }, + "meta": { + "byte_code": "0xf87217845a697a6e82e3998080a06014600c60003960146000f360005b60010180405060405a11630000000257008360306ba0e89268b221aafe24e18f58fb0e672e7fccfd0934555c351ada818a56b3c31ef6a06c70c501108099507aed5f93ab6157a05c4009a5e2c1193aa6f2ca1288bc60f2", + "new_txn_trie_node_byte": "0xf87217845a697a6e82e3998080a06014600c60003960146000f360005b60010180405060405a11630000000257008360306ba0e89268b221aafe24e18f58fb0e672e7fccfd0934555c351ada818a56b3c31ef6a06c70c501108099507aed5f93ab6157a05c4009a5e2c1193aa6f2ca1288bc60f2", + "new_receipt_trie_node_byte": "0xf901080182e06eb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", + "gas_used": 57454 + } + }, + { + "traces": { + "0x8943545177806ed17b9f23f0a21ee5948ecaa776": { + "balance": "0x33b2e3c9fce6255d27ea9b3", + "nonce": "0x19" + }, + "0x2a3365c575a5fc8fd2842b82d29f8035e7f71cec": { + "code_usage": { + "read": "0x69505ae0dba86c46a230f4947bef8a70e7aec5fc6da86a9844e99ee4ac5c0d2d" + } + } + }, + "meta": { + "byte_code": "0xf86718845a697a6e830186a0942a3365c575a5fc8fd2842b82d29f8035e7f71cec80808360306ca0c8dc3eed1dd3970a23d77e794820bd9453d5d59693cf39ca567c2ce1f371d83aa07d332a975c4de8ea6be24d42b555b18924b95d9689f0ad1aa00cff44828a509f", + "new_txn_trie_node_byte": "0xf86718845a697a6e830186a0942a3365c575a5fc8fd2842b82d29f8035e7f71cec80808360306ca0c8dc3eed1dd3970a23d77e794820bd9453d5d59693cf39ca567c2ce1f371d83aa07d332a975c4de8ea6be24d42b555b18924b95d9689f0ad1aa00cff44828a509f", + "new_receipt_trie_node_byte": "0xf9010901830266f3b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", + "gas_used": 99973 + } + }, + { + "traces": { + "0x2a3365c575a5fc8fd2842b82d29f8035e7f71cec": { + "code_usage": { + "read": "0x69505ae0dba86c46a230f4947bef8a70e7aec5fc6da86a9844e99ee4ac5c0d2d" + } + }, + "0x8943545177806ed17b9f23f0a21ee5948ecaa776": { + "balance": "0x33b2e3c9fce393437cdd4ed", + "nonce": "0x1a" + } + }, + "meta": { + "byte_code": "0xf86719845a697a6e830186a0942a3365c575a5fc8fd2842b82d29f8035e7f71cec80808360306ca079739a2467e1939f39cfd6f1294a52bfd2072751abc398fdcfbb65154fcb78baa05bdc0e65c3135382b59df16fa87c856501749b2a1677170115dbb8f0d6c6d8ea", + "new_txn_trie_node_byte": "0xf86719845a697a6e830186a0942a3365c575a5fc8fd2842b82d29f8035e7f71cec80808360306ca079739a2467e1939f39cfd6f1294a52bfd2072751abc398fdcfbb65154fcb78baa05bdc0e65c3135382b59df16fa87c856501749b2a1677170115dbb8f0d6c6d8ea", + "new_receipt_trie_node_byte": "0xf90109018303ed78b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0", + "gas_used": 99973 + } + } + ] + }, + "other_data": { + "b_data": { + "b_meta": { + "block_beneficiary": "0x8943545177806ed17b9f23f0a21ee5948ecaa776", + "block_timestamp": "0x6655368b", + "block_number": "0x6", + "block_difficulty": "0x0", + "block_random": "0xc8a795c1aef6eebfa4df1982489bc354d3b2435a3739ccbd1858c9baaebec22b", + "block_gaslimit": "0x17fb5d1", + "block_chain_id": "0x301824", + "block_base_fee": "0x1af68b8e", + "block_gas_used": "0x3ed78", + "block_blob_gas_used": "0x0", + "block_excess_blob_gas": "0x0", + "parent_beacon_block_root": "0xcb1c950134eb119bd5d884c12a61c13210fc5f37c75f20849b64c3edd8840b56", + "block_bloom": [ + "0x0", + "0x0", + "0x0", + "0x0", + "0x0", + "0x0", + "0x0", + "0x0" + ] + }, + "b_hashes": { + "prev_hashes": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xbd491c81fa88681710feabc6b0502b779985169c92af323fffd790752961145f", + "0xd009e8d31d1f79c2e09bc9d0eb97d0802f0b0b0421d8299a01e43f7d61e1619d", + "0x7ee1f9d1d3f5a50d5c766847ffa644493f7dfe40c15eca0842fffc9966d56fae", + "0xaeb0a1814c75f5a92d4eccf2b57aff945682dd682c9aa35721af4f5d837af1cf", + "0xede379e8b2d9fecba28ac5a82da2f8c981edc9b5820e6ff4dd4816cf1aea3a69", + "0xc21c5ebbc8a026a4256cb0dd606673ab7ce1bb8116659989e29e2602439763c2" + ], + "cur_hash": "0xcba4dcaabefe20b444d9d5bbad6bbdb62d3e5567ace3f2a8bc7ef50b866d8424" + }, + "withdrawals": [] + }, + "checkpoint_state_trie_root": "0xfa446f2a9bf579b0ea805b88e3ee2acf601573f6ff48baaa8880915613aec508" + } + } +] \ No newline at end of file diff --git a/zero_bin/tools/prove_stdio.sh b/zero_bin/tools/prove_stdio.sh index 43f62dd59..e5f5409d0 100755 --- a/zero_bin/tools/prove_stdio.sh +++ b/zero_bin/tools/prove_stdio.sh @@ -52,16 +52,26 @@ if [[ $TEST_ONLY == "test_only" ]]; then export LOGIC_CIRCUIT_SIZE="12..13" export MEMORY_CIRCUIT_SIZE="17..18" else - if [[ $INPUT_FILE == *"witness_b19240705"* ]]; then - # These sizes are configured specifically for block 19240705. Don't use this in other scenarios - echo "Using specific circuit sizes for witness_b19240705.json" - export ARITHMETIC_CIRCUIT_SIZE="16..19" - export BYTE_PACKING_CIRCUIT_SIZE="16..19" - export CPU_CIRCUIT_SIZE="18..21" - export KECCAK_CIRCUIT_SIZE="15..18" + if [[ $INPUT_FILE == *"witness_b19807080"* ]]; then + # These sizes are configured specifically for block 19807080. Don't use this in other scenarios + echo "Using specific circuit sizes for witness_b19807080.json" + export ARITHMETIC_CIRCUIT_SIZE="16..18" + export BYTE_PACKING_CIRCUIT_SIZE="15..19" + export CPU_CIRCUIT_SIZE="17..21" + export KECCAK_CIRCUIT_SIZE="14..17" export KECCAK_SPONGE_CIRCUIT_SIZE="10..13" - export LOGIC_CIRCUIT_SIZE="13..17" + export LOGIC_CIRCUIT_SIZE="13..16" export MEMORY_CIRCUIT_SIZE="20..23" + elif [[ $INPUT_FILE == *"witness_b3_b6"* ]]; then + # These sizes are configured specifically for custom blocks 3 to 6. Don't use this in other scenarios + echo "Using specific circuit sizes for witness_b3_b6.json" + export ARITHMETIC_CIRCUIT_SIZE="16..17" + export BYTE_PACKING_CIRCUIT_SIZE="12..17" + export CPU_CIRCUIT_SIZE="14..19" + export KECCAK_CIRCUIT_SIZE="14..15" + export KECCAK_SPONGE_CIRCUIT_SIZE="10..11" + export LOGIC_CIRCUIT_SIZE="12..13" + export MEMORY_CIRCUIT_SIZE="17..21" else export ARITHMETIC_CIRCUIT_SIZE="16..23" export BYTE_PACKING_CIRCUIT_SIZE="9..21" From 7ff44ed537b5a94e39dee6f1e8e60e6fabd9ce5a Mon Sep 17 00:00:00 2001 From: Marko Atanasievski Date: Tue, 9 Jul 2024 14:04:45 +0200 Subject: [PATCH 36/40] fix: failed to send proof --- zero_bin/prover/src/lib.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/zero_bin/prover/src/lib.rs b/zero_bin/prover/src/lib.rs index da787ede2..bd48cdf36 100644 --- a/zero_bin/prover/src/lib.rs +++ b/zero_bin/prover/src/lib.rs @@ -91,7 +91,7 @@ impl BlockProverInput { pub async fn prove( self, runtime: &Runtime, - _previous: Option>>, + previous: Option>>, save_inputs_on_error: bool, ) -> Result { let block_number = self.get_block_number(); @@ -112,6 +112,12 @@ impl BlockProverInput { .try_collect::>() .await?; + // Wait for previous block proof + let _prev = match previous { + Some(it) => Some(it.await?), + None => None, + }; + // Dummy proof to match expected output type. Ok(GeneratedBlockProof { b_height: block_number From f9212b5b6e6e101334f3795f742c5e8eaf9b2d05 Mon Sep 17 00:00:00 2001 From: Marko Atanasievski Date: Tue, 9 Jul 2024 15:10:15 +0200 Subject: [PATCH 37/40] feat: cancun jerigon test network (#367) * fix: add cancun jerigon test network * fix: test execution * fix: whitespace --- .github/workflows/jerigon.yml | 43 +++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/.github/workflows/jerigon.yml b/.github/workflows/jerigon.yml index 273eff91e..279a4e722 100644 --- a/.github/workflows/jerigon.yml +++ b/.github/workflows/jerigon.yml @@ -29,7 +29,8 @@ jobs: uses: actions/checkout@v4 with: repository: 0xPolygonZero/jerigon-test-network - path: test-jerigon-network + ref: 'feat/kurtosis-network' + path: jerigon-test-network - name: Install nightly toolchain uses: dtolnay/rust-toolchain@nightly @@ -49,37 +50,49 @@ jobs: with: cache-on-failure: true - - name: Run jerigon test network with docker compose + - name: Install kurtosis run: | - cd test-jerigon-network - docker-compose -f docker-compose.yml up -d - docker logs -f smart-contracts - echo "Jerigon network is up and running, ready for testing" + echo "deb [trusted=yes] https://apt.fury.io/kurtosis-tech/ /" | sudo tee /etc/apt/sources.list.d/kurtosis.list + sudo apt update + sudo apt install kurtosis-cli - - name: Rpc test with curl + #It is much easier to use cast tool in scripts so install foundry + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Run cancun test network run: | - curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc": "2.0", "method": "eth_blockNumber", "params": [], "id":83}' localhost:8545 - env: - RUST_LOG: info - + docker pull ghcr.io/0xpolygonzero/erigon:feat-zero + kurtosis run --enclave cancun-testnet github.com/ethpandaops/ethereum-package@4.0.0 --args-file jerigon-test-network/network_params.yml + + - name: Generate blocks with transactions + run: | + ETH_RPC_URL="$(kurtosis port print cancun-testnet el-2-erigon-lighthouse ws-rpc)" + cast rpc eth_blockNumber --rpc-url $ETH_RPC_URL + cd jerigon-test-network && set -a && source .env && set +a + bash ./tests/generate_transactions.sh + - name: Run prove blocks in test_only mode run: | + ETH_RPC_URL="$(kurtosis port print cancun-testnet el-2-erigon-lighthouse ws-rpc)" cd zero_bin/tools - OUTPUT_TO_TERMINAL=true ./prove_rpc.sh 0x2 0x3 http://localhost:8546 jerigon true 0 0 test_only + ulimit -n 8192 + OUTPUT_TO_TERMINAL=true ./prove_rpc.sh 0x1 0xf $ETH_RPC_URL jerigon true 3000 100 test_only echo "Proving blocks in test_only mode finished" - name: Run prove blocks in real mode run: | + ETH_RPC_URL="$(kurtosis port print cancun-testnet el-2-erigon-lighthouse ws-rpc)" cd zero_bin/tools rm -rf proofs/* circuits/* ./proofs.json test.out verify.out leader.out - OUTPUT_TO_TERMINAL=true RUN_VERIFICATION=true ./prove_rpc.sh 0x4 0x5 http://localhost:8546 jerigon true + OUTPUT_TO_TERMINAL=true RUN_VERIFICATION=true ./prove_rpc.sh 0x2 0x8 $ETH_RPC_URL jerigon true 3000 100 echo "Proving blocks in real mode finished" - name: Shut down network run: | - cd test-jerigon-network - docker-compose -f docker-compose.yml down -v + kurtosis enclave rm -f cancun-testnet + kurtosis engine stop From 5ae66297bd6eeec6a826eea97cf5290f1d72142d Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Thu, 11 Jul 2024 04:40:06 +0900 Subject: [PATCH 38/40] fix(cancun): properly update accumulator in fake_exponential() (#376) --- evm_arithmetization/src/generation/prover_input.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm_arithmetization/src/generation/prover_input.rs b/evm_arithmetization/src/generation/prover_input.rs index 62bea6518..29186d364 100644 --- a/evm_arithmetization/src/generation/prover_input.rs +++ b/evm_arithmetization/src/generation/prover_input.rs @@ -991,7 +991,7 @@ fn fake_exponential(factor: U256, numerator: U256, denominator: U256) -> U256 { let mut numerator_accum = factor * denominator; while !numerator_accum.is_zero() { output += numerator_accum; - numerator_accum *= numerator / (denominator * i); + numerator_accum = (numerator_accum * numerator) / (denominator * i); i += 1; } From 3cdecff26a6f5bcef765ed69cc56b61a5d9ab82a Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Fri, 12 Jul 2024 00:20:21 +0900 Subject: [PATCH 39/40] fix(cancun): tweak ranges in integration tests (#377) * fix(cancun): tweak ranges in integration tests * Remove ignored tests --- .../tests/basic_smart_contract.rs | 200 -------- evm_arithmetization/tests/empty_txn_list.rs | 218 -------- evm_arithmetization/tests/log_opcode.rs | 479 +----------------- .../tests/self_balance_gas_cost.rs | 218 -------- 4 files changed, 10 insertions(+), 1105 deletions(-) delete mode 100644 evm_arithmetization/tests/basic_smart_contract.rs delete mode 100644 evm_arithmetization/tests/empty_txn_list.rs delete mode 100644 evm_arithmetization/tests/self_balance_gas_cost.rs diff --git a/evm_arithmetization/tests/basic_smart_contract.rs b/evm_arithmetization/tests/basic_smart_contract.rs deleted file mode 100644 index e00aa12be..000000000 --- a/evm_arithmetization/tests/basic_smart_contract.rs +++ /dev/null @@ -1,200 +0,0 @@ -use std::collections::HashMap; -use std::str::FromStr; -use std::time::Duration; - -use ethereum_types::{Address, H256, U256}; -use evm_arithmetization::cpu::kernel::opcodes::{get_opcode, get_push_opcode}; -use evm_arithmetization::generation::mpt::{AccountRlp, LegacyReceiptRlp}; -use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; -use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; -use evm_arithmetization::prover::prove; -use evm_arithmetization::testing_utils::{ - beacon_roots_account_nibbles, beacon_roots_contract_from_storage, eth_to_wei, - ger_account_nibbles, init_logger, preinitialized_state_and_storage_tries, - update_beacon_roots_account_storage, GLOBAL_EXIT_ROOT_ACCOUNT, -}; -use evm_arithmetization::verifier::verify_proof; -use evm_arithmetization::{AllStark, Node, StarkConfig}; -use hex_literal::hex; -use keccak_hash::keccak; -use mpt_trie::nibbles::Nibbles; -use mpt_trie::partial_trie::{HashedPartialTrie, PartialTrie}; -use plonky2::field::goldilocks_field::GoldilocksField; -use plonky2::plonk::config::KeccakGoldilocksConfig; -use plonky2::util::timing::TimingTree; - -type F = GoldilocksField; -const D: usize = 2; -type C = KeccakGoldilocksConfig; - -/// Test a simple token transfer to a new address. -#[test] -#[ignore] // Too slow to run on CI. -fn test_basic_smart_contract() -> anyhow::Result<()> { - init_logger(); - - let all_stark = AllStark::::default(); - let config = StarkConfig::standard_fast_config(); - - let beneficiary = hex!("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); - let sender = hex!("2c7536e3605d9c16a7a3d7b1898e529396a65c23"); - let to = hex!("a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0"); - - let beneficiary_state_key = keccak(beneficiary); - let sender_state_key = keccak(sender); - let to_state_key = keccak(to); - - let beneficiary_nibbles = Nibbles::from_bytes_be(beneficiary_state_key.as_bytes()).unwrap(); - let sender_nibbles = Nibbles::from_bytes_be(sender_state_key.as_bytes()).unwrap(); - let to_nibbles = Nibbles::from_bytes_be(to_state_key.as_bytes()).unwrap(); - - let push1 = get_push_opcode(1); - let add = get_opcode("ADD"); - let stop = get_opcode("STOP"); - let code = [push1, 3, push1, 4, add, stop]; - let code_gas = 3 + 3 + 3; - let code_hash = keccak(code); - - let beneficiary_account_before = AccountRlp { - nonce: 1.into(), - ..AccountRlp::default() - }; - let sender_account_before = AccountRlp { - nonce: 5.into(), - balance: eth_to_wei(100_000.into()), - ..AccountRlp::default() - }; - let to_account_before = AccountRlp { - code_hash, - ..AccountRlp::default() - }; - - let (mut state_trie_before, storage_tries) = preinitialized_state_and_storage_tries()?; - let mut beacon_roots_account_storage = storage_tries[0].1.clone(); - - state_trie_before.insert( - beneficiary_nibbles, - rlp::encode(&beneficiary_account_before).to_vec(), - )?; - state_trie_before.insert(sender_nibbles, rlp::encode(&sender_account_before).to_vec())?; - state_trie_before.insert(to_nibbles, rlp::encode(&to_account_before).to_vec())?; - - let tries_before = TrieInputs { - state_trie: state_trie_before, - transactions_trie: Node::Empty.into(), - receipts_trie: Node::Empty.into(), - storage_tries, - }; - - let txdata_gas = 2 * 16; - let gas_used = 21_000 + code_gas + txdata_gas; - - // Generated using a little py-evm script. - let txn = hex!("f861050a8255f094a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0648242421ba02c89eb757d9deeb1f5b3859a9d4d679951ef610ac47ad4608dc142beb1b7e313a05af7e9fbab825455d36c36c7f4cfcafbeafa9a77bdff936b52afb36d4fe4bcdd"); - let value = U256::from(100u32); - - let block_metadata = BlockMetadata { - block_beneficiary: Address::from(beneficiary), - block_difficulty: 0x20000.into(), - block_number: 1.into(), - block_chain_id: 1.into(), - block_timestamp: 0x03e8.into(), - block_gaslimit: 0xff112233u32.into(), - block_gas_used: gas_used.into(), - block_base_fee: 0xa.into(), - ..Default::default() - }; - - let mut contract_code = HashMap::new(); - contract_code.insert(keccak(vec![]), vec![]); - contract_code.insert(code_hash, code.to_vec()); - - let expected_state_trie_after = { - let mut state_trie_after = HashedPartialTrie::from(Node::Empty); - update_beacon_roots_account_storage( - &mut beacon_roots_account_storage, - block_metadata.block_timestamp, - block_metadata.parent_beacon_block_root, - )?; - let beacon_roots_account = - beacon_roots_contract_from_storage(&beacon_roots_account_storage); - - let beneficiary_account_after = AccountRlp { - nonce: 1.into(), - ..AccountRlp::default() - }; - let sender_account_after = AccountRlp { - balance: sender_account_before.balance - value - gas_used * 10, - nonce: sender_account_before.nonce + 1, - ..sender_account_before - }; - let to_account_after = AccountRlp { - balance: to_account_before.balance + value, - ..to_account_before - }; - - state_trie_after.insert( - beneficiary_nibbles, - rlp::encode(&beneficiary_account_after).to_vec(), - )?; - state_trie_after.insert(sender_nibbles, rlp::encode(&sender_account_after).to_vec())?; - state_trie_after.insert(to_nibbles, rlp::encode(&to_account_after).to_vec())?; - - state_trie_after.insert( - beacon_roots_account_nibbles(), - rlp::encode(&beacon_roots_account).to_vec(), - )?; - state_trie_after.insert( - ger_account_nibbles(), - rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), - )?; - - state_trie_after - }; - - let receipt_0 = LegacyReceiptRlp { - status: true, - cum_gas_used: gas_used.into(), - bloom: vec![0; 256].into(), - logs: vec![], - }; - let mut receipts_trie = HashedPartialTrie::from(Node::Empty); - receipts_trie.insert( - Nibbles::from_str("0x80").unwrap(), - rlp::encode(&receipt_0).to_vec(), - )?; - let transactions_trie: HashedPartialTrie = Node::Leaf { - nibbles: Nibbles::from_str("0x80").unwrap(), - value: txn.to_vec(), - } - .into(); - - let trie_roots_after = TrieRoots { - state_root: expected_state_trie_after.hash(), - transactions_root: transactions_trie.hash(), - receipts_root: receipts_trie.hash(), - }; - let inputs = GenerationInputs { - signed_txn: Some(txn.to_vec()), - withdrawals: vec![], - global_exit_roots: vec![], - tries: tries_before, - trie_roots_after, - contract_code, - checkpoint_state_trie_root: HashedPartialTrie::from(Node::Empty).hash(), - block_metadata, - txn_number_before: 0.into(), - gas_used_before: 0.into(), - gas_used_after: gas_used.into(), - block_hashes: BlockHashes { - prev_hashes: vec![H256::default(); 256], - cur_hash: H256::default(), - }, - }; - - let mut timing = TimingTree::new("prove", log::Level::Debug); - let proof = prove::(&all_stark, &config, inputs, &mut timing, None)?; - timing.filter(Duration::from_millis(100)).print(); - - verify_proof(&all_stark, proof, &config) -} diff --git a/evm_arithmetization/tests/empty_txn_list.rs b/evm_arithmetization/tests/empty_txn_list.rs deleted file mode 100644 index 8cfad586d..000000000 --- a/evm_arithmetization/tests/empty_txn_list.rs +++ /dev/null @@ -1,218 +0,0 @@ -use core::marker::PhantomData; -use std::collections::HashMap; -use std::time::Duration; - -use ethereum_types::{BigEndianHash, H256}; -use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; -use evm_arithmetization::proof::{BlockHashes, BlockMetadata, PublicValues, TrieRoots}; -use evm_arithmetization::testing_utils::{ - beacon_roots_account_nibbles, beacon_roots_contract_from_storage, ger_account_nibbles, - init_logger, preinitialized_state_and_storage_tries, update_beacon_roots_account_storage, - BEACON_ROOTS_CONTRACT_ADDRESS_HASHED, GLOBAL_EXIT_ROOT_ACCOUNT, -}; -use evm_arithmetization::{AllRecursiveCircuits, AllStark, Node, StarkConfig}; -use hex_literal::hex; -use keccak_hash::keccak; -use log::info; -use mpt_trie::partial_trie::{HashedPartialTrie, PartialTrie}; -use plonky2::field::goldilocks_field::GoldilocksField; -use plonky2::plonk::config::PoseidonGoldilocksConfig; -use plonky2::util::serialization::{DefaultGateSerializer, DefaultGeneratorSerializer}; -use plonky2::util::timing::TimingTree; - -type F = GoldilocksField; -const D: usize = 2; -type C = PoseidonGoldilocksConfig; - -/// Execute the empty list of transactions, i.e. a no-op. -#[test] -#[ignore] // Too slow to run on CI. -fn test_empty_txn_list() -> anyhow::Result<()> { - init_logger(); - - let all_stark = AllStark::::default(); - let config = StarkConfig::standard_fast_config(); - - let block_metadata = BlockMetadata { - block_number: 1.into(), - block_timestamp: 1.into(), - parent_beacon_block_root: H256(hex!( - "44e2566c06c03b132e0ede3e90af477ebca74393b89dd6cb29f9c79cbcb6e963" - )), - ..Default::default() - }; - - let (state_trie, storage_tries) = preinitialized_state_and_storage_tries()?; - let mut beacon_roots_account_storage = storage_tries[0].1.clone(); - let transactions_trie = HashedPartialTrie::from(Node::Empty); - let receipts_trie = HashedPartialTrie::from(Node::Empty); - - let state_trie_after: HashedPartialTrie = { - let mut state_trie_after = HashedPartialTrie::from(Node::Empty); - update_beacon_roots_account_storage( - &mut beacon_roots_account_storage, - block_metadata.block_timestamp, - block_metadata.parent_beacon_block_root, - )?; - let beacon_roots_account = - beacon_roots_contract_from_storage(&beacon_roots_account_storage); - - state_trie_after.insert( - beacon_roots_account_nibbles(), - rlp::encode(&beacon_roots_account).to_vec(), - )?; - state_trie_after.insert( - ger_account_nibbles(), - rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), - )?; - - state_trie_after - }; - - let mut contract_code = HashMap::new(); - contract_code.insert(keccak(vec![]), vec![]); - - // No transactions, but the beacon roots contract has been updated. - let trie_roots_after = TrieRoots { - state_root: state_trie_after.hash(), - transactions_root: transactions_trie.hash(), - receipts_root: receipts_trie.hash(), - }; - let mut initial_block_hashes = vec![H256::default(); 256]; - initial_block_hashes[255] = H256::from_uint(&0x200.into()); - let inputs1 = GenerationInputs { - signed_txn: None, - withdrawals: vec![], - global_exit_roots: vec![], - tries: TrieInputs { - state_trie: state_trie.clone(), - transactions_trie: transactions_trie.clone(), - receipts_trie: receipts_trie.clone(), - storage_tries: storage_tries.clone(), - }, - trie_roots_after, - contract_code: contract_code.clone(), - checkpoint_state_trie_root: state_trie.hash(), - block_metadata: block_metadata.clone(), - txn_number_before: 0.into(), - gas_used_before: 0.into(), - gas_used_after: 0.into(), - block_hashes: BlockHashes { - prev_hashes: initial_block_hashes.clone(), - cur_hash: H256::default(), - }, - }; - - // Initialize the preprocessed circuits for the zkEVM. - let all_circuits = AllRecursiveCircuits::::new( - &all_stark, - // Minimal ranges to prove an empty list - &[16..17, 11..13, 13..15, 14..15, 9..10, 12..13, 17..18], - &config, - ); - - { - let gate_serializer = DefaultGateSerializer; - let generator_serializer = DefaultGeneratorSerializer:: { - _phantom: PhantomData::, - }; - - let timing = TimingTree::new("serialize AllRecursiveCircuits", log::Level::Info); - let all_circuits_bytes = all_circuits - .to_bytes(false, &gate_serializer, &generator_serializer) - .map_err(|_| anyhow::Error::msg("AllRecursiveCircuits serialization failed."))?; - timing.filter(Duration::from_millis(100)).print(); - info!( - "AllRecursiveCircuits length: {} bytes", - all_circuits_bytes.len() - ); - - let timing = TimingTree::new("deserialize AllRecursiveCircuits", log::Level::Info); - let all_circuits_from_bytes = AllRecursiveCircuits::::from_bytes( - &all_circuits_bytes, - false, - &gate_serializer, - &generator_serializer, - ) - .map_err(|_| anyhow::Error::msg("AllRecursiveCircuits deserialization failed."))?; - timing.filter(Duration::from_millis(100)).print(); - - assert_eq!(all_circuits, all_circuits_from_bytes); - } - - let mut timing = TimingTree::new("prove first dummy", log::Level::Info); - let (root_proof, public_values) = - all_circuits.prove_root(&all_stark, &config, inputs1, &mut timing, None)?; - timing.filter(Duration::from_millis(100)).print(); - all_circuits.verify_root(root_proof.clone())?; - - // Test retrieved public values from the proof public inputs. - let retrieved_public_values = PublicValues::from_public_inputs(&root_proof.public_inputs); - assert_eq!(retrieved_public_values, public_values); - - // We cannot duplicate the proof here because even though there weren't any - // transactions, the state has mutated when updating the beacon roots contract. - let trie_roots_after = TrieRoots { - state_root: state_trie_after.hash(), - transactions_root: transactions_trie.hash(), - receipts_root: receipts_trie.hash(), - }; - let inputs2 = GenerationInputs { - signed_txn: None, - withdrawals: vec![], - global_exit_roots: vec![], - tries: TrieInputs { - state_trie: state_trie_after, - transactions_trie, - receipts_trie, - storage_tries: vec![( - H256(BEACON_ROOTS_CONTRACT_ADDRESS_HASHED), - beacon_roots_account_storage, - )], - }, - trie_roots_after, - contract_code, - checkpoint_state_trie_root: state_trie.hash(), - block_metadata, - txn_number_before: 0.into(), - gas_used_before: 0.into(), - gas_used_after: 0.into(), - block_hashes: BlockHashes { - prev_hashes: initial_block_hashes, - cur_hash: H256::default(), - }, - }; - - let mut timing = TimingTree::new("prove second dummy", log::Level::Info); - let (root_proof2, public_values2) = - all_circuits.prove_root(&all_stark, &config, inputs2, &mut timing, None)?; - timing.filter(Duration::from_millis(100)).print(); - all_circuits.verify_root(root_proof2.clone())?; - - let (agg_proof, agg_public_values) = all_circuits.prove_aggregation( - false, - &root_proof, - public_values.clone(), - false, - &root_proof2, - public_values2, - )?; - all_circuits.verify_aggregation(&agg_proof)?; - - // Test retrieved public values from the proof public inputs. - let retrieved_public_values = PublicValues::from_public_inputs(&agg_proof.public_inputs); - assert_eq!(retrieved_public_values, agg_public_values); - - let (block_proof, block_public_values) = - all_circuits.prove_block(None, &agg_proof, agg_public_values)?; - all_circuits.verify_block(&block_proof)?; - - // Test retrieved public values from the proof public inputs. - let retrieved_public_values = PublicValues::from_public_inputs(&block_proof.public_inputs); - assert_eq!(retrieved_public_values, block_public_values); - - // Get the verifier associated to these preprocessed circuits, and have it - // verify the block_proof. - let verifier = all_circuits.final_verifier_data(); - verifier.verify(block_proof) -} diff --git a/evm_arithmetization/tests/log_opcode.rs b/evm_arithmetization/tests/log_opcode.rs index d08c74632..8cd5c57c0 100644 --- a/evm_arithmetization/tests/log_opcode.rs +++ b/evm_arithmetization/tests/log_opcode.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use std::time::Duration; use bytes::Bytes; -use ethereum_types::{Address, BigEndianHash, H256, U256}; +use ethereum_types::{Address, BigEndianHash, H256}; use evm_arithmetization::generation::mpt::transaction_testing::{ AddressOption, LegacyTransactionRlp, }; @@ -12,12 +12,12 @@ use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; use evm_arithmetization::prover::prove; use evm_arithmetization::testing_utils::{ - beacon_roots_account_nibbles, beacon_roots_contract_from_storage, init_logger, - preinitialized_state_and_storage_tries, update_beacon_roots_account_storage, - BEACON_ROOTS_CONTRACT_ADDRESS_HASHED, + beacon_roots_account_nibbles, beacon_roots_contract_from_storage, ger_account_nibbles, + init_logger, preinitialized_state_and_storage_tries, update_beacon_roots_account_storage, + GLOBAL_EXIT_ROOT_ACCOUNT, }; use evm_arithmetization::verifier::verify_proof; -use evm_arithmetization::{AllRecursiveCircuits, AllStark, Node, StarkConfig}; +use evm_arithmetization::{AllStark, Node, StarkConfig}; use hex_literal::hex; use keccak_hash::keccak; use mpt_trie::nibbles::Nibbles; @@ -32,7 +32,6 @@ type C = PoseidonGoldilocksConfig; /// Variation of `add11_yml` testing LOG opcodes. #[test] -#[ignore] // Too slow to run on CI. fn test_log_opcodes() -> anyhow::Result<()> { init_logger(); @@ -61,7 +60,7 @@ fn test_log_opcodes() -> anyhow::Result<()> { 0x60, 99, 0x60, 98, 0x60, 5, 0x60, 27, 0xA2, // LOG2(27, 5, 98, 99) 0x00, ]; - println!("contract: {:02x?}", code); + let code_gas = 3 + 3 + 3 // PUSHs and MSTORE + 3 + 3 + 375 // PUSHs and LOG0 + 3 + 3 + 3 + 3 + 375 + 375*2 + 8*5 + 3// PUSHs, LOG2 and memory expansion @@ -221,6 +220,10 @@ fn test_log_opcodes() -> anyhow::Result<()> { beacon_roots_account_nibbles(), rlp::encode(&beacon_roots_account).to_vec(), )?; + expected_state_trie_after.insert( + ger_account_nibbles(), + rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), + )?; let transactions_trie: HashedPartialTrie = Node::Leaf { nibbles: Nibbles::from_str("0x80").unwrap(), @@ -271,468 +274,6 @@ fn test_log_opcodes() -> anyhow::Result<()> { verify_proof(&all_stark, proof, &config) } -// Tests proving two transactions, one of which with logs, and aggregating them. -#[test] -#[ignore] // Too slow to run on CI. -fn test_log_with_aggreg() -> anyhow::Result<()> { - init_logger(); - - let code = [ - 0x64, 0xA1, 0xB2, 0xC3, 0xD4, 0xE5, 0x60, 0x0, 0x52, // MSTORE(0x0, 0xA1B2C3D4E5) - 0x60, 0x0, 0x60, 0x0, 0xA0, // LOG0(0x0, 0x0) - 0x60, 99, 0x60, 98, 0x60, 5, 0x60, 27, 0xA2, // LOG2(27, 5, 98, 99) - 0x00, - ]; - - let code_gas = 3 + 3 + 3 // PUSHs and MSTORE - + 3 + 3 + 375 // PUSHs and LOG0 - + 3 + 3 + 3 + 3 + 375 + 375*2 + 8*5 // PUSHs and LOG2 - + 3 // Memory expansion - ; - - let gas_used = 21_000 + code_gas; - - let code_hash = keccak(code); - - // First transaction. - let all_stark = AllStark::::default(); - let config = StarkConfig::standard_fast_config(); - - let beneficiary = hex!("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"); - let sender_first = hex!("af1276cbb260bb13deddb4209ae99ae6e497f446"); - let to_first = hex!("095e7baea6a6c7c4c2dfeb977efac326af552d87"); - let to = hex!("095e7baea6a6c7c4c2dfeb977efac326af552e89"); - - let beneficiary_state_key = keccak(beneficiary); - let sender_state_key = keccak(sender_first); - let to_hashed = keccak(to_first); - let to_hashed_2 = keccak(to); - - let beneficiary_nibbles = Nibbles::from_bytes_be(beneficiary_state_key.as_bytes()).unwrap(); - let sender_nibbles = Nibbles::from_bytes_be(sender_state_key.as_bytes()).unwrap(); - let to_nibbles = Nibbles::from_bytes_be(to_hashed.as_bytes()).unwrap(); - let to_second_nibbles = Nibbles::from_bytes_be(to_hashed_2.as_bytes()).unwrap(); - - let beneficiary_account_before = AccountRlp { - nonce: 1.into(), - ..AccountRlp::default() - }; - let sender_balance_before = 1000000000000000000u64.into(); - let sender_account_before = AccountRlp { - balance: sender_balance_before, - ..AccountRlp::default() - }; - let to_account_before = AccountRlp { - ..AccountRlp::default() - }; - let to_account_second_before = AccountRlp { - code_hash, - ..AccountRlp::default() - }; - - // In the first transaction, the sender account sends `txn_value` to - // `to_account`. - let gas_price = 10; - let txn_value = 0xau64; - let (mut state_trie_before, storage_tries) = preinitialized_state_and_storage_tries()?; - let mut beacon_roots_account_storage = storage_tries[0].1.clone(); - state_trie_before.insert( - beneficiary_nibbles, - rlp::encode(&beneficiary_account_before).to_vec(), - )?; - state_trie_before.insert(sender_nibbles, rlp::encode(&sender_account_before).to_vec())?; - state_trie_before.insert(to_nibbles, rlp::encode(&to_account_before).to_vec())?; - state_trie_before.insert( - to_second_nibbles, - rlp::encode(&to_account_second_before).to_vec(), - )?; - let checkpoint_state_trie_root = state_trie_before.hash(); - - let tries_before = TrieInputs { - state_trie: state_trie_before, - transactions_trie: Node::Empty.into(), - receipts_trie: Node::Empty.into(), - storage_tries, - }; - - let txn = hex!("f85f800a82520894095e7baea6a6c7c4c2dfeb977efac326af552d870a8026a0122f370ed4023a6c253350c6bfb87d7d7eb2cd86447befee99e0a26b70baec20a07100ab1b3977f2b4571202b9f4b68850858caf5469222794600b5ce1cfb348ad"); - - let block_1_metadata = BlockMetadata { - block_beneficiary: Address::from(beneficiary), - block_timestamp: 0x03e8.into(), - block_number: 1.into(), - block_difficulty: 0x020000.into(), - block_gaslimit: 0x445566u32.into(), - block_chain_id: 1.into(), - block_base_fee: 0xa.into(), - block_gas_used: (22570 + 21000).into(), - block_bloom: [ - 0.into(), - 0.into(), - U256::from_dec_str( - "55213970774324510299479508399853534522527075462195808724319849722937344", - ) - .unwrap(), - U256::from_dec_str("1361129467683753853853498429727072845824").unwrap(), - 33554432.into(), - U256::from_dec_str("9223372036854775808").unwrap(), - U256::from_dec_str( - "3618502788666131106986593281521497120414687020801267626233049500247285563392", - ) - .unwrap(), - U256::from_dec_str("2722259584404615024560450425766186844160").unwrap(), - ], - ..Default::default() - }; - - let beneficiary_account_after = AccountRlp { - nonce: 1.into(), - ..AccountRlp::default() - }; - - let sender_balance_after = sender_balance_before - gas_price * 21000 - txn_value; - let sender_account_after = AccountRlp { - balance: sender_balance_after, - nonce: 1.into(), - ..AccountRlp::default() - }; - let to_account_after = AccountRlp { - balance: txn_value.into(), - ..AccountRlp::default() - }; - - update_beacon_roots_account_storage( - &mut beacon_roots_account_storage, - block_1_metadata.block_timestamp, - block_1_metadata.parent_beacon_block_root, - )?; - let beacon_roots_account = beacon_roots_contract_from_storage(&beacon_roots_account_storage); - - let mut contract_code = HashMap::new(); - contract_code.insert(keccak(vec![]), vec![]); - contract_code.insert(code_hash, code.to_vec()); - - let mut expected_state_trie_after = HashedPartialTrie::from(Node::Empty); - expected_state_trie_after.insert( - beneficiary_nibbles, - rlp::encode(&beneficiary_account_after).to_vec(), - )?; - expected_state_trie_after - .insert(sender_nibbles, rlp::encode(&sender_account_after).to_vec())?; - expected_state_trie_after.insert(to_nibbles, rlp::encode(&to_account_after).to_vec())?; - expected_state_trie_after.insert( - to_second_nibbles, - rlp::encode(&to_account_second_before).to_vec(), - )?; - expected_state_trie_after.insert( - beacon_roots_account_nibbles(), - rlp::encode(&beacon_roots_account).to_vec(), - )?; - - // Compute new receipt trie. - let mut receipts_trie = HashedPartialTrie::from(Node::Empty); - let receipt_0 = LegacyReceiptRlp { - status: true, - cum_gas_used: 21000u64.into(), - bloom: [0x00; 256].to_vec().into(), - logs: vec![], - }; - receipts_trie.insert( - Nibbles::from_str("0x80").unwrap(), - rlp::encode(&receipt_0).to_vec(), - )?; - - let mut transactions_trie: HashedPartialTrie = Node::Leaf { - nibbles: Nibbles::from_str("0x80").unwrap(), - value: txn.to_vec(), - } - .into(); - - let tries_after = TrieRoots { - state_root: expected_state_trie_after.hash(), - transactions_root: transactions_trie.hash(), - receipts_root: receipts_trie.clone().hash(), - }; - - let block_1_hash = - H256::from_str("0x0101010101010101010101010101010101010101010101010101010101010101")?; - let mut block_hashes = vec![H256::default(); 256]; - - let inputs_first = GenerationInputs { - signed_txn: Some(txn.to_vec()), - withdrawals: vec![], - global_exit_roots: vec![], - tries: tries_before, - trie_roots_after: tries_after, - contract_code, - checkpoint_state_trie_root, - block_metadata: block_1_metadata.clone(), - txn_number_before: 0.into(), - gas_used_before: 0.into(), - gas_used_after: 21000u64.into(), - block_hashes: BlockHashes { - prev_hashes: block_hashes.clone(), - cur_hash: block_1_hash, - }, - }; - - // Preprocess all circuits. - let all_circuits = AllRecursiveCircuits::::new( - &all_stark, - &[16..17, 12..15, 14..18, 14..15, 9..10, 12..13, 17..20], - &config, - ); - - let mut timing = TimingTree::new("prove root first", log::Level::Info); - let (root_proof_first, public_values_first) = - all_circuits.prove_root(&all_stark, &config, inputs_first, &mut timing, None)?; - - timing.filter(Duration::from_millis(100)).print(); - all_circuits.verify_root(root_proof_first.clone())?; - - // The gas used and transaction number are fed to the next transaction, so the - // two proofs can be correctly aggregated. - let gas_used_second = public_values_first.extra_block_data.gas_used_after; - - // Prove second transaction. In this second transaction, the code with logs is - // executed. - - let state_trie_before = expected_state_trie_after; - - let tries_before = TrieInputs { - state_trie: state_trie_before, - transactions_trie: transactions_trie.clone(), - receipts_trie: receipts_trie.clone(), - storage_tries: vec![], - }; - - // Prove a transaction which carries out two LOG opcodes. - let txn_gas_price = 10; - let txn_2 = hex!("f860010a830186a094095e7baea6a6c7c4c2dfeb977efac326af552e89808025a04a223955b0bd3827e3740a9a427d0ea43beb5bafa44a0204bf0a3306c8219f7ba0502c32d78f233e9e7ce9f5df3b576556d5d49731e0678fd5a068cdf359557b5b"); - - let mut contract_code = HashMap::new(); - contract_code.insert(keccak(vec![]), vec![]); - contract_code.insert(code_hash, code.to_vec()); - - // Update the state and receipt tries after the transaction, so that we have the - // correct expected tries: Update accounts. - let beneficiary_account_after = AccountRlp { - nonce: 1.into(), - ..AccountRlp::default() - }; - - let sender_balance_after = sender_balance_after - gas_used * txn_gas_price; - let sender_account_after = AccountRlp { - balance: sender_balance_after, - nonce: 2.into(), - ..AccountRlp::default() - }; - let balance_after = to_account_after.balance; - let to_account_after = AccountRlp { - balance: balance_after, - ..AccountRlp::default() - }; - let to_account_second_after = AccountRlp { - balance: to_account_second_before.balance, - code_hash, - ..AccountRlp::default() - }; - - // Update the receipt trie. - let first_log = LogRlp { - address: to.into(), - topics: vec![], - data: Bytes::new(), - }; - - let second_log = LogRlp { - address: to.into(), - topics: vec![ - hex!("0000000000000000000000000000000000000000000000000000000000000062").into(), /* dec: 98 */ - hex!("0000000000000000000000000000000000000000000000000000000000000063").into(), /* dec: 99 */ - ], - data: hex!("a1b2c3d4e5").to_vec().into(), - }; - - let receipt = LegacyReceiptRlp { - status: true, - cum_gas_used: (22570 + 21000).into(), - bloom: hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000001000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000800000000000000008000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000800002000000000000000000000000000").to_vec().into(), - logs: vec![first_log, second_log], - }; - - let receipt_nibbles = Nibbles::from_str("0x01").unwrap(); // RLP(1) = 0x1 - - receipts_trie.insert(receipt_nibbles, rlp::encode(&receipt).to_vec())?; - - // Update the state trie. - let mut expected_state_trie_after = HashedPartialTrie::from(Node::Empty); - expected_state_trie_after.insert( - beneficiary_nibbles, - rlp::encode(&beneficiary_account_after).to_vec(), - )?; - expected_state_trie_after - .insert(sender_nibbles, rlp::encode(&sender_account_after).to_vec())?; - expected_state_trie_after.insert(to_nibbles, rlp::encode(&to_account_after).to_vec())?; - expected_state_trie_after.insert( - to_second_nibbles, - rlp::encode(&to_account_second_after).to_vec(), - )?; - - // Copy without the beacon roots account for the next block. - let mut state_trie_after_block2 = expected_state_trie_after.clone(); - - expected_state_trie_after.insert( - beacon_roots_account_nibbles(), - rlp::encode(&beacon_roots_account).to_vec(), - )?; - - transactions_trie.insert(Nibbles::from_str("0x01").unwrap(), txn_2.to_vec())?; - - let block_1_state_root = expected_state_trie_after.hash(); - - let trie_roots_after = TrieRoots { - state_root: block_1_state_root, - transactions_root: transactions_trie.hash(), - receipts_root: receipts_trie.hash(), - }; - - let inputs = GenerationInputs { - signed_txn: Some(txn_2.to_vec()), - withdrawals: vec![], - global_exit_roots: vec![], - tries: tries_before, - trie_roots_after: trie_roots_after.clone(), - contract_code, - checkpoint_state_trie_root, - block_metadata: block_1_metadata, - txn_number_before: 1.into(), - gas_used_before: gas_used_second, - gas_used_after: receipt.cum_gas_used, - block_hashes: BlockHashes { - prev_hashes: block_hashes.clone(), - cur_hash: block_1_hash, - }, - }; - - let mut timing = TimingTree::new("prove root second", log::Level::Info); - let (root_proof_second, public_values_second) = - all_circuits.prove_root(&all_stark, &config, inputs, &mut timing, None.clone())?; - timing.filter(Duration::from_millis(100)).print(); - - all_circuits.verify_root(root_proof_second.clone())?; - - let (agg_proof, updated_agg_public_values) = all_circuits.prove_aggregation( - false, - &root_proof_first, - public_values_first, - false, - &root_proof_second, - public_values_second, - )?; - all_circuits.verify_aggregation(&agg_proof)?; - let (first_block_proof, _block_public_values) = - all_circuits.prove_block(None, &agg_proof, updated_agg_public_values)?; - all_circuits.verify_block(&first_block_proof)?; - - // Prove the next, empty block. - - let block_2_hash = - H256::from_str("0x0123456789101112131415161718192021222324252627282930313233343536")?; - block_hashes[255] = block_1_hash; - - let block_2_metadata = BlockMetadata { - block_beneficiary: Address::from(beneficiary), - block_timestamp: 0x03e9.into(), - block_number: 2.into(), - block_difficulty: 0x020000.into(), - block_gaslimit: 0x445566u32.into(), - block_chain_id: 1.into(), - block_base_fee: 0xa.into(), - ..Default::default() - }; - - let mut contract_code = HashMap::new(); - contract_code.insert(keccak(vec![]), vec![]); - - let initial_beacon_roots_account_storage = beacon_roots_account_storage.clone(); - update_beacon_roots_account_storage( - &mut beacon_roots_account_storage, - block_2_metadata.block_timestamp, - block_2_metadata.parent_beacon_block_root, - )?; - let beacon_roots_account = beacon_roots_contract_from_storage(&beacon_roots_account_storage); - - state_trie_after_block2.insert( - beacon_roots_account_nibbles(), - rlp::encode(&beacon_roots_account).to_vec(), - )?; - - let inputs = GenerationInputs { - signed_txn: None, - withdrawals: vec![], - global_exit_roots: vec![], - tries: TrieInputs { - state_trie: expected_state_trie_after, - transactions_trie: Node::Empty.into(), - receipts_trie: Node::Empty.into(), - storage_tries: vec![( - H256(BEACON_ROOTS_CONTRACT_ADDRESS_HASHED), - initial_beacon_roots_account_storage, - )], - }, - trie_roots_after: TrieRoots { - state_root: state_trie_after_block2.hash(), - transactions_root: HashedPartialTrie::from(Node::Empty).hash(), - receipts_root: HashedPartialTrie::from(Node::Empty).hash(), - }, - contract_code, - checkpoint_state_trie_root: block_1_state_root, // We use block 1 as new checkpoint. - block_metadata: block_2_metadata, - txn_number_before: 0.into(), - gas_used_before: 0.into(), - gas_used_after: 0.into(), - block_hashes: BlockHashes { - prev_hashes: block_hashes, - cur_hash: block_2_hash, - }, - }; - let mut inputs2 = inputs.clone(); - - let (root_proof, public_values) = - all_circuits.prove_root(&all_stark, &config, inputs, &mut timing, None)?; - all_circuits.verify_root(root_proof.clone())?; - - // We cannot duplicate the proof here because even though there weren't any - // transactions, the state has mutated when updating the beacon roots contract. - inputs2.tries.storage_tries = vec![( - H256(BEACON_ROOTS_CONTRACT_ADDRESS_HASHED), - beacon_roots_account_storage, - )]; - inputs2.tries.state_trie = state_trie_after_block2; - - let (root_proof2, public_values2) = - all_circuits.prove_root(&all_stark, &config, inputs2, &mut timing, None)?; - all_circuits.verify_root(root_proof2.clone())?; - - let (agg_proof, updated_agg_public_values) = all_circuits.prove_aggregation( - false, - &root_proof, - public_values.clone(), - false, - &root_proof2, - public_values2, - )?; - all_circuits.verify_aggregation(&agg_proof)?; - - let (second_block_proof, _block_public_values) = all_circuits.prove_block( - None, // We don't specify a previous proof, considering block 1 as the new checkpoint. - &agg_proof, - updated_agg_public_values, - )?; - all_circuits.verify_block(&second_block_proof) -} - /// Values taken from the block 1000000 of Goerli: https://goerli.etherscan.io/txs?block=1000000 #[test] fn test_txn_and_receipt_trie_hash() -> anyhow::Result<()> { diff --git a/evm_arithmetization/tests/self_balance_gas_cost.rs b/evm_arithmetization/tests/self_balance_gas_cost.rs deleted file mode 100644 index c10a55d5c..000000000 --- a/evm_arithmetization/tests/self_balance_gas_cost.rs +++ /dev/null @@ -1,218 +0,0 @@ -use std::collections::HashMap; -use std::str::FromStr; -use std::time::Duration; - -use ethereum_types::{Address, H256, U256}; -use evm_arithmetization::generation::mpt::{AccountRlp, LegacyReceiptRlp}; -use evm_arithmetization::generation::{GenerationInputs, TrieInputs}; -use evm_arithmetization::proof::{BlockHashes, BlockMetadata, TrieRoots}; -use evm_arithmetization::prover::prove; -use evm_arithmetization::testing_utils::{ - beacon_roots_account_nibbles, beacon_roots_contract_from_storage, ger_account_nibbles, - init_logger, preinitialized_state_and_storage_tries, update_beacon_roots_account_storage, - GLOBAL_EXIT_ROOT_ACCOUNT, -}; -use evm_arithmetization::verifier::verify_proof; -use evm_arithmetization::{AllStark, Node, StarkConfig}; -use hex_literal::hex; -use keccak_hash::keccak; -use mpt_trie::nibbles::Nibbles; -use mpt_trie::partial_trie::{HashedPartialTrie, PartialTrie}; -use plonky2::field::goldilocks_field::GoldilocksField; -use plonky2::plonk::config::KeccakGoldilocksConfig; -use plonky2::util::timing::TimingTree; - -type F = GoldilocksField; -const D: usize = 2; -type C = KeccakGoldilocksConfig; - -/// The `selfBalanceGasCost` test case from https://github.com/ethereum/tests -#[test] -#[ignore] // Too slow to run on CI. -fn self_balance_gas_cost() -> anyhow::Result<()> { - init_logger(); - - let all_stark = AllStark::::default(); - let config = StarkConfig::standard_fast_config(); - - let beneficiary = hex!("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"); - let sender = hex!("a94f5374fce5edbc8e2a8697c15331677e6ebf0b"); - let to = hex!("1000000000000000000000000000000000000000"); - - let beneficiary_state_key = keccak(beneficiary); - let sender_state_key = keccak(sender); - let to_hashed = keccak(to); - - let beneficiary_nibbles = Nibbles::from_bytes_be(beneficiary_state_key.as_bytes()).unwrap(); - let sender_nibbles = Nibbles::from_bytes_be(sender_state_key.as_bytes()).unwrap(); - let to_nibbles = Nibbles::from_bytes_be(to_hashed.as_bytes()).unwrap(); - - let code = [ - 0x5a, 0x47, 0x5a, 0x90, 0x50, 0x90, 0x03, 0x60, 0x02, 0x90, 0x03, 0x60, 0x01, 0x55, 0x00, - ]; - let code_gas = 2 // GAS - + 5 // SELFBALANCE - + 2 // GAS - + 3 // SWAP1 - + 2 // POP - + 3 // SWAP1 - + 3 // SUB - + 3 // PUSH1 - + 3 // SWAP1 - + 3 // SUB - + 3 // PUSH1 - + 22100; // SSTORE - let code_hash = keccak(code); - - let beneficiary_account_before = AccountRlp { - nonce: 1.into(), - ..AccountRlp::default() - }; - let sender_account_before = AccountRlp { - balance: 0x3635c9adc5dea00000u128.into(), - ..AccountRlp::default() - }; - let to_account_before = AccountRlp { - code_hash, - ..AccountRlp::default() - }; - - let (mut state_trie_before, mut storage_tries) = preinitialized_state_and_storage_tries()?; - let mut beacon_roots_account_storage = storage_tries[0].1.clone(); - state_trie_before.insert( - beneficiary_nibbles, - rlp::encode(&beneficiary_account_before).to_vec(), - )?; - state_trie_before.insert(sender_nibbles, rlp::encode(&sender_account_before).to_vec())?; - state_trie_before.insert(to_nibbles, rlp::encode(&to_account_before).to_vec())?; - - storage_tries.push((to_hashed, Node::Empty.into())); - - let tries_before = TrieInputs { - state_trie: state_trie_before, - transactions_trie: Node::Empty.into(), - receipts_trie: Node::Empty.into(), - storage_tries, - }; - - let txn = hex!("f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509b"); - - let gas_used = 21_000 + code_gas; - - let block_metadata = BlockMetadata { - block_beneficiary: Address::from(beneficiary), - block_difficulty: 0x20000.into(), - block_number: 1.into(), - block_chain_id: 1.into(), - block_timestamp: 0x03e8.into(), - block_gaslimit: 0xff112233u32.into(), - block_gas_used: gas_used.into(), - block_base_fee: 0xa.into(), - ..Default::default() - }; - - let mut contract_code = HashMap::new(); - contract_code.insert(keccak(vec![]), vec![]); - contract_code.insert(code_hash, code.to_vec()); - - let expected_state_trie_after = { - let beneficiary_account_after = AccountRlp { - nonce: 1.into(), - ..AccountRlp::default() - }; - let sender_account_after = AccountRlp { - balance: sender_account_before.balance - U256::from(gas_used) * U256::from(10), - nonce: 1.into(), - ..AccountRlp::default() - }; - let to_account_after = AccountRlp { - code_hash, - // Storage map: { 1 => 5 } - storage_root: HashedPartialTrie::from(Node::Leaf { - nibbles: Nibbles::from_h256_be(keccak(pad32(1))), - value: vec![5], - }) - .hash(), - ..AccountRlp::default() - }; - - update_beacon_roots_account_storage( - &mut beacon_roots_account_storage, - block_metadata.block_timestamp, - block_metadata.parent_beacon_block_root, - )?; - let beacon_roots_account = - beacon_roots_contract_from_storage(&beacon_roots_account_storage); - - let mut expected_state_trie_after = HashedPartialTrie::from(Node::Empty); - expected_state_trie_after.insert( - beneficiary_nibbles, - rlp::encode(&beneficiary_account_after).to_vec(), - )?; - expected_state_trie_after - .insert(sender_nibbles, rlp::encode(&sender_account_after).to_vec())?; - expected_state_trie_after.insert(to_nibbles, rlp::encode(&to_account_after).to_vec())?; - expected_state_trie_after.insert( - beacon_roots_account_nibbles(), - rlp::encode(&beacon_roots_account).to_vec(), - )?; - expected_state_trie_after.insert( - ger_account_nibbles(), - rlp::encode(&GLOBAL_EXIT_ROOT_ACCOUNT).to_vec(), - )?; - - expected_state_trie_after - }; - - let receipt_0 = LegacyReceiptRlp { - status: true, - cum_gas_used: gas_used.into(), - bloom: vec![0; 256].into(), - logs: vec![], - }; - let mut receipts_trie = HashedPartialTrie::from(Node::Empty); - receipts_trie.insert( - Nibbles::from_str("0x80").unwrap(), - rlp::encode(&receipt_0).to_vec(), - )?; - let transactions_trie: HashedPartialTrie = Node::Leaf { - nibbles: Nibbles::from_str("0x80").unwrap(), - value: txn.to_vec(), - } - .into(); - - let trie_roots_after = TrieRoots { - state_root: expected_state_trie_after.hash(), - transactions_root: transactions_trie.hash(), - receipts_root: receipts_trie.hash(), - }; - let inputs = GenerationInputs { - signed_txn: Some(txn.to_vec()), - withdrawals: vec![], - global_exit_roots: vec![], - tries: tries_before, - trie_roots_after, - contract_code, - checkpoint_state_trie_root: HashedPartialTrie::from(Node::Empty).hash(), - block_metadata, - txn_number_before: 0.into(), - gas_used_before: 0.into(), - gas_used_after: gas_used.into(), - block_hashes: BlockHashes { - prev_hashes: vec![H256::default(); 256], - cur_hash: H256::default(), - }, - }; - - let mut timing = TimingTree::new("prove", log::Level::Debug); - let proof = prove::(&all_stark, &config, inputs, &mut timing, None)?; - timing.filter(Duration::from_millis(100)).print(); - - verify_proof(&all_stark, proof, &config) -} - -fn pad32(byte: u8) -> Vec { - let mut data = vec![0; 31]; - data.push(byte); - data -} From 60db32cb617e233dd571e59ce5366a02288ed07b Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Tue, 16 Jul 2024 01:37:52 +0900 Subject: [PATCH 40/40] `cancun`: cleanup pre-release (#392) * clippy::const_fn * taplo fmt * Kernel asm comments * Group local dependencies --- Cargo.toml | 64 ++++++++++--------- compat/Cargo.toml | 4 +- evm_arithmetization/Cargo.toml | 10 +-- .../src/cpu/columns/general.rs | 12 ++-- .../cpu/kernel/asm/core/create_receipt.asm | 2 +- .../cpu/kernel/asm/core/jumpdest_analysis.asm | 2 +- .../asm/curve/bn254/curve_arithmetic/msm.asm | 10 +-- .../src/cpu/kernel/asm/mpt/insert/insert.asm | 2 +- evm_arithmetization/src/witness/traces.rs | 2 +- mpt_trie/Cargo.toml | 4 +- mpt_trie/src/builder.rs | 2 +- mpt_trie/src/nibbles.rs | 2 +- smt_trie/src/bits.rs | 4 +- trace_decoder/src/decoding.rs | 2 +- zero_bin/common/src/prover_state/circuit.rs | 2 +- zero_bin/common/src/prover_state/cli.rs | 5 +- zero_bin/common/src/prover_state/mod.rs | 2 +- zero_bin/ops/Cargo.toml | 2 +- zero_bin/prover/Cargo.toml | 6 +- zero_bin/rpc/src/native/txn.rs | 2 +- zero_bin/rpc/src/provider.rs | 2 +- zero_bin/rpc/src/retry.rs | 2 +- zero_bin/verifier/Cargo.toml | 2 +- 23 files changed, 75 insertions(+), 72 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3d9a44604..a6583d4ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,19 +1,19 @@ [workspace] members = [ - "compat", - "evm_arithmetization", - "mpt_trie", - "proc_macro", - "proof_gen", - "smt_trie", - "trace_decoder", - "zero_bin/common", - "zero_bin/leader", - "zero_bin/ops", - "zero_bin/prover", - "zero_bin/rpc", - "zero_bin/verifier", - "zero_bin/worker", + "compat", + "evm_arithmetization", + "mpt_trie", + "proc_macro", + "proof_gen", + "smt_trie", + "trace_decoder", + "zero_bin/common", + "zero_bin/leader", + "zero_bin/ops", + "zero_bin/prover", + "zero_bin/rpc", + "zero_bin/verifier", + "zero_bin/worker", ] resolver = "2" @@ -28,18 +28,18 @@ categories = ["cryptography::cryptocurrencies"] [workspace.dependencies] __compat_primitive_types = { version = "0.12.2", package = "primitive-types" } alloy = { git = "https://github.com/alloy-rs/alloy", tag = 'v0.1.1', default-features = false, features = [ - "consensus", - "reqwest", - "json-rpc", - "rlp", - "rpc", - "rpc-client", - "rpc-types-eth", - "rpc-types-trace", - "providers", - "transports", - "transport-http", - "rpc-types-debug", + "consensus", + "reqwest", + "json-rpc", + "rlp", + "rpc", + "rpc-client", + "rpc-types-eth", + "rpc-types-trace", + "providers", + "transports", + "transport-http", + "rpc-types-debug", ] } anyhow = "1.0.86" async-stream = "0.3.5" @@ -59,7 +59,6 @@ enumn = "0.1.13" env_logger = "0.11.3" eth_trie = "0.4.0" ethereum-types = "0.14.1" -evm_arithmetization = { path = "evm_arithmetization", version = "0.2.0" } futures = "0.3.30" hashbrown = "0.14.5" hex = "0.4.3" @@ -72,7 +71,6 @@ itertools = "0.13.0" keccak-hash = "0.10.0" log = "0.4.21" lru = "0.12.3" -mpt_trie = { path = "mpt_trie", version = "0.3.0" } num = "0.4.3" num-bigint = "0.4.5" num-traits = "0.2.19" @@ -84,7 +82,6 @@ paste = "1.0.15" pest = "2.7.10" pest_derive = "2.7.10" pretty_env_logger = "0.5.0" -proof_gen = { path = "proof_gen", version = "0.2.0" } rand = "0.8.5" rand_chacha = "0.3.1" ripemd = "0.1.3" @@ -96,14 +93,12 @@ serde_json = "1.0.118" serde_path_to_error = "0.1.16" serde_with = "3.8.1" sha2 = "0.10.8" -smt_trie = { path = "smt_trie", version = "0.1.0" } static_assertions = "1.1.0" thiserror = "1.0.61" tiny-keccak = "2.0.2" tokio = { version = "1.38.0", features = ["full"] } toml = "0.8.14" tower = "0.4" -trace_decoder = { path = "trace_decoder", version = "0.4.0" } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } u4 = "0.1.0" @@ -111,6 +106,13 @@ uint = "0.9.5" url = "2.5.2" winnow = "0.6.13" +# local dependencies +evm_arithmetization = { path = "evm_arithmetization", version = "0.2.0" } +mpt_trie = { path = "mpt_trie", version = "0.3.0" } +proof_gen = { path = "proof_gen", version = "0.2.0" } +smt_trie = { path = "smt_trie", version = "0.1.0" } +trace_decoder = { path = "trace_decoder", version = "0.4.0" } + # zero-bin related dependencies ops = { path = "zero_bin/ops" } prover = { path = "zero_bin/prover" } diff --git a/compat/Cargo.toml b/compat/Cargo.toml index 69ff77eb7..f52e57c45 100644 --- a/compat/Cargo.toml +++ b/compat/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "compat" version = "0.1.0" -publish = false # TODO(): https://github.com/0xPolygonZero/zk_evm/issues/314 find a better place for this +publish = false # TODO(): https://github.com/0xPolygonZero/zk_evm/issues/314 find a better place for this edition.workspace = true license.workspace = true repository.workspace = true @@ -10,5 +10,5 @@ keywords.workspace = true categories.workspace = true [dependencies] -alloy = {workspace = true } +alloy = { workspace = true } __compat_primitive_types = { workspace = true } diff --git a/evm_arithmetization/Cargo.toml b/evm_arithmetization/Cargo.toml index a08ba6928..a1b8e21f1 100644 --- a/evm_arithmetization/Cargo.toml +++ b/evm_arithmetization/Cargo.toml @@ -3,8 +3,8 @@ name = "evm_arithmetization" description = "Implementation of STARKs for the Ethereum Virtual Machine" version = "0.2.0" authors = [ - "Daniel Lubarov ", - "William Borgeaud ", + "Daniel Lubarov ", + "William Borgeaud ", ] readme = "README.md" categories = ["cryptography"] @@ -57,9 +57,9 @@ ripemd = { workspace = true } default = ["parallel"] asmtools = ["hex"] parallel = [ - "plonky2/parallel", - "plonky2_maybe_rayon/parallel", - "starky/parallel", + "plonky2/parallel", + "plonky2_maybe_rayon/parallel", + "starky/parallel", ] [[bin]] diff --git a/evm_arithmetization/src/cpu/columns/general.rs b/evm_arithmetization/src/cpu/columns/general.rs index 125e7e18f..9ce620078 100644 --- a/evm_arithmetization/src/cpu/columns/general.rs +++ b/evm_arithmetization/src/cpu/columns/general.rs @@ -21,7 +21,7 @@ impl CpuGeneralColumnsView { /// View of the columns used for exceptions: they are the exception code /// bits. SAFETY: Each view is a valid interpretation of the underlying /// array. - pub(crate) fn exception(&self) -> &CpuExceptionView { + pub(crate) const fn exception(&self) -> &CpuExceptionView { unsafe { &self.exception } } @@ -34,7 +34,7 @@ impl CpuGeneralColumnsView { /// View of the columns required for logic operations. /// SAFETY: Each view is a valid interpretation of the underlying array. - pub(crate) fn logic(&self) -> &CpuLogicView { + pub(crate) const fn logic(&self) -> &CpuLogicView { unsafe { &self.logic } } @@ -46,7 +46,7 @@ impl CpuGeneralColumnsView { /// View of the columns required for jump operations. /// SAFETY: Each view is a valid interpretation of the underlying array. - pub(crate) fn jumps(&self) -> &CpuJumpsView { + pub(crate) const fn jumps(&self) -> &CpuJumpsView { unsafe { &self.jumps } } @@ -58,7 +58,7 @@ impl CpuGeneralColumnsView { /// View of the columns required for shift operations. /// SAFETY: Each view is a valid interpretation of the underlying array. - pub(crate) fn shift(&self) -> &CpuShiftView { + pub(crate) const fn shift(&self) -> &CpuShiftView { unsafe { &self.shift } } @@ -70,7 +70,7 @@ impl CpuGeneralColumnsView { /// View of the columns required for the stack top. /// SAFETY: Each view is a valid interpretation of the underlying array. - pub(crate) fn stack(&self) -> &CpuStackView { + pub(crate) const fn stack(&self) -> &CpuStackView { unsafe { &self.stack } } @@ -82,7 +82,7 @@ impl CpuGeneralColumnsView { /// View of the columns required for the push operation. /// SAFETY: Each view is a valid interpretation of the underlying array. - pub(crate) fn push(&self) -> &CpuPushView { + pub(crate) const fn push(&self) -> &CpuPushView { unsafe { &self.push } } diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/create_receipt.asm b/evm_arithmetization/src/cpu/kernel/asm/core/create_receipt.asm index fc3632cc6..742c4784c 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/create_receipt.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/create_receipt.asm @@ -129,7 +129,7 @@ process_receipt_logs_loop: MLOAD_GENERAL %append_to_trie_data // stack: addr_ptr, i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, num_nibbles, retdest - //Write num_topics. + // Write num_topics. %increment // stack: num_topics_ptr, i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, num_nibbles, retdest DUP1 diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/jumpdest_analysis.asm b/evm_arithmetization/src/cpu/kernel/asm/core/jumpdest_analysis.asm index 49b59fe63..2d7c53ee2 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/jumpdest_analysis.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/jumpdest_analysis.asm @@ -134,7 +134,7 @@ global write_table_if_jumpdest: %jump_neq_const(0x5b, return) - //stack: jumpdest, ctx, proof_prefix_addr, retdest + // stack: jumpdest, ctx, proof_prefix_addr, retdest SWAP2 DUP1 // stack: proof_prefix_addr, proof_prefix_addr, ctx, jumpdest ISZERO diff --git a/evm_arithmetization/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/msm.asm b/evm_arithmetization/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/msm.asm index d5b97312b..af350dd8f 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/msm.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/curve/bn254/curve_arithmetic/msm.asm @@ -10,7 +10,7 @@ global bn_msm_loop: DUP1 %jumpi(bn_msm_loop_add_a_nonzero) POP msm_loop_add_b: - //stack: accx, accy, i, retdest + // stack: accx, accy, i, retdest DUP3 %bn_mload_wnaf_b // stack: w, accx, accy, i, retdest DUP1 %jumpi(bn_msm_loop_add_b_nonzero) @@ -20,7 +20,7 @@ msm_loop_contd: // TODO: the GLV scalars for the BN curve are 127-bit, so could use 127 here. But this would require modifying `wnaf.asm`. Not sure it's worth it... %eq_const(129) %jumpi(msm_end) %increment - //stack: i+1, accx, accy, retdest + // stack: i+1, accx, accy, retdest %stack (i, accx, accy, retdest) -> (accx, accy, bn_msm_loop, i, retdest) %jump(bn_double) @@ -54,9 +54,9 @@ bn_msm_loop_add_b_nonzero: // stack: w DUP1 %mload_current(@SEGMENT_BN_TABLE_Q) - //stack: Gy, w + // stack: Gy, w SWAP1 %decrement %mload_current(@SEGMENT_BN_TABLE_Q) - //stack: Gx, Gy + // stack: Gx, Gy %endmacro %macro bn_mload_point_b @@ -67,7 +67,7 @@ bn_msm_loop_add_b_nonzero: %stack (bneg, Gy, w) -> (@BN_BASE, Gy, bneg, bneg, Gy, w) SUB SWAP1 ISZERO MUL SWAP2 MUL ADD SWAP1 %decrement %mload_current(@SEGMENT_BN_TABLE_Q) - //stack: Gx, Gy + // stack: Gx, Gy PUSH @BN_GLV_BETA MULFP254 %endmacro diff --git a/evm_arithmetization/src/cpu/kernel/asm/mpt/insert/insert.asm b/evm_arithmetization/src/cpu/kernel/asm/mpt/insert/insert.asm index 33eadd755..83a12b4b0 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/mpt/insert/insert.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/mpt/insert/insert.asm @@ -42,7 +42,7 @@ global mpt_insert_branch: // stack: node_type, node_payload_ptr, num_nibbles, key, value_ptr, retdest POP - //stack: node_payload_ptr, num_nibbles, key, value_ptr, retdest + // stack: node_payload_ptr, num_nibbles, key, value_ptr, retdest // At this point, we branch based on whether the key terminates with this branch node. // stack: node_payload_ptr, num_nibbles, key, value_ptr, retdest diff --git a/evm_arithmetization/src/witness/traces.rs b/evm_arithmetization/src/witness/traces.rs index a8cdfa454..e70f36824 100644 --- a/evm_arithmetization/src/witness/traces.rs +++ b/evm_arithmetization/src/witness/traces.rs @@ -37,7 +37,7 @@ pub(crate) struct Traces { } impl Traces { - pub(crate) fn new() -> Self { + pub(crate) const fn new() -> Self { Traces { arithmetic_ops: vec![], byte_packing_ops: vec![], diff --git a/mpt_trie/Cargo.toml b/mpt_trie/Cargo.toml index 46a14df26..d4aa1571b 100644 --- a/mpt_trie/Cargo.toml +++ b/mpt_trie/Cargo.toml @@ -9,9 +9,7 @@ license.workspace = true repository.workspace = true homepage.workspace = true -exclude = [ - "test_data/*" -] +exclude = ["test_data/*"] [dependencies] bytes = { workspace = true } diff --git a/mpt_trie/src/builder.rs b/mpt_trie/src/builder.rs index 1e0e06bd3..48475f4d2 100644 --- a/mpt_trie/src/builder.rs +++ b/mpt_trie/src/builder.rs @@ -28,7 +28,7 @@ pub struct PartialTrieBuilder { impl PartialTrieBuilder { /// Creates a new `PartialTrieBuilder` with the given root and nodes. - pub fn new(root: H256, nodes: HashMap>) -> Self { + pub const fn new(root: H256, nodes: HashMap>) -> Self { PartialTrieBuilder { root, nodes, diff --git a/mpt_trie/src/nibbles.rs b/mpt_trie/src/nibbles.rs index a417dca4e..1079aa6d2 100644 --- a/mpt_trie/src/nibbles.rs +++ b/mpt_trie/src/nibbles.rs @@ -912,7 +912,7 @@ impl Nibbles { /// Returns a slice of the internal bytes of packed nibbles. /// Only the relevant bytes (up to `count` nibbles) are considered valid. - pub fn as_byte_slice(&self) -> &[u8] { + pub const fn as_byte_slice(&self) -> &[u8] { // Calculate the number of full bytes needed to cover 'count' nibbles let bytes_needed = (self.count + 1) / 2; // each nibble is half a byte diff --git a/smt_trie/src/bits.rs b/smt_trie/src/bits.rs index 4d2d2ed91..0fbcac0e1 100644 --- a/smt_trie/src/bits.rs +++ b/smt_trie/src/bits.rs @@ -44,14 +44,14 @@ impl Add for Bits { } impl Bits { - pub fn empty() -> Self { + pub const fn empty() -> Self { Bits { count: 0, packed: U256::zero(), } } - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.count == 0 } diff --git a/trace_decoder/src/decoding.rs b/trace_decoder/src/decoding.rs index 29b439dc1..98a0d8857 100644 --- a/trace_decoder/src/decoding.rs +++ b/trace_decoder/src/decoding.rs @@ -89,7 +89,7 @@ impl std::error::Error for TraceParsingError {} impl TraceParsingError { /// Function to create a new TraceParsingError with mandatory fields - fn new(reason: TraceParsingErrorReason) -> Self { + const fn new(reason: TraceParsingErrorReason) -> Self { Self { block_num: None, block_chain_id: None, diff --git a/zero_bin/common/src/prover_state/circuit.rs b/zero_bin/common/src/prover_state/circuit.rs index 5eaf2da7a..f23d6ebe9 100644 --- a/zero_bin/common/src/prover_state/circuit.rs +++ b/zero_bin/common/src/prover_state/circuit.rs @@ -200,7 +200,7 @@ impl CircuitConfig { } /// Get all circuits specified in the config. - pub fn as_degree_bits_ranges(&self) -> &[Range; NUM_TABLES] { + pub const fn as_degree_bits_ranges(&self) -> &[Range; NUM_TABLES] { &self.circuits } diff --git a/zero_bin/common/src/prover_state/cli.rs b/zero_bin/common/src/prover_state/cli.rs index 5a78a1863..5355d7f4e 100644 --- a/zero_bin/common/src/prover_state/cli.rs +++ b/zero_bin/common/src/prover_state/cli.rs @@ -33,7 +33,10 @@ pub enum CircuitPersistence { } impl CircuitPersistence { - pub fn with_load_strategy(self, load_strategy: TableLoadStrategy) -> super::CircuitPersistence { + pub const fn with_load_strategy( + self, + load_strategy: TableLoadStrategy, + ) -> super::CircuitPersistence { match self { CircuitPersistence::None => super::CircuitPersistence::None, CircuitPersistence::Disk => super::CircuitPersistence::Disk(load_strategy), diff --git a/zero_bin/common/src/prover_state/mod.rs b/zero_bin/common/src/prover_state/mod.rs index 44463b53d..aacd7c12e 100644 --- a/zero_bin/common/src/prover_state/mod.rs +++ b/zero_bin/common/src/prover_state/mod.rs @@ -125,7 +125,7 @@ pub struct ProverStateManager { } impl ProverStateManager { - pub fn with_load_strategy(self, load_strategy: TableLoadStrategy) -> Self { + pub const fn with_load_strategy(self, load_strategy: TableLoadStrategy) -> Self { match self.persistence { CircuitPersistence::None => self, CircuitPersistence::Disk(_) => Self { diff --git a/zero_bin/ops/Cargo.toml b/zero_bin/ops/Cargo.toml index e1b5dc9e1..05cfecb2e 100644 --- a/zero_bin/ops/Cargo.toml +++ b/zero_bin/ops/Cargo.toml @@ -16,7 +16,7 @@ proof_gen = { workspace = true } tracing = { workspace = true } keccak-hash = { workspace = true } -zero_bin_common ={ path = "../common" } +zero_bin_common = { path = "../common" } [features] default = [] diff --git a/zero_bin/prover/Cargo.toml b/zero_bin/prover/Cargo.toml index f57b2a3d3..d1eaab7b3 100644 --- a/zero_bin/prover/Cargo.toml +++ b/zero_bin/prover/Cargo.toml @@ -17,11 +17,11 @@ paladin-core = { workspace = true } anyhow = { workspace = true } futures = { workspace = true } alloy.workspace = true -tokio = {workspace = true} -serde_json = {workspace = true} +tokio = { workspace = true } +serde_json = { workspace = true } ruint = { workspace = true, features = ["num-traits", "primitive-types"] } ops = { workspace = true } -zero_bin_common ={ workspace = true } +zero_bin_common = { workspace = true } num-traits = { workspace = true } [features] diff --git a/zero_bin/rpc/src/native/txn.rs b/zero_bin/rpc/src/native/txn.rs index 61bcf5f4a..94d280968 100644 --- a/zero_bin/rpc/src/native/txn.rs +++ b/zero_bin/rpc/src/native/txn.rs @@ -278,7 +278,7 @@ async fn process_code( } /// Processes the self destruct for the given account state. -fn process_self_destruct( +const fn process_self_destruct( post_state: Option<&AccountState>, pre_state: Option<&AccountState>, ) -> Option { diff --git a/zero_bin/rpc/src/provider.rs b/zero_bin/rpc/src/provider.rs index fc782ff43..f0c6a2691 100644 --- a/zero_bin/rpc/src/provider.rs +++ b/zero_bin/rpc/src/provider.rs @@ -39,7 +39,7 @@ where &mut self.provider } - pub fn as_provider(&self) -> &ProviderT { + pub const fn as_provider(&self) -> &ProviderT { &self.provider } diff --git a/zero_bin/rpc/src/retry.rs b/zero_bin/rpc/src/retry.rs index 0c6db2291..2fe81cb60 100644 --- a/zero_bin/rpc/src/retry.rs +++ b/zero_bin/rpc/src/retry.rs @@ -32,7 +32,7 @@ impl Clone for RetryPolicy { } impl RetryPolicy { - pub fn new(backoff: tokio::time::Duration, max_retries: u32) -> Self { + pub const fn new(backoff: tokio::time::Duration, max_retries: u32) -> Self { Self { backoff, retries: 0, diff --git a/zero_bin/verifier/Cargo.toml b/zero_bin/verifier/Cargo.toml index 40dd297b7..39968b4a6 100644 --- a/zero_bin/verifier/Cargo.toml +++ b/zero_bin/verifier/Cargo.toml @@ -15,4 +15,4 @@ serde_path_to_error = { workspace = true } proof_gen = { workspace = true } # Local dependencies -zero_bin_common ={ path = "../common" } +zero_bin_common = { path = "../common" }