diff --git a/.gitmodules b/.gitmodules index b4cc2e835..d1988de74 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "sdk/packages/simplex/proto/mpcvaultapis"] path = sdk/packages/simplex/proto/mpcvaultapis url = https://github.com/mpcvault/mpcvaultapis.git +[submodule "evm/lib/gnark-apk-proofs"] + path = evm/lib/gnark-apk-proofs + url = https://github.com/polytope-labs/gnark-apk-proofs diff --git a/Cargo.lock b/Cargo.lock index bf7c08673..40130f703 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -951,6 +951,37 @@ version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +[[package]] +name = "apk-beefy" +version = "0.1.0" +dependencies = [ + "alloy-primitives 1.5.7", + "alloy-sol-types 1.5.7", + "anyhow", + "ark-bls12-381 0.4.0", + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-serialize 0.5.0", + "async-trait", + "beefy-prover", + "beefy-verifier", + "beefy-verifier-primitives", + "gnark-apk-prover", + "gnark-plonk-verifier", + "hex", + "ismp", + "ismp-abi", + "parity-scale-codec", + "polkadot-sdk", + "primitive-types 0.13.1", + "serde_json", + "sp-consensus-beefy", + "subxt 0.42.1", + "subxt-utils", + "tokio", +] + [[package]] name = "approx" version = "0.5.1" @@ -2898,7 +2929,12 @@ dependencies = [ name = "beefy-prover" version = "0.1.1" dependencies = [ + "alloy-primitives 1.5.7", "anyhow", + "ark-bls12-381 0.4.0", + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", "beefy-verifier-primitives", "ckb-merkle-mountain-range", "derive_more 1.0.0", @@ -2906,6 +2942,7 @@ dependencies = [ "hex", "hex-literal 0.4.1", "indicatif 0.18.4", + "ismp-abi", "log", "parity-scale-codec", "polkadot-sdk", @@ -2916,18 +2953,29 @@ dependencies = [ "subxt-utils", "tokio", "tokio-stream", + "w3f-bls", ] [[package]] name = "beefy-verifier" version = "0.1.0" dependencies = [ + "alloy-primitives 1.5.7", "alloy-sol-types 1.5.7", "anyhow", + "ark-bls12-381 0.4.0", + "ark-bls12-381 0.5.0", + "ark-ec 0.4.2", + "ark-ec 0.5.0", + "ark-ff 0.4.2", + "ark-ff 0.5.0", + "ark-serialize 0.4.2", + "ark-serialize 0.5.0", "beefy-prover", "beefy-verifier-primitives", "ckb-merkle-mountain-range", "futures", + "gnark-plonk-verifier", "hex", "hex-literal 0.4.1", "ismp", @@ -2937,12 +2985,15 @@ dependencies = [ "polkadot-sdk", "primitive-types 0.13.1", "rs_merkle", + "serde_json", + "sha2 0.10.9", "sp1-verifier 6.1.0", "subxt 0.42.1", "subxt-core 0.42.1", "subxt-utils", "thiserror 2.0.18", "tokio", + "w3f-bls", ] [[package]] @@ -7791,27 +7842,13 @@ dependencies = [ "sp-runtime", ] -[[package]] -name = "frame-decode" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7cb8796f93fa038f979a014234d632e9688a120e745f936e2635123c77537f7" -dependencies = [ - "frame-metadata 21.0.0", - "parity-scale-codec", - "scale-decode", - "scale-info", - "scale-type-resolver", - "sp-crypto-hashing", -] - [[package]] name = "frame-decode" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e56c0e51972d7b26ff76966c4d0f2307030df9daa5ce0885149ece1ab7ca5ad" dependencies = [ - "frame-metadata 23.0.1", + "frame-metadata", "parity-scale-codec", "scale-decode", "scale-info", @@ -7825,7 +7862,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c470df86cf28818dd3cd2fc4667b80dbefe2236c722c3dc1d09e7c6c82d6dfcd" dependencies = [ - "frame-metadata 23.0.1", + "frame-metadata", "parity-scale-codec", "scale-decode", "scale-encode", @@ -7884,28 +7921,6 @@ dependencies = [ "sp-tracing", ] -[[package]] -name = "frame-metadata" -version = "20.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26de808fa6461f2485dc51811aefed108850064994fb4a62b3ac21ffa62ac8df" -dependencies = [ - "cfg-if", - "parity-scale-codec", - "scale-info", -] - -[[package]] -name = "frame-metadata" -version = "21.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20dfd1d7eae1d94e32e869e2fb272d81f52dd8db57820a373adb83ea24d7d862" -dependencies = [ - "cfg-if", - "parity-scale-codec", - "scale-info", -] - [[package]] name = "frame-metadata" version = "23.0.1" @@ -7963,7 +7978,7 @@ dependencies = [ "derive-where", "docify", "environmental", - "frame-metadata 23.0.1", + "frame-metadata", "frame-support-procedural", "impl-trait-for-tuples", "k256", @@ -8383,6 +8398,7 @@ dependencies = [ "log", "mmr-primitives", "pallet-bandwidth", + "pallet-beefy-apk-digest", "pallet-beefy-consensus-proofs", "pallet-call-decompressor", "pallet-collator-manager", @@ -8646,6 +8662,39 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "gnark-apk-ffi" +version = "0.1.0" +source = "git+https://github.com/polytope-labs/gnark-apk-proofs?rev=1c15d06d8180906b16201c5206711808b80742ef#1c15d06d8180906b16201c5206711808b80742ef" + +[[package]] +name = "gnark-apk-prover" +version = "0.1.0" +source = "git+https://github.com/polytope-labs/gnark-apk-proofs?rev=1c15d06d8180906b16201c5206711808b80742ef#1c15d06d8180906b16201c5206711808b80742ef" +dependencies = [ + "ark-bls12-381 0.5.0", + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "gnark-apk-ffi", + "gnark-plonk-verifier", + "thiserror 2.0.18", +] + +[[package]] +name = "gnark-plonk-verifier" +version = "0.1.0" +source = "git+https://github.com/polytope-labs/gnark-apk-proofs?rev=1c15d06d8180906b16201c5206711808b80742ef#1c15d06d8180906b16201c5206711808b80742ef" +dependencies = [ + "ark-bls12-381 0.5.0", + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "once_cell", + "sha2 0.10.9", + "sha3 0.10.8", + "thiserror 2.0.18", +] + [[package]] name = "governor" version = "0.6.3" @@ -12365,7 +12414,7 @@ checksum = "b3e3e3f549d27d2dc054372f320ddf68045a833fab490563ff70d4cf1b9d91ea" dependencies = [ "array-bytes 9.3.0", "blake3", - "frame-metadata 23.0.1", + "frame-metadata", "parity-scale-codec", "scale-decode", "scale-info", @@ -14666,6 +14715,23 @@ dependencies = [ "sp-staking", ] +[[package]] +name = "pallet-beefy-apk-digest" +version = "0.1.0" +dependencies = [ + "ark-bls12-381 0.5.0", + "ark-serialize 0.5.0", + "beefy-verifier-primitives", + "cumulus-pallet-parachain-system", + "gnark-plonk-verifier", + "hex", + "hex-literal 0.4.1", + "log", + "parity-scale-codec", + "polkadot-sdk", + "scale-info", +] + [[package]] name = "pallet-beefy-consensus-proofs" version = "0.1.0" @@ -18339,7 +18405,7 @@ dependencies = [ "docify", "frame-benchmarking", "frame-benchmarking-cli", - "frame-metadata 23.0.1", + "frame-metadata", "frame-support", "frame-system-rpc-runtime-api", "frame-try-runtime", @@ -24456,7 +24522,7 @@ dependencies = [ "sp-core", "substrate-state-machine", "subxt 0.42.1", - "subxt-signer 0.41.0", + "subxt-signer 0.42.1", "subxt-utils", "tesseract-evm", "tesseract-fisherman", @@ -25693,7 +25759,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d4e7855b9d8b356ffd264ed089bc1063675880742bb4535f206831a87fc725e" dependencies = [ "derive-where", - "frame-metadata 23.0.1", + "frame-metadata", "parity-scale-codec", "scale-info", ] @@ -27415,7 +27481,7 @@ dependencies = [ "cargo_metadata 0.15.4", "console 0.15.11", "filetime", - "frame-metadata 23.0.1", + "frame-metadata", "jobserver", "merkleized-metadata", "parity-scale-codec", @@ -27471,7 +27537,7 @@ dependencies = [ "async-trait", "derive-where", "either", - "frame-metadata 23.0.1", + "frame-metadata", "futures", "hex", "jsonrpsee 0.24.11", @@ -27508,7 +27574,7 @@ dependencies = [ "async-trait", "derive-where", "either", - "frame-metadata 23.0.1", + "frame-metadata", "futures", "hex", "parity-scale-codec", @@ -27569,36 +27635,6 @@ dependencies = [ "thiserror 2.0.18", ] -[[package]] -name = "subxt-core" -version = "0.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66ef00be9d64885ec94e478a58e4e39d222024b20013ae7df4fc6ece545391aa" -dependencies = [ - "base58", - "blake2 0.10.6", - "derive-where", - "frame-decode 0.7.1", - "frame-metadata 20.0.0", - "hashbrown 0.14.5", - "hex", - "impl-serde 0.5.0", - "keccak-hash", - "parity-scale-codec", - "primitive-types 0.13.1", - "scale-bits", - "scale-decode", - "scale-encode", - "scale-info", - "scale-value", - "serde", - "serde_json", - "sp-crypto-hashing", - "subxt-metadata 0.41.0", - "thiserror 2.0.18", - "tracing", -] - [[package]] name = "subxt-core" version = "0.42.1" @@ -27609,7 +27645,7 @@ dependencies = [ "blake2 0.10.6", "derive-where", "frame-decode 0.8.3", - "frame-metadata 23.0.1", + "frame-metadata", "hashbrown 0.14.5", "hex", "impl-serde 0.5.0", @@ -27639,7 +27675,7 @@ dependencies = [ "blake2 0.10.6", "derive-where", "frame-decode 0.9.0", - "frame-metadata 23.0.1", + "frame-metadata", "hashbrown 0.14.5", "hex", "impl-serde 0.5.0", @@ -27737,21 +27773,6 @@ dependencies = [ "syn 2.0.116", ] -[[package]] -name = "subxt-metadata" -version = "0.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff4591673600c4388e21305788282414d26c791b4dee21b7cb0b19c10076f98" -dependencies = [ - "frame-decode 0.7.1", - "frame-metadata 20.0.0", - "hashbrown 0.14.5", - "parity-scale-codec", - "scale-info", - "sp-crypto-hashing", - "thiserror 2.0.18", -] - [[package]] name = "subxt-metadata" version = "0.42.1" @@ -27759,7 +27780,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "243990ca4e0cdb74ef7458f1d5070a1bd5144d744cc146f23a32ab56d23e1db7" dependencies = [ "frame-decode 0.8.3", - "frame-metadata 23.0.1", + "frame-metadata", "hashbrown 0.14.5", "parity-scale-codec", "scale-info", @@ -27774,7 +27795,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b2f2a52d97d7539febc0006d6988081150b1c1a3e4a357ca02ab5cdb34072bc" dependencies = [ "frame-decode 0.9.0", - "frame-metadata 23.0.1", + "frame-metadata", "hashbrown 0.14.5", "parity-scale-codec", "scale-info", @@ -27790,7 +27811,7 @@ checksum = "55313e3652f5360b5ed878bfe1d62fe181ecb8c130c81278ab89d1580f89a7ed" dependencies = [ "derive-where", "finito", - "frame-metadata 23.0.1", + "frame-metadata", "futures", "getrandom 0.2.17", "hex", @@ -27817,7 +27838,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dec54130c797530e6aa6a52e8ba9f95fd296d19da2f9f3e23ed5353a83573f74" dependencies = [ "derive-where", - "frame-metadata 23.0.1", + "frame-metadata", "futures", "hex", "impl-serde 0.5.0", @@ -27835,9 +27856,9 @@ dependencies = [ [[package]] name = "subxt-signer" -version = "0.41.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a2370298a210ed1df26152db7209a85e0ed8cfbce035309c3b37f7b61755377" +checksum = "b58aeda7bebddedbef69ac55ae592fb9eef499927b50d42c43862d1664b5e5b3" dependencies = [ "base64 0.22.1", "bip39", @@ -27856,7 +27877,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "sp-crypto-hashing", - "subxt-core 0.41.0", + "subxt-core 0.42.1", "thiserror 2.0.18", "zeroize", ] @@ -28467,6 +28488,7 @@ dependencies = [ "alloy-primitives 1.5.7", "alloy-sol-types 1.5.7", "anyhow", + "apk-beefy", "async-stream", "async-trait", "beefy-prover", diff --git a/Cargo.toml b/Cargo.toml index 9d822dc51..a3a880928 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,6 +73,7 @@ members = [ "modules/ismp/state-machines/pharos", "modules/pallets/consensus-incentives", "modules/pallets/messaging-incentives", + "modules/pallets/beefy-apk-digest", "modules/pallets/beefy-consensus-proofs", # evm stuff @@ -117,6 +118,7 @@ members = [ "tesseract/consensus/grandpa", "tesseract/consensus/integration-tests", "tesseract/consensus/beefy", + "tesseract/consensus/beefy/apk", "tesseract/consensus/beefy/zk", "tesseract/prover", "tesseract/consensus/admin-relayer", @@ -316,6 +318,7 @@ pallet-ismp-host-executive = { path = "modules/pallets/host-executive", default- pallet-call-decompressor = { path = "modules/pallets/call-decompressor", default-features = false } pallet-consensus-incentives = { path = "modules/pallets/consensus-incentives", default-features = false } pallet-messaging-incentives = { path = "modules/pallets/messaging-incentives", default-features = false } +pallet-beefy-apk-digest = { path = "./modules/pallets/beefy-apk-digest", default-features = false } pallet-beefy-consensus-proofs = { path = "modules/pallets/beefy-consensus-proofs", default-features = false } pallet-collator-manager = { path = "modules/pallets/collator-manager", default-features = false } pallet-state-coprocessor = { path = "modules/pallets/state-coprocessor", default-features = false } @@ -354,7 +357,7 @@ tesseract-config = { path = "tesseract/messaging/config" } arb-host = { path = "tesseract/consensus/arb-host" } op-host = { path = "tesseract/consensus/op-host" } evm-host = { path = "tesseract/consensus/evm-host" } -tesseract-beefy = { path = "tesseract/consensus/beefy" } +tesseract-beefy = { path = "tesseract/consensus/beefy", default-features = false } tesseract-bsc = { path = "tesseract/consensus/bsc" } tesseract-integration-tests = { path = "tesseract/consensus/integration-tests" } tesseract-sync-committee = { path = "tesseract/consensus/sync-committee" } @@ -364,7 +367,7 @@ tesseract-polygon = { path = "tesseract/consensus/polygon" } tesseract-tendermint = { path = "tesseract/consensus/tendermint" } tesseract-pharos = { path = "tesseract/consensus/pharos" } tesseract-parachain = { path = "tesseract/consensus/parachain" } -tesseract-consensus-config = { path = "tesseract/consensus/config" } +tesseract-consensus-config = { path = "tesseract/consensus/config", default-features = false } tesseract = { path = "tesseract/relayer" } [workspace.dependencies.rs_merkle] diff --git a/evm/foundry.toml b/evm/foundry.toml index da91054c5..a5cac4a9f 100644 --- a/evm/foundry.toml +++ b/evm/foundry.toml @@ -10,6 +10,13 @@ evm_version = "cancun" via-ir = true fs_permissions = [{ access = "read-write", path = "./"}] +# EIP-2537 (BLS12-381 precompiles) only exists from Prague, and the default profile is on cancun. +# Kept separate so the deployed contracts' bytecode is unaffected. SimplexPaymaster is skipped +# because its @openzeppelin/community-contracts dependency is not vendored. +[profile.bls] +evm_version = "prague" +skip = ["src/utils/SimplexPaymaster.sol"] + [profile.ci] [lint] diff --git a/evm/lib/gnark-apk-proofs b/evm/lib/gnark-apk-proofs new file mode 160000 index 000000000..a341f84a5 --- /dev/null +++ b/evm/lib/gnark-apk-proofs @@ -0,0 +1 @@ +Subproject commit a341f84a5aa672dd3d8f4eb1276446ede1c9671e diff --git a/evm/remappings.txt b/evm/remappings.txt index 016b0a90d..576c9cd69 100644 --- a/evm/remappings.txt +++ b/evm/remappings.txt @@ -4,4 +4,5 @@ @uniswap/=node_modules/@uniswap/ stringutils/=lib/solidity-stringutils/src/ @sp1-contracts/=lib/sp1-contracts/contracts/src/ +@gnark-apk-proofs/=lib/gnark-apk-proofs/solidity/contracts/ forge-std/=node_modules/forge-std/src/ diff --git a/evm/rust/abi/BlsBeefy.json b/evm/rust/abi/BlsBeefy.json new file mode 100644 index 000000000..ffebd9143 --- /dev/null +++ b/evm/rust/abi/BlsBeefy.json @@ -0,0 +1 @@ +{"abi":[{"type":"constructor","inputs":[{"name":"apkProof","type":"address","internalType":"address"},{"name":"digestParaId","type":"uint32","internalType":"uint32"}],"stateMutability":"nonpayable"},{"type":"function","name":"MMR_ROOT_PAYLOAD_ID","inputs":[],"outputs":[{"name":"","type":"bytes2","internalType":"bytes2"}],"stateMutability":"view"},{"type":"function","name":"_apk","inputs":[],"outputs":[{"name":"","type":"address","internalType":"contract IApkProof"}],"stateMutability":"view"},{"type":"function","name":"_digestParaId","inputs":[],"outputs":[{"name":"","type":"uint32","internalType":"uint32"}],"stateMutability":"view"},{"type":"function","name":"noOp","inputs":[{"name":"s","type":"tuple","internalType":"struct BeefyConsensusState","components":[{"name":"latestHeight","type":"uint256","internalType":"uint256"},{"name":"beefyActivationBlock","type":"uint256","internalType":"uint256"},{"name":"currentAuthoritySet","type":"tuple","internalType":"struct AuthoritySet","components":[{"name":"id","type":"uint256","internalType":"uint256"},{"name":"len","type":"uint256","internalType":"uint256"},{"name":"blsPoseidonHash","type":"uint256","internalType":"uint256"},{"name":"ecdsaMerkleRoot","type":"bytes32","internalType":"bytes32"}]},{"name":"nextAuthoritySet","type":"tuple","internalType":"struct AuthoritySet","components":[{"name":"id","type":"uint256","internalType":"uint256"},{"name":"len","type":"uint256","internalType":"uint256"},{"name":"blsPoseidonHash","type":"uint256","internalType":"uint256"},{"name":"ecdsaMerkleRoot","type":"bytes32","internalType":"bytes32"}]}]},{"name":"p","type":"tuple","internalType":"struct BlsApkBeefyConsensusProof","components":[{"name":"relay","type":"tuple","internalType":"struct BlsApkRelayChainProof","components":[{"name":"commitment","type":"tuple","internalType":"struct Commitment","components":[{"name":"payload","type":"tuple[]","internalType":"struct Payload[]","components":[{"name":"id","type":"bytes2","internalType":"bytes2"},{"name":"data","type":"bytes","internalType":"bytes"}]},{"name":"blockNumber","type":"uint32","internalType":"uint32"},{"name":"validatorSetId","type":"uint64","internalType":"uint64"}]},{"name":"bitlist","type":"uint256[5]","internalType":"uint256[5]"},{"name":"apk","type":"bytes32[3]","internalType":"bytes32[3]"},{"name":"apk2","type":"bytes32[6]","internalType":"bytes32[6]"},{"name":"apkProof","type":"bytes","internalType":"bytes"},{"name":"signature","type":"bytes32[3]","internalType":"bytes32[3]"},{"name":"latestMmrLeaf","type":"tuple","internalType":"struct BeefyMmrLeaf","components":[{"name":"version","type":"uint8","internalType":"uint8"},{"name":"parentNumber","type":"uint32","internalType":"uint32"},{"name":"parentHash","type":"bytes32","internalType":"bytes32"},{"name":"nextAuthoritySet","type":"tuple","internalType":"struct AuthoritySetCommitment","components":[{"name":"id","type":"uint64","internalType":"uint64"},{"name":"len","type":"uint32","internalType":"uint32"},{"name":"root","type":"bytes32","internalType":"bytes32"}]},{"name":"extra","type":"bytes32","internalType":"bytes32"},{"name":"leafIndex","type":"uint256","internalType":"uint256"}]},{"name":"mmrProof","type":"bytes32[]","internalType":"bytes32[]"}]},{"name":"parachain","type":"tuple","internalType":"struct ParachainProof","components":[{"name":"parachains","type":"tuple[]","internalType":"struct Parachain[]","components":[{"name":"index","type":"uint256","internalType":"uint256"},{"name":"id","type":"uint256","internalType":"uint256"},{"name":"header","type":"bytes","internalType":"bytes"}]},{"name":"proof","type":"bytes32[]","internalType":"bytes32[]"},{"name":"leafCount","type":"uint256","internalType":"uint256"}]}]}],"outputs":[],"stateMutability":"pure"},{"type":"function","name":"supportsInterface","inputs":[{"name":"interfaceId","type":"bytes4","internalType":"bytes4"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"verify","inputs":[{"name":"previousState","type":"bytes","internalType":"bytes"},{"name":"proof","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"","type":"bytes","internalType":"bytes"},{"name":"","type":"tuple[]","internalType":"struct IntermediateState[]","components":[{"name":"stateMachineId","type":"uint256","internalType":"uint256"},{"name":"height","type":"uint256","internalType":"uint256"},{"name":"commitment","type":"tuple","internalType":"struct StateCommitment","components":[{"name":"timestamp","type":"uint256","internalType":"uint256"},{"name":"overlayRoot","type":"bytes32","internalType":"bytes32"},{"name":"stateRoot","type":"bytes32","internalType":"bytes32"}]}]},{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"error","name":"EmptyLeaves","inputs":[]},{"type":"error","name":"EmptyTree","inputs":[]},{"type":"error","name":"EmptyTree","inputs":[]},{"type":"error","name":"InvalidAggregateProof","inputs":[]},{"type":"error","name":"InvalidMmrProof","inputs":[]},{"type":"error","name":"InvalidParachainHeaderProof","inputs":[]},{"type":"error","name":"LeafIndexOutOfBounds","inputs":[]},{"type":"error","name":"MissingApkCommitment","inputs":[]},{"type":"error","name":"MmrRootHashMissing","inputs":[]},{"type":"error","name":"OutOfBoundsLeaves","inputs":[]},{"type":"error","name":"ProofExhausted","inputs":[]},{"type":"error","name":"SuperMajorityRequired","inputs":[]},{"type":"error","name":"TimestampNotFound","inputs":[]},{"type":"error","name":"UnconsumedProof","inputs":[]},{"type":"error","name":"UnknownAuthoritySet","inputs":[]},{"type":"error","name":"UnsortedLeaves","inputs":[]},{"type":"error","name":"UnsortedLeaves","inputs":[]}],"bytecode":{"object":"0x60c03461009957601f61324338819003918201601f19168301916001600160401b0383118484101761009d5780849260409485528339810103126100995780516001600160a01b038116919082900361009957602001519063ffffffff821682036100995760805260a05260405161319190816100b282396080518181816108a601526115ab015260a0518181816108f10152610c870152f35b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806301ffc9a71461007457806342a947b11461006f578063af8b91d61461006a578063afb5670a14610065578063e455995b146100605763f7e83aee1461005b575f80fd5b610966565b6108d5565b610891565b610871565b6107cf565b346100c85760203660031901126100c85760043563ffffffff60e01b81168091036100c857637bf41d7760e11b81149081156100b7575b50151560805260206080f35b6301ffc9a760e01b149050816100ab565b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b608081019081106001600160401b038211176100fb57604052565b6100cc565b606081019081106001600160401b038211176100fb57604052565b604081019081106001600160401b038211176100fb57604052565b60c081019081106001600160401b038211176100fb57604052565b90601f801991011681019081106001600160401b038211176100fb57604052565b6040519061018261010083610151565b565b60405190610182608083610151565b60405190610182604083610151565b60405190610182606083610151565b6040519061018260a083610151565b91908260809103126100c8576040516101d8816100e0565b60608082948035845260208101356020850152604081013560408501520135910152565b906101406003198301126100c857604051610216816100e0565b60606102418294600435845260243560208501526102358160446101c0565b604085015260c46101c0565b910152565b6001600160401b0381116100fb5760051b60200190565b6001600160401b0381116100fb57601f01601f191660200190565b81601f820112156100c85780359061028f8261025d565b9261029d6040519485610151565b828452602083830101116100c857815f926020809301838601378301015290565b359063ffffffff821682036100c857565b35906001600160401b03821682036100c857565b9190916060818403126100c857604051906102fd82610100565b819381356001600160401b0381116100c85782019080601f830112156100c85781359161032983610246565b926103376040519485610151565b80845260208085019160051b830101918383116100c85760208101915b83831061038157505050505060408092610241928552610376602082016102be565b6020860152016102cf565b82356001600160401b0381116100c8578201906040828703601f1901126100c857604051906103af8261011b565b60208301356001600160f01b0319811681036100c85782526040830135916001600160401b0383116100c8576103ed88602080969581960101610278565b83820152815201920191610354565b9080601f830112156100c8576040519161041760a084610151565b829060a081019283116100c857905b8282106104335750505090565b8135815260209182019101610426565b9080601f830112156100c8576040519161045e606084610151565b8290606081019283116100c857905b82821061047a5750505090565b813581526020918201910161046d565b9080601f830112156100c857604051916104a560c084610151565b829060c081019283116100c857905b8282106104c15750505090565b81358152602091820191016104b4565b91908260609103126100c8576040516104e981610100565b60408082946104f7816102cf565b8452610505602082016102be565b60208501520135910152565b919091610100818403126100c8576040519061052c82610136565b819381359160ff831683036100c85761056a60e09260a0948652610552602084016102be565b602087015260408301356040870152606083016104d1565b606085015260c081013560808501520135910152565b9080601f830112156100c857813561059781610246565b926105a56040519485610151565b81845260208085019260051b8201019283116100c857602001905b8282106105cd5750505090565b81358152602091820191016105c0565b919091610380818403126100c8576105f3610172565b9281356001600160401b0381116100c857816106109184016102e3565b845261061f81602084016103fc565b60208501526106318160c08401610443565b604085015261064481610120840161048a565b60608501526101e08201356001600160401b0381116100c85781610669918401610278565b608085015261067c816102008401610443565b60a085015261068f816102608401610511565b60c08501526103608201356001600160401b0381116100c8576106b29201610580565b60e0830152565b9190916060818403126100c857604051906106d382610100565b819381356001600160401b0381116100c857820181601f820112156100c8578035906106fe82610246565b9161070c6040519384610151565b80835260208084019160051b830101918483116100c85760208101915b83831061075a575050505083526020820135916001600160401b0383116100c8576105056040939284938301610580565b82356001600160401b0381116100c8578201906060828803601f1901126100c8576040519061078882610100565b60208301358252604083013560208301526060830135916001600160401b0383116100c8576107bf89602080969581960101610278565b6040820152815201920191610729565b346100c8576101603660031901126100c8576107ea366101fc565b50610144356001600160401b0381116100c857604060031982360301126100c857604051906108188261011b565b80600401356001600160401b0381116100c85761083b90600436918401016105dd565b825260248101356001600160401b0381116100c857602091600461086292369201016106b9565b910152005b5f9103126100c857565b346100c8575f3660031901126100c857604051610dad60f31b8152602090f35b346100c8575f3660031901126100c8576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b346100c8575f3660031901126100c857602060405163ffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b9181601f840112156100c8578235916001600160401b0383116100c857602083818601950101116100c857565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b346100c85760403660031901126100c8576004356001600160401b0381116100c857610996903690600401610915565b602435916001600160401b0383116100c8576109b96109c1933690600401610915565b929091610c29565b91906109d860405192606084526060840190610942565b9282840360208401526020808351958681520192015f945b808610610a0557505082935060408301520390f35b9092602060a06001926040808851805184528581015186850152015180518284015284810151606084015201516080820152019401950194906109f0565b90610140828203126100c857610a889060c060405193610a62856100e0565b8035855260208101356020860152610a7d83604083016101c0565b6040860152016101c0565b606082015290565b9190916040818403126100c85780356001600160401b0381116100c85783610ab99183016105dd565b9260208201356001600160401b0381116100c857610ad792016106b9565b90565b6101829092919260c060606101408301958051845260208101516020850152610b2960408201516040860190606080918051845260208101516020850152604081015160408501520151910152565b0151910190606080918051845260208101516020850152604081015160408501520151910152565b60405190610b5e82610100565b5f6040838281528260208201520152565b60405190610b7e602083610151565b5f80835282815b828110610b9157505050565b602090604051610ba081610100565b5f81525f83820152610bb0610b51565b604082015282828501015201610b85565b90610bcb82610246565b610bd86040519182610151565b8281528092610be9601f1991610246565b01905f5b828110610bf957505050565b602090604051610c0881610100565b5f81525f83820152610c18610b51565b604082015282828501015201610bed565b610c3d90610c469392959495810190610a43565b93810190610a90565b8392919251610c69610c6060208651015163ffffffff1690565b63ffffffff1690565b1115610dd857610d77610cac610c8385610d699697610e9d565b90937f0000000000000000000000000000000000000000000000000000000000000000916110b7565b929095610cc084516001600160401b031690565b9360608301948551906001600160401b03825191168181115f14610d8c575050855160408501525080516001600160401b031691606060c06040610d0b602086015163ffffffff1690565b940151920151015180516001600160401b03858116911603610d7f57604063ffffffff910151925b6001600160401b03610d43610184565b951685521660208401526040830152606082015283525b60405194859160208301610ada565b03601f198101855284610151565b515191929190565b5063ffffffff5f92610d33565b9193501480610dcc575b610da2575b5050610d5a565b6040916020610dba610c608285015163ffffffff1690565b91015201516040845101525f80610d9b565b50604082015115610d96565b506040519150610dfd82610def8560208301610ada565b03601f198101845283610151565b6060610e07610b6f565b9301515191929190565b60405190610e1e826100e0565b5f6060838281528260208201528260408201520152565b60405190610e42826100e0565b815f81525f6020820152610e54610e11565b60408201526060610241610e11565b634e487b7160e01b5f52603260045260245ffd5b805115610e845760200190565b610e63565b8051821015610e845760209160051b010190565b9190610ea7610e35565b5080516040808201805191860180515190926001600160401b0316908114159081611004575b50610ff557519051805190916001600160401b031603610fea575b604081015115610fdb57610f059083610f00846112f6565b611577565b805151915f925f5b818110610f575750508215610f4857610f3f610c60602060c094610f34608097868b6117b4565b015163ffffffff1690565b85520151015190565b6323188e3960e21b5f5260045ffd5b610dad60f31b610f8a610f7d610f6e848851610e89565b51516001600160f01b03191690565b6001600160f01b03191690565b1480610fc2575b610f9e575b600101610f0d565b93506001610fba6020610fb2878751610e89565b5101516116c3565b949050610f96565b50602080610fd1838751610e89565b5101515114610f91565b6334f6ba9f60e01b5f5260045ffd5b506060840151610ee8565b637202e68560e11b5f5260045ffd5b905060608701515114155f610ecd565b604051906110218261011b565b5f6020838281520152565b9061103682610246565b6110436040519182610151565b8281528092611054601f1991610246565b01905f5b82811061106457505050565b60209061106f611014565b82828501015201611058565b805191908290602001825e015f815290565b6040516001600160e01b031990911660208201529190610182908390610def90602483019061107b565b929190926110c3610b51565b93805151916110d18361102c565b926110db81610bc1565b945f5b82811061112a5750506110f3575b5050509190565b61110f92826040602061110b95015191015192611dbd565b1590565b61111b575f80806110ec565b6380b6d5fd60e01b5f5260045ffd5b611135818651610e89565b516040810161114481516119ba565b916111ab602082519201926111a561119e611163865163ffffffff1690565b6001600160e01b03199063ff00ff00600882811b9190911691901c62ff00ff1617601081811b63ffff00001691901c61ffff161760e01b1690565b9151611b06565b9061108d565b602081519101206111ba610193565b91825260208201526111cc848a610e89565b526111d78389610e89565b50805160208301516111e884611b2b565b906111f16101a2565b92835260208301526040820152611208848b610e89565b52611213838a610e89565b506112346112288c516001600160401b031690565b6001600160401b031690565b159081611260575b5061124b575b506001016110de565b600191995061125990611c98565b9890611242565b90505163ffffffff8416145f61123c565b60405190611280602083610151565b5f8252565b610def6112a494936112a461018294604051978895602087019061107b565b9061107b565b610182926112a495946112c9600c94604051988995602087019061107b565b6001600160e01b03199290921682526001600160c01b031916600482015203601319810185520183610151565b90815151611302611271565b905f5b8181106113d757509261131b610ad79394611ee8565b916113d16113476040611338611163602087015163ffffffff1690565b9401516001600160401b031690565b67ffffffffffff000067ff00ff00ff00ff0066ff00ff00ff00ff8360081c169260081b169165ffff0000ffff65ffff0000ff0065ffffffffffff67ffff0000ffff0000861666ff0000ffff000085161760101c16941691161760101b161767ffffffff0000000063ffffffff8260201c169160201b166001600160401b0360c01b911760c01b1690565b926112aa565b9161143c60019161141061141e6113f2610f6e888b51610e89565b6040516001600160f01b031990911660208201529182906022820190565b03601f198101835282610151565b611436602061142e888b51610e89565b510151611b06565b91611285565b9201611305565b906060828203126100c85780601f830112156100c85760405191611468606084610151565b8290606081019283116100c857905b8282106114845750505090565b8151815260209182019101611477565b906020610ad7928181520190610942565b6040513d5f823e3d90fd5b905f905b600382106114c157505050565b60208060019285518152019301910190916114b4565b908152939695949291905f602086015b60058210611561575050509161152f61152361153a9361150f879660c06102009901906114b0565b6102c06101208701526102c0860190610942565b976101408501906114b0565b6101a08301906114b0565b015f905b6006821061154b57505050565b602080600192855181520193019101909161153e565b60208060019285518152019301910190916114e7565b602082019061159761110b61158c8451612065565b6020870151906120cc565b6116b4576040516378b8c33160e11b8152937f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031691606090869081906115e89060048301611494565b0381855afa9485156116af575f9561167b575b50604001519151604084015192608085015195606060a0870151960151843b156100c8575f9661164292604051998a988997889763aa8f580360e01b8952600489016114d7565b03915afa9081611661575b50610182576303c6c1a560e21b5f5260045ffd5b8061166f5f61167593610151565b80610867565b5f61164d565b60409195506116a19060603d6060116116a8575b6116998183610151565b810190611443565b94906115fb565b503d61168f565b6114a5565b633aa90f7f60e21b5f5260045ffd5b60208151106116d3576020015190565b60405162461bcd60e51b8152602060048201526024808201527f42797465733a3a20746f427974657333323a206461746120697320746f20736860448201526337b93a1760e11b6064820152608490fd5b634e487b7160e01b5f52601160045260245ffd5b906001820180921161174657565b611724565b906003820180921161174657565b906002820180921161174657565b9190820180921161174657565b604080519091906117858382610151565b6001815291601f1901825f5b82811061179d57505050565b6020906117a8611014565b82828501015201611791565b61110b916118999360e060c083019261185d611858602061183487516117db815160ff1690565b906117ec8482015163ffffffff1690565b906040810151611820608060608401519301519361181461180b6101b1565b60ff9097168752565b63ffffffff1685880152565b604084015260608301526080820152612186565b8051908201209701518651602001516118529063ffffffff16610c60565b9061227a565b611738565b9460a0611868611774565b9551015190611875610193565b918252602082015261188685610e77565b5261189084610e77565b5001519061228f565b61189f57565b630b92186960e31b5f5260045ffd5b6040519060a082018281106001600160401b038211176100fb5760405260606080835f81525f60208201525f60408201525f838201520152565b604051906118f58261011b565b60606020835f81520152565b6040519061012082018281106001600160401b038211176100fb576040525f6101008382815261192f6118e8565b60208201528260408201526119426118e8565b60608201528260808201526119556118e8565b60a08201528260c0820152606060e08201520152565b9061197582610246565b6119826040519182610151565b8281528092611993601f1991610246565b01905f5b8281106119a357505050565b6020906119ae611901565b82828501015201611997565b6119c26118ae565b506119cb610193565b9081525f60208201526119e56119e08261229b565b6116c3565b906119ef816123da565b6119fb6119e08361229b565b611a076119e08461229b565b91611a11846123da565b611a1a8161196b565b945f5b828110611a4957505050611a2f6101b1565b948552602085015260408401526060830152608082015290565b600190611a5583612548565b60ff611a5f611901565b911680611a8b5750600160c08201525b611a79828a610e89565b52611a848189610e89565b5001611a1d565b60048103611aaf575060016040820152611aa4846125c2565b60608201525b611a6f565b60058103611ad2575060016080820152611ac8846125c2565b60a0820152611a6f565b60068103611af2575060018152611ae8846125c2565b6020820152611a6f565b600803611aaa576001610100820152611a6f565b610ad76112a491611410611b1a8251611ee8565b91604051948593602085019061107b565b611b33610b51565b505f5f925f5f5b6080850180518051831015611c64576040611b5884611b6093610e89565b510151151590565b80611c3c575b611bf3575b611b7b6040611b58848451610e89565b80611bba575b611b8f575b50600101611b3a565b819250611bb260206060611ba860809560019551610e89565b510151015161277e565b929150611b86565b50634953544d60e01b63ffffffff60e01b611bec6060611bdb868651610e89565b510151516001600160e01b03191690565b1614611b81565b95509250611c166119e060206060611c0c878a51610e89565b510151015161269e565b92611c366119e060206060611c2c858b51610e89565b5101510151612718565b95611b6b565b5063049534d560e41b63ffffffff60e01b611c5d6060611bdb868651610e89565b1614611b66565b505050925092908215611c8957611c796101a2565b9283526020830152604082015290565b633eba99d160e21b5f5260045ffd5b611ca0610b51565b5f915b6080810180518051851015611db55761110b6040611b5887611cc494610e89565b611da9576341504b4360e01b611cf0611ce36060611bdb888651610e89565b6001600160e01b03191690565b03611da9576060611d048560209351610e89565b510151015191602c835103611d9c57611d27611228611d22856126b4565b61277e565b936001600160401b03851615611d8857505050611d80611d586119e0611d52610c60611d22866126cb565b93612745565b91611d73611d646101a2565b6001600160401b039095168552565b63ffffffff166020840152565b604082015290565b608092945060019193505b01929050611ca3565b9150916001608091611d93565b50916001608091611d93565b505050919050565b919392908115611e86578451611dd2816127f6565b611ddb826127f6565b916001611de786612c6c565b1b5f805b838210611e065750505050611e0294959650612c9c565b1490565b611e10828c610e89565b51519088821015611e77578215159081611e6c575b50611e5d576001906020611e39848e610e89565b510151611e468489610e89565b52808401611e548488610e89565b52910190611deb565b630647f54960e21b5f5260045ffd5b90508111155f611e25565b630834466160e31b5f5260045ffd5b639136328760e01b5f5260045ffd5b60031981019190821161174657565b5f1981019190821161174657565b602003906020821161174657565b9190820391821161174657565b600190610ad7939260ff60f81b9060f81b168152019061107b565b6040811015611f2c57610ad7611f0a611f046114109360021b90565b60ff1690565b60405160f89190911b6001600160f81b03191660208201529182906021820190565b614000811015611f8b57610ad7611f69611f56611f4f6118586114109560021b90565b61ffff1690565b60ff61ff008260081b169160081c161790565b60405160f09190911b6001600160f01b03191660208201529182906022820190565b634000000081101561200857610ad7611fe6611fb5610c60611fb06114109560021b90565b611759565b63ffffff0062ff00ff62ffffff818460081c1616921660081b161763ffff000061ffff8260101c169160101b161790565b60405160e09190911b6001600160e01b03191660208201529182906024820190565b61141061202d61201a61203293612828565b6040519283916020830160209181520190565b612941565b610ad7612053611f0461204e6120488551611e95565b60021b90565b61174b565b61141060405193849260208401611ecd565b905f915f5b60058110156120c857600481036120c15760185b612094600160ff8460051b86015193161b611ea4565b16805b6120a4575060010161206a565b6120ad81611ea4565b16935f198114611746576001019380612097565b60fa61207e565b5050565b90801591821592836120df575b50505090565b90919250600382029180830460031490151715611746578260011b928304600214171561174657115f80806120d9565b94929196959390966040519788966020880161212a9161107b565b9063ffffffff60e01b1681526004016121429161107b565b916001600160401b0360c01b16825263ffffffff60e01b166008820152600c0161216b9161107b565b6121749161107b565b03601f19810183526101829083610151565b612267612244610def610ad7936121c36121a1825160ff1690565b60405160f89190911b6001600160f81b03191660208201529283906021820190565b610def6121da611163602084015163ffffffff1690565b6121f560408401516040519384916020830160209181520190565b606083015193612275608061221461134788516001600160401b031690565b95612252604061222e61116360208c015163ffffffff1690565b9901516040519a8b916020830160209181520190565b03601f1981018b528a610151565b01516040519889916020830160209181520190565b03601f198101895288610151565b61210f565b80612283575090565b81039081116117465790565b929091611e029261298a565b602081019081516020810180911161174657815151106100c8576020905181835182010191829101116117465760206122d391612bc6565b9080519060208201809211611746575290565b906020820191825182810180911161174657815151106100c857811561233457602090518184518201019182910111611746578161232391612bc6565b918051918201809211611746575290565b505050604051612345602082610151565b5f815290565b60ff60049116019060ff821161174657565b1561236457565b60405162461bcd60e51b815260206004820152602860248201527f756e657870656374656420707265666978206465636f64696e6720436f6d706160448201526731ba1e2ab4b73a1f60c11b6064820152608490fd5b906001600160401b03809116911601906001600160401b03821161174657565b6123e381612548565b60038116806123fd5750610ad7915060021c603f16611f04565b6001810361244257506112289061243c611f04612432612422611f04610ad797612548565b60061b67ffffffffffffffc01690565b9260021c603f1690565b906123ba565b600281036124b85750610c60906124ab60ff8461249c82612465610ad798612548565b958161248b8161247d61247788612548565b97612548565b991660081b63ffffff001690565b911617921660101b63ffff00001690565b17921660181b63ff0000001690565b1760021c633fffffff1690565b6003036124f257610ad79160ff6124de6124d9611d2294603f9060021c1690565b61234b565b16906124ed600883111561235d565b6122e6565b60405162461bcd60e51b815260206004820152601a60248201527f436f64652073686f756c6420626520756e726561636861626c650000000000006044820152606490fd5b908151811015610e84570160200190565b6020810190815160018101809111611746578151511061258e575181516001600160f81b0319916125799190612537565b511660f81c906125898151611738565b905290565b60405162461bcd60e51b815260206004820152600c60248201526b4f7574206f662072616e676560a01b6044820152606490fd5b6125ca6118e8565b50602081019081516004810180911161174657815151106100c85760208151818451820101918291011161174657600461260391612bc6565b918051906004820180921161174657525f915f905b600482106126585750508061262f612635926123da565b906122e6565b612650612640610193565b6001600160e01b03199093168352565b602082015290565b90926001600160f81b031961266d8584612537565b5116908460031b9185830460081486151715611746576001926001600160e01b0319918216901c1617930190612618565b80516020116100c857602080610ad79201612bc6565b80516008116100c85760086020610ad79201612bc6565b8051600c116100c85760288101906020018110611746576004610ad791612bc6565b90815181116100c8578015612708576020610ad79201612bc6565b5050604051612345602082610151565b8051806020116100c857601f198101908111611746576040820191602001821061174657610ad791612bc6565b805180600c116100c857600b19810190811161174657602c820191602001821061174657610ad791612bc6565b8015611746575f190190565b80515f91815b61278d57505090565b90915f19830190838211611746576127a58284612537565b5160f81c91600381901b906001600160fd1b038116036117465760ff8111611746576001901b91828102928184041490151715611746576127ef916127e991611767565b92612772565b9081612784565b9061280082610246565b61280d6040519182610151565b828152809261281e601f1991610246565b0190602036910137565b8060081c9060081b907cff000000ff000000ff000000ff000000ff000000ff000000ff000000ff7dff000000ff000000ff000000ff000000ff000000ff000000ff000000ff007fff000000ff000000ff000000ff000000ff000000ff000000ff000000ff00000084167eff000000ff000000ff000000ff000000ff000000ff000000ff000000ff000084161760101c931691161760101b177bffffffff00000000ffffffff00000000ffffffff00000000ffffffff7fffffffff00000000ffffffff00000000ffffffff00000000ffffffff00000000821660201c911660201b1777ffffffffffffffff0000000000000000ffffffffffffffff8019821660401c911660401b1761293d8160801c9160801b90565b1790565b80515f198101908111611746575b6001600160f81b03196129628284612537565b51166129765761297190612772565b61294f565b6001810180911161174657610ad7916126ed565b9290928215611e86578351938415612bb75760015b858110612b8957506001841480612b7f575b80612b6d575b612b52576129cc6129c785612e08565b6127f6565b946129d5610193565b945f8652602086019687526129e8610193565b925f8452602084019485526129fb6101a2565b905f82526020820193845260408201525f91805b612a9e575b50505051612a8f575190515103612a805781515f190182525b815115612a7057612a3d82613110565b612a4683613110565b90612a52845160010190565b84525f5260205260405f20612a6a8451845190610e89565b52612a2d565b91612a7c915051610e77565b5190565b637227423160e11b5f5260045ffd5b63072afb8760e51b5f5260045ffd5b612aa781612e2e565b92612ac1612ab96001861b8094611ec0565b928392611767565b938685612ace8187612eb3565b92602084015180155f14612b06575050505050508551518551145f03612a145780612b01612afb876130f4565b8a6130d9565b612a0f565b60011480612b4a575b15612b36575050506020612b2d826040612b01940151905190610e89565b5101518a6130d9565b91612b0193916002612afb941b0391612f3c565b508015612b0f565b925090925051612a8057612b67602091610e77565b51015190565b50612b7781610e77565b5151156129b7565b50600185146129b1565b612b938183610e89565b5151612ba7612ba183611ea4565b84610e89565b51511015611e5d5760010161299f565b631a14a47760e31b5f5260045ffd5b919091612bd28361025d565b612bdf6040519182610151565b838152612beb8461025d565b602082019190601f1901368337939091905b6020811015612c3c5780612c1d57505f19905b5182518216911916179052565b612c31612c2c612c3692611eb2565b61312c565b611ea4565b90612c10565b909182518152602081018091116117465791602081018091116117465790601f19810190811115612bfd57611724565b6001811115612c97575f19810190811161174657612c8990612e2e565b600181018091116117465790565b505f90565b9192905f83515b6001612cae86610e77565b5114612df957612cd5612c31856001612ccf612cc98a610e77565b51612e2e565b1b611767565b905f915f915b808310612cfe57505050612cf2612cf89194611738565b60011c90565b92612ca3565b909192612d0b8489610e89565b5182612d1686611738565b1080612ddb575b15612d7c5790612d686001926002612d53612d38898c610e89565b51612d4b612d458b611738565b8d610e89565b51908461313b565b9701965b612d61848b610e89565b5260011c90565b612d72828b610e89565b5201929190612cdb565b8987600183188610612dbe5791600180612db4612d6894612dac8c612da58d9e9d869b9a610e89565b5192610e89565b51908561313b565b9801980196612d57565b612d689150916001612dd288829695610e89565b51970196612d57565b50612dee612de886611738565b8a610e89565b516001821814612d1d565b5050915050612a7c9150610e77565b90815f925b612e145750565b915f19830183811161174657600193169283910192612e0d565b806fffffffffffffffffffffffffffffffff1060071b81811c6001600160401b031060061b1781811c63ffffffff1060051b1781811c61ffff1060041b1781811c60ff1060031b1781811c600f1060021b1781811c60031060011b1790811c6001101790565b60405190612ea182610100565b60606040835f81525f60208201520152565b612ebb612e94565b508051612ece6020830191825190611767565b928251905b848210612f15575b50829350612eee61258992935182611ec0565b908451946040810151612eff6101a2565b9687528360208801526040870152528251611ec0565b90612f24816040860151610e89565b5151821115612f365760010190612ed3565b90612edb565b91906020830151835193612f4f826127f6565b93612f59836127f6565b955f5b84811061308b5750505050935b6001612f7484610e77565b511461307e575f945f905b808210612f93575050848084528452612f69565b9095612f9f8786610e89565b51878784612fac83611738565b1080613060575b1561300757600192612fe583612fdd612d45612fd66130019997612fef97610e89565b5192611738565b51908361313b565b612d61848c610e89565b612ff98289610e89565b520196611759565b90612f7f565b5050845160208601515111156130515760019161303d8392612fe561302c8c8c610e89565b516130368a6130f4565b908361313b565b6130478289610e89565b5201960190612f7f565b63d8f29a1560e01b5f5260045ffd5b5061307361306d83611738565b89610e89565b516001841814612fb3565b93505050612a7c90610e77565b6001906130c46040860160206130ac82516130a68689611767565b90610e89565b5101516130b9848d610e89565b525182850190610e89565b515184016130d2828a610e89565b5201612f5c565b906130ea6020830151835190610e89565b5260018151019052565b6131046020820151825190610e89565b51906001815101905290565b6131206020820151825190610e89565b5181515f190190915290565b601f8111611746576101000a90565b60011661314e575f5260205260405f2090565b905f5260205260405f209056fea2646970667358221220fc0273cbfe6c3a223e89de7e32f88ee15ff63e6a4b718841b56fc84ff6f8192464736f6c634300081e0033","sourceMap":"3245:12033:118:-:0;;;;;;;;;;;;;-1:-1:-1;;3245:12033:118;;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;;;;;;;;;;;;;;4610:26;;4646:28;;3245:12033;;;;;;;;4610:26;3245:12033;;;;;;;;;;4646:28;3245:12033;;;;;;;;;;;;-1:-1:-1;3245:12033:118;;;;;;-1:-1:-1;3245:12033:118;;;;;-1:-1:-1;3245:12033:118","linkReferences":{}},"deployedBytecode":{"object":"0x60806040526004361015610011575f80fd5b5f3560e01c806301ffc9a71461007457806342a947b11461006f578063af8b91d61461006a578063afb5670a14610065578063e455995b146100605763f7e83aee1461005b575f80fd5b610966565b6108d5565b610891565b610871565b6107cf565b346100c85760203660031901126100c85760043563ffffffff60e01b81168091036100c857637bf41d7760e11b81149081156100b7575b50151560805260206080f35b6301ffc9a760e01b149050816100ab565b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b608081019081106001600160401b038211176100fb57604052565b6100cc565b606081019081106001600160401b038211176100fb57604052565b604081019081106001600160401b038211176100fb57604052565b60c081019081106001600160401b038211176100fb57604052565b90601f801991011681019081106001600160401b038211176100fb57604052565b6040519061018261010083610151565b565b60405190610182608083610151565b60405190610182604083610151565b60405190610182606083610151565b6040519061018260a083610151565b91908260809103126100c8576040516101d8816100e0565b60608082948035845260208101356020850152604081013560408501520135910152565b906101406003198301126100c857604051610216816100e0565b60606102418294600435845260243560208501526102358160446101c0565b604085015260c46101c0565b910152565b6001600160401b0381116100fb5760051b60200190565b6001600160401b0381116100fb57601f01601f191660200190565b81601f820112156100c85780359061028f8261025d565b9261029d6040519485610151565b828452602083830101116100c857815f926020809301838601378301015290565b359063ffffffff821682036100c857565b35906001600160401b03821682036100c857565b9190916060818403126100c857604051906102fd82610100565b819381356001600160401b0381116100c85782019080601f830112156100c85781359161032983610246565b926103376040519485610151565b80845260208085019160051b830101918383116100c85760208101915b83831061038157505050505060408092610241928552610376602082016102be565b6020860152016102cf565b82356001600160401b0381116100c8578201906040828703601f1901126100c857604051906103af8261011b565b60208301356001600160f01b0319811681036100c85782526040830135916001600160401b0383116100c8576103ed88602080969581960101610278565b83820152815201920191610354565b9080601f830112156100c8576040519161041760a084610151565b829060a081019283116100c857905b8282106104335750505090565b8135815260209182019101610426565b9080601f830112156100c8576040519161045e606084610151565b8290606081019283116100c857905b82821061047a5750505090565b813581526020918201910161046d565b9080601f830112156100c857604051916104a560c084610151565b829060c081019283116100c857905b8282106104c15750505090565b81358152602091820191016104b4565b91908260609103126100c8576040516104e981610100565b60408082946104f7816102cf565b8452610505602082016102be565b60208501520135910152565b919091610100818403126100c8576040519061052c82610136565b819381359160ff831683036100c85761056a60e09260a0948652610552602084016102be565b602087015260408301356040870152606083016104d1565b606085015260c081013560808501520135910152565b9080601f830112156100c857813561059781610246565b926105a56040519485610151565b81845260208085019260051b8201019283116100c857602001905b8282106105cd5750505090565b81358152602091820191016105c0565b919091610380818403126100c8576105f3610172565b9281356001600160401b0381116100c857816106109184016102e3565b845261061f81602084016103fc565b60208501526106318160c08401610443565b604085015261064481610120840161048a565b60608501526101e08201356001600160401b0381116100c85781610669918401610278565b608085015261067c816102008401610443565b60a085015261068f816102608401610511565b60c08501526103608201356001600160401b0381116100c8576106b29201610580565b60e0830152565b9190916060818403126100c857604051906106d382610100565b819381356001600160401b0381116100c857820181601f820112156100c8578035906106fe82610246565b9161070c6040519384610151565b80835260208084019160051b830101918483116100c85760208101915b83831061075a575050505083526020820135916001600160401b0383116100c8576105056040939284938301610580565b82356001600160401b0381116100c8578201906060828803601f1901126100c8576040519061078882610100565b60208301358252604083013560208301526060830135916001600160401b0383116100c8576107bf89602080969581960101610278565b6040820152815201920191610729565b346100c8576101603660031901126100c8576107ea366101fc565b50610144356001600160401b0381116100c857604060031982360301126100c857604051906108188261011b565b80600401356001600160401b0381116100c85761083b90600436918401016105dd565b825260248101356001600160401b0381116100c857602091600461086292369201016106b9565b910152005b5f9103126100c857565b346100c8575f3660031901126100c857604051610dad60f31b8152602090f35b346100c8575f3660031901126100c8576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b346100c8575f3660031901126100c857602060405163ffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b9181601f840112156100c8578235916001600160401b0383116100c857602083818601950101116100c857565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b346100c85760403660031901126100c8576004356001600160401b0381116100c857610996903690600401610915565b602435916001600160401b0383116100c8576109b96109c1933690600401610915565b929091610c29565b91906109d860405192606084526060840190610942565b9282840360208401526020808351958681520192015f945b808610610a0557505082935060408301520390f35b9092602060a06001926040808851805184528581015186850152015180518284015284810151606084015201516080820152019401950194906109f0565b90610140828203126100c857610a889060c060405193610a62856100e0565b8035855260208101356020860152610a7d83604083016101c0565b6040860152016101c0565b606082015290565b9190916040818403126100c85780356001600160401b0381116100c85783610ab99183016105dd565b9260208201356001600160401b0381116100c857610ad792016106b9565b90565b6101829092919260c060606101408301958051845260208101516020850152610b2960408201516040860190606080918051845260208101516020850152604081015160408501520151910152565b0151910190606080918051845260208101516020850152604081015160408501520151910152565b60405190610b5e82610100565b5f6040838281528260208201520152565b60405190610b7e602083610151565b5f80835282815b828110610b9157505050565b602090604051610ba081610100565b5f81525f83820152610bb0610b51565b604082015282828501015201610b85565b90610bcb82610246565b610bd86040519182610151565b8281528092610be9601f1991610246565b01905f5b828110610bf957505050565b602090604051610c0881610100565b5f81525f83820152610c18610b51565b604082015282828501015201610bed565b610c3d90610c469392959495810190610a43565b93810190610a90565b8392919251610c69610c6060208651015163ffffffff1690565b63ffffffff1690565b1115610dd857610d77610cac610c8385610d699697610e9d565b90937f0000000000000000000000000000000000000000000000000000000000000000916110b7565b929095610cc084516001600160401b031690565b9360608301948551906001600160401b03825191168181115f14610d8c575050855160408501525080516001600160401b031691606060c06040610d0b602086015163ffffffff1690565b940151920151015180516001600160401b03858116911603610d7f57604063ffffffff910151925b6001600160401b03610d43610184565b951685521660208401526040830152606082015283525b60405194859160208301610ada565b03601f198101855284610151565b515191929190565b5063ffffffff5f92610d33565b9193501480610dcc575b610da2575b5050610d5a565b6040916020610dba610c608285015163ffffffff1690565b91015201516040845101525f80610d9b565b50604082015115610d96565b506040519150610dfd82610def8560208301610ada565b03601f198101845283610151565b6060610e07610b6f565b9301515191929190565b60405190610e1e826100e0565b5f6060838281528260208201528260408201520152565b60405190610e42826100e0565b815f81525f6020820152610e54610e11565b60408201526060610241610e11565b634e487b7160e01b5f52603260045260245ffd5b805115610e845760200190565b610e63565b8051821015610e845760209160051b010190565b9190610ea7610e35565b5080516040808201805191860180515190926001600160401b0316908114159081611004575b50610ff557519051805190916001600160401b031603610fea575b604081015115610fdb57610f059083610f00846112f6565b611577565b805151915f925f5b818110610f575750508215610f4857610f3f610c60602060c094610f34608097868b6117b4565b015163ffffffff1690565b85520151015190565b6323188e3960e21b5f5260045ffd5b610dad60f31b610f8a610f7d610f6e848851610e89565b51516001600160f01b03191690565b6001600160f01b03191690565b1480610fc2575b610f9e575b600101610f0d565b93506001610fba6020610fb2878751610e89565b5101516116c3565b949050610f96565b50602080610fd1838751610e89565b5101515114610f91565b6334f6ba9f60e01b5f5260045ffd5b506060840151610ee8565b637202e68560e11b5f5260045ffd5b905060608701515114155f610ecd565b604051906110218261011b565b5f6020838281520152565b9061103682610246565b6110436040519182610151565b8281528092611054601f1991610246565b01905f5b82811061106457505050565b60209061106f611014565b82828501015201611058565b805191908290602001825e015f815290565b6040516001600160e01b031990911660208201529190610182908390610def90602483019061107b565b929190926110c3610b51565b93805151916110d18361102c565b926110db81610bc1565b945f5b82811061112a5750506110f3575b5050509190565b61110f92826040602061110b95015191015192611dbd565b1590565b61111b575f80806110ec565b6380b6d5fd60e01b5f5260045ffd5b611135818651610e89565b516040810161114481516119ba565b916111ab602082519201926111a561119e611163865163ffffffff1690565b6001600160e01b03199063ff00ff00600882811b9190911691901c62ff00ff1617601081811b63ffff00001691901c61ffff161760e01b1690565b9151611b06565b9061108d565b602081519101206111ba610193565b91825260208201526111cc848a610e89565b526111d78389610e89565b50805160208301516111e884611b2b565b906111f16101a2565b92835260208301526040820152611208848b610e89565b52611213838a610e89565b506112346112288c516001600160401b031690565b6001600160401b031690565b159081611260575b5061124b575b506001016110de565b600191995061125990611c98565b9890611242565b90505163ffffffff8416145f61123c565b60405190611280602083610151565b5f8252565b610def6112a494936112a461018294604051978895602087019061107b565b9061107b565b610182926112a495946112c9600c94604051988995602087019061107b565b6001600160e01b03199290921682526001600160c01b031916600482015203601319810185520183610151565b90815151611302611271565b905f5b8181106113d757509261131b610ad79394611ee8565b916113d16113476040611338611163602087015163ffffffff1690565b9401516001600160401b031690565b67ffffffffffff000067ff00ff00ff00ff0066ff00ff00ff00ff8360081c169260081b169165ffff0000ffff65ffff0000ff0065ffffffffffff67ffff0000ffff0000861666ff0000ffff000085161760101c16941691161760101b161767ffffffff0000000063ffffffff8260201c169160201b166001600160401b0360c01b911760c01b1690565b926112aa565b9161143c60019161141061141e6113f2610f6e888b51610e89565b6040516001600160f01b031990911660208201529182906022820190565b03601f198101835282610151565b611436602061142e888b51610e89565b510151611b06565b91611285565b9201611305565b906060828203126100c85780601f830112156100c85760405191611468606084610151565b8290606081019283116100c857905b8282106114845750505090565b8151815260209182019101611477565b906020610ad7928181520190610942565b6040513d5f823e3d90fd5b905f905b600382106114c157505050565b60208060019285518152019301910190916114b4565b908152939695949291905f602086015b60058210611561575050509161152f61152361153a9361150f879660c06102009901906114b0565b6102c06101208701526102c0860190610942565b976101408501906114b0565b6101a08301906114b0565b015f905b6006821061154b57505050565b602080600192855181520193019101909161153e565b60208060019285518152019301910190916114e7565b602082019061159761110b61158c8451612065565b6020870151906120cc565b6116b4576040516378b8c33160e11b8152937f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031691606090869081906115e89060048301611494565b0381855afa9485156116af575f9561167b575b50604001519151604084015192608085015195606060a0870151960151843b156100c8575f9661164292604051998a988997889763aa8f580360e01b8952600489016114d7565b03915afa9081611661575b50610182576303c6c1a560e21b5f5260045ffd5b8061166f5f61167593610151565b80610867565b5f61164d565b60409195506116a19060603d6060116116a8575b6116998183610151565b810190611443565b94906115fb565b503d61168f565b6114a5565b633aa90f7f60e21b5f5260045ffd5b60208151106116d3576020015190565b60405162461bcd60e51b8152602060048201526024808201527f42797465733a3a20746f427974657333323a206461746120697320746f20736860448201526337b93a1760e11b6064820152608490fd5b634e487b7160e01b5f52601160045260245ffd5b906001820180921161174657565b611724565b906003820180921161174657565b906002820180921161174657565b9190820180921161174657565b604080519091906117858382610151565b6001815291601f1901825f5b82811061179d57505050565b6020906117a8611014565b82828501015201611791565b61110b916118999360e060c083019261185d611858602061183487516117db815160ff1690565b906117ec8482015163ffffffff1690565b906040810151611820608060608401519301519361181461180b6101b1565b60ff9097168752565b63ffffffff1685880152565b604084015260608301526080820152612186565b8051908201209701518651602001516118529063ffffffff16610c60565b9061227a565b611738565b9460a0611868611774565b9551015190611875610193565b918252602082015261188685610e77565b5261189084610e77565b5001519061228f565b61189f57565b630b92186960e31b5f5260045ffd5b6040519060a082018281106001600160401b038211176100fb5760405260606080835f81525f60208201525f60408201525f838201520152565b604051906118f58261011b565b60606020835f81520152565b6040519061012082018281106001600160401b038211176100fb576040525f6101008382815261192f6118e8565b60208201528260408201526119426118e8565b60608201528260808201526119556118e8565b60a08201528260c0820152606060e08201520152565b9061197582610246565b6119826040519182610151565b8281528092611993601f1991610246565b01905f5b8281106119a357505050565b6020906119ae611901565b82828501015201611997565b6119c26118ae565b506119cb610193565b9081525f60208201526119e56119e08261229b565b6116c3565b906119ef816123da565b6119fb6119e08361229b565b611a076119e08461229b565b91611a11846123da565b611a1a8161196b565b945f5b828110611a4957505050611a2f6101b1565b948552602085015260408401526060830152608082015290565b600190611a5583612548565b60ff611a5f611901565b911680611a8b5750600160c08201525b611a79828a610e89565b52611a848189610e89565b5001611a1d565b60048103611aaf575060016040820152611aa4846125c2565b60608201525b611a6f565b60058103611ad2575060016080820152611ac8846125c2565b60a0820152611a6f565b60068103611af2575060018152611ae8846125c2565b6020820152611a6f565b600803611aaa576001610100820152611a6f565b610ad76112a491611410611b1a8251611ee8565b91604051948593602085019061107b565b611b33610b51565b505f5f925f5f5b6080850180518051831015611c64576040611b5884611b6093610e89565b510151151590565b80611c3c575b611bf3575b611b7b6040611b58848451610e89565b80611bba575b611b8f575b50600101611b3a565b819250611bb260206060611ba860809560019551610e89565b510151015161277e565b929150611b86565b50634953544d60e01b63ffffffff60e01b611bec6060611bdb868651610e89565b510151516001600160e01b03191690565b1614611b81565b95509250611c166119e060206060611c0c878a51610e89565b510151015161269e565b92611c366119e060206060611c2c858b51610e89565b5101510151612718565b95611b6b565b5063049534d560e41b63ffffffff60e01b611c5d6060611bdb868651610e89565b1614611b66565b505050925092908215611c8957611c796101a2565b9283526020830152604082015290565b633eba99d160e21b5f5260045ffd5b611ca0610b51565b5f915b6080810180518051851015611db55761110b6040611b5887611cc494610e89565b611da9576341504b4360e01b611cf0611ce36060611bdb888651610e89565b6001600160e01b03191690565b03611da9576060611d048560209351610e89565b510151015191602c835103611d9c57611d27611228611d22856126b4565b61277e565b936001600160401b03851615611d8857505050611d80611d586119e0611d52610c60611d22866126cb565b93612745565b91611d73611d646101a2565b6001600160401b039095168552565b63ffffffff166020840152565b604082015290565b608092945060019193505b01929050611ca3565b9150916001608091611d93565b50916001608091611d93565b505050919050565b919392908115611e86578451611dd2816127f6565b611ddb826127f6565b916001611de786612c6c565b1b5f805b838210611e065750505050611e0294959650612c9c565b1490565b611e10828c610e89565b51519088821015611e77578215159081611e6c575b50611e5d576001906020611e39848e610e89565b510151611e468489610e89565b52808401611e548488610e89565b52910190611deb565b630647f54960e21b5f5260045ffd5b90508111155f611e25565b630834466160e31b5f5260045ffd5b639136328760e01b5f5260045ffd5b60031981019190821161174657565b5f1981019190821161174657565b602003906020821161174657565b9190820391821161174657565b600190610ad7939260ff60f81b9060f81b168152019061107b565b6040811015611f2c57610ad7611f0a611f046114109360021b90565b60ff1690565b60405160f89190911b6001600160f81b03191660208201529182906021820190565b614000811015611f8b57610ad7611f69611f56611f4f6118586114109560021b90565b61ffff1690565b60ff61ff008260081b169160081c161790565b60405160f09190911b6001600160f01b03191660208201529182906022820190565b634000000081101561200857610ad7611fe6611fb5610c60611fb06114109560021b90565b611759565b63ffffff0062ff00ff62ffffff818460081c1616921660081b161763ffff000061ffff8260101c169160101b161790565b60405160e09190911b6001600160e01b03191660208201529182906024820190565b61141061202d61201a61203293612828565b6040519283916020830160209181520190565b612941565b610ad7612053611f0461204e6120488551611e95565b60021b90565b61174b565b61141060405193849260208401611ecd565b905f915f5b60058110156120c857600481036120c15760185b612094600160ff8460051b86015193161b611ea4565b16805b6120a4575060010161206a565b6120ad81611ea4565b16935f198114611746576001019380612097565b60fa61207e565b5050565b90801591821592836120df575b50505090565b90919250600382029180830460031490151715611746578260011b928304600214171561174657115f80806120d9565b94929196959390966040519788966020880161212a9161107b565b9063ffffffff60e01b1681526004016121429161107b565b916001600160401b0360c01b16825263ffffffff60e01b166008820152600c0161216b9161107b565b6121749161107b565b03601f19810183526101829083610151565b612267612244610def610ad7936121c36121a1825160ff1690565b60405160f89190911b6001600160f81b03191660208201529283906021820190565b610def6121da611163602084015163ffffffff1690565b6121f560408401516040519384916020830160209181520190565b606083015193612275608061221461134788516001600160401b031690565b95612252604061222e61116360208c015163ffffffff1690565b9901516040519a8b916020830160209181520190565b03601f1981018b528a610151565b01516040519889916020830160209181520190565b03601f198101895288610151565b61210f565b80612283575090565b81039081116117465790565b929091611e029261298a565b602081019081516020810180911161174657815151106100c8576020905181835182010191829101116117465760206122d391612bc6565b9080519060208201809211611746575290565b906020820191825182810180911161174657815151106100c857811561233457602090518184518201019182910111611746578161232391612bc6565b918051918201809211611746575290565b505050604051612345602082610151565b5f815290565b60ff60049116019060ff821161174657565b1561236457565b60405162461bcd60e51b815260206004820152602860248201527f756e657870656374656420707265666978206465636f64696e6720436f6d706160448201526731ba1e2ab4b73a1f60c11b6064820152608490fd5b906001600160401b03809116911601906001600160401b03821161174657565b6123e381612548565b60038116806123fd5750610ad7915060021c603f16611f04565b6001810361244257506112289061243c611f04612432612422611f04610ad797612548565b60061b67ffffffffffffffc01690565b9260021c603f1690565b906123ba565b600281036124b85750610c60906124ab60ff8461249c82612465610ad798612548565b958161248b8161247d61247788612548565b97612548565b991660081b63ffffff001690565b911617921660101b63ffff00001690565b17921660181b63ff0000001690565b1760021c633fffffff1690565b6003036124f257610ad79160ff6124de6124d9611d2294603f9060021c1690565b61234b565b16906124ed600883111561235d565b6122e6565b60405162461bcd60e51b815260206004820152601a60248201527f436f64652073686f756c6420626520756e726561636861626c650000000000006044820152606490fd5b908151811015610e84570160200190565b6020810190815160018101809111611746578151511061258e575181516001600160f81b0319916125799190612537565b511660f81c906125898151611738565b905290565b60405162461bcd60e51b815260206004820152600c60248201526b4f7574206f662072616e676560a01b6044820152606490fd5b6125ca6118e8565b50602081019081516004810180911161174657815151106100c85760208151818451820101918291011161174657600461260391612bc6565b918051906004820180921161174657525f915f905b600482106126585750508061262f612635926123da565b906122e6565b612650612640610193565b6001600160e01b03199093168352565b602082015290565b90926001600160f81b031961266d8584612537565b5116908460031b9185830460081486151715611746576001926001600160e01b0319918216901c1617930190612618565b80516020116100c857602080610ad79201612bc6565b80516008116100c85760086020610ad79201612bc6565b8051600c116100c85760288101906020018110611746576004610ad791612bc6565b90815181116100c8578015612708576020610ad79201612bc6565b5050604051612345602082610151565b8051806020116100c857601f198101908111611746576040820191602001821061174657610ad791612bc6565b805180600c116100c857600b19810190811161174657602c820191602001821061174657610ad791612bc6565b8015611746575f190190565b80515f91815b61278d57505090565b90915f19830190838211611746576127a58284612537565b5160f81c91600381901b906001600160fd1b038116036117465760ff8111611746576001901b91828102928184041490151715611746576127ef916127e991611767565b92612772565b9081612784565b9061280082610246565b61280d6040519182610151565b828152809261281e601f1991610246565b0190602036910137565b8060081c9060081b907cff000000ff000000ff000000ff000000ff000000ff000000ff000000ff7dff000000ff000000ff000000ff000000ff000000ff000000ff000000ff007fff000000ff000000ff000000ff000000ff000000ff000000ff000000ff00000084167eff000000ff000000ff000000ff000000ff000000ff000000ff000000ff000084161760101c931691161760101b177bffffffff00000000ffffffff00000000ffffffff00000000ffffffff7fffffffff00000000ffffffff00000000ffffffff00000000ffffffff00000000821660201c911660201b1777ffffffffffffffff0000000000000000ffffffffffffffff8019821660401c911660401b1761293d8160801c9160801b90565b1790565b80515f198101908111611746575b6001600160f81b03196129628284612537565b51166129765761297190612772565b61294f565b6001810180911161174657610ad7916126ed565b9290928215611e86578351938415612bb75760015b858110612b8957506001841480612b7f575b80612b6d575b612b52576129cc6129c785612e08565b6127f6565b946129d5610193565b945f8652602086019687526129e8610193565b925f8452602084019485526129fb6101a2565b905f82526020820193845260408201525f91805b612a9e575b50505051612a8f575190515103612a805781515f190182525b815115612a7057612a3d82613110565b612a4683613110565b90612a52845160010190565b84525f5260205260405f20612a6a8451845190610e89565b52612a2d565b91612a7c915051610e77565b5190565b637227423160e11b5f5260045ffd5b63072afb8760e51b5f5260045ffd5b612aa781612e2e565b92612ac1612ab96001861b8094611ec0565b928392611767565b938685612ace8187612eb3565b92602084015180155f14612b06575050505050508551518551145f03612a145780612b01612afb876130f4565b8a6130d9565b612a0f565b60011480612b4a575b15612b36575050506020612b2d826040612b01940151905190610e89565b5101518a6130d9565b91612b0193916002612afb941b0391612f3c565b508015612b0f565b925090925051612a8057612b67602091610e77565b51015190565b50612b7781610e77565b5151156129b7565b50600185146129b1565b612b938183610e89565b5151612ba7612ba183611ea4565b84610e89565b51511015611e5d5760010161299f565b631a14a47760e31b5f5260045ffd5b919091612bd28361025d565b612bdf6040519182610151565b838152612beb8461025d565b602082019190601f1901368337939091905b6020811015612c3c5780612c1d57505f19905b5182518216911916179052565b612c31612c2c612c3692611eb2565b61312c565b611ea4565b90612c10565b909182518152602081018091116117465791602081018091116117465790601f19810190811115612bfd57611724565b6001811115612c97575f19810190811161174657612c8990612e2e565b600181018091116117465790565b505f90565b9192905f83515b6001612cae86610e77565b5114612df957612cd5612c31856001612ccf612cc98a610e77565b51612e2e565b1b611767565b905f915f915b808310612cfe57505050612cf2612cf89194611738565b60011c90565b92612ca3565b909192612d0b8489610e89565b5182612d1686611738565b1080612ddb575b15612d7c5790612d686001926002612d53612d38898c610e89565b51612d4b612d458b611738565b8d610e89565b51908461313b565b9701965b612d61848b610e89565b5260011c90565b612d72828b610e89565b5201929190612cdb565b8987600183188610612dbe5791600180612db4612d6894612dac8c612da58d9e9d869b9a610e89565b5192610e89565b51908561313b565b9801980196612d57565b612d689150916001612dd288829695610e89565b51970196612d57565b50612dee612de886611738565b8a610e89565b516001821814612d1d565b5050915050612a7c9150610e77565b90815f925b612e145750565b915f19830183811161174657600193169283910192612e0d565b806fffffffffffffffffffffffffffffffff1060071b81811c6001600160401b031060061b1781811c63ffffffff1060051b1781811c61ffff1060041b1781811c60ff1060031b1781811c600f1060021b1781811c60031060011b1790811c6001101790565b60405190612ea182610100565b60606040835f81525f60208201520152565b612ebb612e94565b508051612ece6020830191825190611767565b928251905b848210612f15575b50829350612eee61258992935182611ec0565b908451946040810151612eff6101a2565b9687528360208801526040870152528251611ec0565b90612f24816040860151610e89565b5151821115612f365760010190612ed3565b90612edb565b91906020830151835193612f4f826127f6565b93612f59836127f6565b955f5b84811061308b5750505050935b6001612f7484610e77565b511461307e575f945f905b808210612f93575050848084528452612f69565b9095612f9f8786610e89565b51878784612fac83611738565b1080613060575b1561300757600192612fe583612fdd612d45612fd66130019997612fef97610e89565b5192611738565b51908361313b565b612d61848c610e89565b612ff98289610e89565b520196611759565b90612f7f565b5050845160208601515111156130515760019161303d8392612fe561302c8c8c610e89565b516130368a6130f4565b908361313b565b6130478289610e89565b5201960190612f7f565b63d8f29a1560e01b5f5260045ffd5b5061307361306d83611738565b89610e89565b516001841814612fb3565b93505050612a7c90610e77565b6001906130c46040860160206130ac82516130a68689611767565b90610e89565b5101516130b9848d610e89565b525182850190610e89565b515184016130d2828a610e89565b5201612f5c565b906130ea6020830151835190610e89565b5260018151019052565b6131046020820151825190610e89565b51906001815101905290565b6131206020820151825190610e89565b5181515f190190915290565b601f8111611746576101000a90565b60011661314e575f5260205260405f2090565b905f5260205260405f209056fea2646970667358221220fc0273cbfe6c3a223e89de7e32f88ee15ff63e6a4b718841b56fc84ff6f8192464736f6c634300081e0033","sourceMap":"3245:12033:118:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;:::i;:::-;;:::i;:::-;;:::i;:::-;;;;;;-1:-1:-1;;3245:12033:118;;;;;;;;;;;;;;;;-1:-1:-1;;;4803:45:118;;;:85;;;;3245:12033;;;;;;;;;4803:85;-1:-1:-1;;;829:40:49;;-1:-1:-1;4803:85:118;;;3245:12033;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;:::o;:::-;;:::i;:::-;;;;;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;:::o;:::-;;;;;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;:::o;:::-;;;;;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;:::o;:::-;;;;;;;;;;;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;:::o;:::-;;;;;;;;:::i;:::-;:::o;:::-;;;;;;;;:::i;:::-;13648:11;3245:12033;;;13648:11;3245:12033;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;-1:-1:-1;;3245:12033:118;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;:::o;:::-;-1:-1:-1;;;;;3245:12033:118;;;;;;;;;:::o;:::-;-1:-1:-1;;;;;3245:12033:118;;;;;;-1:-1:-1;;3245:12033:118;;;;:::o;:::-;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;-1:-1:-1;3245:12033:118;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;:::o;:::-;;;-1:-1:-1;;;;;3245:12033:118;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;:::i;:::-;;;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;:::i;:::-;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;;;;;-1:-1:-1;;3245:12033:118;;;;;;;;;;:::i;:::-;;;;;-1:-1:-1;;;;;;3245:12033:118;;;;;;;;;;;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;;;;:::i;:::-;;;;;;;;;:::i;:::-;;;;;;;;;;;:::i;:::-;;;;;;;;;;;:::i;:::-;;;;;;;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;;;;:::i;:::-;;;;;;;;;;;:::i;:::-;;;;;;;;;;;:::i;:::-;;;;;;;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;;:::i;:::-;;;;;:::o;:::-;;;;;;;;;;;;;;;;;:::i;:::-;;;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;;;;;;;:::i;:::-;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;;;;;-1:-1:-1;;3245:12033:118;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;-1:-1:-1;;3245:12033:118;;;;;;;:::i;:::-;;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;;;;;;;:::i;:::-;;;;;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;:::o;:::-;;;;;;-1:-1:-1;;3245:12033:118;;;;;;-1:-1:-1;;;3245:12033:118;;;;;;;;;;;-1:-1:-1;;3245:12033:118;;;;;;3494:31;-1:-1:-1;;;;;3245:12033:118;;;;;;;;;;;;-1:-1:-1;;3245:12033:118;;;;;;;;3815:37;3245:12033;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;-1:-1:-1;3245:12033:118;;;;;;;;-1:-1:-1;;3245:12033:118;;;;:::o;:::-;;;;;;-1:-1:-1;;3245:12033:118;;;;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;;;;;:::i;:::-;;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;;;;;;:::i;:::-;;;;;:::i;:::-;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;-1:-1:-1;3245:12033:118;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;:::o;:::-;;;;;;;;;;;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;;;;:::i;:::-;;;;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;;:::i;:::-;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;3245:12033:118;;;;;;;;;;;;:::o;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;;;;:::i;:::-;;;-1:-1:-1;3245:12033:118;;;;;;;;;:::o;:::-;;;;;;;;:::i;:::-;-1:-1:-1;3245:12033:118;;-1:-1:-1;3245:12033:118;;;;;;:::i;:::-;;;;;;;;;;;;;;4940:2380;5164:48;4940:2380;5306:58;4940:2380;;;;;5164:48;;;;:::i;:::-;5306:58;;;;;:::i;:::-;3245:12033;;;;;5463:59;3245:12033;5494:28;:16;;:28;3245:12033;;;;;;;;;;5463:59;-1:-1:-1;5463:59:118;5459:189;;7247:20;5852:63;5717:43;;7247:20;5717:43;;;:::i;:::-;5901:13;;;5852:63;;:::i;:::-;3245:12033;;;;;;-1:-1:-1;;;;;3245:12033:118;;;;6494:25;;;;;;;3245:12033;-1:-1:-1;;;;;3245:12033:118;;;;6479:43;;;6475:754;6479:43;;;-1:-1:-1;;6569:25:118;;6538:28;;;:56;-1:-1:-1;3245:12033:118;;-1:-1:-1;;;;;3245:12033:118;6706:10;6494:25;6803:19;6538:28;3245:12033;5494:28;6706:10;;3245:12033;;;;;;6751:17;;3245:12033;6803:19;;;:36;;3245:12033;;-1:-1:-1;;;;;3245:12033:118;;;;;6803:55;:152;;6538:28;3245:12033;6881:41;;3245:12033;6803:152;;-1:-1:-1;;;;;3245:12033:118;;:::i;:::-;;;;;;5494:28;6636:334;;3245:12033;6538:28;6636:334;;3245:12033;6494:25;6636:334;;3245:12033;6608:362;;6475:754;3245:12033;;7247:20;;;5494:28;7247:20;;;:::i;:::-;;3245:12033;;7247:20;;;;;;:::i;:::-;7284:25;3245:12033;7239:74;;;4940:2380;:::o;6803:152::-;;3245:12033;;6803:152;;;6475:754;6991:44;;-1:-1:-1;6991:44:118;;:94;;6475:754;6987:242;;6475:754;;;;;6987:242;7201:17;7133:10;5494:28;7101:42;3245:12033;7133:10;;;3245:12033;;;;;7101:42;:29;;3245:12033;7201:17;3245:12033;7201:17;7157:25;;:41;3245:12033;6987:242;;;;6991:94;7039:41;;;;3245:12033;7039:46;6991:94;;5459:189;-1:-1:-1;3245:12033:118;;;-1:-1:-1;5546:26:118;3245:12033;5546:26;;5494:28;5546:26;;;:::i;:::-;;3245:12033;;5546:26;;;;;;:::i;:::-;5602:31;5574:26;;:::i;:::-;5602:31;;;3245:12033;5538:99;;;;:::o;3245:12033::-;;;;;;;:::i;:::-;-1:-1:-1;3245:12033:118;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;:::i;:::-;;-1:-1:-1;3245:12033:118;;-1:-1:-1;3245:12033:118;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;:::i;:::-;;;;;;;;;;;;;;;:::o;7405:1697::-;;;3245:12033;;:::i;:::-;-1:-1:-1;7646:21:118;;7694:25;;;;3245:12033;;7723:32;;;;;3245:12033;7723:32;;-1:-1:-1;;;;;3245:12033:118;7694:64;;;;;;:145;;7405:1697;7677:226;;;3245:12033;7959:32;;3245:12033;;7959:32;;-1:-1:-1;;;;;3245:12033:118;7930:64;8039:76;;;7694:25;8359:28;;3245:12033;8359:33;8355:68;;8490:12;8452:24;;;;;:::i;:::-;8490:12;:::i;:::-;8538:18;;3245:12033;8573:15;3245:12033;8603:13;3245:12033;8618:17;;;;;;8861:21;;;;8857:54;;8981:50;3245:12033;9009:22;9064:24;8962:7;;9064:30;8962:7;;;;:::i;:::-;9009:22;3245:12033;;;;;8981:50;3245:12033;;9064:24;;:30;3245:12033;7405:1697;:::o;8857:54::-;8891:20;;;3245:12033;8891:20;;3245:12033;8891:20;8637:3;3245:12033;;;8660:47;:24;:21;:18;;;:21;:::i;:::-;;3245:12033;-1:-1:-1;;;;;;3245:12033:118;;;8660:24;-1:-1:-1;;;;;;3245:12033:118;;;8660:47;;:90;;;8637:3;8656:182;;8637:3;3245:12033;;8603:13;;8656:182;8796:18;;3245:12033;8780:43;8796:26;:21;:18;;;:21;:::i;:::-;;:26;;8780:43;:::i;:::-;8656:182;;;;;8660:90;8711:18;:26;:18;:21;:18;;;:21;:::i;:::-;;:26;;3245:12033;8711:39;8660:90;;8355:68;8401:22;;;3245:12033;8401:22;;3245:12033;8401:22;8039:76;8086:29;;;;;8039:76;;7677:226;7871:21;;;3245:12033;7871:21;;3245:12033;7871:21;7694:145;7807:29;;;;;;3245:12033;7778:61;;7694:145;;;3245:12033;;;;;;;:::i;:::-;-1:-1:-1;3245:12033:118;;;;;;;:::o;:::-;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;;;;:::i;:::-;;;-1:-1:-1;3245:12033:118;;;;;;;;;:::o;:::-;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;-1:-1:-1;;;;;;3245:12033:118;;;;;;;;;;;;;;;;;;;;:::i;13071:1645::-;;;;;3245:12033;;:::i;:::-;13304:16;;;3245:12033;13377:32;;;;:::i;:::-;13462:28;;;;:::i;:::-;13506:13;-1:-1:-1;13521:7:118;;;;;;14483;;14479:190;;13501:968;14679:30;;;;13071:1645;:::o;14479:190::-;14614:6;14559:11;;13648;13804:7;14519:77;14559:11;;;14580:15;;3245:12033;14519:77;;:::i;:::-;14614:6;;3245:12033;14614:6;14610:48;;14479:190;;;;;14610:48;14629:29;;;-1:-1:-1;14629:29:118;;-1:-1:-1;14629:29:118;13530:3;13573:19;:16;;;:19;:::i;:::-;;13648:11;;;13629:31;13648:11;;13629:31;:::i;:::-;3245:12033;13764:87;13804:7;3245:12033;;13804:7;;3245:12033;13815:35;13777:36;13797:15;3245:12033;;;;;;13797:15;-1:-1:-1;;;;;;3245:12033:118;;;;;;;;;;;;;8200:10:65;3245:12033:118;8168:49:65;3245:12033:118;;;;;;;;;;;8266:21:65;3245:12033:118;;;8815:111:65;;13777:36:118;13838:11;;13815:35;:::i;:::-;13764:87;;:::i;:::-;13804:7;3245:12033;;;;13754:98;3245:12033;;:::i;:::-;;;;13804:7;13687:179;;3245:12033;13675:191;;;;:::i;:::-;;;;;;:::i;:::-;;3245:12033;;13804:7;13985:13;;3245:12033;14028:34;;;:::i;:::-;3245:12033;;;:::i;:::-;;;;13804:7;13900:177;;3245:12033;13648:11;13900:177;;3245:12033;13881:196;;;;:::i;:::-;;;;;;:::i;:::-;;14339:17;3245:12033;;;-1:-1:-1;;;;;3245:12033:118;;;;-1:-1:-1;;;;;3245:12033:118;;;14339:17;;:44;;;;13530:3;14335:124;;;13530:3;;3245:12033;;13506:13;;14335:124;3245:12033;14412:32;;;;;;:::i;:::-;14335:124;;;;14339:44;3245:12033;;;;;;14360:23;14339:44;;;3245:12033;;;;;;;;:::i;:::-;;;;:::o;:::-;;;;;;;;;;;;;;;;;;:::i;:::-;;;:::i;:::-;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;;3245:12033:118;;;;;;-1:-1:-1;;;;;;3245:12033:118;;;;;;-1:-1:-1;;3245:12033:118;;;;;;;:::i;1604:684:119:-;;1718:18;;3245:12033:118;;;:::i;:::-;1800:13:119;-1:-1:-1;1815:14:119;;;;;;2093:40;;;2067:214;2093:40;;;:::i;:::-;2188:22;2225:46;3245:12033:118;;2168:43:119;3245:12033:118;1915:42:119;2188:22;;3245:12033:118;;;;;2168:43:119;2245:25;;3245:12033:118;-1:-1:-1;;;;;3245:12033:118;;;;;7788:18:65;3245:12033:118;;;;;;;;;;7937:18:65;7933:22;3245:12033:118;7902:18:65;7898:22;;;;;;3245:12033:118;;;7932:30:65;7933:22;;;;3245:12033:118;;;7896:67:65;3245:12033:118;;;;;;8025:7:65;3245:12033:118;;;-1:-1:-1;;;;;3245:12033:118;;8012:21:65;;3245:12033:118;;;8698:111:65;;2225:46:119;2067:214;;:::i;1831:3::-;1932:18;1860:179;3245:12033:118;1932:18:119;1915:42;;1932:24;:21;:18;;;:21;:::i;:24::-;3245:12033:118;;-1:-1:-1;;;;;;3245:12033:118;;;1915:42:119;;;3245:12033:118;;;;;;;;;1915:42:119;;3245:12033:118;;1915:42:119;;;;;;:::i;:::-;1975:50;1915:42;1998:21;:18;;;:21;:::i;:::-;;:26;;1975:50;:::i;:::-;1860:179;;:::i;:::-;1831:3;3245:12033:118;1800:13:119;;3245:12033:118;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;:::i;:::-;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9696:912;9914:18;;;;9947:54;9948:53;9901:32;9914:18;;9901:32;:::i;:::-;9914:18;9984:16;;3245:12033;9948:53;;:::i;9947:54::-;9943:90;;3245:12033;;-1:-1:-1;;;10072:32:118;;3245:12033;10072:4;-1:-1:-1;;;;;3245:12033:118;;10072:32;;3245:12033;;;;10072:32;;;;;;:::i;:::-;;;;;;;;;;;-1:-1:-1;10072:32:118;;;9696:912;10322:28;3245:12033;10322:28;3245:12033;10364:18;;3245:12033;10396:14;;;10424:19;;;;;10478:20;10072:32;10478:20;;;;10512:15;;;10297:240;;;;;-1:-1:-1;3245:12033:118;10297:240;3245:12033;;;;;;;;;;;;;10297:240;;10072:32;10297:240;;;:::i;:::-;;;;;;;;;9696:912;-1:-1:-1;10293:309:118;;10568:23;;;-1:-1:-1;10568:23:118;10072:32;-1:-1:-1;10568:23:118;10297:240;;;-1:-1:-1;10297:240:118;;;:::i;:::-;;;:::i;:::-;;;;10072:32;3245:12033;10072:32;;;;;;;;;;;;;;;;:::i;:::-;;;;;:::i;:::-;;;;;;;;;;;;:::i;9943:90::-;10010:23;;;-1:-1:-1;10010:23:118;;-1:-1:-1;10010:23:118;4543:226:58;4650:2;3245:12033:118;;4635:17:58;3245:12033:118;;4650:2:58;4703:60;;4543:226;:::o;3245:12033:118:-;;;-1:-1:-1;;;3245:12033:118;;4650:2:58;3245:12033:118;;;;;;;;;;;;;;-1:-1:-1;;;3245:12033:118;;;;;;;;;;;;;;;;;;;;;12559:1;3245:12033;;;;;;;:::o;:::-;;:::i;:::-;;5093:1:65;3245:12033:118;;;;;;;:::o;:::-;;4835:1:65;3245:12033:118;;;;;;;:::o;:::-;;;;;;;;;;:::o;:::-;;;;;;;;;;;:::i;:::-;12559:1;3245:12033;;;-1:-1:-1;;3245:12033:118;;-1:-1:-1;3245:12033:118;;;;;;;;;:::o;:::-;;;;;:::i;:::-;;;;;;;;;;11826:1071;12769:75;11826:1071;12858:6;11826:1071;12810:14;12116:19;;;;12478:82;:78;12179:32;12035:403;12116:19;;3245:12033;;;;;;;;12179:32;3245:12033;12179:32;;;3245:12033;;;;;;12245:30;;;;3245:12033;12065:359;12380:25;12315:36;;;;12380:25;;3245:12033;;12065:359;3245:12033;;:::i;:::-;;;;;;;;12065:359;3245:12033;;12065:359;;;3245:12033;;12065:359;12245:30;12065:359;;3245:12033;12315:36;12065:359;;3245:12033;12380:25;12065:359;;3245:12033;12035:403;:::i;:::-;3245:12033;;;;;12012:436;12488:33;;3245:12033;12523:19;;12179:32;12523;3245:12033;12478:78;;3245:12033;;12523:32;3245:12033;12478:78;;;:::i;:::-;:82;:::i;:::-;12614:33;3245:12033;12614:33;;:::i;:::-;12702:19;;:29;3245:12033;;;;:::i;:::-;;;;12179:32;12669:76;;3245:12033;12657:88;;;:::i;:::-;;;;;:::i;:::-;;12810:14;;12769:75;;:::i;12858:6::-;12854:36;;11826:1071::o;12854:36::-;12873:17;;;-1:-1:-1;12873:17:118;;-1:-1:-1;12873:17:118;3245:12033;;;;;;;;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;;;;-1:-1:-1;3245:12033:118;;-1:-1:-1;3245:12033:118;;;;-1:-1:-1;3245:12033:118;;;;-1:-1:-1;3245:12033:118;;;;;;:::o;:::-;;;;;;;:::i;:::-;;;;-1:-1:-1;3245:12033:118;;;;:::o;:::-;;;;;;;;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;-1:-1:-1;3245:12033:118;;;;;;;:::i;:::-;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;:::o;:::-;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;;;;:::i;:::-;;;-1:-1:-1;3245:12033:118;;;;;;;;;:::o;:::-;;;;;:::i;:::-;;;;;;;;;;2883:1495:119;3245:12033:118;;:::i;:::-;;;;:::i;:::-;;;;3019:1:119;3000:21;;;3245:12033:118;3052:38:119;3068:21;;;:::i;:::-;3052:38;:::i;:::-;3122:35;;;;:::i;:::-;3187:38;3203:21;;;:::i;3187:38::-;3260;3276:21;;;:::i;3260:38::-;3326:35;;;;:::i;:::-;3397:20;;;:::i;:::-;3433:13;3019:1;3448:10;;;;;;3245:12033:118;;;;;:::i;:::-;;;;3000:21:119;4304:67;;3245:12033:118;;4304:67:119;;3245:12033:118;4304:67:119;;;3245:12033:118;4304:67:119;;;3245:12033:118;2883:1495:119;:::o;3460:3::-;3245:12033:118;3492:21:119;;;;:::i;:::-;3245:12033:118;;;:::i;:::-;;;3565:25:119;;;-1:-1:-1;3245:12033:118;3610:14:119;;;1540:1;3610:21;4257:19;;;;:::i;:::-;;;;;;:::i;:::-;;3245:12033:118;3433:13:119;;3561:683;1361:1;3656:29;;1361:1;;-1:-1:-1;3245:12033:118;;3705:18:119;;1540:1;3767:23;;;:::i;:::-;3748:16;;;:42;3652:592;3561:683;;3652:592;1411:1;3815:24;;1411:1;;-1:-1:-1;3245:12033:118;3859:13:119;;;1540:1;3911:23;;;:::i;:::-;3897:11;;;:37;3561:683;;3811:433;1467:1;3959:30;;1467:1;;-1:-1:-1;3245:12033:118;1540:1:119;;4073:23;;;:::i;:::-;3000:21;4053:17;;:43;3561:683;;3955:289;1540:1;4121:47;3811:433;4117:127;3245:12033:118;4188:34:119;;;1540:1;3561:683;;9049:172:65;9158:56;3245:12033:118;9049:172:65;3245:12033:118;9175:31:65;3245:12033:118;;9175:31:65;:::i;:::-;3245:12033:118;;;9158:56:65;;;;;;3245:12033:118;;:::i;8762:967:123:-;3245:12033:118;;:::i;:::-;;-1:-1:-1;;8920:17:123;-1:-1:-1;;8993:3:123;8972:12;;;;;3245:12033:118;;8968:23:123;;;;;9016:27;:15;;:27;:15;;:::i;:::-;;:27;3245:12033:118;;;;;9016:27:123;:89;;;8993:3;9012:305;;8993:3;9335:27;9016;9335:15;:12;;;:15;:::i;:27::-;:89;;;8993:3;9331:196;;8993:3;-1:-1:-1;3245:12033:118;;8953:13:123;;9331:196;9481:12;;;9456:56;9481:30;:25;:15;8972:12;9481;3245:12033:118;9481:12:123;;:15;:::i;:::-;;:25;;:30;;9456:56;:::i;:::-;9331:196;;;;;9335:89;8237:14;;;;3245:12033:118;;;9366:37:123;:25;:15;:12;;;:15;:::i;:::-;;:25;;3245:12033:118;-1:-1:-1;;;;;;3245:12033:118;;;9366:37:123;3245:12033:118;9366:58:123;9335:89;;9012:305;9164:12;;;;9135:68;9151:51;9164:30;:25;:15;:12;;;:15;:::i;:::-;;:25;;:30;;9151:51;:::i;9135:68::-;9266:12;9237:65;9253:48;9164:30;:25;9266:15;:12;;;:15;:::i;:::-;;:25;;:30;;9253:48;:::i;9237:65::-;9012:305;;;9016:89;3245:12033:118;;;;;;;9047:37:123;:25;:15;:12;;;:15;:::i;:37::-;3245:12033:118;9047:58:123;9016:89;;8968:23;;;;;;;;9575:14;;9571:46;;3245:12033:118;;:::i;:::-;;;;9635:87:123;;;3245:12033:118;9016:27:123;9635:87;;3245:12033:118;8762:967:123;:::o;9571:46::-;9598:19;;;-1:-1:-1;9598:19:123;;-1:-1:-1;9598:19:123;10579:974;3245:12033:118;;:::i;:::-;10697:1:123;10680:867;10725:3;10704:12;;;;;3245:12033:118;;10700:23:123;;;;;10749:27;;:15;;10748:28;10749:15;;:::i;10748:28::-;10744:42;;3245:12033:118;;;10804:58:123;:37;:25;:15;:12;;;:15;:::i;:37::-;-1:-1:-1;;;;;;3245:12033:118;;;10804:58:123;;10800:72;;10804:25;10907:15;:12;:30;:12;;:15;:::i;:::-;;:25;;:30;;3245:12033:118;11162:2:123;3245:12033:118;;11147:17:123;11143:31;;11204:58;11211:50;11236:24;;;:::i;:::-;11211:50;:::i;11204:58::-;3245:12033:118;-1:-1:-1;;;;;3245:12033:118;;11280:10:123;11276:24;;11417;;;11322:214;11481:39;11497:22;11385:58;11392:50;11417:24;;;:::i;11385:58::-;11497:22;;:::i;11481:39::-;3245:12033:118;11322:214:123;3245:12033:118;;:::i;:::-;-1:-1:-1;;;;;3245:12033:118;;;8525:14:123;;;11322:214;3245:12033:118;;10907:30:123;11322:214;;3245:12033:118;;11322:214:123;10749:27;11322:214;;3245:12033:118;11315:221:123;:::o;11276:24::-;10704:12;11292:8;;;3245:12033:118;11292:8:123;;;10685:13;3245:12033:118;10685:13:123;;;;;11143:31;11166:8;;;3245:12033:118;10704:12:123;11166:8;;;10800:72;10864:8;;3245:12033:118;10704:12:123;10864:8;;;10700:23;;;;;;;10579:974::o;1990:238:56:-;;;;;3884:14;;3880:38;;3245:12033:118;;3995:18:56;;;:::i;:::-;4049;;;:::i;:::-;4106:20;4101:1;4106:20;;;:::i;:::-;3245:12033:118;-1:-1:-1;;4179:7:56;;;;;;4577:42;;;;;;;;;;:::i;:::-;2174:47;1990:238;:::o;4168:9::-;4217;;;;:::i;:::-;;3245:12033:118;4250:16:56;;;;;4246:51;;4315:6;;;:26;;;;4168:9;4311:55;;;4101:1;4392:9;:14;:9;;;;:::i;:::-;;:14;3245:12033:118;4380:26:56;;;;:::i;:::-;3245:12033:118;;;;4448:33:56;;;;:::i;:::-;3245:12033:118;;;4168:9:56;;;4311:55;4350:16;;;-1:-1:-1;4350:16:56;;-1:-1:-1;4350:16:56;4315:26;4325:16;;;;;4315:26;;;4246:51;4275:22;;;-1:-1:-1;4275:22:56;;-1:-1:-1;4275:22:56;3880:38;3907:11;;;-1:-1:-1;3907:11:56;;-1:-1:-1;3907:11:56;3245:12033:118;-1:-1:-1;;3245:12033:118;;;;;;;;:::o;:::-;-1:-1:-1;;3245:12033:118;;;;;;;;:::o;:::-;;;;;;;;;:::o;:::-;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;:::i;4484:686:65:-;4577:2;4573:6;;4577:2;;;4602:31;4619:13;4625:6;4602:31;4625:6;3245:12033:118;;;;4625:6:65;3245:12033:118;;;;4619:13:65;4577:2;3245:12033:118;;;;;;-1:-1:-1;;;;;;3245:12033:118;4602:31:65;;;3245:12033:118;;;;;;;;;4569:595:65;4658:7;4654:11;;4658:7;;;4688:51;4705:33;4715:22;4723:12;4724:6;4688:51;4724:6;3245:12033:118;;;;4723:12:65;3245:12033:118;;;;4715:22:65;3245:12033:118;;;8428:1:65;3245:12033:118;;;8428:1:65;3245:12033:118;;8422:19:65;8300:148;;4705:33;4577:2;3245:12033:118;;;;;;-1:-1:-1;;;;;;3245:12033:118;4688:51:65;;;3245:12033:118;;;;;;;;;4650:514:65;4764:7;4760:11;;4764:7;;;4794:51;4811:33;4821:22;4829:12;4830:6;4794:51;4830:6;3245:12033:118;;;;4830:6:65;4829:12;:::i;4821:22::-;3245:12033:118;8200:10:65;3245:12033:118;;;;;;;8195:21:65;8196:14;3245:12033:118;;;8168:49:65;3245:12033:118;;;;;;8279:7:65;3245:12033:118;;;8266:21:65;8046:248;;4811:33;4577:2;3245:12033:118;;;;;;-1:-1:-1;;;;;;3245:12033:118;4794:51:65;;;3245:12033:118;;;;;;;;;4756:408:65;4942:31;;4959:13;4902:85;4959:13;;:::i;:::-;4577:2;3245:12033:118;4942:31:65;;;;;;3245:12033:118;;;;;;;4942:31:65;4902:85;:::i;:::-;5117:36;5065:30;5071:23;5072:17;5073:10;3245:12033:118;;5073:10:65;:::i;:::-;3245:12033:118;;;;5072:17:65;5071:23;:::i;5065:30::-;5117:36;4577:2;3245:12033:118;5117:36:65;;;4942:31;5117:36;;;:::i;11118:367:118:-;;3245:12033;11220:13;3245:12033;11242:3;11239:1;11235:5;;;;;11282:1;11277:6;;11282:1;;11286:2;11277:17;11337:25;11346:1;3245:12033;;11239:1;3245:12033;;;;;;;11337:25;:::i;:::-;11323:40;11377:92;11384:9;;;11242:3;11346:1;3245:12033;11220:13;;11377:92;11421:8;;;:::i;:::-;11413:16;;-1:-1:-1;;3245:12033:118;;;;;;;;11377:92;;11277:17;11291:3;11277:17;;11235:5;;;11118:367::o;11577:156::-;;11691:9;;;;;:35;;;;11577:156;11684:42;;;11577:156;:::o;11691:35::-;3245:12033;;;;11713:1;3245:12033;;;;;;11713:1;3245:12033;;;;;;;;;;;;;11725:1;3245:12033;;;;;11704:22;11691:35;;;;;3245:12033;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;:::i;:::-;;-1:-1:-1;;;;;3245:12033:118;;;;;;;;;;;;;;;;;;:::i;:::-;;;;:::i;:::-;;;;;;;;;;;;:::i;2339:494:119:-;2788:28;2730:44;2468:30;2442:384;2339:494;2468:30;3245:12033:118;;;;;;;;;;;;;;;-1:-1:-1;;;;;;3245:12033:118;2468:30:119;;;3245:12033:118;;;;;;;;;2468:30:119;2564:33;2512:38;3245:12033:118;2468:30:119;2532:17;;3245:12033:118;;;;;2512:38:119;2564:33;3245:12033:118;2581:15:119;;3245:12033:118;;;2564:33:119;;;2468:30;2564:33;;3245:12033:118;;;;;;;2564:33:119;2631:21;;;;3245:12033:118;2788:28:119;2805:10;2611:45;3245:12033:118;;;-1:-1:-1;;;;;3245:12033:118;;;2611:45:119;2690:25;2730:44;3245:12033:118;2670:46:119;3245:12033:118;2468:30:119;2690:25;;3245:12033:118;;;;;2670:46:119;2747:26;;3245:12033:118;;;2730:44:119;;;2468:30;2730:44;;3245:12033:118;;;;;;;2730:44:119;;3245:12033:118;;2730:44:119;;;;;;:::i;:::-;2805:10;3245:12033:118;;;2788:28:119;;;2468:30;2788:28;;3245:12033:118;;;;;;;2788:28:119;;3245:12033:118;;2788:28:119;;;;;;:::i;:::-;2442:384;:::i;14804:190:118:-;14919:20;;;:68;14804:190;:::o;14919:68::-;3245:12033;;;;;;;14804:190;:::o;3366:228:55:-;;;;3548:39;3366:228;3548:39;:::i;2089:399:58:-;3000:21:119;2216:11:58;;3245:12033:118;;;3000:21:119;3245:12033:118;;;;;;;2237:9:58;;3245:12033:118;-1:-1:-1;3245:12033:118;;3000:21:119;2264:48:58;2351:9;3245:12033:118;;;;;;1938:75:59;;;;3245:12033:118;;;3000:21:119;2392:39:58;;;:::i;:::-;3245:12033:118;;;;3000:21:119;3245:12033:118;;;;;;;;2089:399:58;:::o;:::-;;2216:11;;;3245:12033:118;;;;;;;;;;;2237:9:58;;3245:12033:118;-1:-1:-1;3245:12033:118;;2268:8:58;;2264:48;;2216:11;2351:9;;3245:12033:118;;;;;;1938:75:59;;;;3245:12033:118;;;2392:39:58;;;;:::i;:::-;3245:12033:118;;;;;;;;;;;;2089:399:58;:::o;2264:48::-;3245:12033:118;;;;;;;;;:::i;:::-;-1:-1:-1;3245:12033:118;;2292:9:58;:::o;3245:12033:118:-;;;;;;;;;;;;:::o;:::-;;;;:::o;:::-;;;-1:-1:-1;;;3245:12033:118;;;;;;;;;;;;;;;;;-1:-1:-1;;;3245:12033:118;;;;;;;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;-1:-1:-1;;;;;3245:12033:118;;;;:::o;1205:1510:65:-;1323:20;;;:::i;:::-;3245:12033:118;;;;1453:9:65;;-1:-1:-1;1501:14:65;;-1:-1:-1;3245:12033:118;;;;1509:6:65;3245:12033:118;1449:1238:65;1579:1;1571:9;;1579:1;;1634:20;1782:11;1634:20;1782:11;1787:6;1740:7;1692:13;1634:20;1842:9;1634:20;;:::i;1692:13::-;3245:12033:118;;;;;;1740:7:65;1787:6;3245:12033:118;;;;;;1782:11:65;;;:::i;1567:1120::-;1880:1;1872:9;;1880:1;;1943:20;2275:8;1943:20;2243:16;3245:12033:118;1943:20:65;2194:16;1943:20;;2336:11;1943:20;;:::i;:::-;2013;;2118:15;2013:20;2058;2013;;;:::i;:::-;2058;;:::i;:::-;3245:12033:118;;;;;;;;2118:15:65;3245:12033:118;;2105:29:65;;3245:12033:118;;;;;;;2194:16:65;2188:23;;3245:12033:118;;;;;;;2243:16:65;2237:23;3245:12033:118;;;;;;1868:819:65;2376:1;2368:9;2376:1;;2575:34;2450:6;3245:12033:118;2449:12:65;2450:6;2589:19;2450:6;3245:12033:118;;;;;;;2450:6:65;2449:12;:::i;:::-;3245:12033:118;2503:6:65;2495:59;2508:1;2503:6;;;2495:59;:::i;:::-;2589:19;:::i;2364:323::-;3245:12033:118;;-1:-1:-1;;;2640:36:65;;3245:12033:118;1393:1:65;2640:36;;3245:12033:118;;;;;;;;;;;;;2640:36:65;3245:12033:118;;;;;;;;;;;;;:::o;1564:269:58:-;1649:11;;;3245:12033:118;;;1663:1:58;3245:12033:118;;;;;;;1667:9:58;;3245:12033:118;-1:-1:-1;1645:87:58;;1758:9;3245:12033:118;;-1:-1:-1;;;;;;3245:12033:118;1758:22:58;;3245:12033:118;1758:22:58;:::i;:::-;3245:12033:118;;;;;1791:16:58;3245:12033:118;;1791:16:58;:::i;:::-;3245:12033:118;;1564:269:58;:::o;1645:87::-;3245:12033:118;;-1:-1:-1;;;1699:22:58;;1649:11;1699:22;;;3245:12033:118;;;;;;-1:-1:-1;;;3245:12033:118;;;;1699:22:58;;;4479:308:119;3245:12033:118;;:::i;:::-;;2216:11:58;;;3245:12033:118;;;4620:1:119;3245:12033:118;;;;;;;2237:9:58;;3245:12033:118;-1:-1:-1;3245:12033:118;;2216:11:58;2351:9;;3245:12033:118;;;;;;1938:75:59;;;;3245:12033:118;;;4620:1:119;2392:39:58;;;:::i;:::-;3245:12033:118;;;;4620:1:119;3245:12033:118;;;;;;;;4624:1:119;5413:13:58;4624:1:119;5408:106:58;5428:5;4620:1:119;5428:5:58;;;;4653:35:119;;;;4718:25;4653:35;;:::i;:::-;4718:25;;:::i;:::-;4760:20;3245:12033:118;;:::i;:::-;-1:-1:-1;;;;;;3245:12033:118;;;;;;4760:20:119;2216:11:58;4760:20:119;;3245:12033:118;4479:308:119;:::o;5435:3:58:-;3245:12033:118;;-1:-1:-1;;;;;;5468:16:58;3245:12033:118;;5468:16:58;:::i;:::-;3245:12033:118;;;;;;;;;;5501:1:58;3245:12033:118;;;;;;;;;-1:-1:-1;;;;;;3245:12033:118;;;;;;5454:49:58;5435:3;3245:12033:118;5413:13:58;;;3313:349;3245:12033:118;;9164:30:123;3466:31:58;3245:12033:118;;9164:30:123;3512:8:58;3617:38;3512:8;1938:75:59;3617:38:58;:::i;3313:349::-;3245:12033:118;;11258:1:123;3466:31:58;3245:12033:118;;11258:1:123;1938:75:59;3617:38:58;3512:8;1938:75:59;3617:38:58;:::i;3313:349::-;3245:12033:118;;;3466:31:58;3245:12033:118;;;;;;1938:75:59;;3245:12033:118;-1:-1:-1;3245:12033:118;;11439:1:123;3617:38:58;;;:::i;3313:349::-;;3245:12033:118;;3466:31:58;;3245:12033:118;;3512:8:58;;3508:48;;1938:75:59;3617:38:58;3245:12033:118;1938:75:59;3617:38:58;:::i;3508:48::-;3245:12033:118;;;;;;;;:::i;2744:313:58:-;3245:12033:118;;2876:25:58;9164:30:123;2876:25:58;3245:12033:118;;-1:-1:-1;;3245:12033:118;;;;;;;;;;;9164:30:123;1938:75:59;3245:12033:118;-1:-1:-1;3245:12033:118;;3012:38:58;;;:::i;2744:313::-;3245:12033:118;;2876:25:58;11516:2:123;2876:25:58;3245:12033:118;;-1:-1:-1;;3245:12033:118;;;;;;;;;;;1938:75:59;;3245:12033:118;-1:-1:-1;3245:12033:118;;3012:38:58;;;:::i;3245:12033:118:-;;;;;-1:-1:-1;;3245:12033:118;;:::o;823:320:65:-;3245:12033:118;;;;;961:5:65;;;1123:13;;823:320;:::o;968:3::-;3245:12033:118;;-1:-1:-1;;3245:12033:118;;;;;;;;1051:11:65;;;;:::i;:::-;3245:12033:118;;;;;;;;;-1:-1:-1;;;;;3245:12033:118;;;;;;;;;;1060:1:65;3245:12033:118;;1037:66:65;3245:12033:118;;;;;;;;;;;;;;968:3:65;1012:91;;;;:::i;:::-;968:3;;:::i;:::-;936:23;;;;3245:12033:118;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;;;;:::i;:::-;;;;;;;;:::o;5627:1354:65:-;3245:12033:118;5873:1:65;3245:12033:118;;5873:1:65;3245:12033:118;6063:110:65;6191:86;;6064;;;;;;;3245:12033:118;;6190:110:65;6191:86;;;;3245:12033:118;;6062:239:65;6511:66;6384;6364:86;;3245:12033:118;;6490:110:65;6491:86;3245:12033:118;;6362:239:65;6811:66;6684;;6664:86;;3245:12033:118;;6790:110:65;6791:86;3245:12033:118;;6662:239:65;6965:8;3245:12033:118;;;6965:8:65;3245:12033:118;;;;6965:8:65;6951:23;5627:1354;:::o;6202:380:58:-;3245:12033:118;;-1:-1:-1;;3245:12033:118;;;;;;;6414:3:58;-1:-1:-1;;;;;;6437:7:58;;;;:::i;:::-;3245:12033:118;;6433:86:58;;6414:3;;;:::i;:::-;6382:22;;6433:86;6403:1;3245:12033:118;;;;;;;6546:29:58;;;:::i;4448:2801:55:-;;;;4610:14;;4606:38;;3245:12033:118;;4702:14:55;;;4698:40;;4810:1;4813:13;;;;;;5012:14;4810:1;5012:14;;:32;;;4793:159;5012:56;;;4793:159;5008:169;;5235:35;5249:20;;;:::i;:::-;5235:35;:::i;:::-;3245:12033:118;;;:::i;:::-;;;;;5219:52:55;;;3245:12033:118;;;;;:::i;:::-;;;;;5219:52:55;5313:22;;3245:12033:118;;;;;:::i;:::-;;;;;5219:52:55;5376:34;;3245:12033:118;;;;5376:34:55;;3245:12033:118;;5455:29:55;5494:940;5501:14;;;5494:940;3245:12033:118;;;;6490:52:55;;3245:12033:118;6642:14:55;;3245:12033:118;6622:41:55;6618:71;;3245:12033:118;;-1:-1:-1;;3245:12033:118;;;6763:445:55;3245:12033:118;;6770:21:55;;;6823:20;;;:::i;:::-;6872;;;:::i;:::-;3245:12033:118;6934:18:55;3245:12033:118;;;;;;6934:18:55;3245:12033:118;;;7006:139:55;5219:52;7006:139;3245:12033:118;;7006:139:55;7158:39;:14;;3245:12033:118;;7158:39:55;;:::i;:::-;3245:12033:118;6763:445:55;;6770:21;;7225:17;6770:21;;7225:14;:17;:::i;:::-;3245:12033:118;4448:2801:55;:::o;6618:71::-;5114:17;;;3245:12033:118;6672:17:55;;3245:12033:118;6672:17:55;6490:52;6523:19;;;3245:12033:118;6523:19:55;;3245:12033:118;6523:19:55;5494:940;5548:16;;;:::i;:::-;3245:12033:118;5663:31:55;5625:24;4810:1;3245:12033:118;;5625:24:55;;;:::i;:::-;5663:31;;;;:::i;:::-;5745:42;;;;;;;:::i;:::-;5806:20;5219:52;5806:20;;3245:12033:118;5806:25:55;;5802:622;5806:25;;;5855:14;;;;;;;;3245:12033:118;;;5855:41:55;5851:174;5880:16;5920:5;5880:16;5989;;;;;:::i;:::-;;;:::i;:::-;5494:940;;5802:622;4810:1;6049:25;:40;;;5802:622;6045:379;;;6126:18;;;5219:52;6126:40;:18;3245:12033:118;6126:45:55;:18;;;3245:12033:118;;6126:40:55;;:::i;:::-;;:45;3245:12033:118;6126:45:55;;:::i;6045:379::-;3245:12033:118;6353:55:55;3245:12033:118;;6282:1:55;6353:55;3245:12033:118;;;6353:55:55;;:::i;6049:40::-;6078:11;;;6049:40;;5008:169;3245:12033:118;;;;;;5084:47:55;;5152:9;:14;:9;;:::i;:::-;;:14;3245:12033:118;5145:21:55;:::o;5012:56::-;5048:9;;;;:::i;:::-;;3245:12033:118;5048:20:55;5012:56;;:32;5030:14;4810:1;5030:14;;5012:32;;4798:13;4847:9;;;;:::i;:::-;;3245:12033:118;4866:13:55;4873:5;;;:::i;:::-;4866:13;;:::i;:::-;;3245:12033:118;-1:-1:-1;4847:38:55;4843:67;;4810:1;3245:12033:118;4798:13:55;;4698:40;4725:13;;;3245:12033:118;4725:13:55;;3245:12033:118;4725:13:55;2284:287:59;;;;3245:12033:118;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;-1:-1:-1;;3245:12033:118;;;;2560:3:59;;;3244:193;3251:16;3245:12033:118;3251:16:59;;;;;3494:8;;;-1:-1:-1;;;3245:12033:118;3494:132:59;3636:173;;;;;;;;;;;2284:287::o;3494:132::-;3598:24;3606:15;3598:28;3606:15;;:::i;:::-;3598:24;:::i;:::-;:28;:::i;:::-;3494:132;;;3269:16;3301:65;;;;;;3245:12033:118;;;;;;;;;;;;;;;;;3269:16:59;-1:-1:-1;;3245:12033:118;;;;;;3244:193:59;3245:12033:118;;:::i;7197:131:56:-;7277:1;7272:6;;;7268:20;;-1:-1:-1;;3245:12033:118;;;;;;;7305:12:56;;;:::i;:::-;7277:1;3245:12033:118;;;;;;;7197:131:56;:::o;7268:20::-;7280:8;3245:12033:118;7280:8:56;:::o;5056:1349::-;;;;3245:12033:118;;;5309:1063:56;5332:1;5316:12;;;:::i;:::-;3245:12033:118;5316:17:56;;;5369:45;:41;5381:12;5332:1;5375:19;5381:12;;;:::i;:::-;3245:12033:118;5375:19:56;:::i;:::-;3245:12033:118;5369:41:56;:::i;:45::-;5428:9;3245:12033:118;5457:9:56;3245:12033:118;5452:836:56;5468:7;;;;;;6302;;;6339:16;6338:23;6302:7;6339:16;;:::i;:::-;3245:12033:118;;;;6338:23:56;5309:1063;;;5457:9;5510:12;;;;;;;:::i;:::-;3245:12033:118;5563:5:56;;;;:::i;:::-;:11;:44;;;5457:9;5719:442;;;5786:9;6230:8;5332:1;5786:9;5850:1;5771:40;5786:9;;;;:::i;:::-;3245:12033:118;5797:13:56;5804:5;;;:::i;:::-;5797:13;;:::i;:::-;3245:12033:118;5771:40:56;;;:::i;:::-;3245:12033:118;;5719:442:56;;6179:18;;;;:::i;:::-;3245:12033:118;;;;;6230:8:56;6215:23;;;;:::i;:::-;3245:12033:118;;5457:9:56;;;;;5719:442;5647:7;;5332:1;5647:7;;5646:22;-1:-1:-1;5646:22:56;;5943:9;5332:1;5943:9;5928:35;6230:8;5943:9;5954:8;5943:9;;;;;;;;;:::i;:::-;3245:12033:118;5954:8:56;;:::i;:::-;3245:12033:118;5928:35:56;;;:::i;:::-;3245:12033:118;;;;5878:283:56;5719:442;;5878:283;6230:8;6063:9;;;5332:1;6063:9;;;;;;:::i;:::-;3245:12033:118;;;5878:283:56;5719:442;;5563:44;5588:5;5578:16;5588:5;;;:::i;:::-;5578:16;;:::i;:::-;3245:12033:118;5332:1:56;5599:7;;5578:29;5563:44;;5316:17;;;;;;6389:9;5316:17;;6389:9;:::i;13095:196:55:-;;13147:13;3245:12033:118;13172:113:55;13179:6;;;13095:196;:::o;13172:113::-;3245:12033:118;-1:-1:-1;;3245:12033:118;;;;;;;13210:1:55;13201:10;;3245:12033:118;;;;13172:113:55;;;13353:537;13422:462;;;;;;;;-1:-1:-1;;;;;13422:462:55;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;13353:537;:::o;3245:12033:118:-;;;;;;;:::i;:::-;;;;-1:-1:-1;3245:12033:118;;-1:-1:-1;3245:12033:118;;;;;;:::o;7670:742:55:-;3245:12033:118;;:::i;:::-;;;;7846:33:55;7864:15;;;3245:12033:118;;;7846:33:55;;:::i;:::-;3245:12033:118;;;7935:210:55;7942:15;;;;;;7935:210;3245:12033:118;;;;8175:27:55;8348:28;3245:12033:118;;;8175:27:55;;:::i;:::-;3245:12033:118;;;8287:13:55;7998;8287;;;3245:12033:118;;:::i;:::-;;;;8246:55:55;7864:15;8246:55;;3245:12033:118;7998:13:55;8246:55;;3245:12033:118;;;;8348:28:55;:::i;7935:210::-;7998:13;:24;:13;;;;;:24;:::i;:::-;;3245:12033:118;7978:50:55;;;7974:94;;3245:12033:118;;7935:210:55;;;7974:94;8048:5;;;9445:1927;;;9640:15;;;3245:12033:118;;;9737:21:55;;;;:::i;:::-;9794;;;;:::i;:::-;9830:9;3245:12033:118;9841:10:55;;;;;;10071:20;;;;10182:1157;;3245:12033:118;10189:12:55;;;:::i;:::-;3245:12033:118;10189:17:55;;;3245:12033:118;10248:9:55;3245:12033:118;10272:851:55;10279:7;;;;;;11204:10;;;11228:101;;;;;10182:1157;;10272:851;10320:12;;;;;;:::i;:::-;3245:12033:118;10355:5:55;;;;;;:::i;:::-;:11;:44;;;10272:851;10351:758;;;3245:12033:118;10496:9:55;10481:40;10496:9;10507:13;10514:5;10496:9;10677:6;10496:9;;10561:8;10496:9;;:::i;:::-;3245:12033:118;10514:5:55;;:::i;10507:13::-;3245:12033:118;10481:40:55;;;:::i;:::-;10466:55;;;;:::i;10561:8::-;10543:26;;;;:::i;:::-;3245:12033:118;;10677:6:55;;:::i;:::-;10351:758;10272:851;;10351:758;3245:12033:118;;;;9640:15:55;10801:14;;;3245:12033:118;-1:-1:-1;10781:41:55;10777:70;;3245:12033:118;10899:9:55;10967:8;10899:9;;10884:43;10899:9;;;;:::i;:::-;3245:12033:118;10910:16:55;;;:::i;:::-;10884:43;;;:::i;10967:8::-;10949:26;;;;:::i;:::-;3245:12033:118;;;;10351:758:55;10272:851;;10777:70;10831:16;;;3245:12033:118;10831:16:55;;3245:12033:118;10831:16:55;10355:44;10380:5;10370:16;10380:5;;;:::i;:::-;10370:16;;:::i;:::-;3245:12033:118;;10391:7:55;;10370:29;10355:44;;10189:17;;;;;11356:9;10189:17;11356:9;:::i;9830:::-;3245:12033:118;9880:13:55;9985:25;9880:13;;;9640:15;9880:25;:13;;9894:10;;;;:::i;:::-;9880:25;;:::i;:::-;;:30;3245:12033:118;9868:42:55;;;;:::i;:::-;3245:12033:118;9985:13:55;3245:12033:118;;;9985:25:55;;:::i;:::-;;3245:12033:118;;;9952:64:55;;;;:::i;:::-;3245:12033:118;;9830:9:55;;11444:188;;11527:37;:13;;;;3245:12033:118;;11527:37:55;;:::i;:::-;3245:12033:118;;;;;;;11444:188:55:o;11706:222::-;11808:30;:13;;;;3245:12033:118;;11808:30:55;;:::i;:::-;3245:12033:118;;;;;;;;11706:222:55;:::o;12000:226::-;12106:30;:13;;;;3245:12033:118;;12106:30:55;;:::i;:::-;3245:12033:118;;;-1:-1:-1;;3245:12033:118;;;;;12000:226:55:o;713:2:59:-;;;;;;;;;:::o;6686:471:56:-;6806:1;6800:7;6806:1;;3245:12033:118;6829:141:56;;;;3245:12033:118;6829:141:56;6795:356;6686:471::o;6795:356::-;7000:141;3245:12033:118;7000:141:56;;;;3245:12033:118;7000:141:56;6795:356;6686:471::o","linkReferences":{},"immutableReferences":{"76100":[{"start":2214,"length":32},{"start":5547,"length":32}],"76103":[{"start":2289,"length":32},{"start":3207,"length":32}]}},"methodIdentifiers":{"MMR_ROOT_PAYLOAD_ID()":"af8b91d6","_apk()":"afb5670a","_digestParaId()":"e455995b","noOp((uint256,uint256,(uint256,uint256,uint256,bytes32),(uint256,uint256,uint256,bytes32)),((((bytes2,bytes)[],uint32,uint64),uint256[5],bytes32[3],bytes32[6],bytes,bytes32[3],(uint8,uint32,bytes32,(uint64,uint32,bytes32),bytes32,uint256),bytes32[]),((uint256,uint256,bytes)[],bytes32[],uint256)))":"42a947b1","supportsInterface(bytes4)":"01ffc9a7","verify(bytes,bytes)":"f7e83aee"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.30+commit.73712a01\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"apkProof\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"digestParaId\",\"type\":\"uint32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"EmptyLeaves\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"EmptyTree\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"EmptyTree\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidAggregateProof\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidMmrProof\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidParachainHeaderProof\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"LeafIndexOutOfBounds\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MissingApkCommitment\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MmrRootHashMissing\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OutOfBoundsLeaves\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ProofExhausted\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"SuperMajorityRequired\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"TimestampNotFound\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnconsumedProof\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnknownAuthoritySet\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnsortedLeaves\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnsortedLeaves\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MMR_ROOT_PAYLOAD_ID\",\"outputs\":[{\"internalType\":\"bytes2\",\"name\":\"\",\"type\":\"bytes2\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_apk\",\"outputs\":[{\"internalType\":\"contract IApkProof\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_digestParaId\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"latestHeight\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"beefyActivationBlock\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"len\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"blsPoseidonHash\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"ecdsaMerkleRoot\",\"type\":\"bytes32\"}],\"internalType\":\"struct AuthoritySet\",\"name\":\"currentAuthoritySet\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"len\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"blsPoseidonHash\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"ecdsaMerkleRoot\",\"type\":\"bytes32\"}],\"internalType\":\"struct AuthoritySet\",\"name\":\"nextAuthoritySet\",\"type\":\"tuple\"}],\"internalType\":\"struct BeefyConsensusState\",\"name\":\"s\",\"type\":\"tuple\"},{\"components\":[{\"components\":[{\"components\":[{\"components\":[{\"internalType\":\"bytes2\",\"name\":\"id\",\"type\":\"bytes2\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"internalType\":\"struct Payload[]\",\"name\":\"payload\",\"type\":\"tuple[]\"},{\"internalType\":\"uint32\",\"name\":\"blockNumber\",\"type\":\"uint32\"},{\"internalType\":\"uint64\",\"name\":\"validatorSetId\",\"type\":\"uint64\"}],\"internalType\":\"struct Commitment\",\"name\":\"commitment\",\"type\":\"tuple\"},{\"internalType\":\"uint256[5]\",\"name\":\"bitlist\",\"type\":\"uint256[5]\"},{\"internalType\":\"bytes32[3]\",\"name\":\"apk\",\"type\":\"bytes32[3]\"},{\"internalType\":\"bytes32[6]\",\"name\":\"apk2\",\"type\":\"bytes32[6]\"},{\"internalType\":\"bytes\",\"name\":\"apkProof\",\"type\":\"bytes\"},{\"internalType\":\"bytes32[3]\",\"name\":\"signature\",\"type\":\"bytes32[3]\"},{\"components\":[{\"internalType\":\"uint8\",\"name\":\"version\",\"type\":\"uint8\"},{\"internalType\":\"uint32\",\"name\":\"parentNumber\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"parentHash\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"uint64\",\"name\":\"id\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"len\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"}],\"internalType\":\"struct AuthoritySetCommitment\",\"name\":\"nextAuthoritySet\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"extra\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"leafIndex\",\"type\":\"uint256\"}],\"internalType\":\"struct BeefyMmrLeaf\",\"name\":\"latestMmrLeaf\",\"type\":\"tuple\"},{\"internalType\":\"bytes32[]\",\"name\":\"mmrProof\",\"type\":\"bytes32[]\"}],\"internalType\":\"struct BlsApkRelayChainProof\",\"name\":\"relay\",\"type\":\"tuple\"},{\"components\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"header\",\"type\":\"bytes\"}],\"internalType\":\"struct Parachain[]\",\"name\":\"parachains\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"proof\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint256\",\"name\":\"leafCount\",\"type\":\"uint256\"}],\"internalType\":\"struct ParachainProof\",\"name\":\"parachain\",\"type\":\"tuple\"}],\"internalType\":\"struct BlsApkBeefyConsensusProof\",\"name\":\"p\",\"type\":\"tuple\"}],\"name\":\"noOp\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"previousState\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"proof\",\"type\":\"bytes\"}],\"name\":\"verify\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"stateMachineId\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"height\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"overlayRoot\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"stateRoot\",\"type\":\"bytes32\"}],\"internalType\":\"struct StateCommitment\",\"name\":\"commitment\",\"type\":\"tuple\"}],\"internalType\":\"struct IntermediateState[]\",\"name\":\"\",\"type\":\"tuple[]\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"details\":\"Naming each signer and proving their public key against the authority set's keyset root costs roughly 17k gas per signer, for the G2 addition and the compression needed to rebuild the leaf. At a few hundred validators that dominates everything else. Here a SNARK does that work instead. The prover shows that an aggregate public key corresponds to exactly the validators named in a bitlist, against a commitment to the whole set, and the contract checks one proof and one pairing. Nothing in the calldata or the verification grows with the number of signers. The commitment the proof is checked against does not come from the relay chain's MMR leaf, the way the keyset root does. Hyperbridge computes it over the relay's next authority set and publishes it in a header digest, so a client picks it up from a header it has already verified and carries it in its consensus state. That is what `AuthoritySet.blsPoseidonHash` holds, and why the state has to be seeded with the starting set's commitment at initialisation. Requires Prague for the EIP-2537 precompiles.\",\"kind\":\"dev\",\"methods\":{\"noOp((uint256,uint256,(uint256,uint256,uint256,bytes32),(uint256,uint256,uint256,bytes32)),((((bytes2,bytes)[],uint32,uint64),uint256[5],bytes32[3],bytes32[6],bytes,bytes32[3],(uint8,uint32,bytes32,(uint64,uint32,bytes32),bytes32,uint256),bytes32[]),((uint256,uint256,bytes)[],bytes32[],uint256)))\":{\"details\":\"Only here so the structs appear in the ABI, which is what the Rust bindings are generated from. `verify` takes bytes, so without this they would be invisible.\"},\"supportsInterface(bytes4)\":{\"details\":\"Returns true if this contract implements the interface defined by `interfaceId`. See the corresponding https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section] to learn more about how these ids are created. This function call must use less than 30 000 gas.\"},\"verify(bytes,bytes)\":{\"details\":\"IConsensusV2 entry point.\"}},\"title\":\"BEEFY consensus verified by an aggregate public key proof\",\"version\":1},\"userdoc\":{\"errors\":{\"InvalidAggregateProof()\":[{\"notice\":\"The APK proof or the aggregate signature did not verify.\"}],\"InvalidMmrProof()\":[{\"notice\":\"The mmr leaf was not in the tree the commitment attests to.\"}],\"InvalidParachainHeaderProof()\":[{\"notice\":\"A parachain header was not in the heads root.\"}],\"MissingApkCommitment()\":[{\"notice\":\"The authority set has no APK commitment yet, so no proof against it can be checked.\"}],\"MmrRootHashMissing()\":[{\"notice\":\"The commitment carried no mmr root payload.\"}],\"SuperMajorityRequired()\":[{\"notice\":\"Fewer than two thirds of the set signed.\"}],\"UnknownAuthoritySet()\":[{\"notice\":\"The commitment was signed by a set this client does not know.\"}]},\"kind\":\"user\",\"methods\":{\"MMR_ROOT_PAYLOAD_ID()\":{\"notice\":\"The payload id for the mmr root in a BEEFY commitment, \\\"mh\\\"\"},\"_apk()\":{\"notice\":\"The APK proof verifier, holding the circuit's verifying key.\"},\"_digestParaId()\":{\"notice\":\"The parachain whose header digests carry apk commitments, which is hyperbridge. Every parachain in a proof is proven against the heads root, so a digest from any of them is authentic; only this one's says anything about the relay chain's authorities.\"}},\"notice\":\"Verifies BEEFY finality without touching individual validator keys.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/consensus/BlsBeefy.sol\":\"BlsBeefy\"},\"evmVersion\":\"prague\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@gnark-apk-proofs/=lib/gnark-apk-proofs/solidity/contracts/\",\":@hyperbridge/core/=node_modules/@hyperbridge/core/contracts/\",\":@openzeppelin/=node_modules/@openzeppelin/\",\":@polytope-labs/=node_modules/@polytope-labs/\",\":@sp1-contracts/=lib/sp1-contracts/contracts/src/\",\":@uniswap/=node_modules/@uniswap/\",\":ds-test/=lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/sp1-contracts/contracts/lib/openzeppelin-contracts/lib/erc4626-tests/\",\":forge-std/=node_modules/forge-std/src/\",\":gnark-apk-proofs/=lib/gnark-apk-proofs/\",\":openzeppelin-contracts/=lib/sp1-contracts/contracts/lib/openzeppelin-contracts/\",\":solidity-stringutils/=lib/solidity-stringutils/\",\":sp1-contracts/=lib/sp1-contracts/contracts/\",\":stringutils/=lib/solidity-stringutils/src/\"],\"viaIR\":true},\"sources\":{\"node_modules/@hyperbridge/core/contracts/interfaces/IConsensusV2.sol\":{\"keccak256\":\"0x71dcb5168f8f0f95effac221bdc49e0f662011c1bf86a9369cc0db183b8ac4c3\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://d84388af7b50f5f31110fe3d711930c816432f8c2ebcc7417639a727760544ff\",\"dweb:/ipfs/QmWp89jgUMhCqrGVDphLAEhJm5kPP6q1y9yid3D2a3JFZ9\"]},\"node_modules/@openzeppelin/contracts/utils/introspection/ERC165.sol\":{\"keccak256\":\"0x2d9dc2fe26180f74c11c13663647d38e259e45f95eb88f57b61d2160b0109d3e\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://81233d1f98060113d9922180bb0f14f8335856fe9f339134b09335e9f678c377\",\"dweb:/ipfs/QmWh6R35SarhAn4z2wH8SU456jJSYL2FgucfTFgbHJJN4E\"]},\"node_modules/@openzeppelin/contracts/utils/introspection/IERC165.sol\":{\"keccak256\":\"0x8891738ffe910f0cf2da09566928589bf5d63f4524dd734fd9cedbac3274dd5c\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://971f954442df5c2ef5b5ebf1eb245d7105d9fbacc7386ee5c796df1d45b21617\",\"dweb:/ipfs/QmadRjHbkicwqwwh61raUEapaVEtaLMcYbQZWs9gUkgj3u\"]},\"node_modules/@polytope-labs/solidity-merkle-trees/src/MerkleMountainRange.sol\":{\"keccak256\":\"0x014237038bb77bdf371b50c1268d02bf7eeaf7068c35483d5d0684ca7c30d544\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://beb3ae60094f49716a2b0cfa6229ee27af1778934cf5f2dfc704433fda5a687b\",\"dweb:/ipfs/QmQ339wEFT9X1woHyFTDbbhUCx8ekD9MGLRnhS3PpYp1pm\"]},\"node_modules/@polytope-labs/solidity-merkle-trees/src/MerkleMultiProof.sol\":{\"keccak256\":\"0xd4f6e6a9eceaa7d1cdf9684bfe7f3f552adf21dfcf7f1372f7943a4c2f15deb7\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://ec900b3e79ea8ef9d275697937d1984e994d10117aa2be066bd73f6a31aedf10\",\"dweb:/ipfs/QmZQGLiJtbvUSEW5H2HEenzxp58fN43XHRY3SoNTC9Hobg\"]},\"node_modules/@polytope-labs/solidity-merkle-trees/src/trie/Bytes.sol\":{\"keccak256\":\"0xd305383358b93285d8fcee512795487484eadfd7b602df16ff4e9b01afbefec7\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://57c86cd2fe6ab591264c5632d503d77f04748d722e4defa9c2badb238ee1f9bd\",\"dweb:/ipfs/QmTZ6xTcaYkRBshq6YZVMx2kkk94ZtJsRM6xLpC5qWT3oA\"]},\"node_modules/@polytope-labs/solidity-merkle-trees/src/trie/Memory.sol\":{\"keccak256\":\"0x59e3a56caa42c1aac30231173439817d38c7f359e40bd36e9bb418d3f82ceab7\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://e060fed46c6b420624166ea02a326f0f566897941bdc322257e690ae134b8179\",\"dweb:/ipfs/QmbXW8yG2ZntjLMyUEmQGcRZroZj4dVZkBhHxK2PFKfUKB\"]},\"node_modules/@polytope-labs/solidity-merkle-trees/src/trie/Node.sol\":{\"keccak256\":\"0xca611969a68f7fe63dcdc742c9caf9bc1b26495561b68f4676c209279a4576ba\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://17b7ec2cf65d484f0a2c188a7ae33430b015b3a55bf10393167f53839e2dde56\",\"dweb:/ipfs/QmTQbT8J7rBRNHFXSR7S4KbpwCteMrFt8mvrzCD1vYYTwX\"]},\"node_modules/@polytope-labs/solidity-merkle-trees/src/trie/polkadot/ScaleCodec.sol\":{\"keccak256\":\"0x9ac4df46e68718f7deaaa5b7443778533f53dc0ff3736cc386cf4991099da2aa\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://85d08b00d9173358323105be975ce85b7e206ba52df2d80a14d2581dec1ddd11\",\"dweb:/ipfs/QmQCqVvSdJUqfhLH8TRBGNDGiEzkGduhRacnYhoKcm7e2a\"]},\"src/consensus/BlsBeefy.sol\":{\"keccak256\":\"0x15657e2fd4f84139b1f018b0bb0ed83b8b75cc7259654d061f9f7c59838cf73d\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://85d7bebe51acb750406079fa98fa4b6fa1945afc18a3291926cb80940b1523cc\",\"dweb:/ipfs/QmNSwQ4bxgrp9cEdS8Lp4V82Etc4rFPCd5cYKgUBfggXTb\"]},\"src/consensus/Codec.sol\":{\"keccak256\":\"0x477b62396db5a5c1d89d0388b0724dde80e0a0423b89c07f7b3b5622ff03b5d4\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://6a584c6500ae347c6f9d76d06a96757b024dcb892601c6d9c1a945b9d76811d6\",\"dweb:/ipfs/QmduKRb2Zr9eqeZbi5TFRw5Dyig3XVY3rMvPkxLktLFYrN\"]},\"src/consensus/Types.sol\":{\"keccak256\":\"0xe4b169eefb4afd0f83f09335f4460705fe96e7736314c2030235720cf976e76f\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://4ad510e26689491bcba165fd73c08e1b80302148804e27a184861712d966540b\",\"dweb:/ipfs/QmVuaZ3yB3eBkHRh2DS6Cy1E6B3oige6fDtvCrcYssc3qi\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.30+commit.73712a01"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"address","name":"apkProof","type":"address"},{"internalType":"uint32","name":"digestParaId","type":"uint32"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"type":"error","name":"EmptyLeaves"},{"inputs":[],"type":"error","name":"EmptyTree"},{"inputs":[],"type":"error","name":"EmptyTree"},{"inputs":[],"type":"error","name":"InvalidAggregateProof"},{"inputs":[],"type":"error","name":"InvalidMmrProof"},{"inputs":[],"type":"error","name":"InvalidParachainHeaderProof"},{"inputs":[],"type":"error","name":"LeafIndexOutOfBounds"},{"inputs":[],"type":"error","name":"MissingApkCommitment"},{"inputs":[],"type":"error","name":"MmrRootHashMissing"},{"inputs":[],"type":"error","name":"OutOfBoundsLeaves"},{"inputs":[],"type":"error","name":"ProofExhausted"},{"inputs":[],"type":"error","name":"SuperMajorityRequired"},{"inputs":[],"type":"error","name":"TimestampNotFound"},{"inputs":[],"type":"error","name":"UnconsumedProof"},{"inputs":[],"type":"error","name":"UnknownAuthoritySet"},{"inputs":[],"type":"error","name":"UnsortedLeaves"},{"inputs":[],"type":"error","name":"UnsortedLeaves"},{"inputs":[],"stateMutability":"view","type":"function","name":"MMR_ROOT_PAYLOAD_ID","outputs":[{"internalType":"bytes2","name":"","type":"bytes2"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"_apk","outputs":[{"internalType":"contract IApkProof","name":"","type":"address"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"_digestParaId","outputs":[{"internalType":"uint32","name":"","type":"uint32"}]},{"inputs":[{"internalType":"struct BeefyConsensusState","name":"s","type":"tuple","components":[{"internalType":"uint256","name":"latestHeight","type":"uint256"},{"internalType":"uint256","name":"beefyActivationBlock","type":"uint256"},{"internalType":"struct AuthoritySet","name":"currentAuthoritySet","type":"tuple","components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"len","type":"uint256"},{"internalType":"uint256","name":"blsPoseidonHash","type":"uint256"},{"internalType":"bytes32","name":"ecdsaMerkleRoot","type":"bytes32"}]},{"internalType":"struct AuthoritySet","name":"nextAuthoritySet","type":"tuple","components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"len","type":"uint256"},{"internalType":"uint256","name":"blsPoseidonHash","type":"uint256"},{"internalType":"bytes32","name":"ecdsaMerkleRoot","type":"bytes32"}]}]},{"internalType":"struct BlsApkBeefyConsensusProof","name":"p","type":"tuple","components":[{"internalType":"struct BlsApkRelayChainProof","name":"relay","type":"tuple","components":[{"internalType":"struct Commitment","name":"commitment","type":"tuple","components":[{"internalType":"struct Payload[]","name":"payload","type":"tuple[]","components":[{"internalType":"bytes2","name":"id","type":"bytes2"},{"internalType":"bytes","name":"data","type":"bytes"}]},{"internalType":"uint32","name":"blockNumber","type":"uint32"},{"internalType":"uint64","name":"validatorSetId","type":"uint64"}]},{"internalType":"uint256[5]","name":"bitlist","type":"uint256[5]"},{"internalType":"bytes32[3]","name":"apk","type":"bytes32[3]"},{"internalType":"bytes32[6]","name":"apk2","type":"bytes32[6]"},{"internalType":"bytes","name":"apkProof","type":"bytes"},{"internalType":"bytes32[3]","name":"signature","type":"bytes32[3]"},{"internalType":"struct BeefyMmrLeaf","name":"latestMmrLeaf","type":"tuple","components":[{"internalType":"uint8","name":"version","type":"uint8"},{"internalType":"uint32","name":"parentNumber","type":"uint32"},{"internalType":"bytes32","name":"parentHash","type":"bytes32"},{"internalType":"struct AuthoritySetCommitment","name":"nextAuthoritySet","type":"tuple","components":[{"internalType":"uint64","name":"id","type":"uint64"},{"internalType":"uint32","name":"len","type":"uint32"},{"internalType":"bytes32","name":"root","type":"bytes32"}]},{"internalType":"bytes32","name":"extra","type":"bytes32"},{"internalType":"uint256","name":"leafIndex","type":"uint256"}]},{"internalType":"bytes32[]","name":"mmrProof","type":"bytes32[]"}]},{"internalType":"struct ParachainProof","name":"parachain","type":"tuple","components":[{"internalType":"struct Parachain[]","name":"parachains","type":"tuple[]","components":[{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bytes","name":"header","type":"bytes"}]},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"},{"internalType":"uint256","name":"leafCount","type":"uint256"}]}]}],"stateMutability":"pure","type":"function","name":"noOp"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"stateMutability":"view","type":"function","name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}]},{"inputs":[{"internalType":"bytes","name":"previousState","type":"bytes"},{"internalType":"bytes","name":"proof","type":"bytes"}],"stateMutability":"view","type":"function","name":"verify","outputs":[{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"struct IntermediateState[]","name":"","type":"tuple[]","components":[{"internalType":"uint256","name":"stateMachineId","type":"uint256"},{"internalType":"uint256","name":"height","type":"uint256"},{"internalType":"struct StateCommitment","name":"commitment","type":"tuple","components":[{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bytes32","name":"overlayRoot","type":"bytes32"},{"internalType":"bytes32","name":"stateRoot","type":"bytes32"}]}]},{"internalType":"uint256","name":"","type":"uint256"}]}],"devdoc":{"kind":"dev","methods":{"noOp((uint256,uint256,(uint256,uint256,uint256,bytes32),(uint256,uint256,uint256,bytes32)),((((bytes2,bytes)[],uint32,uint64),uint256[5],bytes32[3],bytes32[6],bytes,bytes32[3],(uint8,uint32,bytes32,(uint64,uint32,bytes32),bytes32,uint256),bytes32[]),((uint256,uint256,bytes)[],bytes32[],uint256)))":{"details":"Only here so the structs appear in the ABI, which is what the Rust bindings are generated from. `verify` takes bytes, so without this they would be invisible."},"supportsInterface(bytes4)":{"details":"Returns true if this contract implements the interface defined by `interfaceId`. See the corresponding https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section] to learn more about how these ids are created. This function call must use less than 30 000 gas."},"verify(bytes,bytes)":{"details":"IConsensusV2 entry point."}},"version":1},"userdoc":{"kind":"user","methods":{"MMR_ROOT_PAYLOAD_ID()":{"notice":"The payload id for the mmr root in a BEEFY commitment, \"mh\""},"_apk()":{"notice":"The APK proof verifier, holding the circuit's verifying key."},"_digestParaId()":{"notice":"The parachain whose header digests carry apk commitments, which is hyperbridge. Every parachain in a proof is proven against the heads root, so a digest from any of them is authentic; only this one's says anything about the relay chain's authorities."}},"version":1}},"settings":{"remappings":["@gnark-apk-proofs/=lib/gnark-apk-proofs/solidity/contracts/","@hyperbridge/core/=node_modules/@hyperbridge/core/contracts/","@openzeppelin/=node_modules/@openzeppelin/","@polytope-labs/=node_modules/@polytope-labs/","@sp1-contracts/=lib/sp1-contracts/contracts/src/","@uniswap/=node_modules/@uniswap/","ds-test/=lib/forge-std/lib/ds-test/src/","erc4626-tests/=lib/sp1-contracts/contracts/lib/openzeppelin-contracts/lib/erc4626-tests/","forge-std/=node_modules/forge-std/src/","gnark-apk-proofs/=lib/gnark-apk-proofs/","openzeppelin-contracts/=lib/sp1-contracts/contracts/lib/openzeppelin-contracts/","solidity-stringutils/=lib/solidity-stringutils/","sp1-contracts/=lib/sp1-contracts/contracts/","stringutils/=lib/solidity-stringutils/src/"],"optimizer":{"enabled":true,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"src/consensus/BlsBeefy.sol":"BlsBeefy"},"evmVersion":"prague","libraries":{},"viaIR":true},"sources":{"node_modules/@hyperbridge/core/contracts/interfaces/IConsensusV2.sol":{"keccak256":"0x71dcb5168f8f0f95effac221bdc49e0f662011c1bf86a9369cc0db183b8ac4c3","urls":["bzz-raw://d84388af7b50f5f31110fe3d711930c816432f8c2ebcc7417639a727760544ff","dweb:/ipfs/QmWp89jgUMhCqrGVDphLAEhJm5kPP6q1y9yid3D2a3JFZ9"],"license":"Apache-2.0"},"node_modules/@openzeppelin/contracts/utils/introspection/ERC165.sol":{"keccak256":"0x2d9dc2fe26180f74c11c13663647d38e259e45f95eb88f57b61d2160b0109d3e","urls":["bzz-raw://81233d1f98060113d9922180bb0f14f8335856fe9f339134b09335e9f678c377","dweb:/ipfs/QmWh6R35SarhAn4z2wH8SU456jJSYL2FgucfTFgbHJJN4E"],"license":"MIT"},"node_modules/@openzeppelin/contracts/utils/introspection/IERC165.sol":{"keccak256":"0x8891738ffe910f0cf2da09566928589bf5d63f4524dd734fd9cedbac3274dd5c","urls":["bzz-raw://971f954442df5c2ef5b5ebf1eb245d7105d9fbacc7386ee5c796df1d45b21617","dweb:/ipfs/QmadRjHbkicwqwwh61raUEapaVEtaLMcYbQZWs9gUkgj3u"],"license":"MIT"},"node_modules/@polytope-labs/solidity-merkle-trees/src/MerkleMountainRange.sol":{"keccak256":"0x014237038bb77bdf371b50c1268d02bf7eeaf7068c35483d5d0684ca7c30d544","urls":["bzz-raw://beb3ae60094f49716a2b0cfa6229ee27af1778934cf5f2dfc704433fda5a687b","dweb:/ipfs/QmQ339wEFT9X1woHyFTDbbhUCx8ekD9MGLRnhS3PpYp1pm"],"license":"Apache-2.0"},"node_modules/@polytope-labs/solidity-merkle-trees/src/MerkleMultiProof.sol":{"keccak256":"0xd4f6e6a9eceaa7d1cdf9684bfe7f3f552adf21dfcf7f1372f7943a4c2f15deb7","urls":["bzz-raw://ec900b3e79ea8ef9d275697937d1984e994d10117aa2be066bd73f6a31aedf10","dweb:/ipfs/QmZQGLiJtbvUSEW5H2HEenzxp58fN43XHRY3SoNTC9Hobg"],"license":"Apache-2.0"},"node_modules/@polytope-labs/solidity-merkle-trees/src/trie/Bytes.sol":{"keccak256":"0xd305383358b93285d8fcee512795487484eadfd7b602df16ff4e9b01afbefec7","urls":["bzz-raw://57c86cd2fe6ab591264c5632d503d77f04748d722e4defa9c2badb238ee1f9bd","dweb:/ipfs/QmTZ6xTcaYkRBshq6YZVMx2kkk94ZtJsRM6xLpC5qWT3oA"],"license":"Apache-2.0"},"node_modules/@polytope-labs/solidity-merkle-trees/src/trie/Memory.sol":{"keccak256":"0x59e3a56caa42c1aac30231173439817d38c7f359e40bd36e9bb418d3f82ceab7","urls":["bzz-raw://e060fed46c6b420624166ea02a326f0f566897941bdc322257e690ae134b8179","dweb:/ipfs/QmbXW8yG2ZntjLMyUEmQGcRZroZj4dVZkBhHxK2PFKfUKB"],"license":"Apache-2.0"},"node_modules/@polytope-labs/solidity-merkle-trees/src/trie/Node.sol":{"keccak256":"0xca611969a68f7fe63dcdc742c9caf9bc1b26495561b68f4676c209279a4576ba","urls":["bzz-raw://17b7ec2cf65d484f0a2c188a7ae33430b015b3a55bf10393167f53839e2dde56","dweb:/ipfs/QmTQbT8J7rBRNHFXSR7S4KbpwCteMrFt8mvrzCD1vYYTwX"],"license":"Apache-2.0"},"node_modules/@polytope-labs/solidity-merkle-trees/src/trie/polkadot/ScaleCodec.sol":{"keccak256":"0x9ac4df46e68718f7deaaa5b7443778533f53dc0ff3736cc386cf4991099da2aa","urls":["bzz-raw://85d08b00d9173358323105be975ce85b7e206ba52df2d80a14d2581dec1ddd11","dweb:/ipfs/QmQCqVvSdJUqfhLH8TRBGNDGiEzkGduhRacnYhoKcm7e2a"],"license":"Apache-2.0"},"src/consensus/BlsBeefy.sol":{"keccak256":"0x15657e2fd4f84139b1f018b0bb0ed83b8b75cc7259654d061f9f7c59838cf73d","urls":["bzz-raw://85d7bebe51acb750406079fa98fa4b6fa1945afc18a3291926cb80940b1523cc","dweb:/ipfs/QmNSwQ4bxgrp9cEdS8Lp4V82Etc4rFPCd5cYKgUBfggXTb"],"license":"Apache-2.0"},"src/consensus/Codec.sol":{"keccak256":"0x477b62396db5a5c1d89d0388b0724dde80e0a0423b89c07f7b3b5622ff03b5d4","urls":["bzz-raw://6a584c6500ae347c6f9d76d06a96757b024dcb892601c6d9c1a945b9d76811d6","dweb:/ipfs/QmduKRb2Zr9eqeZbi5TFRw5Dyig3XVY3rMvPkxLktLFYrN"],"license":"Apache-2.0"},"src/consensus/Types.sol":{"keccak256":"0xe4b169eefb4afd0f83f09335f4460705fe96e7736314c2030235720cf976e76f","urls":["bzz-raw://4ad510e26689491bcba165fd73c08e1b80302148804e27a184861712d966540b","dweb:/ipfs/QmVuaZ3yB3eBkHRh2DS6Cy1E6B3oige6fDtvCrcYssc3qi"],"license":"Apache-2.0"}},"version":1},"id":118} \ No newline at end of file diff --git a/evm/rust/abi/EcdsaBeefy.json b/evm/rust/abi/EcdsaBeefy.json index c9b48161f..b2270fea4 100644 --- a/evm/rust/abi/EcdsaBeefy.json +++ b/evm/rust/abi/EcdsaBeefy.json @@ -1 +1 @@ -{"abi":[{"type":"function","name":"MMR_ROOT_PAYLOAD_ID","inputs":[],"outputs":[{"name":"","type":"bytes2","internalType":"bytes2"}],"stateMutability":"view"},{"type":"function","name":"noOp","inputs":[{"name":"s","type":"tuple","internalType":"struct BeefyConsensusState","components":[{"name":"latestHeight","type":"uint256","internalType":"uint256"},{"name":"beefyActivationBlock","type":"uint256","internalType":"uint256"},{"name":"currentAuthoritySet","type":"tuple","internalType":"struct AuthoritySetCommitment","components":[{"name":"id","type":"uint64","internalType":"uint64"},{"name":"len","type":"uint32","internalType":"uint32"},{"name":"root","type":"bytes32","internalType":"bytes32"}]},{"name":"nextAuthoritySet","type":"tuple","internalType":"struct AuthoritySetCommitment","components":[{"name":"id","type":"uint64","internalType":"uint64"},{"name":"len","type":"uint32","internalType":"uint32"},{"name":"root","type":"bytes32","internalType":"bytes32"}]}]},{"name":"p","type":"tuple","internalType":"struct BeefyConsensusProof","components":[{"name":"relay","type":"tuple","internalType":"struct RelayChainProof","components":[{"name":"signedCommitment","type":"tuple","internalType":"struct SignedCommitment","components":[{"name":"commitment","type":"tuple","internalType":"struct Commitment","components":[{"name":"payload","type":"tuple[]","internalType":"struct Payload[]","components":[{"name":"id","type":"bytes2","internalType":"bytes2"},{"name":"data","type":"bytes","internalType":"bytes"}]},{"name":"blockNumber","type":"uint32","internalType":"uint32"},{"name":"validatorSetId","type":"uint64","internalType":"uint64"}]},{"name":"votes","type":"tuple[]","internalType":"struct Vote[]","components":[{"name":"signature","type":"bytes","internalType":"bytes"},{"name":"authorityIndex","type":"uint256","internalType":"uint256"}]}]},{"name":"latestMmrLeaf","type":"tuple","internalType":"struct BeefyMmrLeaf","components":[{"name":"version","type":"uint8","internalType":"uint8"},{"name":"parentNumber","type":"uint32","internalType":"uint32"},{"name":"parentHash","type":"bytes32","internalType":"bytes32"},{"name":"nextAuthoritySet","type":"tuple","internalType":"struct AuthoritySetCommitment","components":[{"name":"id","type":"uint64","internalType":"uint64"},{"name":"len","type":"uint32","internalType":"uint32"},{"name":"root","type":"bytes32","internalType":"bytes32"}]},{"name":"extra","type":"bytes32","internalType":"bytes32"},{"name":"leafIndex","type":"uint256","internalType":"uint256"}]},{"name":"mmrProof","type":"bytes32[]","internalType":"bytes32[]"},{"name":"proof","type":"bytes32[]","internalType":"bytes32[]"}]},{"name":"parachain","type":"tuple","internalType":"struct ParachainProof","components":[{"name":"parachains","type":"tuple[]","internalType":"struct Parachain[]","components":[{"name":"index","type":"uint256","internalType":"uint256"},{"name":"id","type":"uint256","internalType":"uint256"},{"name":"header","type":"bytes","internalType":"bytes"}]},{"name":"proof","type":"bytes32[]","internalType":"bytes32[]"},{"name":"leafCount","type":"uint256","internalType":"uint256"}]}]}],"outputs":[],"stateMutability":"pure"},{"type":"function","name":"supportsInterface","inputs":[{"name":"interfaceId","type":"bytes4","internalType":"bytes4"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"verify","inputs":[{"name":"previousState","type":"bytes","internalType":"bytes"},{"name":"proof","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"","type":"bytes","internalType":"bytes"},{"name":"","type":"tuple[]","internalType":"struct IntermediateState[]","components":[{"name":"stateMachineId","type":"uint256","internalType":"uint256"},{"name":"height","type":"uint256","internalType":"uint256"},{"name":"commitment","type":"tuple","internalType":"struct StateCommitment","components":[{"name":"timestamp","type":"uint256","internalType":"uint256"},{"name":"overlayRoot","type":"bytes32","internalType":"bytes32"},{"name":"stateRoot","type":"bytes32","internalType":"bytes32"}]}]},{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"pure"},{"type":"function","name":"verifyConsensus","inputs":[{"name":"encodedState","type":"bytes","internalType":"bytes"},{"name":"encodedProof","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"","type":"bytes","internalType":"bytes"},{"name":"","type":"tuple[]","internalType":"struct IntermediateState[]","components":[{"name":"stateMachineId","type":"uint256","internalType":"uint256"},{"name":"height","type":"uint256","internalType":"uint256"},{"name":"commitment","type":"tuple","internalType":"struct StateCommitment","components":[{"name":"timestamp","type":"uint256","internalType":"uint256"},{"name":"overlayRoot","type":"bytes32","internalType":"bytes32"},{"name":"stateRoot","type":"bytes32","internalType":"bytes32"}]}]}],"stateMutability":"pure"},{"type":"error","name":"ECDSAInvalidSignature","inputs":[]},{"type":"error","name":"ECDSAInvalidSignatureLength","inputs":[{"name":"length","type":"uint256","internalType":"uint256"}]},{"type":"error","name":"ECDSAInvalidSignatureS","inputs":[{"name":"s","type":"bytes32","internalType":"bytes32"}]},{"type":"error","name":"EmptyLeaves","inputs":[]},{"type":"error","name":"EmptyTree","inputs":[]},{"type":"error","name":"EmptyTree","inputs":[]},{"type":"error","name":"IllegalGenesisBlock","inputs":[]},{"type":"error","name":"InvalidAuthoritiesProof","inputs":[]},{"type":"error","name":"InvalidMmrProof","inputs":[]},{"type":"error","name":"LeafIndexOutOfBounds","inputs":[]},{"type":"error","name":"MmrRootHashMissing","inputs":[]},{"type":"error","name":"OutOfBoundsLeaves","inputs":[]},{"type":"error","name":"ProofExhausted","inputs":[]},{"type":"error","name":"SuperMajorityRequired","inputs":[]},{"type":"error","name":"TimestampNotFound","inputs":[]},{"type":"error","name":"UnconsumedProof","inputs":[]},{"type":"error","name":"UnknownAuthoritySet","inputs":[]},{"type":"error","name":"UnsortedLeaves","inputs":[]},{"type":"error","name":"UnsortedLeaves","inputs":[]}],"bytecode":{"object":"0x60808060405234601557613455908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c806301ffc9a7146100645780637163f3121461005f5780637d7555981461005a578063af8b91d6146100555763f7e83aee14610050575f80fd5b610aac565b610a31565b610926565b6107e5565b346100d45760203660031901126100d45760043563ffffffff60e01b81168091036100d457630faeaab360e31b81149081156100c3575b81156100b2575b501515608052607f1960a0016080f35b6301ffc9a760e01b149050816100a2565b637bf41d7760e11b8114915061009b565b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b606081019081106001600160401b0382111761010757604052565b6100d8565b608081019081106001600160401b0382111761010757604052565b604081019081106001600160401b0382111761010757604052565b60c081019081106001600160401b0382111761010757604052565b90601f801991011681019081106001600160401b0382111761010757604052565b6040519061018d60808361015d565b565b6040519061018d60408361015d565b6040519061018d60608361015d565b6040519061018d60a08361015d565b6001600160401b038116036100d457565b359061018d826101bc565b63ffffffff8116036100d457565b359061018d826101d8565b91908260609103126100d457604051610209816100ec565b60408082948035610219816101bc565b84526020810135610229816101d8565b60208501520135910152565b906101006003198301126100d45760405161024f8161010c565b606061027a82946004358452602435602085015261026e8160446101f1565b604085015260a46101f1565b910152565b9190610100838203126100d457606061027a6040519261029e8461010c565b60a0849680358652602081013560208701526102bd83604083016101f1565b6040870152016101f1565b6001600160401b0381116101075760051b60200190565b6001600160f01b03198116036100d457565b6001600160401b03811161010757601f01601f191660200190565b81601f820112156100d457803590610323826102f1565b92610331604051948561015d565b828452602083830101116100d457815f926020809301838601378301015290565b81601f820112156100d457803590610369826102c8565b92610377604051948561015d565b82845260208085019360051b830101918183116100d45760208101935b8385106103a357505050505090565b84356001600160401b0381116100d45782016040818503601f1901126100d457604051916103d083610127565b6020820135926001600160401b0384116100d4576040836103f888602080988198010161030c565b8352013583820152815201940193610394565b91906040838203126100d4576040519061042482610127565b819380356001600160401b0381116100d45781016060818403126100d4576040519061044f826100ec565b80356001600160401b0381116100d457810184601f820112156100d457803590610478826102c8565b91610486604051938461015d565b80835260208084019160051b830101918783116100d45760208101915b8383106104f657505050506104ce9160409184526104c3602082016101e6565b6020850152016101cd565b604082015283526020810135916001600160401b0383116100d45760209261027a9201610352565b82356001600160401b0381116100d4578201906040828b03601f1901126100d4576040519061052482610127565b6020830135610532816102df565b82526040830135916001600160401b0383116100d45761055a8c60208096958196010161030c565b838201528152019201916104a3565b60ff8116036100d457565b919091610100818403126100d45760a060e06040519261059384610142565b6105cb849682356105a381610569565b865260208301356105b3816101d8565b602087015260408301356040870152606083016101f1565b606085015260c081013560808501520135910152565b9080601f830112156100d45781356105f8816102c8565b92610606604051948561015d565b81845260208085019260051b8201019283116100d457602001905b82821061062e5750505090565b8135815260209182019101610621565b919091610160818403126100d45761065461017e565b9281356001600160401b0381116100d4578161067191840161040b565b84526106808160208401610574565b60208501526101208201356001600160401b0381116100d457816106a59184016105e1565b60408501526101408201356001600160401b0381116100d4576106c892016105e1565b6060830152565b9190916060818403126100d457604051906106e9826100ec565b819381356001600160401b0381116100d457820181601f820112156100d457803590610714826102c8565b91610722604051938461015d565b80835260208084019160051b830101918483116100d45760208101915b838310610770575050505083526020820135916001600160401b0383116100d45761022960409392849383016105e1565b82356001600160401b0381116100d4578201906060828803601f1901126100d4576040519061079e826100ec565b60208301358252604083013560208301526060830135916001600160401b0383116100d4576107d58960208096958196010161030c565b604082015281520192019161073f565b346100d4576101203660031901126100d45761080036610235565b50610104356001600160401b0381116100d457604060031982360301126100d4576040519061082e82610127565b80600401356001600160401b0381116100d457610851906004369184010161063e565b825260248101356001600160401b0381116100d457602091600461087892369201016106cf565b910152005b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b90602080835192838152019201905f5b8181106108be5750505090565b909192602060a0600192604080885180518452858101518685015201518051828401528481015160608401520151608082015201940191019190916108b1565b90916109156109239360408452604084019061087d565b9160208184039101526108a1565b90565b346100d45760403660031901126100d4576004356001600160401b0381116100d45761095690369060040161030c565b6024356001600160401b0381116100d45761097590369060040161030c565b815182019061010083602084019303126100d457610a1e926109e76109ff926109d3610a109560c0604051956109aa8761010c565b60208101518752604081015160208801526109c88360608301610bcf565b604088015201610bcf565b606084015260208082518301019101611050565b6109f292919261018f565b92835260208301526112a3565b92906040519283916020830161111c565b03601f19810183528261015d565b610a2d604051928392836108fe565b0390f35b346100d4575f3660031901126100d457604051610dad60f31b8152602090f35b9181601f840112156100d4578235916001600160401b0383116100d457602083818601950101116100d457565b939291610aa790610a9960409360608852606088019061087d565b9086820360208801526108a1565b930152565b346100d45760403660031901126100d4576004356001600160401b0381116100d457610adc903690600401610a51565b6024356001600160401b0381116100d457610afb903690600401610a51565b9290918101610100828203126100d457610b149161027f565b918101916040828403126100d45781356001600160401b0381116100d45783610b3e91840161063e565b916020810135936001600160401b0385116100d457610b6994610b6192016106cf565b6109f261018f565b90610a2d6001600160401b03610bac606060405194610b9d86610b8f836020830161111c565b03601f19810188528761015d565b0151516001600160401b031690565b1660405193849384610a7e565b519061018d826101bc565b519061018d826101d8565b91908260609103126100d457604051610be7816100ec565b60408082948051610bf7816101bc565b84526020810151610c07816101d8565b60208501520151910152565b81601f820112156100d457805190610c2a826102f1565b92610c38604051948561015d565b828452602083830101116100d457815f9260208093018386015e8301015290565b81601f820112156100d457805190610c70826102c8565b92610c7e604051948561015d565b82845260208085019360051b830101918183116100d45760208101935b838510610caa57505050505090565b84516001600160401b0381116100d45782016040818503601f1901126100d45760405191610cd783610127565b6020820151926001600160401b0384116100d457604083610cff886020809881980101610c13565b8352015183820152815201940193610c9b565b91906040838203126100d45760405190610d2b82610127565b819380516001600160401b0381116100d45781016060818403126100d45760405190610d56826100ec565b80516001600160401b0381116100d457810184601f820112156100d457805190610d7f826102c8565b91610d8d604051938461015d565b80835260208084019160051b830101918783116100d45760208101915b838310610dfd5750505050610dd5916040918452610dca60208201610bc4565b602085015201610bb9565b604082015283526020810151916001600160401b0383116100d45760209261027a9201610c59565b82516001600160401b0381116100d4578201906040828b03601f1901126100d45760405190610e2b82610127565b6020830151610e39816102df565b82526040830151916001600160401b0383116100d457610e618c602080969581960101610c13565b83820152815201920191610daa565b919091610100818403126100d45760a060e060405192610e8f84610142565b610ec784968251610e9f81610569565b86526020830151610eaf816101d8565b60208701526040830151604087015260608301610bcf565b606085015260c081015160808501520151910152565b9080601f830112156100d4578151610ef4816102c8565b92610f02604051948561015d565b81845260208085019260051b8201019283116100d457602001905b828210610f2a5750505090565b8151815260209182019101610f1d565b9190916060818403126100d45760405190610f54826100ec565b819381516001600160401b0381116100d457820181601f820112156100d457805190610f7f826102c8565b91610f8d604051938461015d565b80835260208084019160051b830101918483116100d45760208101915b838310610fdb575050505083526020820151916001600160401b0383116100d457610c076040939284938301610edd565b82516001600160401b0381116100d4578201906060828803601f1901126100d45760405190611009826100ec565b60208301518252604083015160208301526060830151916001600160401b0383116100d45761104089602080969581960101610c13565b6040820152815201920191610faa565b9190916040818403126100d45780516001600160401b0381116100d4578101610160818503126100d45761108261017e565b9080516001600160401b0381116100d4578561109f918301610d12565b82526110ae8560208301610e70565b60208301526101208101516001600160401b0381116100d457856110d3918301610edd565b6040830152610140810151906001600160401b0382116100d4576110f991869101610edd565b60608201529260208201516001600160401b0381116100d4576109239201610f3a565b61018d9092919260a06060610100830195805184526020810151602085015261117060408201516040860190604080916001600160401b03815116845263ffffffff60208201511660208501520151910152565b0151910190604080916001600160401b03815116845263ffffffff60208201511660208501520151910152565b604051906111aa826100ec565b5f6040838281528260208201520152565b604051906111c88261010c565b815f81525f60208201526111da61119d565b6040820152606061027a61119d565b604051906111f860208361015d565b5f80835282815b82811061120b57505050565b60209060405161121a816100ec565b5f81525f8382015261122a61119d565b6040820152828285010152016111ff565b90611245826102c8565b611252604051918261015d565b8281528092611263601f19916102c8565b01905f5b82811061127357505050565b602090604051611282816100ec565b5f81525f8382015261129261119d565b604082015282828501015201611267565b91906112ad6111bb565b50825163ffffffff6020835151510151161115611604578051906112cf6111bb565b508151916020830151519251906112fa6112f16020840163ffffffff90511690565b63ffffffff1690565b93604083019061131182516001600160401b031690565b9160408901926001600160401b0361134161133586516001600160401b0390511690565b6001600160401b031690565b91169081141590816115e1575b506115d257516001600160401b03168251906001600160401b0361137c61133584516001600160401b031690565b9116036115c657905b60208201916113ab6113a76113a16112f1865163ffffffff1690565b846118b4565b1590565b6115b757855151955f965f5b8181106115335750508615611524576113cf906119c3565b60208151910120916113e08161165f565b925f5b8281106114a5575050506040015160608501519251611416936113a793916114109063ffffffff166112f1565b92611b18565b611496576109239460208361142f82966080968c611c30565b01916114486060845101516001600160401b0390511690565b60608a01918251916001600160401b0361146c61133585516001600160401b031690565b911611611485575b5050508752510151910151906116f8565b5260608351015190525f8080611474565b63528bd3ef60e01b5f5260045ffd5b806114b760019260208b510151611633565b5160206114c5825186611b02565b910151906040516114f581610a106020820194856014916bffffffffffffffffffffffff199060601b1681520190565b51902061150061018f565b91825260208201526115128288611633565b5261151d8187611633565b50016113e3565b6323188e3960e21b5f5260045ffd5b610dad60f31b61156661155961154a848751611633565b51516001600160f01b03191690565b6001600160f01b03191690565b148061159e575b61157a575b6001016113b7565b97506001611596602061158e8b8651611633565b5101516118dd565b989050611572565b506020806115ad838651611633565b510151511461156d565b633aa90f7f60e21b5f5260045ffd5b50606088015190611385565b637202e68560e11b5f5260045ffd5b90506115fc61133560608c01516001600160401b0390511690565b14155f61134e565b506109236111e9565b634e487b7160e01b5f52603260045260245ffd5b80511561162e5760200190565b61160d565b805182101561162e5760209160051b010190565b6040519061165482610127565b5f6020838281520152565b90611669826102c8565b611676604051918261015d565b8281528092611687601f19916102c8565b01905f5b82811061169757505050565b6020906116a2611647565b8282850101520161168b565b805191908290602001825e015f815290565b6040516001600160e01b03199091166020820152919061018d9083906116ea9060248301906116ae565b03601f19810184528361015d565b815151916117058361165f565b9161170f8461123b565b935f5b8181106117585750611725575b50505090565b61173d9282604060206113a795015191015192611b18565b611749575f808061171f565b630b92186960e31b5f5260045ffd5b611763818551611633565b519060408201916117748351611e25565b92602084019182511561185557600194816117ee602061182094519201946117e86117e16117a6885163ffffffff1690565b6001600160e01b03199063ff00ff00600882811b9190911691901c62ff00ff1617601081811b63ffff00001691901c61ffff161760e01b1690565b9151611f71565b906116c0565b602081519101206117fd61018f565b918252602082015261180f868c611633565b5261181a858b611633565b50611f96565b9051915161182c61019e565b928352602083015260408201526118438289611633565b5261184e8188611633565b5001611712565b63b4eb9e5160e01b5f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b906001820180921161188657565b611864565b906003820180921161188657565b906002820180921161188657565b9190820180921161188657565b906001600160ff1b03811681036118865760039060011b04906001820180921161188657101590565b60208151106118ed576020015190565b60405162461bcd60e51b8152602060048201526024808201527f42797465733a3a20746f427974657333323a206461746120697320746f20736860448201526337b93a1760e11b6064820152608490fd5b6040519061194d60208361015d565b5f8252565b6116ea611971949361197161018d9460405197889560208701906116ae565b906116ae565b61018d926119719594611996600c9460405198899560208701906116ae565b6001600160e01b03199290921682526001600160c01b03191660048201520360131981018552018361015d565b908151516119cf61193e565b905f5b818110611aa45750926119e86109239394612156565b91611a9e611a146040611a056117a6602087015163ffffffff1690565b9401516001600160401b031690565b67ffffffffffff000067ff00ff00ff00ff0066ff00ff00ff00ff8360081c169260081b169165ffff0000ffff65ffff0000ff0065ffffffffffff67ffff0000ffff0000861666ff0000ffff000085161760101c16941691161760101b161767ffffffff0000000063ffffffff8260201c169160201b166001600160401b0360c01b911760c01b1690565b92611977565b91611afb600191610a10611add611abf61154a888b51611633565b6040516001600160f01b031990911660208201529182906022820190565b611af56020611aed888b51611633565b510151611f71565b91611952565b92016119d2565b61092391611b0f916122d3565b9092919261232b565b919392908115611be1578451611b2d816123a7565b611b36826123a7565b916001611b4286612c4e565b1b5f805b838210611b615750505050611b5d94959650612c7e565b1490565b611b6b828c611633565b51519088821015611bd2578215159081611bc7575b50611bb8576001906020611b94848e611633565b510151611ba18489611633565b52808401611baf8488611633565b52910190611b46565b630647f54960e21b5f5260045ffd5b90508111155f611b80565b630834466160e31b5f5260045ffd5b639136328760e01b5f5260045ffd5b60408051909190611c01838261015d565b6001815291601f1901825f5b828110611c1957505050565b602090611c24611647565b82828501015201611c0d565b6113a791611d139360406020830192611cd7611cd26020611cae8751611c57815160ff1690565b90611c688482015163ffffffff1690565b9088810151611c9b6080606084015193015193611c8f611c866101ad565b60ff9097168752565b63ffffffff1685880152565b8984015260608301526080820152612450565b805190820120970151865160200151611ccc9063ffffffff166112f1565b90612544565b611878565b9460a0611ce2611bf0565b9551015190611cef61018f565b9182526020820152611d0085611621565b52611d0a84611621565b50015190612559565b61174957565b6040519060a082018281106001600160401b038211176101075760405260606080835f81525f60208201525f60408201525f838201520152565b60405190611d6082610127565b60606020835f81520152565b6040519061012082018281106001600160401b03821117610107576040525f61010083828152611d9a611d53565b6020820152826040820152611dad611d53565b6060820152826080820152611dc0611d53565b60a08201528260c0820152606060e08201520152565b90611de0826102c8565b611ded604051918261015d565b8281528092611dfe601f19916102c8565b01905f5b828110611e0e57505050565b602090611e19611d6c565b82828501015201611e02565b611e2d611d19565b50611e3661018f565b9081525f6020820152611e50611e4b82612565565b6118dd565b90611e5a816126a4565b611e66611e4b83612565565b611e72611e4b84612565565b91611e7c846126a4565b611e8581611dd6565b945f5b828110611eb457505050611e9a6101ad565b948552602085015260408401526060830152608082015290565b600190611ec083612817565b60ff611eca611d6c565b911680611ef65750600160c08201525b611ee4828a611633565b52611eef8189611633565b5001611e88565b60048103611f1a575060016040820152611f0f84612891565b60608201525b611eda565b60058103611f3d575060016080820152611f3384612891565b60a0820152611eda565b60068103611f5d575060018152611f5384612891565b6020820152611eda565b600803611f15576001610100820152611eda565b61092361197191610a10611f858251612156565b9160405194859360208501906116ae565b611f9e61119d565b505f5f925f5f5b60808501805180518310156120cf576040611fc384611fcb93611633565b510151151590565b806120a7575b61205e575b611fe66040611fc3848451611633565b80612025575b611ffa575b50600101611fa5565b81925061201d6020606061201360809560019551611633565b51015101516129e7565b929150611ff1565b50634953544d60e01b63ffffffff60e01b6120576060612046868651611633565b510151516001600160e01b03191690565b1614611fec565b95509250612081611e4b60206060612077878a51611633565b510151015161296d565b926120a1611e4b60206060612097858b51611633565b51015101516129ae565b95611fd6565b5063049534d560e41b63ffffffff60e01b6120c86060612046868651611633565b1614611fd1565b5050509250929082156120f4576120e461019e565b9283526020830152604082015290565b633eba99d160e21b5f5260045ffd5b60031981019190821161188657565b5f1981019190821161188657565b602003906020821161188657565b9190820391821161188657565b600190610923939260ff60f81b9060f81b16815201906116ae565b604081101561219a57610923612178612172610a109360021b90565b60ff1690565b60405160f89190911b6001600160f81b03191660208201529182906021820190565b6140008110156121f9576109236121d76121c46121bd611cd2610a109560021b90565b61ffff1690565b60ff61ff008260081b169160081c161790565b60405160f09190911b6001600160f01b03191660208201529182906022820190565b6340000000811015612276576109236122546122236112f161221e610a109560021b90565b611899565b63ffffff0062ff00ff62ffffff818460081c1616921660081b161763ffff000061ffff8260101c169160101b161790565b60405160e09190911b6001600160e01b03191660208201529182906024820190565b610a1061229b6122886122a093612a5f565b6040519283916020830160209181520190565b612b78565b6109236122c16121726122bc6122b68551612103565b60021b90565b61188b565b610a106040519384926020840161213b565b8151919060418303612303576122fc9250602082015190606060408401519301515f1a90612bc1565b9192909190565b50505f9160029190565b6004111561231757565b634e487b7160e01b5f52602160045260245ffd5b6123348161230d565b8061233d575050565b6123468161230d565b6001810361235d5763f645eedf60e01b5f5260045ffd5b6123668161230d565b60028103612381575063fce698f760e01b5f5260045260245ffd5b8061238d60039261230d565b146123955750565b6335e2f38360e21b5f5260045260245ffd5b906123b1826102c8565b6123be604051918261015d565b82815280926123cf601f19916102c8565b0190602036910137565b9492919695939096604051978896602088016123f4916116ae565b9063ffffffff60e01b16815260040161240c916116ae565b916001600160401b0360c01b16825263ffffffff60e01b166008820152600c01612435916116ae565b61243e916116ae565b03601f198101835261018d908361015d565b61253161250e6116ea6109239361248d61246b825160ff1690565b60405160f89190911b6001600160f81b03191660208201529283906021820190565b6116ea6124a46117a6602084015163ffffffff1690565b6124bf60408401516040519384916020830160209181520190565b60608301519361253f60806124de611a1488516001600160401b031690565b9561251c60406124f86117a660208c015163ffffffff1690565b9901516040519a8b916020830160209181520190565b03601f1981018b528a61015d565b01516040519889916020830160209181520190565b03601f19810189528861015d565b6123d9565b8061254d575090565b81039081116118865790565b929091611b5d92612df3565b602081019081516020810180911161188657815151106100d45760209051818351820101918291011161188657602061259d9161302b565b9080519060208201809211611886575290565b906020820191825182810180911161188657815151106100d45781156125fe5760209051818451820101918291011161188657816125ed9161302b565b918051918201809211611886575290565b50505060405161260f60208261015d565b5f815290565b60ff60049116019060ff821161188657565b1561262e57565b60405162461bcd60e51b815260206004820152602860248201527f756e657870656374656420707265666978206465636f64696e6720436f6d706160448201526731ba1e2ab4b73a1f60c11b6064820152608490fd5b906001600160401b03809116911601906001600160401b03821161188657565b6126ad81612817565b60038116806126c75750610923915060021c603f16612172565b6001810361270c5750611335906127066121726126fc6126ec61217261092397612817565b60061b67ffffffffffffffc01690565b9260021c603f1690565b90612684565b6002810361278257506112f19061277560ff846127668261272f61092398612817565b95816127558161274761274188612817565b97612817565b991660081b63ffffff001690565b911617921660101b63ffff00001690565b17921660181b63ff0000001690565b1760021c633fffffff1690565b6003036127c1576109239160ff6127a86127a36127bc94603f9060021c1690565b612615565b16906127b76008831115612627565b6125b0565b6129e7565b60405162461bcd60e51b815260206004820152601a60248201527f436f64652073686f756c6420626520756e726561636861626c650000000000006044820152606490fd5b90815181101561162e570160200190565b6020810190815160018101809111611886578151511061285d575181516001600160f81b0319916128489190612806565b511660f81c906128588151611878565b905290565b60405162461bcd60e51b815260206004820152600c60248201526b4f7574206f662072616e676560a01b6044820152606490fd5b612899611d53565b50602081019081516004810180911161188657815151106100d4576020815181845182010191829101116118865760046128d29161302b565b918051906004820180921161188657525f915f905b60048210612927575050806128fe612904926126a4565b906125b0565b61291f61290f61018f565b6001600160e01b03199093168352565b602082015290565b90926001600160f81b031961293c8584612806565b5116908460031b9185830460081486151715611886576001926001600160e01b0319918216901c16179301906128e7565b80516020116100d457602080610923920161302b565b90815181116100d457801561299e576020610923920161302b565b505060405161260f60208261015d565b8051806020116100d457601f1981019081116118865760408201916020018210611886576109239161302b565b8015611886575f190190565b80515f91815b6129f657505090565b90915f1983019083821161188657612a0e8284612806565b5160f81c91600381901b906001600160fd1b038116036118865760ff8111611886576001901b9182810292818404149015171561188657612a5891612a52916118a7565b926129db565b90816129ed565b8060081c9060081b907cff000000ff000000ff000000ff000000ff000000ff000000ff000000ff7dff000000ff000000ff000000ff000000ff000000ff000000ff000000ff007fff000000ff000000ff000000ff000000ff000000ff000000ff000000ff00000084167eff000000ff000000ff000000ff000000ff000000ff000000ff000000ff000084161760101c931691161760101b177bffffffff00000000ffffffff00000000ffffffff00000000ffffffff7fffffffff00000000ffffffff00000000ffffffff00000000ffffffff00000000821660201c911660201b1777ffffffffffffffff0000000000000000ffffffffffffffff8019821660401c911660401b17612b748160801c9160801b90565b1790565b80515f198101908111611886575b6001600160f81b0319612b998284612806565b5116612bad57612ba8906129db565b612b86565b600181018091116118865761092391612983565b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411612c43579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15612c38575f516001600160a01b03811615612c2e57905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f9160039190565b6001811115612c79575f19810190811161188657612c6b906130cc565b600181018091116118865790565b505f90565b9192905f83515b6001612c9086611621565b5114612de057612cbc612cb7856001612cb1612cab8a611621565b516130cc565b1b6118a7565b612112565b905f915f915b808310612ce557505050612cd9612cdf9194611878565b60011c90565b92612c85565b909192612cf28489611633565b5182612cfd86611878565b1080612dc2575b15612d635790612d4f6001926002612d3a612d1f898c611633565b51612d32612d2c8b611878565b8d611633565b519084613132565b9701965b612d48848b611633565b5260011c90565b612d59828b611633565b5201929190612cc2565b8987600183188610612da55791600180612d9b612d4f94612d938c612d8c8d9e9d869b9a611633565b5192611633565b519085613132565b9801980196612d3e565b612d4f9150916001612db988829695611633565b51970196612d3e565b50612dd5612dcf86611878565b8a611633565b516001821814612d04565b5050915050612def9150611621565b5190565b9290928215611be157835193841561301c5760015b858110612fee57506001841480612fe4575b80612fd2575b612fb757612e35612e3085613152565b6123a7565b94612e3e61018f565b945f865260208601968752612e5161018f565b925f845260208401948552612e6461019e565b905f82526020820193845260408201525f91805b612f03575b50505051612ef4575190515103612ee55781515f190182525b815115612ed957612ea6826133f4565b612eaf836133f4565b90612ebb845160010190565b84525f5260205260405f20612ed38451845190611633565b52612e96565b91612def915051611621565b637227423160e11b5f5260045ffd5b63072afb8760e51b5f5260045ffd5b612f0c816130cc565b92612f26612f1e6001861b809461212e565b9283926118a7565b938685612f338187613197565b92602084015180155f14612f6b575050505050508551518551145f03612e7d5780612f66612f60876133d8565b8a6133bd565b612e78565b60011480612faf575b15612f9b575050506020612f92826040612f66940151905190611633565b5101518a6133bd565b91612f6693916002612f60941b0391613220565b508015612f74565b925090925051612ee557612fcc602091611621565b51015190565b50612fdc81611621565b515115612e20565b5060018514612e1a565b612ff88183611633565b515161300c61300683612112565b84611633565b51511015611bb857600101612e08565b631a14a47760e31b5f5260045ffd5b919091613037836102f1565b613044604051918261015d565b838152613050846102f1565b602082019190601f1901368337939091905b602081101561309c578061308257505f19905b5182518216911916179052565b612cb761309161309692612120565b613410565b90613075565b909182518152602081018091116118865791602081018091116118865790601f1981019081111561306257611864565b806fffffffffffffffffffffffffffffffff1060071b81811c6001600160401b031060061b1781811c63ffffffff1060051b1781811c61ffff1060041b1781811c60ff1060031b1781811c600f1060021b1781811c60031060011b1790811c6001101790565b600116613145575f5260205260405f2090565b905f5260205260405f2090565b90815f925b61315e5750565b915f19830183811161188657600193169283910192613157565b60405190613185826100ec565b60606040835f81525f60208201520152565b61319f613178565b5080516131b260208301918251906118a7565b928251905b8482106131f9575b508293506131d26128589293518261212e565b9084519460408101516131e361019e565b968752836020880152604087015252825161212e565b90613208816040860151611633565b515182111561321a57600101906131b7565b906131bf565b91906020830151835193613233826123a7565b9361323d836123a7565b955f5b84811061336f5750505050935b600161325884611621565b5114613362575f945f905b80821061327757505084808452845261324d565b90956132838786611633565b5187878461329083611878565b1080613344575b156132eb576001926132c9836132c1612d2c6132ba6132e599976132d397611633565b5192611878565b519083613132565b612d48848c611633565b6132dd8289611633565b520196611899565b90613263565b5050845160208601515111156133355760019161332183926132c96133108c8c611633565b5161331a8a6133d8565b9083613132565b61332b8289611633565b5201960190613263565b63d8f29a1560e01b5f5260045ffd5b5061335761335183611878565b89611633565b516001841814613297565b93505050612def90611621565b6001906133a8604086016020613390825161338a86896118a7565b90611633565b51015161339d848d611633565b525182850190611633565b515184016133b6828a611633565b5201613240565b906133ce6020830151835190611633565b5260018151019052565b6133e86020820151825190611633565b51906001815101905290565b6134046020820151825190611633565b5181515f190190915290565b601f8111611886576101000a9056fea2646970667358221220ef5ccb88ede39e8e09e4f5d0e653ecda99ce9449ab4b4e971bbc15048d77a4e864736f6c634300081e0033","sourceMap":"2851:9773:150:-:0;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x60806040526004361015610011575f80fd5b5f3560e01c806301ffc9a7146100645780637163f3121461005f5780637d7555981461005a578063af8b91d6146100555763f7e83aee14610050575f80fd5b610aac565b610a31565b610926565b6107e5565b346100d45760203660031901126100d45760043563ffffffff60e01b81168091036100d457630faeaab360e31b81149081156100c3575b81156100b2575b501515608052607f1960a0016080f35b6301ffc9a760e01b149050816100a2565b637bf41d7760e11b8114915061009b565b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b606081019081106001600160401b0382111761010757604052565b6100d8565b608081019081106001600160401b0382111761010757604052565b604081019081106001600160401b0382111761010757604052565b60c081019081106001600160401b0382111761010757604052565b90601f801991011681019081106001600160401b0382111761010757604052565b6040519061018d60808361015d565b565b6040519061018d60408361015d565b6040519061018d60608361015d565b6040519061018d60a08361015d565b6001600160401b038116036100d457565b359061018d826101bc565b63ffffffff8116036100d457565b359061018d826101d8565b91908260609103126100d457604051610209816100ec565b60408082948035610219816101bc565b84526020810135610229816101d8565b60208501520135910152565b906101006003198301126100d45760405161024f8161010c565b606061027a82946004358452602435602085015261026e8160446101f1565b604085015260a46101f1565b910152565b9190610100838203126100d457606061027a6040519261029e8461010c565b60a0849680358652602081013560208701526102bd83604083016101f1565b6040870152016101f1565b6001600160401b0381116101075760051b60200190565b6001600160f01b03198116036100d457565b6001600160401b03811161010757601f01601f191660200190565b81601f820112156100d457803590610323826102f1565b92610331604051948561015d565b828452602083830101116100d457815f926020809301838601378301015290565b81601f820112156100d457803590610369826102c8565b92610377604051948561015d565b82845260208085019360051b830101918183116100d45760208101935b8385106103a357505050505090565b84356001600160401b0381116100d45782016040818503601f1901126100d457604051916103d083610127565b6020820135926001600160401b0384116100d4576040836103f888602080988198010161030c565b8352013583820152815201940193610394565b91906040838203126100d4576040519061042482610127565b819380356001600160401b0381116100d45781016060818403126100d4576040519061044f826100ec565b80356001600160401b0381116100d457810184601f820112156100d457803590610478826102c8565b91610486604051938461015d565b80835260208084019160051b830101918783116100d45760208101915b8383106104f657505050506104ce9160409184526104c3602082016101e6565b6020850152016101cd565b604082015283526020810135916001600160401b0383116100d45760209261027a9201610352565b82356001600160401b0381116100d4578201906040828b03601f1901126100d4576040519061052482610127565b6020830135610532816102df565b82526040830135916001600160401b0383116100d45761055a8c60208096958196010161030c565b838201528152019201916104a3565b60ff8116036100d457565b919091610100818403126100d45760a060e06040519261059384610142565b6105cb849682356105a381610569565b865260208301356105b3816101d8565b602087015260408301356040870152606083016101f1565b606085015260c081013560808501520135910152565b9080601f830112156100d45781356105f8816102c8565b92610606604051948561015d565b81845260208085019260051b8201019283116100d457602001905b82821061062e5750505090565b8135815260209182019101610621565b919091610160818403126100d45761065461017e565b9281356001600160401b0381116100d4578161067191840161040b565b84526106808160208401610574565b60208501526101208201356001600160401b0381116100d457816106a59184016105e1565b60408501526101408201356001600160401b0381116100d4576106c892016105e1565b6060830152565b9190916060818403126100d457604051906106e9826100ec565b819381356001600160401b0381116100d457820181601f820112156100d457803590610714826102c8565b91610722604051938461015d565b80835260208084019160051b830101918483116100d45760208101915b838310610770575050505083526020820135916001600160401b0383116100d45761022960409392849383016105e1565b82356001600160401b0381116100d4578201906060828803601f1901126100d4576040519061079e826100ec565b60208301358252604083013560208301526060830135916001600160401b0383116100d4576107d58960208096958196010161030c565b604082015281520192019161073f565b346100d4576101203660031901126100d45761080036610235565b50610104356001600160401b0381116100d457604060031982360301126100d4576040519061082e82610127565b80600401356001600160401b0381116100d457610851906004369184010161063e565b825260248101356001600160401b0381116100d457602091600461087892369201016106cf565b910152005b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b90602080835192838152019201905f5b8181106108be5750505090565b909192602060a0600192604080885180518452858101518685015201518051828401528481015160608401520151608082015201940191019190916108b1565b90916109156109239360408452604084019061087d565b9160208184039101526108a1565b90565b346100d45760403660031901126100d4576004356001600160401b0381116100d45761095690369060040161030c565b6024356001600160401b0381116100d45761097590369060040161030c565b815182019061010083602084019303126100d457610a1e926109e76109ff926109d3610a109560c0604051956109aa8761010c565b60208101518752604081015160208801526109c88360608301610bcf565b604088015201610bcf565b606084015260208082518301019101611050565b6109f292919261018f565b92835260208301526112a3565b92906040519283916020830161111c565b03601f19810183528261015d565b610a2d604051928392836108fe565b0390f35b346100d4575f3660031901126100d457604051610dad60f31b8152602090f35b9181601f840112156100d4578235916001600160401b0383116100d457602083818601950101116100d457565b939291610aa790610a9960409360608852606088019061087d565b9086820360208801526108a1565b930152565b346100d45760403660031901126100d4576004356001600160401b0381116100d457610adc903690600401610a51565b6024356001600160401b0381116100d457610afb903690600401610a51565b9290918101610100828203126100d457610b149161027f565b918101916040828403126100d45781356001600160401b0381116100d45783610b3e91840161063e565b916020810135936001600160401b0385116100d457610b6994610b6192016106cf565b6109f261018f565b90610a2d6001600160401b03610bac606060405194610b9d86610b8f836020830161111c565b03601f19810188528761015d565b0151516001600160401b031690565b1660405193849384610a7e565b519061018d826101bc565b519061018d826101d8565b91908260609103126100d457604051610be7816100ec565b60408082948051610bf7816101bc565b84526020810151610c07816101d8565b60208501520151910152565b81601f820112156100d457805190610c2a826102f1565b92610c38604051948561015d565b828452602083830101116100d457815f9260208093018386015e8301015290565b81601f820112156100d457805190610c70826102c8565b92610c7e604051948561015d565b82845260208085019360051b830101918183116100d45760208101935b838510610caa57505050505090565b84516001600160401b0381116100d45782016040818503601f1901126100d45760405191610cd783610127565b6020820151926001600160401b0384116100d457604083610cff886020809881980101610c13565b8352015183820152815201940193610c9b565b91906040838203126100d45760405190610d2b82610127565b819380516001600160401b0381116100d45781016060818403126100d45760405190610d56826100ec565b80516001600160401b0381116100d457810184601f820112156100d457805190610d7f826102c8565b91610d8d604051938461015d565b80835260208084019160051b830101918783116100d45760208101915b838310610dfd5750505050610dd5916040918452610dca60208201610bc4565b602085015201610bb9565b604082015283526020810151916001600160401b0383116100d45760209261027a9201610c59565b82516001600160401b0381116100d4578201906040828b03601f1901126100d45760405190610e2b82610127565b6020830151610e39816102df565b82526040830151916001600160401b0383116100d457610e618c602080969581960101610c13565b83820152815201920191610daa565b919091610100818403126100d45760a060e060405192610e8f84610142565b610ec784968251610e9f81610569565b86526020830151610eaf816101d8565b60208701526040830151604087015260608301610bcf565b606085015260c081015160808501520151910152565b9080601f830112156100d4578151610ef4816102c8565b92610f02604051948561015d565b81845260208085019260051b8201019283116100d457602001905b828210610f2a5750505090565b8151815260209182019101610f1d565b9190916060818403126100d45760405190610f54826100ec565b819381516001600160401b0381116100d457820181601f820112156100d457805190610f7f826102c8565b91610f8d604051938461015d565b80835260208084019160051b830101918483116100d45760208101915b838310610fdb575050505083526020820151916001600160401b0383116100d457610c076040939284938301610edd565b82516001600160401b0381116100d4578201906060828803601f1901126100d45760405190611009826100ec565b60208301518252604083015160208301526060830151916001600160401b0383116100d45761104089602080969581960101610c13565b6040820152815201920191610faa565b9190916040818403126100d45780516001600160401b0381116100d4578101610160818503126100d45761108261017e565b9080516001600160401b0381116100d4578561109f918301610d12565b82526110ae8560208301610e70565b60208301526101208101516001600160401b0381116100d457856110d3918301610edd565b6040830152610140810151906001600160401b0382116100d4576110f991869101610edd565b60608201529260208201516001600160401b0381116100d4576109239201610f3a565b61018d9092919260a06060610100830195805184526020810151602085015261117060408201516040860190604080916001600160401b03815116845263ffffffff60208201511660208501520151910152565b0151910190604080916001600160401b03815116845263ffffffff60208201511660208501520151910152565b604051906111aa826100ec565b5f6040838281528260208201520152565b604051906111c88261010c565b815f81525f60208201526111da61119d565b6040820152606061027a61119d565b604051906111f860208361015d565b5f80835282815b82811061120b57505050565b60209060405161121a816100ec565b5f81525f8382015261122a61119d565b6040820152828285010152016111ff565b90611245826102c8565b611252604051918261015d565b8281528092611263601f19916102c8565b01905f5b82811061127357505050565b602090604051611282816100ec565b5f81525f8382015261129261119d565b604082015282828501015201611267565b91906112ad6111bb565b50825163ffffffff6020835151510151161115611604578051906112cf6111bb565b508151916020830151519251906112fa6112f16020840163ffffffff90511690565b63ffffffff1690565b93604083019061131182516001600160401b031690565b9160408901926001600160401b0361134161133586516001600160401b0390511690565b6001600160401b031690565b91169081141590816115e1575b506115d257516001600160401b03168251906001600160401b0361137c61133584516001600160401b031690565b9116036115c657905b60208201916113ab6113a76113a16112f1865163ffffffff1690565b846118b4565b1590565b6115b757855151955f965f5b8181106115335750508615611524576113cf906119c3565b60208151910120916113e08161165f565b925f5b8281106114a5575050506040015160608501519251611416936113a793916114109063ffffffff166112f1565b92611b18565b611496576109239460208361142f82966080968c611c30565b01916114486060845101516001600160401b0390511690565b60608a01918251916001600160401b0361146c61133585516001600160401b031690565b911611611485575b5050508752510151910151906116f8565b5260608351015190525f8080611474565b63528bd3ef60e01b5f5260045ffd5b806114b760019260208b510151611633565b5160206114c5825186611b02565b910151906040516114f581610a106020820194856014916bffffffffffffffffffffffff199060601b1681520190565b51902061150061018f565b91825260208201526115128288611633565b5261151d8187611633565b50016113e3565b6323188e3960e21b5f5260045ffd5b610dad60f31b61156661155961154a848751611633565b51516001600160f01b03191690565b6001600160f01b03191690565b148061159e575b61157a575b6001016113b7565b97506001611596602061158e8b8651611633565b5101516118dd565b989050611572565b506020806115ad838651611633565b510151511461156d565b633aa90f7f60e21b5f5260045ffd5b50606088015190611385565b637202e68560e11b5f5260045ffd5b90506115fc61133560608c01516001600160401b0390511690565b14155f61134e565b506109236111e9565b634e487b7160e01b5f52603260045260245ffd5b80511561162e5760200190565b61160d565b805182101561162e5760209160051b010190565b6040519061165482610127565b5f6020838281520152565b90611669826102c8565b611676604051918261015d565b8281528092611687601f19916102c8565b01905f5b82811061169757505050565b6020906116a2611647565b8282850101520161168b565b805191908290602001825e015f815290565b6040516001600160e01b03199091166020820152919061018d9083906116ea9060248301906116ae565b03601f19810184528361015d565b815151916117058361165f565b9161170f8461123b565b935f5b8181106117585750611725575b50505090565b61173d9282604060206113a795015191015192611b18565b611749575f808061171f565b630b92186960e31b5f5260045ffd5b611763818551611633565b519060408201916117748351611e25565b92602084019182511561185557600194816117ee602061182094519201946117e86117e16117a6885163ffffffff1690565b6001600160e01b03199063ff00ff00600882811b9190911691901c62ff00ff1617601081811b63ffff00001691901c61ffff161760e01b1690565b9151611f71565b906116c0565b602081519101206117fd61018f565b918252602082015261180f868c611633565b5261181a858b611633565b50611f96565b9051915161182c61019e565b928352602083015260408201526118438289611633565b5261184e8188611633565b5001611712565b63b4eb9e5160e01b5f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b906001820180921161188657565b611864565b906003820180921161188657565b906002820180921161188657565b9190820180921161188657565b906001600160ff1b03811681036118865760039060011b04906001820180921161188657101590565b60208151106118ed576020015190565b60405162461bcd60e51b8152602060048201526024808201527f42797465733a3a20746f427974657333323a206461746120697320746f20736860448201526337b93a1760e11b6064820152608490fd5b6040519061194d60208361015d565b5f8252565b6116ea611971949361197161018d9460405197889560208701906116ae565b906116ae565b61018d926119719594611996600c9460405198899560208701906116ae565b6001600160e01b03199290921682526001600160c01b03191660048201520360131981018552018361015d565b908151516119cf61193e565b905f5b818110611aa45750926119e86109239394612156565b91611a9e611a146040611a056117a6602087015163ffffffff1690565b9401516001600160401b031690565b67ffffffffffff000067ff00ff00ff00ff0066ff00ff00ff00ff8360081c169260081b169165ffff0000ffff65ffff0000ff0065ffffffffffff67ffff0000ffff0000861666ff0000ffff000085161760101c16941691161760101b161767ffffffff0000000063ffffffff8260201c169160201b166001600160401b0360c01b911760c01b1690565b92611977565b91611afb600191610a10611add611abf61154a888b51611633565b6040516001600160f01b031990911660208201529182906022820190565b611af56020611aed888b51611633565b510151611f71565b91611952565b92016119d2565b61092391611b0f916122d3565b9092919261232b565b919392908115611be1578451611b2d816123a7565b611b36826123a7565b916001611b4286612c4e565b1b5f805b838210611b615750505050611b5d94959650612c7e565b1490565b611b6b828c611633565b51519088821015611bd2578215159081611bc7575b50611bb8576001906020611b94848e611633565b510151611ba18489611633565b52808401611baf8488611633565b52910190611b46565b630647f54960e21b5f5260045ffd5b90508111155f611b80565b630834466160e31b5f5260045ffd5b639136328760e01b5f5260045ffd5b60408051909190611c01838261015d565b6001815291601f1901825f5b828110611c1957505050565b602090611c24611647565b82828501015201611c0d565b6113a791611d139360406020830192611cd7611cd26020611cae8751611c57815160ff1690565b90611c688482015163ffffffff1690565b9088810151611c9b6080606084015193015193611c8f611c866101ad565b60ff9097168752565b63ffffffff1685880152565b8984015260608301526080820152612450565b805190820120970151865160200151611ccc9063ffffffff166112f1565b90612544565b611878565b9460a0611ce2611bf0565b9551015190611cef61018f565b9182526020820152611d0085611621565b52611d0a84611621565b50015190612559565b61174957565b6040519060a082018281106001600160401b038211176101075760405260606080835f81525f60208201525f60408201525f838201520152565b60405190611d6082610127565b60606020835f81520152565b6040519061012082018281106001600160401b03821117610107576040525f61010083828152611d9a611d53565b6020820152826040820152611dad611d53565b6060820152826080820152611dc0611d53565b60a08201528260c0820152606060e08201520152565b90611de0826102c8565b611ded604051918261015d565b8281528092611dfe601f19916102c8565b01905f5b828110611e0e57505050565b602090611e19611d6c565b82828501015201611e02565b611e2d611d19565b50611e3661018f565b9081525f6020820152611e50611e4b82612565565b6118dd565b90611e5a816126a4565b611e66611e4b83612565565b611e72611e4b84612565565b91611e7c846126a4565b611e8581611dd6565b945f5b828110611eb457505050611e9a6101ad565b948552602085015260408401526060830152608082015290565b600190611ec083612817565b60ff611eca611d6c565b911680611ef65750600160c08201525b611ee4828a611633565b52611eef8189611633565b5001611e88565b60048103611f1a575060016040820152611f0f84612891565b60608201525b611eda565b60058103611f3d575060016080820152611f3384612891565b60a0820152611eda565b60068103611f5d575060018152611f5384612891565b6020820152611eda565b600803611f15576001610100820152611eda565b61092361197191610a10611f858251612156565b9160405194859360208501906116ae565b611f9e61119d565b505f5f925f5f5b60808501805180518310156120cf576040611fc384611fcb93611633565b510151151590565b806120a7575b61205e575b611fe66040611fc3848451611633565b80612025575b611ffa575b50600101611fa5565b81925061201d6020606061201360809560019551611633565b51015101516129e7565b929150611ff1565b50634953544d60e01b63ffffffff60e01b6120576060612046868651611633565b510151516001600160e01b03191690565b1614611fec565b95509250612081611e4b60206060612077878a51611633565b510151015161296d565b926120a1611e4b60206060612097858b51611633565b51015101516129ae565b95611fd6565b5063049534d560e41b63ffffffff60e01b6120c86060612046868651611633565b1614611fd1565b5050509250929082156120f4576120e461019e565b9283526020830152604082015290565b633eba99d160e21b5f5260045ffd5b60031981019190821161188657565b5f1981019190821161188657565b602003906020821161188657565b9190820391821161188657565b600190610923939260ff60f81b9060f81b16815201906116ae565b604081101561219a57610923612178612172610a109360021b90565b60ff1690565b60405160f89190911b6001600160f81b03191660208201529182906021820190565b6140008110156121f9576109236121d76121c46121bd611cd2610a109560021b90565b61ffff1690565b60ff61ff008260081b169160081c161790565b60405160f09190911b6001600160f01b03191660208201529182906022820190565b6340000000811015612276576109236122546122236112f161221e610a109560021b90565b611899565b63ffffff0062ff00ff62ffffff818460081c1616921660081b161763ffff000061ffff8260101c169160101b161790565b60405160e09190911b6001600160e01b03191660208201529182906024820190565b610a1061229b6122886122a093612a5f565b6040519283916020830160209181520190565b612b78565b6109236122c16121726122bc6122b68551612103565b60021b90565b61188b565b610a106040519384926020840161213b565b8151919060418303612303576122fc9250602082015190606060408401519301515f1a90612bc1565b9192909190565b50505f9160029190565b6004111561231757565b634e487b7160e01b5f52602160045260245ffd5b6123348161230d565b8061233d575050565b6123468161230d565b6001810361235d5763f645eedf60e01b5f5260045ffd5b6123668161230d565b60028103612381575063fce698f760e01b5f5260045260245ffd5b8061238d60039261230d565b146123955750565b6335e2f38360e21b5f5260045260245ffd5b906123b1826102c8565b6123be604051918261015d565b82815280926123cf601f19916102c8565b0190602036910137565b9492919695939096604051978896602088016123f4916116ae565b9063ffffffff60e01b16815260040161240c916116ae565b916001600160401b0360c01b16825263ffffffff60e01b166008820152600c01612435916116ae565b61243e916116ae565b03601f198101835261018d908361015d565b61253161250e6116ea6109239361248d61246b825160ff1690565b60405160f89190911b6001600160f81b03191660208201529283906021820190565b6116ea6124a46117a6602084015163ffffffff1690565b6124bf60408401516040519384916020830160209181520190565b60608301519361253f60806124de611a1488516001600160401b031690565b9561251c60406124f86117a660208c015163ffffffff1690565b9901516040519a8b916020830160209181520190565b03601f1981018b528a61015d565b01516040519889916020830160209181520190565b03601f19810189528861015d565b6123d9565b8061254d575090565b81039081116118865790565b929091611b5d92612df3565b602081019081516020810180911161188657815151106100d45760209051818351820101918291011161188657602061259d9161302b565b9080519060208201809211611886575290565b906020820191825182810180911161188657815151106100d45781156125fe5760209051818451820101918291011161188657816125ed9161302b565b918051918201809211611886575290565b50505060405161260f60208261015d565b5f815290565b60ff60049116019060ff821161188657565b1561262e57565b60405162461bcd60e51b815260206004820152602860248201527f756e657870656374656420707265666978206465636f64696e6720436f6d706160448201526731ba1e2ab4b73a1f60c11b6064820152608490fd5b906001600160401b03809116911601906001600160401b03821161188657565b6126ad81612817565b60038116806126c75750610923915060021c603f16612172565b6001810361270c5750611335906127066121726126fc6126ec61217261092397612817565b60061b67ffffffffffffffc01690565b9260021c603f1690565b90612684565b6002810361278257506112f19061277560ff846127668261272f61092398612817565b95816127558161274761274188612817565b97612817565b991660081b63ffffff001690565b911617921660101b63ffff00001690565b17921660181b63ff0000001690565b1760021c633fffffff1690565b6003036127c1576109239160ff6127a86127a36127bc94603f9060021c1690565b612615565b16906127b76008831115612627565b6125b0565b6129e7565b60405162461bcd60e51b815260206004820152601a60248201527f436f64652073686f756c6420626520756e726561636861626c650000000000006044820152606490fd5b90815181101561162e570160200190565b6020810190815160018101809111611886578151511061285d575181516001600160f81b0319916128489190612806565b511660f81c906128588151611878565b905290565b60405162461bcd60e51b815260206004820152600c60248201526b4f7574206f662072616e676560a01b6044820152606490fd5b612899611d53565b50602081019081516004810180911161188657815151106100d4576020815181845182010191829101116118865760046128d29161302b565b918051906004820180921161188657525f915f905b60048210612927575050806128fe612904926126a4565b906125b0565b61291f61290f61018f565b6001600160e01b03199093168352565b602082015290565b90926001600160f81b031961293c8584612806565b5116908460031b9185830460081486151715611886576001926001600160e01b0319918216901c16179301906128e7565b80516020116100d457602080610923920161302b565b90815181116100d457801561299e576020610923920161302b565b505060405161260f60208261015d565b8051806020116100d457601f1981019081116118865760408201916020018210611886576109239161302b565b8015611886575f190190565b80515f91815b6129f657505090565b90915f1983019083821161188657612a0e8284612806565b5160f81c91600381901b906001600160fd1b038116036118865760ff8111611886576001901b9182810292818404149015171561188657612a5891612a52916118a7565b926129db565b90816129ed565b8060081c9060081b907cff000000ff000000ff000000ff000000ff000000ff000000ff000000ff7dff000000ff000000ff000000ff000000ff000000ff000000ff000000ff007fff000000ff000000ff000000ff000000ff000000ff000000ff000000ff00000084167eff000000ff000000ff000000ff000000ff000000ff000000ff000000ff000084161760101c931691161760101b177bffffffff00000000ffffffff00000000ffffffff00000000ffffffff7fffffffff00000000ffffffff00000000ffffffff00000000ffffffff00000000821660201c911660201b1777ffffffffffffffff0000000000000000ffffffffffffffff8019821660401c911660401b17612b748160801c9160801b90565b1790565b80515f198101908111611886575b6001600160f81b0319612b998284612806565b5116612bad57612ba8906129db565b612b86565b600181018091116118865761092391612983565b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411612c43579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15612c38575f516001600160a01b03811615612c2e57905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f9160039190565b6001811115612c79575f19810190811161188657612c6b906130cc565b600181018091116118865790565b505f90565b9192905f83515b6001612c9086611621565b5114612de057612cbc612cb7856001612cb1612cab8a611621565b516130cc565b1b6118a7565b612112565b905f915f915b808310612ce557505050612cd9612cdf9194611878565b60011c90565b92612c85565b909192612cf28489611633565b5182612cfd86611878565b1080612dc2575b15612d635790612d4f6001926002612d3a612d1f898c611633565b51612d32612d2c8b611878565b8d611633565b519084613132565b9701965b612d48848b611633565b5260011c90565b612d59828b611633565b5201929190612cc2565b8987600183188610612da55791600180612d9b612d4f94612d938c612d8c8d9e9d869b9a611633565b5192611633565b519085613132565b9801980196612d3e565b612d4f9150916001612db988829695611633565b51970196612d3e565b50612dd5612dcf86611878565b8a611633565b516001821814612d04565b5050915050612def9150611621565b5190565b9290928215611be157835193841561301c5760015b858110612fee57506001841480612fe4575b80612fd2575b612fb757612e35612e3085613152565b6123a7565b94612e3e61018f565b945f865260208601968752612e5161018f565b925f845260208401948552612e6461019e565b905f82526020820193845260408201525f91805b612f03575b50505051612ef4575190515103612ee55781515f190182525b815115612ed957612ea6826133f4565b612eaf836133f4565b90612ebb845160010190565b84525f5260205260405f20612ed38451845190611633565b52612e96565b91612def915051611621565b637227423160e11b5f5260045ffd5b63072afb8760e51b5f5260045ffd5b612f0c816130cc565b92612f26612f1e6001861b809461212e565b9283926118a7565b938685612f338187613197565b92602084015180155f14612f6b575050505050508551518551145f03612e7d5780612f66612f60876133d8565b8a6133bd565b612e78565b60011480612faf575b15612f9b575050506020612f92826040612f66940151905190611633565b5101518a6133bd565b91612f6693916002612f60941b0391613220565b508015612f74565b925090925051612ee557612fcc602091611621565b51015190565b50612fdc81611621565b515115612e20565b5060018514612e1a565b612ff88183611633565b515161300c61300683612112565b84611633565b51511015611bb857600101612e08565b631a14a47760e31b5f5260045ffd5b919091613037836102f1565b613044604051918261015d565b838152613050846102f1565b602082019190601f1901368337939091905b602081101561309c578061308257505f19905b5182518216911916179052565b612cb761309161309692612120565b613410565b90613075565b909182518152602081018091116118865791602081018091116118865790601f1981019081111561306257611864565b806fffffffffffffffffffffffffffffffff1060071b81811c6001600160401b031060061b1781811c63ffffffff1060051b1781811c61ffff1060041b1781811c60ff1060031b1781811c600f1060021b1781811c60031060011b1790811c6001101790565b600116613145575f5260205260405f2090565b905f5260205260405f2090565b90815f925b61315e5750565b915f19830183811161188657600193169283910192613157565b60405190613185826100ec565b60606040835f81525f60208201520152565b61319f613178565b5080516131b260208301918251906118a7565b928251905b8482106131f9575b508293506131d26128589293518261212e565b9084519460408101516131e361019e565b968752836020880152604087015252825161212e565b90613208816040860151611633565b515182111561321a57600101906131b7565b906131bf565b91906020830151835193613233826123a7565b9361323d836123a7565b955f5b84811061336f5750505050935b600161325884611621565b5114613362575f945f905b80821061327757505084808452845261324d565b90956132838786611633565b5187878461329083611878565b1080613344575b156132eb576001926132c9836132c1612d2c6132ba6132e599976132d397611633565b5192611878565b519083613132565b612d48848c611633565b6132dd8289611633565b520196611899565b90613263565b5050845160208601515111156133355760019161332183926132c96133108c8c611633565b5161331a8a6133d8565b9083613132565b61332b8289611633565b5201960190613263565b63d8f29a1560e01b5f5260045ffd5b5061335761335183611878565b89611633565b516001841814613297565b93505050612def90611621565b6001906133a8604086016020613390825161338a86896118a7565b90611633565b51015161339d848d611633565b525182850190611633565b515184016133b6828a611633565b5201613240565b906133ce6020830151835190611633565b5260018151019052565b6133e86020820151825190611633565b51906001815101905290565b6134046020820151825190611633565b5181515f190190915290565b601f8111611886576101000a9056fea2646970667358221220ef5ccb88ede39e8e09e4f5d0e653ecda99ce9449ab4b4e971bbc15048d77a4e864736f6c634300081e0033","sourceMap":"2851:9773:150:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;:::i;:::-;;:::i;:::-;;;;;;-1:-1:-1;;2851:9773:150;;;;;;;;;;;;;;;;-1:-1:-1;;;3683:43:150;;;:92;;;;2851:9773;3683:144;;;;2851:9773;-1:-1:-1;2851:9773:150;;;;-1:-1:-1;;2851:9773:150;;;;3683:144;-1:-1:-1;;;829:40:62;;-1:-1:-1;3683:144:150;;;:92;-1:-1:-1;;;3730:45:150;;;-1:-1:-1;3683:92:150;;2851:9773;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;:::o;:::-;;:::i;:::-;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;:::o;:::-;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;:::o;:::-;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;:::o;:::-;;;;;;;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;:::o;:::-;;;;;;;;:::i;:::-;:::o;:::-;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;-1:-1:-1;;;;;2851:9773:150;;;;;:::o;:::-;;;;;;:::i;:::-;;;;;;;:::o;:::-;;;;;;:::i;:::-;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;:::i;:::-;;;;;;;;;;:::i;:::-;;;;;;;;;;:::o;:::-;;;-1:-1:-1;;2851:9773:150;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;:::o;:::-;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;:::i;:::-;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;:::o;:::-;-1:-1:-1;;;;;;2851:9773:150;;;;;:::o;:::-;-1:-1:-1;;;;;2851:9773:150;;;;;;-1:-1:-1;;2851:9773:150;;;;:::o;:::-;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;-1:-1:-1;2851:9773:150;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;-1:-1:-1;;2851:9773:150;;;;;;;;;;:::i;:::-;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;;;;;;;;;:::i;:::-;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;:::i;:::-;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;-1:-1:-1;;2851:9773:150;;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;:::i;:::-;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;:::i;:::-;;;;;;;;;:::i;:::-;;;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;:::i;:::-;;;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;:::i;:::-;;;;;:::o;:::-;;;;;;;;;;;;;;;;;:::i;:::-;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;;;:::i;:::-;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;-1:-1:-1;;2851:9773:150;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;-1:-1:-1;;2851:9773:150;;;;;;;:::i;:::-;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;;;:::i;:::-;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;2851:9773:150;;;;;;;;-1:-1:-1;;2851:9773:150;;;;:::o;:::-;;;;;;;;;;;;;;-1:-1:-1;2851:9773:150;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;:::i;:::-;;:::o;:::-;;;;;;-1:-1:-1;;2851:9773:150;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;:::i;:::-;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;:::i;:::-;;;4986:47;;;2851:9773;4986:47;2851:9773;4986:47;;2851:9773;;;;;5374:20;2851:9773;5121:59;5285:70;2851:9773;;5374:20;2851:9773;;;;;;;;:::i;:::-;;4986:47;;2851:9773;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;;5121:59;;;;;;:::i;:::-;2851:9773;;;;;:::i;:::-;;;;;5317:37;;2851:9773;5285:70;:::i;:::-;2851:9773;;;;5374:20;;;2851:9773;5374:20;;;:::i;:::-;;2851:9773;;5374:20;;;;;;:::i;:::-;2851:9773;;;;;;;;:::i;:::-;;;;;;;;;;-1:-1:-1;;2851:9773:150;;;;;;-1:-1:-1;;;2851:9773:150;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;:::i;:::-;;;;:::o;:::-;;;;;;-1:-1:-1;;2851:9773:150;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;:::i;:::-;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;:::i;:::-;4220:48;;;;;2851:9773;;;;;;;;;;:::i;:::-;4356:52;;;2851:9773;;;;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;:::i;:::-;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;4513:70;2851:9773;;;;;:::i;:::-;;;:::i;4513:70::-;2851:9773;;-1:-1:-1;;;;;4639:28:150;:25;2851:9773;;4602:20;;;;;2851:9773;4602:20;;;:::i;:::-;;2851:9773;;4602:20;;;;;;:::i;:::-;4639:25;;2851:9773;-1:-1:-1;;;;;2851:9773:150;;;4639:28;2851:9773;;;;;;;;:::i;:::-;;;;;;:::i;:::-;;;;;;:::i;:::-;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;:::i;:::-;;;;;;;;;;:::i;:::-;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;-1:-1:-1;2851:9773:150;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;-1:-1:-1;;2851:9773:150;;;;;;;;;;:::i;:::-;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;;;;;;;;;:::i;:::-;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;:::i;:::-;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;-1:-1:-1;;2851:9773:150;;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;:::i;:::-;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;;;:::i;:::-;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;-1:-1:-1;;2851:9773:150;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;;;;;:::i;:::-;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;:::i;:::-;;;;;;;;;:::i;:::-;;;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;:::i;:::-;;;;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;:::i;:::-;;;;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;2851:9773:150;;;;;;;;;;;;:::o;:::-;;;;;;;:::i;:::-;;-1:-1:-1;2851:9773:150;;-1:-1:-1;2851:9773:150;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;5983:25;2851:9773;;;;5983:25;2851:9773;;;;;;;;;:::o;:::-;;;;;;;;:::i;:::-;5983:25;2851:9773;;5983:25;2851:9773;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;;;;:::i;:::-;;;-1:-1:-1;2851:9773:150;;;;;;;;;:::o;:::-;;;;;;;;:::i;:::-;-1:-1:-1;2851:9773:150;;-1:-1:-1;2851:9773:150;;;;;;:::i;:::-;;;;;;;;;;;;;;5575:827;;;2851:9773;;:::i;:::-;5750:26;2851:9773;;;6012:51;:11;;:28;:39;:51;2851:9773;;-1:-1:-1;5983:80:150;5979:160;;6239:11;;2851:9773;;;:::i;:::-;;7074:27;;:33;6012:51;7074:33;;;2851:9773;7147:38;;:50;7124:73;7147:50;6012:51;7147:50;;2851:9773;;;;;;7147:50;2851:9773;;;;7124:73;7303:25;;;;2851:9773;;;;-1:-1:-1;;;;;2851:9773:150;;;;7332:32;7303:25;7332:32;;;-1:-1:-1;;;;;7303:64:150;7332:35;:32;;-1:-1:-1;;;;;2851:9773:150;;;;;7332:35;-1:-1:-1;;;;;2851:9773:150;;;7303:64;2851:9773;;7303:64;;;;:145;;;;5575:827;7286:226;;;2851:9773;-1:-1:-1;;;;;2851:9773:150;7579:32;;2851:9773;-1:-1:-1;;;;;7550:64:150;2851:9773;;;-1:-1:-1;;;;;2851:9773:150;;;7550:64;2851:9773;;7550:64;7681:87;;;;6012:51;7819:16;;2851:9773;7782:54;7783:53;;2851:9773;;;;;;;7783:53;;;:::i;:::-;7782:54;;2851:9773;7782:54;7778:90;;7903:18;;2851:9773;7938:15;-1:-1:-1;7968:13:150;-1:-1:-1;7983:17:150;;;;;;8226:21;;;;8222:54;;8355:24;;;:::i;:::-;6012:51;2851:9773;;;;8345:35;8435;;;;:::i;:::-;8485:13;-1:-1:-1;8500:10:150;;;;;;-1:-1:-1;;;7303:25:150;8872:17;2851:9773;;8891:16;;;2851:9773;;8953:6;;8843:96;;8872:17;8843:96;;2851:9773;;;;8843:96;;;:::i;8953:6::-;8949:44;;6303:54;9044:7;6012:51;9044:7;;;;9405:30;9044:7;;;:::i;:::-;9066:24;;:44;2851:9773;9066:24;;:41;;-1:-1:-1;;;;;2851:9773:150;;;;;9066:44;2851:9773;9113:29;;;;;2851:9773;-1:-1:-1;;;;;9066:79:150;2851:9773;;;-1:-1:-1;;;;;2851:9773:150;;;9066:79;2851:9773;;9066:79;9062:261;;8480:340;2851:9773;;;;;9405:24;:30;2851:9773;6341:15;;;6303:54;;:::i;9062:261::-;9161:64;2851:9773;9271:24;;:41;;9239:73;;9062:261;;;;;8949:44;8968:25;;;-1:-1:-1;8968:25:150;;-1:-1:-1;8968:25:150;8512:3;8550:27;:36;2851:9773;8550:27;6012:51;8550:27;;:33;;:36;:::i;:::-;;6012:51;8620:45;8650:14;;8620:45;;:::i;:::-;8742:19;;2851:9773;;7303:25;2851:9773;8779:27;;;6012:51;8779:27;;;;2851:9773;;;;;;;;;;;;;8779:27;2851:9773;8769:38;;2851:9773;;:::i;:::-;;;;6012:51;8712:97;;2851:9773;8679:130;;;;:::i;:::-;;;;;;:::i;:::-;;2851:9773;8485:13;;8222:54;8256:20;;;-1:-1:-1;8256:20:150;;-1:-1:-1;8256:20:150;8002:3;2851:9773;;;8025:47;:24;:21;:18;;;:21;:::i;:::-;;2851:9773;-1:-1:-1;;;;;;2851:9773:150;;;8025:24;-1:-1:-1;;;;;;2851:9773:150;;;8025:47;;:90;;;8002:3;8021:182;;8002:3;2851:9773;;7968:13;;8021:182;8161:18;;2851:9773;8145:43;6012:51;8161:21;:18;;;:21;:::i;:::-;;:26;;8145:43;:::i;:::-;8021:182;;;;;8025:90;8076:18;6012:51;8076:18;:21;:18;;;:21;:::i;:::-;;:26;;2851:9773;8076:39;8025:90;;7778;7845:23;;;-1:-1:-1;7845:23:150;;-1:-1:-1;7845:23:150;7681:87;7739:29;2851:9773;7739:29;;;7681:87;;;7286:226;7480:21;;;-1:-1:-1;7480:21:150;;-1:-1:-1;7480:21:150;7303:145;7416:29;;7387:61;7416:32;2851:9773;7416:29;;;-1:-1:-1;;;;;2851:9773:150;;;;;7387:61;;;7303:145;;;5979:160;6101:26;;;:::i;2851:9773::-;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;:::i;:::-;;;;;;;;;;;;;;;:::o;:::-;;;;;;;:::i;:::-;-1:-1:-1;2851:9773:150;;;;;;;:::o;:::-;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;;;;:::i;:::-;;;-1:-1:-1;2851:9773:150;;;;;;;;;:::o;:::-;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;-1:-1:-1;;;;;;2851:9773:150;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;:::i;10662:1266::-;10849:16;;2851:9773;10922:32;;;;:::i;:::-;11007:28;;;;:::i;:::-;11051:13;-1:-1:-1;11066:7:150;;;;;;11717;11713:178;;11046:657;11901:20;;;10662:1266;:::o;11713:178::-;11848:6;11793:11;;11193;11223:13;11753:77;11793:11;;;11814:15;;2851:9773;11753:77;;:::i;11848:6::-;11844:36;;11713:178;;;;;11844:36;11863:17;;;-1:-1:-1;11863:17:150;;-1:-1:-1;11863:17:150;11075:3;11118:19;:16;;;:19;:::i;:::-;;11193:11;;;;;11174:31;11193:11;;11174:31;:::i;:::-;11223:13;;;;2851:9773;;;11223:18;11219:52;;2851:9773;;;11375:87;11223:13;11528:24;2851:9773;;11415:7;;2851:9773;11426:35;11388:36;11408:15;2851:9773;;;;;;11408:15;-1:-1:-1;;;;;;2851:9773:150;;;;;;;;;;;;;8200:10:78;2851:9773:150;8168:49:78;2851:9773:150;;;;;;;;;;;8266:21:78;2851:9773:150;;;8815:111:78;;11388:36:150;11449:11;;11426:35;:::i;:::-;11375:87;;:::i;:::-;11223:13;2851:9773;;;;11365:98;2851:9773;;:::i;:::-;;;;11223:13;11298:179;;2851:9773;11286:191;;;;:::i;:::-;;;;;;:::i;:::-;;11528:24;:::i;:::-;2851:9773;;;;;;:::i;:::-;;;;11223:13;11601:91;;2851:9773;11193:11;11601:91;;2851:9773;11566:126;;;;:::i;:::-;;;;;;:::i;:::-;;2851:9773;11051:13;;11219:52;11250:21;;;-1:-1:-1;11250:21:150;;-1:-1:-1;11250:21:150;2851:9773;;;;;;;;;;;;;;10208:1;2851:9773;;;;;;;:::o;:::-;;:::i;:::-;;5093:1:78;2851:9773:150;;;;;;;:::o;:::-;;4835:1:78;2851:9773:150;;;;;;;:::o;:::-;;;;;;;;;;:::o;12327:146::-;;-1:-1:-1;;;;;2851:9773:150;;;;;;12460:1;2851:9773;;;;;;;;;;;;;12438:28;;12327:146;:::o;4543:226:71:-;4650:2;2851:9773:150;;4635:17:71;2851:9773:150;;4650:2:71;4703:60;;4543:226;:::o;2851:9773:150:-;;;-1:-1:-1;;;2851:9773:150;;4650:2:71;2851:9773:150;;;;;;;;;;;;;;-1:-1:-1;;;2851:9773:150;;;;;;;;;;;;;;;:::i;:::-;;;;:::o;:::-;;;;;;;;;;;;;;;;;;:::i;:::-;;;:::i;:::-;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;;2851:9773:150;;;;;;-1:-1:-1;;;;;;2851:9773:150;;;;;;-1:-1:-1;;2851:9773:150;;;;;;;:::i;1570:684:148:-;;1684:18;;2851:9773:150;;;:::i;:::-;1766:13:148;-1:-1:-1;1781:14:148;;;;;;2059:40;;;2033:214;2059:40;;;:::i;:::-;2154:22;2191:46;2851:9773:150;;2134:43:148;2851:9773:150;1881:42:148;2154:22;;2851:9773:150;;;;;2134:43:148;2211:25;;2851:9773:150;-1:-1:-1;;;;;2851:9773:150;;;;;7788:18:78;2851:9773:150;;;;;;;;;;7937:18:78;7933:22;2851:9773:150;7902:18:78;7898:22;;;;;;2851:9773:150;;;7932:30:78;7933:22;;;;2851:9773:150;;;7896:67:78;2851:9773:150;;;;;;8025:7:78;2851:9773:150;;;-1:-1:-1;;;;;2851:9773:150;;8012:21:78;;2851:9773:150;;;8698:111:78;;2191:46:148;2033:214;;:::i;1797:3::-;1898:18;1826:179;2851:9773:150;1898:18:148;1881:42;;1898:24;:21;:18;;;:21;:::i;:24::-;2851:9773:150;;-1:-1:-1;;;;;;2851:9773:150;;;1881:42:148;;;2851:9773:150;;;;;;;;;1881:42:148;1941:50;1881:42;1964:21;:18;;;:21;:::i;:::-;;:26;;1941:50;:::i;:::-;1826:179;;:::i;:::-;1797:3;2851:9773:150;1766:13:148;;3714:255:58;3927:8;3714:255;3871:27;3714:255;3871:27;:::i;:::-;3927:8;;;;;:::i;1990:238:69:-;;;;;3884:14;;3880:38;;2851:9773:150;;3995:18:69;;;:::i;:::-;4049;;;:::i;:::-;4106:20;4101:1;4106:20;;;:::i;:::-;2851:9773:150;-1:-1:-1;;4179:7:69;;;;;;4577:42;;;;;;;;;;:::i;:::-;2174:47;1990:238;:::o;4168:9::-;4217;;;;:::i;:::-;;2851:9773:150;4250:16:69;;;;;4246:51;;4315:6;;;:26;;;;4168:9;4311:55;;;4101:1;4392:9;:14;:9;;;;:::i;:::-;;:14;2851:9773:150;4380:26:69;;;;:::i;:::-;2851:9773:150;;;;4448:33:69;;;;:::i;:::-;2851:9773:150;;;4168:9:69;;;4311:55;4350:16;;;-1:-1:-1;4350:16:69;;-1:-1:-1;4350:16:69;4315:26;4325:16;;;;;4315:26;;;4246:51;4275:22;;;-1:-1:-1;4275:22:69;;-1:-1:-1;4275:22:69;3880:38;3907:11;;;-1:-1:-1;3907:11:69;;-1:-1:-1;3907:11:69;2851:9773:150;;;;;;;;;;;:::i;:::-;10208:1;2851:9773;;;-1:-1:-1;;2851:9773:150;;-1:-1:-1;2851:9773:150;;;;;;;;;:::o;:::-;;;;;:::i;:::-;;;;;;;;;;9491:1054;10416:75;9491:1054;10506:6;9491:1054;9894:30;9765:19;;;;10127:82;:78;9765:19;9684:403;9765:19;;2851:9773;;;;;;;;9828:32;2851:9773;9828:32;;;2851:9773;;;;;;9894:30;;;;2851:9773;9714:359;10029:25;9964:36;;;;10029:25;;2851:9773;;9714:359;2851:9773;;:::i;:::-;;;;;;;;9714:359;2851:9773;;9714:359;;;2851:9773;;9714:359;;;;2851:9773;9964:36;9714:359;;2851:9773;10029:25;9714:359;;2851:9773;9684:403;:::i;:::-;2851:9773;;;;;9661:436;10137:33;;2851:9773;10172:19;;9765;10172:32;2851:9773;10127:78;;2851:9773;;10172:32;2851:9773;10127:78;;;:::i;:::-;:82;:::i;:::-;10262:33;2851:9773;10262:33;;:::i;:::-;10350:19;;:29;2851:9773;;;;:::i;:::-;;;;9765:19;10317:76;;2851:9773;10305:88;;;:::i;:::-;;;;;:::i;:::-;;10457:14;;10416:75;;:::i;10506:6::-;10502:36;;9491:1054::o;2851:9773::-;;;;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;-1:-1:-1;2851:9773:150;;-1:-1:-1;2851:9773:150;;;;-1:-1:-1;2851:9773:150;;;;-1:-1:-1;2851:9773:150;;;;;;:::o;:::-;;;;;;;:::i;:::-;;;;-1:-1:-1;2851:9773:150;;;;:::o;:::-;;;;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;-1:-1:-1;2851:9773:150;;;;;;;:::i;:::-;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;:::o;:::-;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;;;;:::i;:::-;;;-1:-1:-1;2851:9773:150;;;;;;;;;:::o;:::-;;;;;:::i;:::-;;;;;;;;;;2849:1495:148;2851:9773:150;;:::i;:::-;;;;:::i;:::-;;;;2985:1:148;2966:21;;;2851:9773:150;3018:38:148;3034:21;;;:::i;:::-;3018:38;:::i;:::-;3088:35;;;;:::i;:::-;3153:38;3169:21;;;:::i;3153:38::-;3226;3242:21;;;:::i;3226:38::-;3292:35;;;;:::i;:::-;3363:20;;;:::i;:::-;3399:13;2985:1;3414:10;;;;;;2851:9773:150;;;;;:::i;:::-;;;;2966:21:148;4270:67;;2851:9773:150;;4270:67:148;;2851:9773:150;4270:67:148;;;2851:9773:150;4270:67:148;;;2851:9773:150;2849:1495:148;:::o;3426:3::-;2851:9773:150;3458:21:148;;;;:::i;:::-;2851:9773:150;;;:::i;:::-;;;3531:25:148;;;-1:-1:-1;2851:9773:150;3576:14:148;;;1506:1;3576:21;4223:19;;;;:::i;:::-;;;;;;:::i;:::-;;2851:9773:150;3399:13:148;;3527:683;1327:1;3622:29;;1327:1;;-1:-1:-1;2851:9773:150;;3671:18:148;;1506:1;3733:23;;;:::i;:::-;3714:16;;;:42;3618:592;3527:683;;3618:592;1377:1;3781:24;;1377:1;;-1:-1:-1;2851:9773:150;3825:13:148;;;1506:1;3877:23;;;:::i;:::-;3863:11;;;:37;3527:683;;3777:433;1433:1;3925:30;;1433:1;;-1:-1:-1;2851:9773:150;1506:1:148;;4039:23;;;:::i;:::-;2966:21;4019:17;;:43;3527:683;;3921:289;1506:1;4087:47;3777:433;4083:127;2851:9773:150;4154:34:148;;;1506:1;3527:683;;9049:172:78;9158:56;2851:9773:150;9049:172:78;2851:9773:150;9175:31:78;2851:9773:150;;9175:31:78;:::i;:::-;2851:9773:150;;;9158:56:78;;;;;;2851:9773:150;;:::i;5508:967:152:-;2851:9773:150;;:::i;:::-;;-1:-1:-1;;5666:17:152;-1:-1:-1;;5739:3:152;5718:12;;;;;2851:9773:150;;5714:23:152;;;;;5762:27;:15;;:27;:15;;:::i;:::-;;:27;2851:9773:150;;;;;5762:27:152;:89;;;5739:3;5758:305;;5739:3;6081:27;5762;6081:15;:12;;;:15;:::i;:27::-;:89;;;5739:3;6077:196;;5739:3;-1:-1:-1;2851:9773:150;;5699:13:152;;6077:196;6227:12;;;6202:56;6227:30;:25;:15;5718:12;6227;2851:9773:150;6227:12:152;;:15;:::i;:::-;;:25;;:30;;6202:56;:::i;:::-;6077:196;;;;;6081:89;5133:14;;;;2851:9773:150;;;6112:37:152;:25;:15;:12;;;:15;:::i;:::-;;:25;;2851:9773:150;-1:-1:-1;;;;;;2851:9773:150;;;6112:37:152;2851:9773:150;6112:58:152;6081:89;;5758:305;5910:12;;;;5881:68;5897:51;5910:30;:25;:15;:12;;;:15;:::i;:::-;;:25;;:30;;5897:51;:::i;5881:68::-;6012:12;5983:65;5999:48;5910:30;:25;6012:15;:12;;;:15;:::i;:::-;;:25;;:30;;5999:48;:::i;5983:65::-;5758:305;;;5762:89;2851:9773:150;;;;;;;5793:37:152;:25;:15;:12;;;:15;:::i;:37::-;2851:9773:150;5793:58:152;5762:89;;5714:23;;;;;;;;6321:14;;6317:46;;2851:9773:150;;:::i;:::-;;;;6381:87:152;;;2851:9773:150;5762:27:152;6381:87;;2851:9773:150;5508:967:152;:::o;6317:46::-;6344:19;;;-1:-1:-1;6344:19:152;;-1:-1:-1;6344:19:152;2851:9773:150;-1:-1:-1;;2851:9773:150;;;;;;;;:::o;:::-;-1:-1:-1;;2851:9773:150;;;;;;;;:::o;:::-;;;;;;;;;:::o;:::-;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;:::i;4484:686:78:-;4577:2;4573:6;;4577:2;;;4602:31;4619:13;4625:6;4602:31;4625:6;2851:9773:150;;;;4625:6:78;2851:9773:150;;;;4619:13:78;4577:2;2851:9773:150;;;;;;-1:-1:-1;;;;;;2851:9773:150;4602:31:78;;;2851:9773:150;;;;;;;;;4569:595:78;4658:7;4654:11;;4658:7;;;4688:51;4705:33;4715:22;4723:12;4724:6;4688:51;4724:6;2851:9773:150;;;;4723:12:78;2851:9773:150;;;;4715:22:78;2851:9773:150;;;8428:1:78;2851:9773:150;;;8428:1:78;2851:9773:150;;8422:19:78;8300:148;;4705:33;4577:2;2851:9773:150;;;;;;-1:-1:-1;;;;;;2851:9773:150;4688:51:78;;;2851:9773:150;;;;;;;;;4650:514:78;4764:7;4760:11;;4764:7;;;4794:51;4811:33;4821:22;4829:12;4830:6;4794:51;4830:6;2851:9773:150;;;;4830:6:78;4829:12;:::i;4821:22::-;2851:9773:150;8200:10:78;2851:9773:150;;;;;;;8195:21:78;8196:14;2851:9773:150;;;8168:49:78;2851:9773:150;;;;;;8279:7:78;2851:9773:150;;;8266:21:78;8046:248;;4811:33;4577:2;2851:9773:150;;;;;;-1:-1:-1;;;;;;2851:9773:150;4794:51:78;;;2851:9773:150;;;;;;;;;4756:408:78;4942:31;;4959:13;4902:85;4959:13;;:::i;:::-;4577:2;2851:9773:150;4942:31:78;;;;;;2851:9773:150;;;;;;;4942:31:78;4902:85;:::i;:::-;5117:36;5065:30;5071:23;5072:17;5073:10;2851:9773:150;;5073:10:78;:::i;:::-;2851:9773:150;;;;5072:17:78;5071:23;:::i;5065:30::-;5117:36;4577:2;2851:9773:150;5117:36:78;;;4942:31;5117:36;;;:::i;2129:778:58:-;2851:9773:150;;;2129:778:58;2319:2;2299:22;;2319:2;;2751:25;2535:196;;;;;;;;;;;;;;;-1:-1:-1;2535:196:58;2751:25;;:::i;:::-;2744:32;;;;;:::o;2295:606::-;2807:83;;2823:1;2807:83;2827:35;2807:83;;:::o;2851:9773:150:-;;-1:-1:-1;2851:9773:150;;;:::o;:::-;;;;;;;;;;;;7280:532:58;2851:9773:150;;;:::i;:::-;7366:29:58;;;7411:7;;:::o;7362:444::-;2851:9773:150;;;:::i;:::-;7471:29:58;7462:38;;7471:29;;7523:23;;;7375:20;7523:23;;7375:20;7523:23;7458:348;2851:9773:150;;;:::i;:::-;7576:35:58;7567:44;;7576:35;;7634:46;;;;7375:20;7634:46;7763:32;2851:9773:150;;7375:20:58;7634:46;7563:243;2851:9773:150;;7710:30:58;2851:9773:150;;:::i;:::-;7701:39:58;7697:109;;7563:243;7280:532::o;7697:109::-;7763:32;;;7375:20;7763:32;;2851:9773:150;;7375:20:58;7763:32;2851:9773:150;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;;;;:::i;:::-;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;:::i;:::-;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;;;;;;;;;:::i;:::-;;;;:::i;:::-;;;;;;;;;;;;:::i;2305:494:148:-;2754:28;2696:44;2434:30;2408:384;2305:494;2434:30;2851:9773:150;;;;;;;;;;;;;;;-1:-1:-1;;;;;;2851:9773:150;2434:30:148;;;2851:9773:150;;;;;;;;;2434:30:148;2530:33;2478:38;2851:9773:150;2434:30:148;2498:17;;2851:9773:150;;;;;2478:38:148;2530:33;2851:9773:150;2547:15:148;;2851:9773:150;;;2530:33:148;;;2434:30;2530:33;;2851:9773:150;;;;;;;2530:33:148;2597:21;;;;2851:9773:150;2754:28:148;2771:10;2577:45;2851:9773:150;;;-1:-1:-1;;;;;2851:9773:150;;;2577:45:148;2656:25;2696:44;2851:9773:150;2636:46:148;2851:9773:150;2434:30:148;2656:25;;2851:9773:150;;;;;2636:46:148;2713:26;;2851:9773:150;;;2696:44:148;;;2434:30;2696:44;;2851:9773:150;;;;;;;2696:44:148;;2851:9773:150;;2696:44:148;;;;;;:::i;:::-;2771:10;2851:9773:150;;;2754:28:148;;;2434:30;2754:28;;2851:9773:150;;;;;;;2754:28:148;;2851:9773:150;;2754:28:148;;;;;;:::i;:::-;2408:384;:::i;12018:252:150:-;12130:20;;;12166:19;;:::o;12126:138::-;2851:9773;;;;;;;12216:37;:::o;3366:228:68:-;;;;3548:39;3366:228;3548:39;:::i;2089:399:71:-;2966:21:148;2216:11:71;;2851:9773:150;;;2966:21:148;2851:9773:150;;;;;;;2237:9:71;;2851:9773:150;-1:-1:-1;2851:9773:150;;2966:21:148;2264:48:71;2351:9;2851:9773:150;;;;;;1938:75:72;;;;2851:9773:150;;;2966:21:148;2392:39:71;;;:::i;:::-;2851:9773:150;;;;2966:21:148;2851:9773:150;;;;;;;;2089:399:71;:::o;:::-;;2216:11;;;2851:9773:150;;;;;;;;;;;2237:9:71;;2851:9773:150;-1:-1:-1;2851:9773:150;;2268:8:71;;2264:48;;2216:11;2351:9;;2851:9773:150;;;;;;1938:75:72;;;;2851:9773:150;;;2392:39:71;;;;:::i;:::-;2851:9773:150;;;;;;;;;;;;2089:399:71;:::o;2264:48::-;2851:9773:150;;;;;;;;;:::i;:::-;-1:-1:-1;2851:9773:150;;2292:9:71;:::o;2851:9773:150:-;;;;;;;;;;;;:::o;:::-;;;;:::o;:::-;;;-1:-1:-1;;;2851:9773:150;;;;;;;;;;;;;;;;;-1:-1:-1;;;2851:9773:150;;;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;:::o;1205:1510:78:-;1323:20;;;:::i;:::-;2851:9773:150;;;;1453:9:78;;-1:-1:-1;1501:14:78;;-1:-1:-1;2851:9773:150;;;;1509:6:78;2851:9773:150;1449:1238:78;1579:1;1571:9;;1579:1;;1634:20;1782:11;1634:20;1782:11;1787:6;1740:7;1692:13;1634:20;1842:9;1634:20;;:::i;1692:13::-;2851:9773:150;;;;;;1740:7:78;1787:6;2851:9773:150;;;;;;1782:11:78;;;:::i;1567:1120::-;1880:1;1872:9;;1880:1;;1943:20;2275:8;1943:20;2243:16;2851:9773:150;1943:20:78;2194:16;1943:20;;2336:11;1943:20;;:::i;:::-;2013;;2118:15;2013:20;2058;2013;;;:::i;:::-;2058;;:::i;:::-;2851:9773:150;;;;;;;;2118:15:78;2851:9773:150;;2105:29:78;;2851:9773:150;;;;;;;2194:16:78;2188:23;;2851:9773:150;;;;;;;2243:16:78;2237:23;2851:9773:150;;;;;;1868:819:78;2376:1;2368:9;2376:1;;2575:34;2450:6;2851:9773:150;2449:12:78;2450:6;2589:19;2450:6;2851:9773:150;;;;;;;2450:6:78;2449:12;:::i;:::-;2851:9773:150;2503:6:78;2495:59;2508:1;2503:6;;;2495:59;:::i;:::-;2589:19;:::i;:::-;2575:34;:::i;2364:323::-;2851:9773:150;;-1:-1:-1;;;2640:36:78;;2851:9773:150;1393:1:78;2640:36;;2851:9773:150;;;;;;;;;;;;;2640:36:78;2851:9773:150;;;;;;;;;;;;;:::o;1564:269:71:-;1649:11;;;2851:9773:150;;;1663:1:71;2851:9773:150;;;;;;;1667:9:71;;2851:9773:150;-1:-1:-1;1645:87:71;;1758:9;2851:9773:150;;-1:-1:-1;;;;;;2851:9773:150;1758:22:71;;2851:9773:150;1758:22:71;:::i;:::-;2851:9773:150;;;;;1791:16:71;2851:9773:150;;1791:16:71;:::i;:::-;2851:9773:150;;1564:269:71;:::o;1645:87::-;2851:9773:150;;-1:-1:-1;;;1699:22:71;;1649:11;1699:22;;;2851:9773:150;;;;;;-1:-1:-1;;;2851:9773:150;;;;1699:22:71;;;4445:308:148;2851:9773:150;;:::i;:::-;;2216:11:71;;;2851:9773:150;;;4586:1:148;2851:9773:150;;;;;;;2237:9:71;;2851:9773:150;-1:-1:-1;2851:9773:150;;2216:11:71;2351:9;;2851:9773:150;;;;;;1938:75:72;;;;2851:9773:150;;;4586:1:148;2392:39:71;;;:::i;:::-;2851:9773:150;;;;4586:1:148;2851:9773:150;;;;;;;;4590:1:148;5413:13:71;4590:1:148;5408:106:71;5428:5;4586:1:148;5428:5:71;;;;4619:35:148;;;;4684:25;4619:35;;:::i;:::-;4684:25;;:::i;:::-;4726:20;2851:9773:150;;:::i;:::-;-1:-1:-1;;;;;;2851:9773:150;;;;;;4726:20:148;2216:11:71;4726:20:148;;2851:9773:150;4445:308:148;:::o;5435:3:71:-;2851:9773:150;;-1:-1:-1;;;;;;5468:16:71;2851:9773:150;;5468:16:71;:::i;:::-;2851:9773:150;;;;;;;;;;5501:1:71;2851:9773:150;;;;;;;;;-1:-1:-1;;;;;;2851:9773:150;;;;;;5454:49:71;5435:3;2851:9773:150;5413:13:71;;;3313:349;2851:9773:150;;5910:30:152;3466:31:71;2851:9773:150;;5910:30:152;3512:8:71;3617:38;3512:8;1938:75:72;3617:38:71;:::i;3313:349::-;;2851:9773:150;;3466:31:71;;2851:9773:150;;3512:8:71;;3508:48;;1938:75:72;3617:38:71;2851:9773:150;1938:75:72;3617:38:71;:::i;3508:48::-;2851:9773:150;;;;;;;;:::i;2744:313:71:-;2851:9773:150;;2876:25:71;5910:30:152;2876:25:71;2851:9773:150;;-1:-1:-1;;2851:9773:150;;;;;;;;;;;5910:30:152;1938:75:72;2851:9773:150;-1:-1:-1;2851:9773:150;;3012:38:71;;;:::i;2851:9773:150:-;;;;;-1:-1:-1;;2851:9773:150;;:::o;823:320:78:-;2851:9773:150;;;;;961:5:78;;;1123:13;;823:320;:::o;968:3::-;2851:9773:150;;-1:-1:-1;;2851:9773:150;;;;;;;;1051:11:78;;;;:::i;:::-;2851:9773:150;;;;;;;;;-1:-1:-1;;;;;2851:9773:150;;;;;;;;;;1060:1:78;2851:9773:150;;1037:66:78;2851:9773:150;;;;;;;;;;;;;;968:3:78;1012:91;;;;:::i;:::-;968:3;;:::i;:::-;936:23;;;;5627:1354;2851:9773:150;5873:1:78;2851:9773:150;;5873:1:78;2851:9773:150;6063:110:78;6191:86;;6064;;;;;;;2851:9773:150;;6190:110:78;6191:86;;;;2851:9773:150;;6062:239:78;6511:66;6384;6364:86;;2851:9773:150;;6490:110:78;6491:86;2851:9773:150;;6362:239:78;6811:66;6684;;6664:86;;2851:9773:150;;6790:110:78;6791:86;2851:9773:150;;6662:239:78;6965:8;2851:9773:150;;;6965:8:78;2851:9773:150;;;;6965:8:78;6951:23;5627:1354;:::o;6202:380:71:-;2851:9773:150;;-1:-1:-1;;2851:9773:150;;;;;;;6414:3:71;-1:-1:-1;;;;;;6437:7:71;;;;:::i;:::-;2851:9773:150;;6433:86:71;;6414:3;;;:::i;:::-;6382:22;;6433:86;6403:1;2851:9773:150;;;;;;;6546:29:71;;;:::i;5203:1551:58:-;;;6283:66;6270:79;;6266:164;;2851:9773:150;;;;;;-1:-1:-1;2851:9773:150;;;;;;;;;;;;;;;;;;;6541:24:58;;;;;;;;;-1:-1:-1;6541:24:58;-1:-1:-1;;;;;2851:9773:150;;6579:20:58;6575:113;;6698:49;-1:-1:-1;6698:49:58;-1:-1:-1;5203:1551:58;:::o;6575:113::-;6615:62;-1:-1:-1;6615:62:58;6541:24;6615:62;-1:-1:-1;6615:62:58;:::o;6541:24::-;2851:9773:150;;;;;;;;;6266:164:58;6365:54;;;6381:1;6365:54;6385:30;6365:54;;:::o;7197:131:69:-;7277:1;7272:6;;;7268:20;;-1:-1:-1;;2851:9773:150;;;;;;;7305:12:69;;;:::i;:::-;7277:1;2851:9773:150;;;;;;;7197:131:69;:::o;7268:20::-;7280:8;2851:9773:150;7280:8:69;:::o;5056:1349::-;;;;2851:9773:150;;;5309:1063:69;5332:1;5316:12;;;:::i;:::-;2851:9773:150;5316:17:69;;;5369:45;:41;5381:12;5332:1;5375:19;5381:12;;;:::i;:::-;2851:9773:150;5375:19:69;:::i;:::-;2851:9773:150;5369:41:69;:::i;:::-;:45;:::i;:::-;5428:9;2851:9773:150;5457:9:69;2851:9773:150;5452:836:69;5468:7;;;;;;6302;;;6339:16;6338:23;6302:7;6339:16;;:::i;:::-;2851:9773:150;;;;6338:23:69;5309:1063;;;5457:9;5510:12;;;;;;;:::i;:::-;2851:9773:150;5563:5:69;;;;:::i;:::-;:11;:44;;;5457:9;5719:442;;;5786:9;6230:8;5332:1;5786:9;5850:1;5771:40;5786:9;;;;:::i;:::-;2851:9773:150;5797:13:69;5804:5;;;:::i;:::-;5797:13;;:::i;:::-;2851:9773:150;5771:40:69;;;:::i;:::-;2851:9773:150;;5719:442:69;;6179:18;;;;:::i;:::-;2851:9773:150;;;;;6230:8:69;6215:23;;;;:::i;:::-;2851:9773:150;;5457:9:69;;;;;5719:442;5647:7;;5332:1;5647:7;;5646:22;-1:-1:-1;5646:22:69;;5943:9;5332:1;5943:9;5928:35;6230:8;5943:9;5954:8;5943:9;;;;;;;;;:::i;:::-;2851:9773:150;5954:8:69;;:::i;:::-;2851:9773:150;5928:35:69;;;:::i;:::-;2851:9773:150;;;;5878:283:69;5719:442;;5878:283;6230:8;6063:9;;;5332:1;6063:9;;;;;;:::i;:::-;2851:9773:150;;;5878:283:69;5719:442;;5563:44;5588:5;5578:16;5588:5;;;:::i;:::-;5578:16;;:::i;:::-;2851:9773:150;5332:1:69;5599:7;;5578:29;5563:44;;5316:17;;;;;;6389:9;5316:17;;6389:9;:::i;:::-;2851:9773:150;5056:1349:69;:::o;4448:2801:68:-;;;;4610:14;;4606:38;;2851:9773:150;;4702:14:68;;;4698:40;;4810:1;4813:13;;;;;;5012:14;4810:1;5012:14;;:32;;;4793:159;5012:56;;;4793:159;5008:169;;5235:35;5249:20;;;:::i;:::-;5235:35;:::i;:::-;2851:9773:150;;;:::i;:::-;;;;;5219:52:68;;;2851:9773:150;;;;;:::i;:::-;;;;;5219:52:68;5313:22;;2851:9773:150;;;;;:::i;:::-;;;;;5219:52:68;5376:34;;2851:9773:150;;;;5376:34:68;;2851:9773:150;;5455:29:68;5494:940;5501:14;;;5494:940;2851:9773:150;;;;6490:52:68;;2851:9773:150;6642:14:68;;2851:9773:150;6622:41:68;6618:71;;2851:9773:150;;-1:-1:-1;;2851:9773:150;;;6763:445:68;2851:9773:150;;6770:21:68;;;6823:20;;;:::i;:::-;6872;;;:::i;:::-;2851:9773:150;6934:18:68;2851:9773:150;;;;;;6934:18:68;2851:9773:150;;;7006:139:68;5219:52;7006:139;2851:9773:150;;7006:139:68;7158:39;:14;;2851:9773:150;;7158:39:68;;:::i;:::-;2851:9773:150;6763:445:68;;6770:21;;7225:17;6770:21;;7225:14;:17;:::i;6618:71::-;5114:17;;;2851:9773:150;6672:17:68;;2851:9773:150;6672:17:68;6490:52;6523:19;;;2851:9773:150;6523:19:68;;2851:9773:150;6523:19:68;5494:940;5548:16;;;:::i;:::-;2851:9773:150;5663:31:68;5625:24;4810:1;2851:9773:150;;5625:24:68;;;:::i;:::-;5663:31;;;;:::i;:::-;5745:42;;;;;;;:::i;:::-;5806:20;5219:52;5806:20;;2851:9773:150;5806:25:68;;5802:622;5806:25;;;5855:14;;;;;;;;2851:9773:150;;;5855:41:68;5851:174;5880:16;5920:5;5880:16;5989;;;;;:::i;:::-;;;:::i;:::-;5494:940;;5802:622;4810:1;6049:25;:40;;;5802:622;6045:379;;;6126:18;;;5219:52;6126:40;:18;2851:9773:150;6126:45:68;:18;;;2851:9773:150;;6126:40:68;;:::i;:::-;;:45;2851:9773:150;6126:45:68;;:::i;6045:379::-;2851:9773:150;6353:55:68;2851:9773:150;;6282:1:68;6353:55;2851:9773:150;;;6353:55:68;;:::i;6049:40::-;6078:11;;;6049:40;;5008:169;2851:9773:150;;;;;;5084:47:68;;5152:9;:14;:9;;:::i;:::-;;:14;2851:9773:150;5145:21:68;:::o;5012:56::-;5048:9;;;;:::i;:::-;;2851:9773:150;5048:20:68;5012:56;;:32;5030:14;4810:1;5030:14;;5012:32;;4798:13;4847:9;;;;:::i;:::-;;2851:9773:150;4866:13:68;4873:5;;;:::i;:::-;4866:13;;:::i;:::-;;2851:9773:150;-1:-1:-1;4847:38:68;4843:67;;4810:1;2851:9773:150;4798:13:68;;4698:40;4725:13;;;2851:9773:150;4725:13:68;;2851:9773:150;4725:13:68;2284:287:72;;;;2851:9773:150;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;-1:-1:-1;;2851:9773:150;;;;2560:3:72;;;3244:193;3251:16;2851:9773:150;3251:16:72;;;;;3494:8;;;-1:-1:-1;;;2851:9773:150;3494:132:72;3636:173;;;;;;;;;;;2284:287::o;3494:132::-;3598:24;3606:15;3598:28;3606:15;;:::i;:::-;3598:24;:::i;:28::-;3494:132;;;3269:16;3301:65;;;;;;2851:9773:150;;;;;;;;;;;;;;;;;3269:16:72;-1:-1:-1;;2851:9773:150;;;;;;3244:193:72;2851:9773:150;;:::i;7390:537:69:-;7459:462;;;;;;;;-1:-1:-1;;;;;7459:462:69;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7390:537;:::o;6686:471::-;6806:1;6800:7;6806:1;;2851:9773:150;6829:141:69;;;;2851:9773:150;6829:141:69;6795:356;6686:471::o;6795:356::-;7000:141;2851:9773:150;7000:141:69;;;;2851:9773:150;7000:141:69;6795:356;6686:471::o;13095:196:68:-;;13147:13;2851:9773:150;13172:113:68;13179:6;;;13095:196;:::o;13172:113::-;2851:9773:150;-1:-1:-1;;2851:9773:150;;;;;;;13210:1:68;13201:10;;2851:9773:150;;;;13172:113:68;;;2851:9773:150;;;;;;;:::i;:::-;;;;-1:-1:-1;2851:9773:150;;-1:-1:-1;2851:9773:150;;;;;;:::o;7670:742:68:-;2851:9773:150;;:::i;:::-;;;;7846:33:68;7864:15;;;2851:9773:150;;;7846:33:68;;:::i;:::-;2851:9773:150;;;7935:210:68;7942:15;;;;;;7935:210;2851:9773:150;;;;8175:27:68;8348:28;2851:9773:150;;;8175:27:68;;:::i;:::-;2851:9773:150;;;8287:13:68;7998;8287;;;2851:9773:150;;:::i;:::-;;;;8246:55:68;7864:15;8246:55;;2851:9773:150;7998:13:68;8246:55;;2851:9773:150;;;;8348:28:68;:::i;7935:210::-;7998:13;:24;:13;;;;;:24;:::i;:::-;;2851:9773:150;7978:50:68;;;7974:94;;2851:9773:150;;7935:210:68;;;7974:94;8048:5;;;9445:1927;;;9640:15;;;2851:9773:150;;;9737:21:68;;;;:::i;:::-;9794;;;;:::i;:::-;9830:9;2851:9773:150;9841:10:68;;;;;;10071:20;;;;10182:1157;;2851:9773:150;10189:12:68;;;:::i;:::-;2851:9773:150;10189:17:68;;;2851:9773:150;10248:9:68;2851:9773:150;10272:851:68;10279:7;;;;;;11204:10;;;11228:101;;;;;10182:1157;;10272:851;10320:12;;;;;;:::i;:::-;2851:9773:150;10355:5:68;;;;;;:::i;:::-;:11;:44;;;10272:851;10351:758;;;2851:9773:150;10496:9:68;10481:40;10496:9;10507:13;10514:5;10496:9;10677:6;10496:9;;10561:8;10496:9;;:::i;:::-;2851:9773:150;10514:5:68;;:::i;10507:13::-;2851:9773:150;10481:40:68;;;:::i;:::-;10466:55;;;;:::i;10561:8::-;10543:26;;;;:::i;:::-;2851:9773:150;;10677:6:68;;:::i;:::-;10351:758;10272:851;;10351:758;2851:9773:150;;;;9640:15:68;10801:14;;;2851:9773:150;-1:-1:-1;10781:41:68;10777:70;;2851:9773:150;10899:9:68;10967:8;10899:9;;10884:43;10899:9;;;;:::i;:::-;2851:9773:150;10910:16:68;;;:::i;:::-;10884:43;;;:::i;10967:8::-;10949:26;;;;:::i;:::-;2851:9773:150;;;;10351:758:68;10272:851;;10777:70;10831:16;;;2851:9773:150;10831:16:68;;2851:9773:150;10831:16:68;10355:44;10380:5;10370:16;10380:5;;;:::i;:::-;10370:16;;:::i;:::-;2851:9773:150;;10391:7:68;;10370:29;10355:44;;10189:17;;;;;11356:9;10189:17;11356:9;:::i;9830:::-;2851:9773:150;9880:13:68;9985:25;9880:13;;;9640:15;9880:25;:13;;9894:10;;;;:::i;:::-;9880:25;;:::i;:::-;;:30;2851:9773:150;9868:42:68;;;;:::i;:::-;2851:9773:150;9985:13:68;2851:9773:150;;;9985:25:68;;:::i;:::-;;2851:9773:150;;;9952:64:68;;;;:::i;:::-;2851:9773:150;;9830:9:68;;11444:188;;11527:37;:13;;;;2851:9773:150;;11527:37:68;;:::i;:::-;2851:9773:150;;;;;;;11444:188:68:o;11706:222::-;11808:30;:13;;;;2851:9773:150;;11808:30:68;;:::i;:::-;2851:9773:150;;;;;;;;11706:222:68;:::o;12000:226::-;12106:30;:13;;;;2851:9773:150;;12106:30:68;;:::i;:::-;2851:9773:150;;;-1:-1:-1;;2851:9773:150;;;;;12000:226:68:o;713:2:72:-;;;;;;;;;:::o","linkReferences":{}},"methodIdentifiers":{"MMR_ROOT_PAYLOAD_ID()":"af8b91d6","noOp((uint256,uint256,(uint64,uint32,bytes32),(uint64,uint32,bytes32)),(((((bytes2,bytes)[],uint32,uint64),(bytes,uint256)[]),(uint8,uint32,bytes32,(uint64,uint32,bytes32),bytes32,uint256),bytes32[],bytes32[]),((uint256,uint256,bytes)[],bytes32[],uint256)))":"7163f312","supportsInterface(bytes4)":"01ffc9a7","verify(bytes,bytes)":"f7e83aee","verifyConsensus(bytes,bytes)":"7d755598"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.30+commit.73712a01\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"ECDSAInvalidSignature\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"length\",\"type\":\"uint256\"}],\"name\":\"ECDSAInvalidSignatureLength\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"ECDSAInvalidSignatureS\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"EmptyLeaves\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"EmptyTree\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"EmptyTree\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"IllegalGenesisBlock\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidAuthoritiesProof\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidMmrProof\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"LeafIndexOutOfBounds\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MmrRootHashMissing\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OutOfBoundsLeaves\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ProofExhausted\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"SuperMajorityRequired\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"TimestampNotFound\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnconsumedProof\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnknownAuthoritySet\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnsortedLeaves\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnsortedLeaves\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MMR_ROOT_PAYLOAD_ID\",\"outputs\":[{\"internalType\":\"bytes2\",\"name\":\"\",\"type\":\"bytes2\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"latestHeight\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"beefyActivationBlock\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"uint64\",\"name\":\"id\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"len\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"}],\"internalType\":\"struct AuthoritySetCommitment\",\"name\":\"currentAuthoritySet\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint64\",\"name\":\"id\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"len\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"}],\"internalType\":\"struct AuthoritySetCommitment\",\"name\":\"nextAuthoritySet\",\"type\":\"tuple\"}],\"internalType\":\"struct BeefyConsensusState\",\"name\":\"s\",\"type\":\"tuple\"},{\"components\":[{\"components\":[{\"components\":[{\"components\":[{\"components\":[{\"internalType\":\"bytes2\",\"name\":\"id\",\"type\":\"bytes2\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"internalType\":\"struct Payload[]\",\"name\":\"payload\",\"type\":\"tuple[]\"},{\"internalType\":\"uint32\",\"name\":\"blockNumber\",\"type\":\"uint32\"},{\"internalType\":\"uint64\",\"name\":\"validatorSetId\",\"type\":\"uint64\"}],\"internalType\":\"struct Commitment\",\"name\":\"commitment\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"authorityIndex\",\"type\":\"uint256\"}],\"internalType\":\"struct Vote[]\",\"name\":\"votes\",\"type\":\"tuple[]\"}],\"internalType\":\"struct SignedCommitment\",\"name\":\"signedCommitment\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint8\",\"name\":\"version\",\"type\":\"uint8\"},{\"internalType\":\"uint32\",\"name\":\"parentNumber\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"parentHash\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"uint64\",\"name\":\"id\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"len\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"}],\"internalType\":\"struct AuthoritySetCommitment\",\"name\":\"nextAuthoritySet\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"extra\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"leafIndex\",\"type\":\"uint256\"}],\"internalType\":\"struct BeefyMmrLeaf\",\"name\":\"latestMmrLeaf\",\"type\":\"tuple\"},{\"internalType\":\"bytes32[]\",\"name\":\"mmrProof\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"proof\",\"type\":\"bytes32[]\"}],\"internalType\":\"struct RelayChainProof\",\"name\":\"relay\",\"type\":\"tuple\"},{\"components\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"header\",\"type\":\"bytes\"}],\"internalType\":\"struct Parachain[]\",\"name\":\"parachains\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"proof\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint256\",\"name\":\"leafCount\",\"type\":\"uint256\"}],\"internalType\":\"struct ParachainProof\",\"name\":\"parachain\",\"type\":\"tuple\"}],\"internalType\":\"struct BeefyConsensusProof\",\"name\":\"p\",\"type\":\"tuple\"}],\"name\":\"noOp\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"previousState\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"proof\",\"type\":\"bytes\"}],\"name\":\"verify\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"stateMachineId\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"height\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"overlayRoot\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"stateRoot\",\"type\":\"bytes32\"}],\"internalType\":\"struct StateCommitment\",\"name\":\"commitment\",\"type\":\"tuple\"}],\"internalType\":\"struct IntermediateState[]\",\"name\":\"\",\"type\":\"tuple[]\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"encodedState\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"encodedProof\",\"type\":\"bytes\"}],\"name\":\"verifyConsensus\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"stateMachineId\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"height\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"overlayRoot\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"stateRoot\",\"type\":\"bytes32\"}],\"internalType\":\"struct StateCommitment\",\"name\":\"commitment\",\"type\":\"tuple\"}],\"internalType\":\"struct IntermediateState[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}],\"devdoc\":{\"author\":\"Polytope Labs (hello@polytope.technology)\",\"details\":\"The verification flow is: 1. Confirm the commitment's validator set id matches a known authority set. 2. Verify that enough signatures are present to meet the supermajority threshold. 3. Recover signer addresses via ecrecover and verify their membership in the authority set via a merkle multi-proof against the authority set root. 4. Extract the MMR root from the commitment payload and verify the latest MMR leaf inclusion via a merkle mountain range proof. 5. Verify parachain header inclusion in the MMR leaf's parachain heads root. 6. Decode each parachain header to extract finalized state commitments. Stale proofs (commitment block number <= trusted latest height) are treated as no-ops.\",\"errors\":{\"ECDSAInvalidSignature()\":[{\"details\":\"The signature derives the `address(0)`.\"}],\"ECDSAInvalidSignatureLength(uint256)\":[{\"details\":\"The signature has an invalid length.\"}],\"ECDSAInvalidSignatureS(bytes32)\":[{\"details\":\"The signature has an S value that is in the upper half order.\"}]},\"kind\":\"dev\",\"methods\":{\"supportsInterface(bytes4)\":{\"details\":\"See {IERC165-supportsInterface}.\"},\"verify(bytes,bytes)\":{\"details\":\"IConsensusV2 entry point. Decodes the proof, verifies consensus, and returns the updated state along with the latest authority set id.\"},\"verifyConsensus(bytes,bytes)\":{\"details\":\"IConsensus entry point. Decodes the proof and verifies consensus.\"}},\"title\":\"The ECDSA BEEFY Consensus Client.\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"notice\":\"Verifies BEEFY consensus proofs by checking a 2/3+1 supermajority of secp256k1 signatures on-chain, along with merkle multi-proofs of authority set membership. This is the most gas-expensive verifier but requires no off-chain proving infrastructure.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/consensus/EcdsaBeefy.sol\":\"EcdsaBeefy\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@hyperbridge/core/=node_modules/@hyperbridge/core/contracts/\",\":@openzeppelin/=node_modules/@openzeppelin/\",\":@polytope-labs/=node_modules/@polytope-labs/\",\":@sp1-contracts/=lib/sp1-contracts/contracts/src/\",\":@uniswap/=node_modules/@uniswap/\",\":ds-test/=lib/solidity-stringutils/lib/ds-test/src/\",\":erc4626-tests/=lib/sp1-contracts/contracts/lib/openzeppelin-contracts/lib/erc4626-tests/\",\":forge-std/=node_modules/forge-std/src/\",\":openzeppelin-contracts/=lib/sp1-contracts/contracts/lib/openzeppelin-contracts/\",\":solidity-stringutils/=lib/solidity-stringutils/\",\":sp1-contracts/=lib/sp1-contracts/contracts/\",\":stringutils/=lib/solidity-stringutils/src/\"],\"viaIR\":true},\"sources\":{\"node_modules/@hyperbridge/core/contracts/interfaces/IConsensus.sol\":{\"keccak256\":\"0x56b093b9ca6913da8d12c54beca9ba8d970c1a1a0cf39838c2c341cd4ef23afb\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://6be3495c82c3c5dd95ccc5855289b5e7079a419a9b0d09af843344ac86b6d286\",\"dweb:/ipfs/QmNQZugSBtnVcggYqnTXdcsZV3nLkGTK4VEXCnYX3fGKpV\"]},\"node_modules/@hyperbridge/core/contracts/interfaces/IConsensusV2.sol\":{\"keccak256\":\"0xa680bb1b902d419b862d155c49ae6fd2371e9b8a0a42d67cf07a6b4306300b90\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://df9f403774a0fb637e331e23a599aa1cd3dc2a70baef0ebdd3b7dd4676dab81a\",\"dweb:/ipfs/QmfA14ersZ1D88DkxEaK3krNs7XsCYe5UTmTANKcGB8tE4\"]},\"node_modules/@hyperbridge/core/contracts/libraries/StateMachine.sol\":{\"keccak256\":\"0x860289ae856ea354df5cca2131612da5c15a129fc14ae5ac0d528e0ce7c809c3\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://2ab5a7d6463b634da1e05e2b06818416a6f2d59b5c10815cc74bf26d003c2b95\",\"dweb:/ipfs/QmZ4dpEoDhNefAcKwKBXumLfSHqxhcBctErTzMvvPHXY5G\"]},\"node_modules/@openzeppelin/contracts/utils/Panic.sol\":{\"keccak256\":\"0xf7fe324703a64fc51702311dc51562d5cb1497734f074e4f483bfb6717572d7a\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://c6a5ff4f9fd8649b7ee20800b7fa387d3465bd77cf20c2d1068cd5c98e1ed57a\",\"dweb:/ipfs/QmVSaVJf9FXFhdYEYeCEfjMVHrxDh5qL4CGkxdMWpQCrqG\"]},\"node_modules/@openzeppelin/contracts/utils/Strings.sol\":{\"keccak256\":\"0xad148d59f05165f9217d0a9e1ac8f772abb02ea6aaad8a756315c532bf79f9f4\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://15e3599867c2182f5831e9268b274b2ef2047825837df6b4d81c9e89254b093e\",\"dweb:/ipfs/QmZbL7XAYr5RmaNaooPgZRmcDXaudfsYQfYD9y5iAECvpS\"]},\"node_modules/@openzeppelin/contracts/utils/cryptography/ECDSA.sol\":{\"keccak256\":\"0x69f54c02b7d81d505910ec198c11ed4c6a728418a868b906b4a0cf29946fda84\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://8e25e4bdb7ae1f21d23bfee996e22736fc0ab44cfabedac82a757b1edc5623b9\",\"dweb:/ipfs/QmQdWQvB6JCP9ZMbzi8EvQ1PTETqkcTWrbcVurS7DKpa5n\"]},\"node_modules/@openzeppelin/contracts/utils/introspection/ERC165.sol\":{\"keccak256\":\"0x2d9dc2fe26180f74c11c13663647d38e259e45f95eb88f57b61d2160b0109d3e\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://81233d1f98060113d9922180bb0f14f8335856fe9f339134b09335e9f678c377\",\"dweb:/ipfs/QmWh6R35SarhAn4z2wH8SU456jJSYL2FgucfTFgbHJJN4E\"]},\"node_modules/@openzeppelin/contracts/utils/introspection/IERC165.sol\":{\"keccak256\":\"0x8891738ffe910f0cf2da09566928589bf5d63f4524dd734fd9cedbac3274dd5c\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://971f954442df5c2ef5b5ebf1eb245d7105d9fbacc7386ee5c796df1d45b21617\",\"dweb:/ipfs/QmadRjHbkicwqwwh61raUEapaVEtaLMcYbQZWs9gUkgj3u\"]},\"node_modules/@openzeppelin/contracts/utils/math/Math.sol\":{\"keccak256\":\"0x1225214420c83ebcca88f2ae2b50f053aaa7df7bd684c3e878d334627f2edfc6\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://6c5fab4970634f9ab9a620983dc1c8a30153981a0b1a521666e269d0a11399d3\",\"dweb:/ipfs/QmVRnBC575MESGkEHndjujtR7qub2FzU9RWy9eKLp4hPZB\"]},\"node_modules/@openzeppelin/contracts/utils/math/SafeCast.sol\":{\"keccak256\":\"0x195533c86d0ef72bcc06456a4f66a9b941f38eb403739b00f21fd7c1abd1ae54\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://b1d578337048cad08c1c03041cca5978eff5428aa130c781b271ad9e5566e1f8\",\"dweb:/ipfs/QmPFKL2r9CBsMwmUqqdcFPfHZB2qcs9g1HDrPxzWSxomvy\"]},\"node_modules/@openzeppelin/contracts/utils/math/SignedMath.sol\":{\"keccak256\":\"0xb1970fac7b64e6c09611e6691791e848d5e3fe410fa5899e7df2e0afd77a99e3\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://db5fbb3dddd8b7047465b62575d96231ba8a2774d37fb4737fbf23340fabbb03\",\"dweb:/ipfs/QmVUSvooZKEdEdap619tcJjTLcAuH6QBdZqAzWwnAXZAWJ\"]},\"node_modules/@polytope-labs/solidity-merkle-trees/src/MerkleMountainRange.sol\":{\"keccak256\":\"0x014237038bb77bdf371b50c1268d02bf7eeaf7068c35483d5d0684ca7c30d544\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://beb3ae60094f49716a2b0cfa6229ee27af1778934cf5f2dfc704433fda5a687b\",\"dweb:/ipfs/QmQ339wEFT9X1woHyFTDbbhUCx8ekD9MGLRnhS3PpYp1pm\"]},\"node_modules/@polytope-labs/solidity-merkle-trees/src/MerkleMultiProof.sol\":{\"keccak256\":\"0xd4f6e6a9eceaa7d1cdf9684bfe7f3f552adf21dfcf7f1372f7943a4c2f15deb7\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://ec900b3e79ea8ef9d275697937d1984e994d10117aa2be066bd73f6a31aedf10\",\"dweb:/ipfs/QmZQGLiJtbvUSEW5H2HEenzxp58fN43XHRY3SoNTC9Hobg\"]},\"node_modules/@polytope-labs/solidity-merkle-trees/src/trie/Bytes.sol\":{\"keccak256\":\"0xd305383358b93285d8fcee512795487484eadfd7b602df16ff4e9b01afbefec7\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://57c86cd2fe6ab591264c5632d503d77f04748d722e4defa9c2badb238ee1f9bd\",\"dweb:/ipfs/QmTZ6xTcaYkRBshq6YZVMx2kkk94ZtJsRM6xLpC5qWT3oA\"]},\"node_modules/@polytope-labs/solidity-merkle-trees/src/trie/Memory.sol\":{\"keccak256\":\"0x59e3a56caa42c1aac30231173439817d38c7f359e40bd36e9bb418d3f82ceab7\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://e060fed46c6b420624166ea02a326f0f566897941bdc322257e690ae134b8179\",\"dweb:/ipfs/QmbXW8yG2ZntjLMyUEmQGcRZroZj4dVZkBhHxK2PFKfUKB\"]},\"node_modules/@polytope-labs/solidity-merkle-trees/src/trie/Node.sol\":{\"keccak256\":\"0xca611969a68f7fe63dcdc742c9caf9bc1b26495561b68f4676c209279a4576ba\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://17b7ec2cf65d484f0a2c188a7ae33430b015b3a55bf10393167f53839e2dde56\",\"dweb:/ipfs/QmTQbT8J7rBRNHFXSR7S4KbpwCteMrFt8mvrzCD1vYYTwX\"]},\"node_modules/@polytope-labs/solidity-merkle-trees/src/trie/polkadot/ScaleCodec.sol\":{\"keccak256\":\"0x9ac4df46e68718f7deaaa5b7443778533f53dc0ff3736cc386cf4991099da2aa\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://85d08b00d9173358323105be975ce85b7e206ba52df2d80a14d2581dec1ddd11\",\"dweb:/ipfs/QmQCqVvSdJUqfhLH8TRBGNDGiEzkGduhRacnYhoKcm7e2a\"]},\"src/consensus/Codec.sol\":{\"keccak256\":\"0xfbda8d0aef81312f23b53eb34ba4fcd19ab886cbd010a0f100d86271eecee126\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://bc4a4b1a88f702efa6727c368894b8ac13b69769efc81355b71a9a0d7a2efd7b\",\"dweb:/ipfs/QmYwLJgyCr4pPRyyk2oLYMLq5QGRhCSZKLYUEdW6U2SBzx\"]},\"src/consensus/EcdsaBeefy.sol\":{\"keccak256\":\"0x143e08b4250eefd6a9000466df7d5e8ca13f37f19c352be92875bd8bd364cd7b\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://88dc9f7f539608ca28479c3f937a0583d39dcfc698c001b65a478320dd8e1790\",\"dweb:/ipfs/QmaD4DbxtRGCApbQtuovPwBwRt2PZEVrRLck3vFyejkhrH\"]},\"src/consensus/Types.sol\":{\"keccak256\":\"0xcd1866064ae11c71575fefa857a7317d10e18da0c204c09ae42308c4c13a0268\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://c083e149c4607f2cd5b769c1a624bbc5a861e8d5c485dd2ce06a0e23f7e762dd\",\"dweb:/ipfs/QmPMSM8S2sav9t4X2peHJnpTQhowEgEW5phYoiy3LbGAsv\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.30+commit.73712a01"},"language":"Solidity","output":{"abi":[{"inputs":[],"type":"error","name":"ECDSAInvalidSignature"},{"inputs":[{"internalType":"uint256","name":"length","type":"uint256"}],"type":"error","name":"ECDSAInvalidSignatureLength"},{"inputs":[{"internalType":"bytes32","name":"s","type":"bytes32"}],"type":"error","name":"ECDSAInvalidSignatureS"},{"inputs":[],"type":"error","name":"EmptyLeaves"},{"inputs":[],"type":"error","name":"EmptyTree"},{"inputs":[],"type":"error","name":"EmptyTree"},{"inputs":[],"type":"error","name":"IllegalGenesisBlock"},{"inputs":[],"type":"error","name":"InvalidAuthoritiesProof"},{"inputs":[],"type":"error","name":"InvalidMmrProof"},{"inputs":[],"type":"error","name":"LeafIndexOutOfBounds"},{"inputs":[],"type":"error","name":"MmrRootHashMissing"},{"inputs":[],"type":"error","name":"OutOfBoundsLeaves"},{"inputs":[],"type":"error","name":"ProofExhausted"},{"inputs":[],"type":"error","name":"SuperMajorityRequired"},{"inputs":[],"type":"error","name":"TimestampNotFound"},{"inputs":[],"type":"error","name":"UnconsumedProof"},{"inputs":[],"type":"error","name":"UnknownAuthoritySet"},{"inputs":[],"type":"error","name":"UnsortedLeaves"},{"inputs":[],"type":"error","name":"UnsortedLeaves"},{"inputs":[],"stateMutability":"view","type":"function","name":"MMR_ROOT_PAYLOAD_ID","outputs":[{"internalType":"bytes2","name":"","type":"bytes2"}]},{"inputs":[{"internalType":"struct BeefyConsensusState","name":"s","type":"tuple","components":[{"internalType":"uint256","name":"latestHeight","type":"uint256"},{"internalType":"uint256","name":"beefyActivationBlock","type":"uint256"},{"internalType":"struct AuthoritySetCommitment","name":"currentAuthoritySet","type":"tuple","components":[{"internalType":"uint64","name":"id","type":"uint64"},{"internalType":"uint32","name":"len","type":"uint32"},{"internalType":"bytes32","name":"root","type":"bytes32"}]},{"internalType":"struct AuthoritySetCommitment","name":"nextAuthoritySet","type":"tuple","components":[{"internalType":"uint64","name":"id","type":"uint64"},{"internalType":"uint32","name":"len","type":"uint32"},{"internalType":"bytes32","name":"root","type":"bytes32"}]}]},{"internalType":"struct BeefyConsensusProof","name":"p","type":"tuple","components":[{"internalType":"struct RelayChainProof","name":"relay","type":"tuple","components":[{"internalType":"struct SignedCommitment","name":"signedCommitment","type":"tuple","components":[{"internalType":"struct Commitment","name":"commitment","type":"tuple","components":[{"internalType":"struct Payload[]","name":"payload","type":"tuple[]","components":[{"internalType":"bytes2","name":"id","type":"bytes2"},{"internalType":"bytes","name":"data","type":"bytes"}]},{"internalType":"uint32","name":"blockNumber","type":"uint32"},{"internalType":"uint64","name":"validatorSetId","type":"uint64"}]},{"internalType":"struct Vote[]","name":"votes","type":"tuple[]","components":[{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"uint256","name":"authorityIndex","type":"uint256"}]}]},{"internalType":"struct BeefyMmrLeaf","name":"latestMmrLeaf","type":"tuple","components":[{"internalType":"uint8","name":"version","type":"uint8"},{"internalType":"uint32","name":"parentNumber","type":"uint32"},{"internalType":"bytes32","name":"parentHash","type":"bytes32"},{"internalType":"struct AuthoritySetCommitment","name":"nextAuthoritySet","type":"tuple","components":[{"internalType":"uint64","name":"id","type":"uint64"},{"internalType":"uint32","name":"len","type":"uint32"},{"internalType":"bytes32","name":"root","type":"bytes32"}]},{"internalType":"bytes32","name":"extra","type":"bytes32"},{"internalType":"uint256","name":"leafIndex","type":"uint256"}]},{"internalType":"bytes32[]","name":"mmrProof","type":"bytes32[]"},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"}]},{"internalType":"struct ParachainProof","name":"parachain","type":"tuple","components":[{"internalType":"struct Parachain[]","name":"parachains","type":"tuple[]","components":[{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bytes","name":"header","type":"bytes"}]},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"},{"internalType":"uint256","name":"leafCount","type":"uint256"}]}]}],"stateMutability":"pure","type":"function","name":"noOp"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"stateMutability":"view","type":"function","name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}]},{"inputs":[{"internalType":"bytes","name":"previousState","type":"bytes"},{"internalType":"bytes","name":"proof","type":"bytes"}],"stateMutability":"pure","type":"function","name":"verify","outputs":[{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"struct IntermediateState[]","name":"","type":"tuple[]","components":[{"internalType":"uint256","name":"stateMachineId","type":"uint256"},{"internalType":"uint256","name":"height","type":"uint256"},{"internalType":"struct StateCommitment","name":"commitment","type":"tuple","components":[{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bytes32","name":"overlayRoot","type":"bytes32"},{"internalType":"bytes32","name":"stateRoot","type":"bytes32"}]}]},{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"bytes","name":"encodedState","type":"bytes"},{"internalType":"bytes","name":"encodedProof","type":"bytes"}],"stateMutability":"pure","type":"function","name":"verifyConsensus","outputs":[{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"struct IntermediateState[]","name":"","type":"tuple[]","components":[{"internalType":"uint256","name":"stateMachineId","type":"uint256"},{"internalType":"uint256","name":"height","type":"uint256"},{"internalType":"struct StateCommitment","name":"commitment","type":"tuple","components":[{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bytes32","name":"overlayRoot","type":"bytes32"},{"internalType":"bytes32","name":"stateRoot","type":"bytes32"}]}]}]}],"devdoc":{"kind":"dev","methods":{"supportsInterface(bytes4)":{"details":"See {IERC165-supportsInterface}."},"verify(bytes,bytes)":{"details":"IConsensusV2 entry point. Decodes the proof, verifies consensus, and returns the updated state along with the latest authority set id."},"verifyConsensus(bytes,bytes)":{"details":"IConsensus entry point. Decodes the proof and verifies consensus."}},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@hyperbridge/core/=node_modules/@hyperbridge/core/contracts/","@openzeppelin/=node_modules/@openzeppelin/","@polytope-labs/=node_modules/@polytope-labs/","@sp1-contracts/=lib/sp1-contracts/contracts/src/","@uniswap/=node_modules/@uniswap/","ds-test/=lib/solidity-stringutils/lib/ds-test/src/","erc4626-tests/=lib/sp1-contracts/contracts/lib/openzeppelin-contracts/lib/erc4626-tests/","forge-std/=node_modules/forge-std/src/","openzeppelin-contracts/=lib/sp1-contracts/contracts/lib/openzeppelin-contracts/","solidity-stringutils/=lib/solidity-stringutils/","sp1-contracts/=lib/sp1-contracts/contracts/","stringutils/=lib/solidity-stringutils/src/"],"optimizer":{"enabled":true,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"src/consensus/EcdsaBeefy.sol":"EcdsaBeefy"},"evmVersion":"cancun","libraries":{},"viaIR":true},"sources":{"node_modules/@hyperbridge/core/contracts/interfaces/IConsensus.sol":{"keccak256":"0x56b093b9ca6913da8d12c54beca9ba8d970c1a1a0cf39838c2c341cd4ef23afb","urls":["bzz-raw://6be3495c82c3c5dd95ccc5855289b5e7079a419a9b0d09af843344ac86b6d286","dweb:/ipfs/QmNQZugSBtnVcggYqnTXdcsZV3nLkGTK4VEXCnYX3fGKpV"],"license":"Apache-2.0"},"node_modules/@hyperbridge/core/contracts/interfaces/IConsensusV2.sol":{"keccak256":"0xa680bb1b902d419b862d155c49ae6fd2371e9b8a0a42d67cf07a6b4306300b90","urls":["bzz-raw://df9f403774a0fb637e331e23a599aa1cd3dc2a70baef0ebdd3b7dd4676dab81a","dweb:/ipfs/QmfA14ersZ1D88DkxEaK3krNs7XsCYe5UTmTANKcGB8tE4"],"license":"Apache-2.0"},"node_modules/@hyperbridge/core/contracts/libraries/StateMachine.sol":{"keccak256":"0x860289ae856ea354df5cca2131612da5c15a129fc14ae5ac0d528e0ce7c809c3","urls":["bzz-raw://2ab5a7d6463b634da1e05e2b06818416a6f2d59b5c10815cc74bf26d003c2b95","dweb:/ipfs/QmZ4dpEoDhNefAcKwKBXumLfSHqxhcBctErTzMvvPHXY5G"],"license":"Apache-2.0"},"node_modules/@openzeppelin/contracts/utils/Panic.sol":{"keccak256":"0xf7fe324703a64fc51702311dc51562d5cb1497734f074e4f483bfb6717572d7a","urls":["bzz-raw://c6a5ff4f9fd8649b7ee20800b7fa387d3465bd77cf20c2d1068cd5c98e1ed57a","dweb:/ipfs/QmVSaVJf9FXFhdYEYeCEfjMVHrxDh5qL4CGkxdMWpQCrqG"],"license":"MIT"},"node_modules/@openzeppelin/contracts/utils/Strings.sol":{"keccak256":"0xad148d59f05165f9217d0a9e1ac8f772abb02ea6aaad8a756315c532bf79f9f4","urls":["bzz-raw://15e3599867c2182f5831e9268b274b2ef2047825837df6b4d81c9e89254b093e","dweb:/ipfs/QmZbL7XAYr5RmaNaooPgZRmcDXaudfsYQfYD9y5iAECvpS"],"license":"MIT"},"node_modules/@openzeppelin/contracts/utils/cryptography/ECDSA.sol":{"keccak256":"0x69f54c02b7d81d505910ec198c11ed4c6a728418a868b906b4a0cf29946fda84","urls":["bzz-raw://8e25e4bdb7ae1f21d23bfee996e22736fc0ab44cfabedac82a757b1edc5623b9","dweb:/ipfs/QmQdWQvB6JCP9ZMbzi8EvQ1PTETqkcTWrbcVurS7DKpa5n"],"license":"MIT"},"node_modules/@openzeppelin/contracts/utils/introspection/ERC165.sol":{"keccak256":"0x2d9dc2fe26180f74c11c13663647d38e259e45f95eb88f57b61d2160b0109d3e","urls":["bzz-raw://81233d1f98060113d9922180bb0f14f8335856fe9f339134b09335e9f678c377","dweb:/ipfs/QmWh6R35SarhAn4z2wH8SU456jJSYL2FgucfTFgbHJJN4E"],"license":"MIT"},"node_modules/@openzeppelin/contracts/utils/introspection/IERC165.sol":{"keccak256":"0x8891738ffe910f0cf2da09566928589bf5d63f4524dd734fd9cedbac3274dd5c","urls":["bzz-raw://971f954442df5c2ef5b5ebf1eb245d7105d9fbacc7386ee5c796df1d45b21617","dweb:/ipfs/QmadRjHbkicwqwwh61raUEapaVEtaLMcYbQZWs9gUkgj3u"],"license":"MIT"},"node_modules/@openzeppelin/contracts/utils/math/Math.sol":{"keccak256":"0x1225214420c83ebcca88f2ae2b50f053aaa7df7bd684c3e878d334627f2edfc6","urls":["bzz-raw://6c5fab4970634f9ab9a620983dc1c8a30153981a0b1a521666e269d0a11399d3","dweb:/ipfs/QmVRnBC575MESGkEHndjujtR7qub2FzU9RWy9eKLp4hPZB"],"license":"MIT"},"node_modules/@openzeppelin/contracts/utils/math/SafeCast.sol":{"keccak256":"0x195533c86d0ef72bcc06456a4f66a9b941f38eb403739b00f21fd7c1abd1ae54","urls":["bzz-raw://b1d578337048cad08c1c03041cca5978eff5428aa130c781b271ad9e5566e1f8","dweb:/ipfs/QmPFKL2r9CBsMwmUqqdcFPfHZB2qcs9g1HDrPxzWSxomvy"],"license":"MIT"},"node_modules/@openzeppelin/contracts/utils/math/SignedMath.sol":{"keccak256":"0xb1970fac7b64e6c09611e6691791e848d5e3fe410fa5899e7df2e0afd77a99e3","urls":["bzz-raw://db5fbb3dddd8b7047465b62575d96231ba8a2774d37fb4737fbf23340fabbb03","dweb:/ipfs/QmVUSvooZKEdEdap619tcJjTLcAuH6QBdZqAzWwnAXZAWJ"],"license":"MIT"},"node_modules/@polytope-labs/solidity-merkle-trees/src/MerkleMountainRange.sol":{"keccak256":"0x014237038bb77bdf371b50c1268d02bf7eeaf7068c35483d5d0684ca7c30d544","urls":["bzz-raw://beb3ae60094f49716a2b0cfa6229ee27af1778934cf5f2dfc704433fda5a687b","dweb:/ipfs/QmQ339wEFT9X1woHyFTDbbhUCx8ekD9MGLRnhS3PpYp1pm"],"license":"Apache-2.0"},"node_modules/@polytope-labs/solidity-merkle-trees/src/MerkleMultiProof.sol":{"keccak256":"0xd4f6e6a9eceaa7d1cdf9684bfe7f3f552adf21dfcf7f1372f7943a4c2f15deb7","urls":["bzz-raw://ec900b3e79ea8ef9d275697937d1984e994d10117aa2be066bd73f6a31aedf10","dweb:/ipfs/QmZQGLiJtbvUSEW5H2HEenzxp58fN43XHRY3SoNTC9Hobg"],"license":"Apache-2.0"},"node_modules/@polytope-labs/solidity-merkle-trees/src/trie/Bytes.sol":{"keccak256":"0xd305383358b93285d8fcee512795487484eadfd7b602df16ff4e9b01afbefec7","urls":["bzz-raw://57c86cd2fe6ab591264c5632d503d77f04748d722e4defa9c2badb238ee1f9bd","dweb:/ipfs/QmTZ6xTcaYkRBshq6YZVMx2kkk94ZtJsRM6xLpC5qWT3oA"],"license":"Apache-2.0"},"node_modules/@polytope-labs/solidity-merkle-trees/src/trie/Memory.sol":{"keccak256":"0x59e3a56caa42c1aac30231173439817d38c7f359e40bd36e9bb418d3f82ceab7","urls":["bzz-raw://e060fed46c6b420624166ea02a326f0f566897941bdc322257e690ae134b8179","dweb:/ipfs/QmbXW8yG2ZntjLMyUEmQGcRZroZj4dVZkBhHxK2PFKfUKB"],"license":"Apache-2.0"},"node_modules/@polytope-labs/solidity-merkle-trees/src/trie/Node.sol":{"keccak256":"0xca611969a68f7fe63dcdc742c9caf9bc1b26495561b68f4676c209279a4576ba","urls":["bzz-raw://17b7ec2cf65d484f0a2c188a7ae33430b015b3a55bf10393167f53839e2dde56","dweb:/ipfs/QmTQbT8J7rBRNHFXSR7S4KbpwCteMrFt8mvrzCD1vYYTwX"],"license":"Apache-2.0"},"node_modules/@polytope-labs/solidity-merkle-trees/src/trie/polkadot/ScaleCodec.sol":{"keccak256":"0x9ac4df46e68718f7deaaa5b7443778533f53dc0ff3736cc386cf4991099da2aa","urls":["bzz-raw://85d08b00d9173358323105be975ce85b7e206ba52df2d80a14d2581dec1ddd11","dweb:/ipfs/QmQCqVvSdJUqfhLH8TRBGNDGiEzkGduhRacnYhoKcm7e2a"],"license":"Apache-2.0"},"src/consensus/Codec.sol":{"keccak256":"0xfbda8d0aef81312f23b53eb34ba4fcd19ab886cbd010a0f100d86271eecee126","urls":["bzz-raw://bc4a4b1a88f702efa6727c368894b8ac13b69769efc81355b71a9a0d7a2efd7b","dweb:/ipfs/QmYwLJgyCr4pPRyyk2oLYMLq5QGRhCSZKLYUEdW6U2SBzx"],"license":"Apache-2.0"},"src/consensus/EcdsaBeefy.sol":{"keccak256":"0x143e08b4250eefd6a9000466df7d5e8ca13f37f19c352be92875bd8bd364cd7b","urls":["bzz-raw://88dc9f7f539608ca28479c3f937a0583d39dcfc698c001b65a478320dd8e1790","dweb:/ipfs/QmaD4DbxtRGCApbQtuovPwBwRt2PZEVrRLck3vFyejkhrH"],"license":"Apache-2.0"},"src/consensus/Types.sol":{"keccak256":"0xcd1866064ae11c71575fefa857a7317d10e18da0c204c09ae42308c4c13a0268","urls":["bzz-raw://c083e149c4607f2cd5b769c1a624bbc5a861e8d5c485dd2ce06a0e23f7e762dd","dweb:/ipfs/QmPMSM8S2sav9t4X2peHJnpTQhowEgEW5phYoiy3LbGAsv"],"license":"Apache-2.0"}},"version":1},"id":150} \ No newline at end of file +{"abi":[{"type":"constructor","inputs":[{"name":"digestParaId","type":"uint256","internalType":"uint256"}],"stateMutability":"nonpayable"},{"type":"function","name":"MMR_ROOT_PAYLOAD_ID","inputs":[],"outputs":[{"name":"","type":"bytes2","internalType":"bytes2"}],"stateMutability":"view"},{"type":"function","name":"_digestParaId","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"noOp","inputs":[{"name":"s","type":"tuple","internalType":"struct BeefyConsensusState","components":[{"name":"latestHeight","type":"uint256","internalType":"uint256"},{"name":"beefyActivationBlock","type":"uint256","internalType":"uint256"},{"name":"currentAuthoritySet","type":"tuple","internalType":"struct AuthoritySet","components":[{"name":"id","type":"uint256","internalType":"uint256"},{"name":"len","type":"uint256","internalType":"uint256"},{"name":"blsPoseidonHash","type":"uint256","internalType":"uint256"},{"name":"ecdsaMerkleRoot","type":"bytes32","internalType":"bytes32"}]},{"name":"nextAuthoritySet","type":"tuple","internalType":"struct AuthoritySet","components":[{"name":"id","type":"uint256","internalType":"uint256"},{"name":"len","type":"uint256","internalType":"uint256"},{"name":"blsPoseidonHash","type":"uint256","internalType":"uint256"},{"name":"ecdsaMerkleRoot","type":"bytes32","internalType":"bytes32"}]}]},{"name":"p","type":"tuple","internalType":"struct BeefyConsensusProof","components":[{"name":"relay","type":"tuple","internalType":"struct RelayChainProof","components":[{"name":"signedCommitment","type":"tuple","internalType":"struct SignedCommitment","components":[{"name":"commitment","type":"tuple","internalType":"struct Commitment","components":[{"name":"payload","type":"tuple[]","internalType":"struct Payload[]","components":[{"name":"id","type":"bytes2","internalType":"bytes2"},{"name":"data","type":"bytes","internalType":"bytes"}]},{"name":"blockNumber","type":"uint32","internalType":"uint32"},{"name":"validatorSetId","type":"uint64","internalType":"uint64"}]},{"name":"votes","type":"tuple[]","internalType":"struct Vote[]","components":[{"name":"signature","type":"bytes","internalType":"bytes"},{"name":"authorityIndex","type":"uint256","internalType":"uint256"}]}]},{"name":"latestMmrLeaf","type":"tuple","internalType":"struct BeefyMmrLeaf","components":[{"name":"version","type":"uint8","internalType":"uint8"},{"name":"parentNumber","type":"uint32","internalType":"uint32"},{"name":"parentHash","type":"bytes32","internalType":"bytes32"},{"name":"nextAuthoritySet","type":"tuple","internalType":"struct AuthoritySetCommitment","components":[{"name":"id","type":"uint64","internalType":"uint64"},{"name":"len","type":"uint32","internalType":"uint32"},{"name":"root","type":"bytes32","internalType":"bytes32"}]},{"name":"extra","type":"bytes32","internalType":"bytes32"},{"name":"leafIndex","type":"uint256","internalType":"uint256"}]},{"name":"mmrProof","type":"bytes32[]","internalType":"bytes32[]"},{"name":"proof","type":"bytes32[]","internalType":"bytes32[]"}]},{"name":"parachain","type":"tuple","internalType":"struct ParachainProof","components":[{"name":"parachains","type":"tuple[]","internalType":"struct Parachain[]","components":[{"name":"index","type":"uint256","internalType":"uint256"},{"name":"id","type":"uint256","internalType":"uint256"},{"name":"header","type":"bytes","internalType":"bytes"}]},{"name":"proof","type":"bytes32[]","internalType":"bytes32[]"},{"name":"leafCount","type":"uint256","internalType":"uint256"}]}]}],"outputs":[],"stateMutability":"pure"},{"type":"function","name":"supportsInterface","inputs":[{"name":"interfaceId","type":"bytes4","internalType":"bytes4"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"verify","inputs":[{"name":"previousState","type":"bytes","internalType":"bytes"},{"name":"proof","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"","type":"bytes","internalType":"bytes"},{"name":"","type":"tuple[]","internalType":"struct IntermediateState[]","components":[{"name":"stateMachineId","type":"uint256","internalType":"uint256"},{"name":"height","type":"uint256","internalType":"uint256"},{"name":"commitment","type":"tuple","internalType":"struct StateCommitment","components":[{"name":"timestamp","type":"uint256","internalType":"uint256"},{"name":"overlayRoot","type":"bytes32","internalType":"bytes32"},{"name":"stateRoot","type":"bytes32","internalType":"bytes32"}]}]},{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"error","name":"ECDSAInvalidSignature","inputs":[]},{"type":"error","name":"ECDSAInvalidSignatureLength","inputs":[{"name":"length","type":"uint256","internalType":"uint256"}]},{"type":"error","name":"ECDSAInvalidSignatureS","inputs":[{"name":"s","type":"bytes32","internalType":"bytes32"}]},{"type":"error","name":"EmptyLeaves","inputs":[]},{"type":"error","name":"EmptyTree","inputs":[]},{"type":"error","name":"EmptyTree","inputs":[]},{"type":"error","name":"IllegalGenesisBlock","inputs":[]},{"type":"error","name":"InvalidAuthoritiesProof","inputs":[]},{"type":"error","name":"InvalidMmrProof","inputs":[]},{"type":"error","name":"LeafIndexOutOfBounds","inputs":[]},{"type":"error","name":"MmrRootHashMissing","inputs":[]},{"type":"error","name":"OutOfBoundsLeaves","inputs":[]},{"type":"error","name":"ProofExhausted","inputs":[]},{"type":"error","name":"SuperMajorityRequired","inputs":[]},{"type":"error","name":"TimestampNotFound","inputs":[]},{"type":"error","name":"UnconsumedProof","inputs":[]},{"type":"error","name":"UnknownAuthoritySet","inputs":[]},{"type":"error","name":"UnsortedLeaves","inputs":[]},{"type":"error","name":"UnsortedLeaves","inputs":[]}],"bytecode":{"object":"0x60a034605e57601f61308f38819003918201601f19168301916001600160401b03831184841017606257808492602094604052833981010312605e5751608052604051613018908161007782396080518181816108640152610c810152f35b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806301ffc9a7146100645780639442d9fc1461005f578063af8b91d61461005a578063e455995b146100555763f7e83aee14610050575f80fd5b6108b4565b61084d565b61082d565b610795565b346100b85760203660031901126100b85760043563ffffffff60e01b81168091036100b857637bf41d7760e11b81149081156100a7575b50151560805260206080f35b6301ffc9a760e01b1490508161009b565b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b608081019081106001600160401b038211176100eb57604052565b6100bc565b604081019081106001600160401b038211176100eb57604052565b606081019081106001600160401b038211176100eb57604052565b60c081019081106001600160401b038211176100eb57604052565b90601f801991011681019081106001600160401b038211176100eb57604052565b60405190610171608083610141565b565b60405190610171604083610141565b60405190610171606083610141565b6040519061017160a083610141565b91908260809103126100b8576040516101b8816100d0565b60608082948035845260208101356020850152604081013560408501520135910152565b906101406003198301126100b8576040516101f6816100d0565b60606102218294600435845260243560208501526102158160446101a0565b604085015260c46101a0565b910152565b6001600160401b0381116100eb5760051b60200190565b6001600160401b0381116100eb57601f01601f191660200190565b81601f820112156100b85780359061026f8261023d565b9261027d6040519485610141565b828452602083830101116100b857815f926020809301838601378301015290565b359063ffffffff821682036100b857565b35906001600160401b03821682036100b857565b81601f820112156100b8578035906102da82610226565b926102e86040519485610141565b82845260208085019360051b830101918183116100b85760208101935b83851061031457505050505090565b84356001600160401b0381116100b85782016040818503601f1901126100b85760405191610341836100f0565b6020820135926001600160401b0384116100b857604083610369886020809881980101610258565b8352013583820152815201940193610305565b91906040838203126100b85760405190610395826100f0565b819380356001600160401b0381116100b85781016060818403126100b857604051906103c08261010b565b80356001600160401b0381116100b857810184601f820112156100b8578035906103e982610226565b916103f76040519384610141565b80835260208084019160051b830101918783116100b85760208101915b838310610467575050505061043f9160409184526104346020820161029e565b6020850152016102af565b604082015283526020810135916001600160401b0383116100b85760209261022192016102c3565b82356001600160401b0381116100b8578201906040828b03601f1901126100b85760405190610495826100f0565b60208301356001600160f01b0319811681036100b85782526040830135916001600160401b0383116100b8576104d38c602080969581960101610258565b83820152815201920191610414565b91908260609103126100b8576040516104fa8161010b565b6040808294610508816102af565b84526105166020820161029e565b60208501520135910152565b919091610100818403126100b8576040519061053d82610126565b819381359160ff831683036100b85761057b60e09260a09486526105636020840161029e565b602087015260408301356040870152606083016104e2565b606085015260c081013560808501520135910152565b9080601f830112156100b85781356105a881610226565b926105b66040519485610141565b81845260208085019260051b8201019283116100b857602001905b8282106105de5750505090565b81358152602091820191016105d1565b919091610160818403126100b857610604610162565b9281356001600160401b0381116100b8578161062191840161037c565b84526106308160208401610522565b60208501526101208201356001600160401b0381116100b85781610655918401610591565b60408501526101408201356001600160401b0381116100b8576106789201610591565b6060830152565b9190916060818403126100b857604051906106998261010b565b819381356001600160401b0381116100b857820181601f820112156100b8578035906106c482610226565b916106d26040519384610141565b80835260208084019160051b830101918483116100b85760208101915b838310610720575050505083526020820135916001600160401b0383116100b8576105166040939284938301610591565b82356001600160401b0381116100b8578201906060828803601f1901126100b8576040519061074e8261010b565b60208301358252604083013560208301526060830135916001600160401b0383116100b85761078589602080969581960101610258565b60408201528152019201916106ef565b346100b8576101603660031901126100b8576107b0366101dc565b50610144356001600160401b0381116100b857604060031982360301126100b857604051906107de826100f0565b80600401356001600160401b0381116100b85761080190600436918401016105ee565b825260248101356001600160401b0381116100b8576020916004610828923692010161067f565b910152005b346100b8575f3660031901126100b857604051610dad60f31b8152602090f35b346100b8575f3660031901126100b85760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b9181601f840112156100b8578235916001600160401b0383116100b857602083818601950101116100b857565b346100b85760403660031901126100b8576004356001600160401b0381116100b8576108e4903690600401610887565b602435916001600160401b0383116100b85761090761090f933690600401610887565b929091610b95565b919060206040519260608452805191829182606087015201608085015e5f60808285010152601f8019910116820192602060a060808601956080868203018387015284518097520192015f945b80861061097157505082935060408301520390f35b9092602060a060019260408088518051845285810151868501520151805182840152848101516060840152015160808201520194019501949061095c565b90610140828203126100b8576109f49060c0604051936109ce856100d0565b80358552602081013560208601526109e983604083016101a0565b6040860152016101a0565b606082015290565b9190916040818403126100b85780356001600160401b0381116100b85783610a259183016105ee565b9260208201356001600160401b0381116100b857610a43920161067f565b90565b6101719092919260c060606101408301958051845260208101516020850152610a9560408201516040860190606080918051845260208101516020850152604081015160408501520151910152565b0151910190606080918051845260208101516020850152604081015160408501520151910152565b60405190610aca8261010b565b5f6040838281528260208201520152565b60405190610aea602083610141565b5f80835282815b828110610afd57505050565b602090604051610b0c8161010b565b5f81525f83820152610b1c610abd565b604082015282828501015201610af1565b90610b3782610226565b610b446040519182610141565b8281528092610b55601f1991610226565b01905f5b828110610b6557505050565b602090604051610b748161010b565b5f81525f83820152610b84610abd565b604082015282828501015201610b59565b610ba990610bb293929594958101906109af565b938101906109fc565b8392919251610bd6610bcd6020865151015163ffffffff1690565b63ffffffff1690565b1115610cd857610c488160606020610bfd610bf588610c3a999a610e04565b9490946110b6565b970151015180516001600160401b03169360608301948551906001600160401b038251911611610c50575b50505060405194859160208301610a46565b03601f198101855284610141565b515191929190565b604084015281516001600160401b03169163ffffffff6040610ca6610c7c602085015163ffffffff1690565b9451867f000000000000000000000000000000000000000000000000000000000000000091611222565b920151926001600160401b03610cba610162565b951685521660208401526040830152606082015283525f8080610c28565b506040519150610cfd82610cef8560208301610a46565b03601f198101845283610141565b6060610d07610adb565b9301515191929190565b60405190610d1e826100d0565b5f6060838281528260208201528260408201520152565b60405190610d42826100d0565b815f81525f6020820152610d54610d11565b60408201526060610221610d11565b634e487b7160e01b5f52603260045260245ffd5b805115610d845760200190565b610d63565b8051821015610d845760209160051b010190565b60405190610daa826100f0565b5f6020838281520152565b90610dbf82610226565b610dcc6040519182610141565b8281528092610ddd601f1991610226565b01905f5b828110610ded57505050565b602090610df8610d9d565b82828501015201610de1565b9190610e0e610d35565b50805190602082015151915190610e30610bcd6020840163ffffffff90511690565b9260408301610e4681516001600160401b031690565b9060408801916001600160401b03835151911690811415908161106a575b5061105b57519051805190916001600160401b03160361104f57905b6020820191610e97610e938451846112ea565b1590565b61104057845151945f955f5b818110610fbc5750508515610fad57610ebb906113f9565b6020815191012091610ecc81610db5565b925f5b828110610f205750505091610e93916060610ef494015191606086015190519261154e565b610f1157608092610f086020938388611666565b85520151015190565b63528bd3ef60e01b5f5260045ffd5b80610f3260019260208a510151610d89565b516020610f40825186611538565b91015190604051610f7e81610f706020820194856014916bffffffffffffffffffffffff199060601b1681520190565b03601f198101835282610141565b519020610f89610173565b9182526020820152610f9b8288610d89565b52610fa68187610d89565b5001610ecf565b6323188e3960e21b5f5260045ffd5b610dad60f31b610fef610fe2610fd3848751610d89565b51516001600160f01b03191690565b6001600160f01b03191690565b1480611027575b611003575b600101610ea3565b9650600161101f60206110178a8651610d89565b510151611313565b979050610ffb565b50602080611036838651610d89565b5101515114610ff6565b633aa90f7f60e21b5f5260045ffd5b50606086015190610e80565b637202e68560e11b5f5260045ffd5b905060608901515114155f610e64565b805191908290602001825e015f815290565b6040516001600160e01b031990911660208201529190610171908390610cef90602483019061107a565b815151916110c383610db5565b916110cd84610b2d565b935f5b81811061111657506110e3575b50505090565b6110fb928260406020610e939501519101519261154e565b611107575f80806110dd565b630b92186960e31b5f5260045ffd5b611121818551610d89565b51906040820191611132835161185b565b92602084019182511561121357600194816111ac60206111de94519201946111a661119f611164885163ffffffff1690565b6001600160e01b03199063ff00ff00600882811b9190911691901c62ff00ff1617601081811b63ffff00001691901c61ffff161760e01b1690565b91516119a7565b9061108c565b602081519101206111bb610173565b91825260208201526111cd868c610d89565b526111d8858b610d89565b506119cc565b905191516111ea610182565b928352602083015260408201526112018289610d89565b5261120c8188610d89565b50016110d0565b63b4eb9e5160e01b5f5260045ffd5b9190915f915b81518310156112925780602061123e8585610d89565b51015103611288575060406112596112669361126193610d89565b51015161185b565b612507565b906001600160401b03808351169116145f14611283576040015190565b505f90565b9160010191611228565b505050505f90565b634e487b7160e01b5f52601160045260245ffd5b90600182018092116112bc57565b61129a565b90600382018092116112bc57565b90600282018092116112bc57565b919082018092116112bc57565b906001600160ff1b03811681036112bc5760039060011b0490600182018092116112bc57101590565b6020815110611323576020015190565b60405162461bcd60e51b8152602060048201526024808201527f42797465733a3a20746f427974657333323a206461746120697320746f20736860448201526337b93a1760e11b6064820152608490fd5b60405190611383602083610141565b5f8252565b610cef6113a794936113a761017194604051978895602087019061107a565b9061107a565b610171926113a795946113cc600c94604051988995602087019061107a565b6001600160e01b03199290921682526001600160c01b031916600482015203601319810185520183610141565b90815151611405611374565b905f5b8181106114da57509261141e610a439394611b8c565b916114d461144a604061143b611164602087015163ffffffff1690565b9401516001600160401b031690565b67ffffffffffff000067ff00ff00ff00ff0066ff00ff00ff00ff8360081c169260081b169165ffff0000ffff65ffff0000ff0065ffffffffffff67ffff0000ffff0000861666ff0000ffff000085161760101c16941691161760101b161767ffffffff0000000063ffffffff8260201c169160201b166001600160401b0360c01b911760c01b1690565b926113ad565b91611531600191610f706115136114f5610fd3888b51610d89565b6040516001600160f01b031990911660208201529182906022820190565b61152b6020611523888b51610d89565b5101516119a7565b91611388565b9201611408565b610a439161154591611d09565b90929192611d61565b91939290811561161757845161156381611ddd565b61156c82611ddd565b91600161157886612816565b1b5f805b838210611597575050505061159394959650612841565b1490565b6115a1828c610d89565b515190888210156116085782151590816115fd575b506115ee5760019060206115ca848e610d89565b5101516115d78489610d89565b528084016115e58488610d89565b5291019061157c565b630647f54960e21b5f5260045ffd5b90508111155f6115b6565b630834466160e31b5f5260045ffd5b639136328760e01b5f5260045ffd5b604080519091906116378382610141565b6001815291601f1901825f5b82811061164f57505050565b60209061165a610d9d565b82828501015201611643565b610e9391611749936040602083019261170d61170860206116e4875161168d815160ff1690565b9061169e8482015163ffffffff1690565b90888101516116d160806060840151930151936116c56116bc610191565b60ff9097168752565b63ffffffff1685880152565b8984015260608301526080820152611e86565b8051908201209701518651602001516117029063ffffffff16610bcd565b90611f7a565b6112ae565b9460a0611718611626565b9551015190611725610173565b918252602082015261173685610d77565b5261174084610d77565b50015190611f8f565b61110757565b6040519060a082018281106001600160401b038211176100eb5760405260606080835f81525f60208201525f60408201525f838201520152565b60405190611796826100f0565b60606020835f81520152565b6040519061012082018281106001600160401b038211176100eb576040525f610100838281526117d0611789565b60208201528260408201526117e3611789565b60608201528260808201526117f6611789565b60a08201528260c0820152606060e08201520152565b9061181682610226565b6118236040519182610141565b8281528092611834601f1991610226565b01905f5b82811061184457505050565b60209061184f6117a2565b82828501015201611838565b61186361174f565b5061186c610173565b9081525f602082015261188661188182611f9b565b611313565b90611890816120da565b61189c61188183611f9b565b6118a861188184611f9b565b916118b2846120da565b6118bb8161180c565b945f5b8281106118ea575050506118d0610191565b948552602085015260408401526060830152608082015290565b6001906118f683612259565b60ff6119006117a2565b91168061192c5750600160c08201525b61191a828a610d89565b526119258189610d89565b50016118be565b60048103611950575060016040820152611945846122d3565b60608201525b611910565b60058103611973575060016080820152611969846122d3565b60a0820152611910565b60068103611993575060018152611989846122d3565b6020820152611910565b60080361194b576001610100820152611910565b610a436113a791610f706119bb8251611b8c565b91604051948593602085019061107a565b6119d4610abd565b505f5f925f5f5b6080850180518051831015611b055760406119f984611a0193610d89565b510151151590565b80611add575b611a94575b611a1c60406119f9848451610d89565b80611a5b575b611a30575b506001016119db565b819250611a5360206060611a4960809560019551610d89565b510151015161248f565b929150611a27565b50634953544d60e01b63ffffffff60e01b611a8d6060611a7c868651610d89565b510151516001600160e01b03191690565b1614611a22565b95509250611ab761188160206060611aad878a51610d89565b51015101516123af565b92611ad761188160206060611acd858b51610d89565b5101510151612429565b95611a0c565b5063049534d560e41b63ffffffff60e01b611afe6060611a7c868651610d89565b1614611a07565b505050925092908215611b2a57611b1a610182565b9283526020830152604082015290565b633eba99d160e21b5f5260045ffd5b6003198101919082116112bc57565b5f198101919082116112bc57565b60200390602082116112bc57565b919082039182116112bc57565b600190610a43939260ff60f81b9060f81b168152019061107a565b6040811015611bd057610a43611bae611ba8610f709360021b90565b60ff1690565b60405160f89190911b6001600160f81b03191660208201529182906021820190565b614000811015611c2f57610a43611c0d611bfa611bf3611708610f709560021b90565b61ffff1690565b60ff61ff008260081b169160081c161790565b60405160f09190911b6001600160f01b03191660208201529182906022820190565b6340000000811015611cac57610a43611c8a611c59610bcd611c54610f709560021b90565b6112cf565b63ffffff0062ff00ff62ffffff818460081c1616921660081b161763ffff000061ffff8260101c169160101b161790565b60405160e09190911b6001600160e01b03191660208201529182906024820190565b610f70611cd1611cbe611cd693612627565b6040519283916020830160209181520190565b612740565b610a43611cf7611ba8611cf2611cec8551611b39565b60021b90565b6112c1565b610f7060405193849260208401611b71565b8151919060418303611d3957611d329250602082015190606060408401519301515f1a90612789565b9192909190565b50505f9160029190565b60041115611d4d57565b634e487b7160e01b5f52602160045260245ffd5b611d6a81611d43565b80611d73575050565b611d7c81611d43565b60018103611d935763f645eedf60e01b5f5260045ffd5b611d9c81611d43565b60028103611db7575063fce698f760e01b5f5260045260245ffd5b80611dc3600392611d43565b14611dcb5750565b6335e2f38360e21b5f5260045260245ffd5b90611de782610226565b611df46040519182610141565b8281528092611e05601f1991610226565b0190602036910137565b949291969593909660405197889660208801611e2a9161107a565b9063ffffffff60e01b168152600401611e429161107a565b916001600160401b0360c01b16825263ffffffff60e01b166008820152600c01611e6b9161107a565b611e749161107a565b03601f19810183526101719083610141565b611f67611f44610cef610a4393611ec3611ea1825160ff1690565b60405160f89190911b6001600160f81b03191660208201529283906021820190565b610cef611eda611164602084015163ffffffff1690565b611ef560408401516040519384916020830160209181520190565b606083015193611f756080611f1461144a88516001600160401b031690565b95611f526040611f2e61116460208c015163ffffffff1690565b9901516040519a8b916020830160209181520190565b03601f1981018b528a610141565b01516040519889916020830160209181520190565b03601f198101895288610141565b611e0f565b80611f83575090565b81039081116112bc5790565b929091611593926129b6565b60208101908151602081018091116112bc57815151106100b8576020905181835182010191829101116112bc576020611fd391612bee565b90805190602082018092116112bc575290565b90602082019182518281018091116112bc57815151106100b8578115612034576020905181845182010191829101116112bc578161202391612bee565b9180519182018092116112bc575290565b505050604051612045602082610141565b5f815290565b60ff60049116019060ff82116112bc57565b1561206457565b60405162461bcd60e51b815260206004820152602860248201527f756e657870656374656420707265666978206465636f64696e6720436f6d706160448201526731ba1e2ab4b73a1f60c11b6064820152608490fd5b906001600160401b03809116911601906001600160401b0382116112bc57565b6120e381612259565b60038116806120fd5750610a43915060021c603f16611ba8565b6001810361214e57506121429061213c611ba8612132612122611ba8610a4397612259565b60061b67ffffffffffffffc01690565b9260021c603f1690565b906120ba565b6001600160401b031690565b600281036121c45750610bcd906121b760ff846121a882612171610a4398612259565b95816121978161218961218388612259565b97612259565b991660081b63ffffff001690565b911617921660101b63ffff00001690565b17921660181b63ff0000001690565b1760021c633fffffff1690565b60030361220357610a439160ff6121ea6121e56121fe94603f9060021c1690565b61204b565b16906121f9600883111561205d565b611fe6565b61248f565b60405162461bcd60e51b815260206004820152601a60248201527f436f64652073686f756c6420626520756e726561636861626c650000000000006044820152606490fd5b908151811015610d84570160200190565b60208101908151600181018091116112bc578151511061229f575181516001600160f81b03199161228a9190612248565b511660f81c9061229a81516112ae565b905290565b60405162461bcd60e51b815260206004820152600c60248201526b4f7574206f662072616e676560a01b6044820152606490fd5b6122db611789565b5060208101908151600481018091116112bc57815151106100b8576020815181845182010191829101116112bc57600461231491612bee565b91805190600482018092116112bc57525f915f905b6004821061236957505080612340612346926120da565b90611fe6565b612361612351610173565b6001600160e01b03199093168352565b602082015290565b90926001600160f81b031961237e8584612248565b5116908460031b91858304600814861517156112bc576001926001600160e01b0319918216901c1617930190612329565b80516020116100b857602080610a439201612bee565b80516008116100b85760086020610a439201612bee565b8051600c116100b857602881019060200181106112bc576004610a4391612bee565b90815181116100b8578015612419576020610a439201612bee565b5050604051612045602082610141565b8051806020116100b857601f1981019081116112bc57604082019160200182106112bc57610a4391612bee565b805180600c116100b857600b1981019081116112bc57602c82019160200182106112bc57610a4391612bee565b80156112bc575f190190565b80515f91815b61249e57505090565b90915f198301908382116112bc576124b68284612248565b5160f81c91600381901b906001600160fd1b038116036112bc5760ff81116112bc576001901b918281029281840414901517156112bc57612500916124fa916112dd565b92612483565b9081612495565b61250f610abd565b5f915b608081018051805185101561261f57610e9360406119f98761253394610d89565b612613576341504b4360e01b61255f6125526060611a7c888651610d89565b6001600160e01b03191690565b036126135760606125738560209351610d89565b510151015191602c835103612606576125916121426121fe856123c5565b936001600160401b038516156125f2575050506125ea6125c26118816125bc610bcd6121fe866123dc565b93612456565b916125dd6125ce610182565b6001600160401b039095168552565b63ffffffff166020840152565b604082015290565b608092945060019193505b01929050612512565b91509160016080916125fd565b509160016080916125fd565b505050919050565b8060081c9060081b907cff000000ff000000ff000000ff000000ff000000ff000000ff000000ff7dff000000ff000000ff000000ff000000ff000000ff000000ff000000ff007fff000000ff000000ff000000ff000000ff000000ff000000ff000000ff00000084167eff000000ff000000ff000000ff000000ff000000ff000000ff000000ff000084161760101c931691161760101b177bffffffff00000000ffffffff00000000ffffffff00000000ffffffff7fffffffff00000000ffffffff00000000ffffffff00000000ffffffff00000000821660201c911660201b1777ffffffffffffffff0000000000000000ffffffffffffffff8019821660401c911660401b1761273c8160801c9160801b90565b1790565b80515f1981019081116112bc575b6001600160f81b03196127618284612248565b51166127755761277090612483565b61274e565b600181018091116112bc57610a43916123fe565b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161280b579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15612800575f516001600160a01b038116156127f657905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f9160039190565b6001811115611283575f1981019081116112bc5761283390612c8f565b600181018091116112bc5790565b9192905f83515b600161285386610d77565b51146129a35761287f61287a85600161287461286e8a610d77565b51612c8f565b1b6112dd565b611b48565b905f915f915b8083106128a85750505061289c6128a291946112ae565b60011c90565b92612848565b9091926128b58489610d89565b51826128c0866112ae565b1080612985575b15612926579061291260019260026128fd6128e2898c610d89565b516128f56128ef8b6112ae565b8d610d89565b519084612cf5565b9701965b61290b848b610d89565b5260011c90565b61291c828b610d89565b5201929190612885565b8987600183188610612968579160018061295e612912946129568c61294f8d9e9d869b9a610d89565b5192610d89565b519085612cf5565b9801980196612901565b612912915091600161297c88829695610d89565b51970196612901565b50612998612992866112ae565b8a610d89565b5160018218146128c7565b50509150506129b29150610d77565b5190565b9290928215611617578351938415612bdf5760015b858110612bb157506001841480612ba7575b80612b95575b612b7a576129f86129f385612d15565b611ddd565b94612a01610173565b945f865260208601968752612a14610173565b925f845260208401948552612a27610182565b905f82526020820193845260408201525f91805b612ac6575b50505051612ab7575190515103612aa85781515f190182525b815115612a9c57612a6982612fb7565b612a7283612fb7565b90612a7e845160010190565b84525f5260205260405f20612a968451845190610d89565b52612a59565b916129b2915051610d77565b637227423160e11b5f5260045ffd5b63072afb8760e51b5f5260045ffd5b612acf81612c8f565b92612ae9612ae16001861b8094611b64565b9283926112dd565b938685612af68187612d5a565b92602084015180155f14612b2e575050505050508551518551145f03612a405780612b29612b2387612f9b565b8a612f80565b612a3b565b60011480612b72575b15612b5e575050506020612b55826040612b29940151905190610d89565b5101518a612f80565b91612b2993916002612b23941b0391612de3565b508015612b37565b925090925051612aa857612b8f602091610d77565b51015190565b50612b9f81610d77565b5151156129e3565b50600185146129dd565b612bbb8183610d89565b5151612bcf612bc983611b48565b84610d89565b515110156115ee576001016129cb565b631a14a47760e31b5f5260045ffd5b919091612bfa8361023d565b612c076040519182610141565b838152612c138461023d565b602082019190601f1901368337939091905b6020811015612c5f5780612c4557505f19905b5182518216911916179052565b61287a612c54612c5992611b56565b612fd3565b90612c38565b909182518152602081018091116112bc5791602081018091116112bc5790601f19810190811115612c255761129a565b806fffffffffffffffffffffffffffffffff1060071b81811c6001600160401b031060061b1781811c63ffffffff1060051b1781811c61ffff1060041b1781811c60ff1060031b1781811c600f1060021b1781811c60031060011b1790811c6001101790565b600116612d08575f5260205260405f2090565b905f5260205260405f2090565b90815f925b612d215750565b915f1983018381116112bc57600193169283910192612d1a565b60405190612d488261010b565b60606040835f81525f60208201520152565b612d62612d3b565b508051612d7560208301918251906112dd565b928251905b848210612dbc575b50829350612d9561229a92935182611b64565b908451946040810151612da6610182565b9687528360208801526040870152528251611b64565b90612dcb816040860151610d89565b5151821115612ddd5760010190612d7a565b90612d82565b91906020830151835193612df682611ddd565b93612e0083611ddd565b955f5b848110612f325750505050935b6001612e1b84610d77565b5114612f25575f945f905b808210612e3a575050848084528452612e10565b9095612e468786610d89565b51878784612e53836112ae565b1080612f07575b15612eae57600192612e8c83612e846128ef612e7d612ea89997612e9697610d89565b51926112ae565b519083612cf5565b61290b848c610d89565b612ea08289610d89565b5201966112cf565b90612e26565b505084516020860151511115612ef857600191612ee48392612e8c612ed38c8c610d89565b51612edd8a612f9b565b9083612cf5565b612eee8289610d89565b5201960190612e26565b63d8f29a1560e01b5f5260045ffd5b50612f1a612f14836112ae565b89610d89565b516001841814612e5a565b935050506129b290610d77565b600190612f6b604086016020612f538251612f4d86896112dd565b90610d89565b510151612f60848d610d89565b525182850190610d89565b51518401612f79828a610d89565b5201612e03565b90612f916020830151835190610d89565b5260018151019052565b612fab6020820151825190610d89565b51906001815101905290565b612fc76020820151825190610d89565b5181515f190190915290565b601f81116112bc576101000a9056fea2646970667358221220b54cab27f9309d22218000ba9b81a2beac680cddb6189b628244d20391b8b15264736f6c634300081e0033","sourceMap":"2797:9411:121:-:0;;;;;;;;;;;;;-1:-1:-1;;2797:9411:121;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;;;;;;;;;;;;;;;;;;3220:28;;2797:9411;;;;;;;;3220:28;2797:9411;;;;;;;;;;;;-1:-1:-1;2797:9411:121;;;;;;-1:-1:-1;2797:9411:121;;;;;-1:-1:-1;2797:9411:121","linkReferences":{}},"deployedBytecode":{"object":"0x60806040526004361015610011575f80fd5b5f3560e01c806301ffc9a7146100645780639442d9fc1461005f578063af8b91d61461005a578063e455995b146100555763f7e83aee14610050575f80fd5b6108b4565b61084d565b61082d565b610795565b346100b85760203660031901126100b85760043563ffffffff60e01b81168091036100b857637bf41d7760e11b81149081156100a7575b50151560805260206080f35b6301ffc9a760e01b1490508161009b565b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b608081019081106001600160401b038211176100eb57604052565b6100bc565b604081019081106001600160401b038211176100eb57604052565b606081019081106001600160401b038211176100eb57604052565b60c081019081106001600160401b038211176100eb57604052565b90601f801991011681019081106001600160401b038211176100eb57604052565b60405190610171608083610141565b565b60405190610171604083610141565b60405190610171606083610141565b6040519061017160a083610141565b91908260809103126100b8576040516101b8816100d0565b60608082948035845260208101356020850152604081013560408501520135910152565b906101406003198301126100b8576040516101f6816100d0565b60606102218294600435845260243560208501526102158160446101a0565b604085015260c46101a0565b910152565b6001600160401b0381116100eb5760051b60200190565b6001600160401b0381116100eb57601f01601f191660200190565b81601f820112156100b85780359061026f8261023d565b9261027d6040519485610141565b828452602083830101116100b857815f926020809301838601378301015290565b359063ffffffff821682036100b857565b35906001600160401b03821682036100b857565b81601f820112156100b8578035906102da82610226565b926102e86040519485610141565b82845260208085019360051b830101918183116100b85760208101935b83851061031457505050505090565b84356001600160401b0381116100b85782016040818503601f1901126100b85760405191610341836100f0565b6020820135926001600160401b0384116100b857604083610369886020809881980101610258565b8352013583820152815201940193610305565b91906040838203126100b85760405190610395826100f0565b819380356001600160401b0381116100b85781016060818403126100b857604051906103c08261010b565b80356001600160401b0381116100b857810184601f820112156100b8578035906103e982610226565b916103f76040519384610141565b80835260208084019160051b830101918783116100b85760208101915b838310610467575050505061043f9160409184526104346020820161029e565b6020850152016102af565b604082015283526020810135916001600160401b0383116100b85760209261022192016102c3565b82356001600160401b0381116100b8578201906040828b03601f1901126100b85760405190610495826100f0565b60208301356001600160f01b0319811681036100b85782526040830135916001600160401b0383116100b8576104d38c602080969581960101610258565b83820152815201920191610414565b91908260609103126100b8576040516104fa8161010b565b6040808294610508816102af565b84526105166020820161029e565b60208501520135910152565b919091610100818403126100b8576040519061053d82610126565b819381359160ff831683036100b85761057b60e09260a09486526105636020840161029e565b602087015260408301356040870152606083016104e2565b606085015260c081013560808501520135910152565b9080601f830112156100b85781356105a881610226565b926105b66040519485610141565b81845260208085019260051b8201019283116100b857602001905b8282106105de5750505090565b81358152602091820191016105d1565b919091610160818403126100b857610604610162565b9281356001600160401b0381116100b8578161062191840161037c565b84526106308160208401610522565b60208501526101208201356001600160401b0381116100b85781610655918401610591565b60408501526101408201356001600160401b0381116100b8576106789201610591565b6060830152565b9190916060818403126100b857604051906106998261010b565b819381356001600160401b0381116100b857820181601f820112156100b8578035906106c482610226565b916106d26040519384610141565b80835260208084019160051b830101918483116100b85760208101915b838310610720575050505083526020820135916001600160401b0383116100b8576105166040939284938301610591565b82356001600160401b0381116100b8578201906060828803601f1901126100b8576040519061074e8261010b565b60208301358252604083013560208301526060830135916001600160401b0383116100b85761078589602080969581960101610258565b60408201528152019201916106ef565b346100b8576101603660031901126100b8576107b0366101dc565b50610144356001600160401b0381116100b857604060031982360301126100b857604051906107de826100f0565b80600401356001600160401b0381116100b85761080190600436918401016105ee565b825260248101356001600160401b0381116100b8576020916004610828923692010161067f565b910152005b346100b8575f3660031901126100b857604051610dad60f31b8152602090f35b346100b8575f3660031901126100b85760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b9181601f840112156100b8578235916001600160401b0383116100b857602083818601950101116100b857565b346100b85760403660031901126100b8576004356001600160401b0381116100b8576108e4903690600401610887565b602435916001600160401b0383116100b85761090761090f933690600401610887565b929091610b95565b919060206040519260608452805191829182606087015201608085015e5f60808285010152601f8019910116820192602060a060808601956080868203018387015284518097520192015f945b80861061097157505082935060408301520390f35b9092602060a060019260408088518051845285810151868501520151805182840152848101516060840152015160808201520194019501949061095c565b90610140828203126100b8576109f49060c0604051936109ce856100d0565b80358552602081013560208601526109e983604083016101a0565b6040860152016101a0565b606082015290565b9190916040818403126100b85780356001600160401b0381116100b85783610a259183016105ee565b9260208201356001600160401b0381116100b857610a43920161067f565b90565b6101719092919260c060606101408301958051845260208101516020850152610a9560408201516040860190606080918051845260208101516020850152604081015160408501520151910152565b0151910190606080918051845260208101516020850152604081015160408501520151910152565b60405190610aca8261010b565b5f6040838281528260208201520152565b60405190610aea602083610141565b5f80835282815b828110610afd57505050565b602090604051610b0c8161010b565b5f81525f83820152610b1c610abd565b604082015282828501015201610af1565b90610b3782610226565b610b446040519182610141565b8281528092610b55601f1991610226565b01905f5b828110610b6557505050565b602090604051610b748161010b565b5f81525f83820152610b84610abd565b604082015282828501015201610b59565b610ba990610bb293929594958101906109af565b938101906109fc565b8392919251610bd6610bcd6020865151015163ffffffff1690565b63ffffffff1690565b1115610cd857610c488160606020610bfd610bf588610c3a999a610e04565b9490946110b6565b970151015180516001600160401b03169360608301948551906001600160401b038251911611610c50575b50505060405194859160208301610a46565b03601f198101855284610141565b515191929190565b604084015281516001600160401b03169163ffffffff6040610ca6610c7c602085015163ffffffff1690565b9451867f000000000000000000000000000000000000000000000000000000000000000091611222565b920151926001600160401b03610cba610162565b951685521660208401526040830152606082015283525f8080610c28565b506040519150610cfd82610cef8560208301610a46565b03601f198101845283610141565b6060610d07610adb565b9301515191929190565b60405190610d1e826100d0565b5f6060838281528260208201528260408201520152565b60405190610d42826100d0565b815f81525f6020820152610d54610d11565b60408201526060610221610d11565b634e487b7160e01b5f52603260045260245ffd5b805115610d845760200190565b610d63565b8051821015610d845760209160051b010190565b60405190610daa826100f0565b5f6020838281520152565b90610dbf82610226565b610dcc6040519182610141565b8281528092610ddd601f1991610226565b01905f5b828110610ded57505050565b602090610df8610d9d565b82828501015201610de1565b9190610e0e610d35565b50805190602082015151915190610e30610bcd6020840163ffffffff90511690565b9260408301610e4681516001600160401b031690565b9060408801916001600160401b03835151911690811415908161106a575b5061105b57519051805190916001600160401b03160361104f57905b6020820191610e97610e938451846112ea565b1590565b61104057845151945f955f5b818110610fbc5750508515610fad57610ebb906113f9565b6020815191012091610ecc81610db5565b925f5b828110610f205750505091610e93916060610ef494015191606086015190519261154e565b610f1157608092610f086020938388611666565b85520151015190565b63528bd3ef60e01b5f5260045ffd5b80610f3260019260208a510151610d89565b516020610f40825186611538565b91015190604051610f7e81610f706020820194856014916bffffffffffffffffffffffff199060601b1681520190565b03601f198101835282610141565b519020610f89610173565b9182526020820152610f9b8288610d89565b52610fa68187610d89565b5001610ecf565b6323188e3960e21b5f5260045ffd5b610dad60f31b610fef610fe2610fd3848751610d89565b51516001600160f01b03191690565b6001600160f01b03191690565b1480611027575b611003575b600101610ea3565b9650600161101f60206110178a8651610d89565b510151611313565b979050610ffb565b50602080611036838651610d89565b5101515114610ff6565b633aa90f7f60e21b5f5260045ffd5b50606086015190610e80565b637202e68560e11b5f5260045ffd5b905060608901515114155f610e64565b805191908290602001825e015f815290565b6040516001600160e01b031990911660208201529190610171908390610cef90602483019061107a565b815151916110c383610db5565b916110cd84610b2d565b935f5b81811061111657506110e3575b50505090565b6110fb928260406020610e939501519101519261154e565b611107575f80806110dd565b630b92186960e31b5f5260045ffd5b611121818551610d89565b51906040820191611132835161185b565b92602084019182511561121357600194816111ac60206111de94519201946111a661119f611164885163ffffffff1690565b6001600160e01b03199063ff00ff00600882811b9190911691901c62ff00ff1617601081811b63ffff00001691901c61ffff161760e01b1690565b91516119a7565b9061108c565b602081519101206111bb610173565b91825260208201526111cd868c610d89565b526111d8858b610d89565b506119cc565b905191516111ea610182565b928352602083015260408201526112018289610d89565b5261120c8188610d89565b50016110d0565b63b4eb9e5160e01b5f5260045ffd5b9190915f915b81518310156112925780602061123e8585610d89565b51015103611288575060406112596112669361126193610d89565b51015161185b565b612507565b906001600160401b03808351169116145f14611283576040015190565b505f90565b9160010191611228565b505050505f90565b634e487b7160e01b5f52601160045260245ffd5b90600182018092116112bc57565b61129a565b90600382018092116112bc57565b90600282018092116112bc57565b919082018092116112bc57565b906001600160ff1b03811681036112bc5760039060011b0490600182018092116112bc57101590565b6020815110611323576020015190565b60405162461bcd60e51b8152602060048201526024808201527f42797465733a3a20746f427974657333323a206461746120697320746f20736860448201526337b93a1760e11b6064820152608490fd5b60405190611383602083610141565b5f8252565b610cef6113a794936113a761017194604051978895602087019061107a565b9061107a565b610171926113a795946113cc600c94604051988995602087019061107a565b6001600160e01b03199290921682526001600160c01b031916600482015203601319810185520183610141565b90815151611405611374565b905f5b8181106114da57509261141e610a439394611b8c565b916114d461144a604061143b611164602087015163ffffffff1690565b9401516001600160401b031690565b67ffffffffffff000067ff00ff00ff00ff0066ff00ff00ff00ff8360081c169260081b169165ffff0000ffff65ffff0000ff0065ffffffffffff67ffff0000ffff0000861666ff0000ffff000085161760101c16941691161760101b161767ffffffff0000000063ffffffff8260201c169160201b166001600160401b0360c01b911760c01b1690565b926113ad565b91611531600191610f706115136114f5610fd3888b51610d89565b6040516001600160f01b031990911660208201529182906022820190565b61152b6020611523888b51610d89565b5101516119a7565b91611388565b9201611408565b610a439161154591611d09565b90929192611d61565b91939290811561161757845161156381611ddd565b61156c82611ddd565b91600161157886612816565b1b5f805b838210611597575050505061159394959650612841565b1490565b6115a1828c610d89565b515190888210156116085782151590816115fd575b506115ee5760019060206115ca848e610d89565b5101516115d78489610d89565b528084016115e58488610d89565b5291019061157c565b630647f54960e21b5f5260045ffd5b90508111155f6115b6565b630834466160e31b5f5260045ffd5b639136328760e01b5f5260045ffd5b604080519091906116378382610141565b6001815291601f1901825f5b82811061164f57505050565b60209061165a610d9d565b82828501015201611643565b610e9391611749936040602083019261170d61170860206116e4875161168d815160ff1690565b9061169e8482015163ffffffff1690565b90888101516116d160806060840151930151936116c56116bc610191565b60ff9097168752565b63ffffffff1685880152565b8984015260608301526080820152611e86565b8051908201209701518651602001516117029063ffffffff16610bcd565b90611f7a565b6112ae565b9460a0611718611626565b9551015190611725610173565b918252602082015261173685610d77565b5261174084610d77565b50015190611f8f565b61110757565b6040519060a082018281106001600160401b038211176100eb5760405260606080835f81525f60208201525f60408201525f838201520152565b60405190611796826100f0565b60606020835f81520152565b6040519061012082018281106001600160401b038211176100eb576040525f610100838281526117d0611789565b60208201528260408201526117e3611789565b60608201528260808201526117f6611789565b60a08201528260c0820152606060e08201520152565b9061181682610226565b6118236040519182610141565b8281528092611834601f1991610226565b01905f5b82811061184457505050565b60209061184f6117a2565b82828501015201611838565b61186361174f565b5061186c610173565b9081525f602082015261188661188182611f9b565b611313565b90611890816120da565b61189c61188183611f9b565b6118a861188184611f9b565b916118b2846120da565b6118bb8161180c565b945f5b8281106118ea575050506118d0610191565b948552602085015260408401526060830152608082015290565b6001906118f683612259565b60ff6119006117a2565b91168061192c5750600160c08201525b61191a828a610d89565b526119258189610d89565b50016118be565b60048103611950575060016040820152611945846122d3565b60608201525b611910565b60058103611973575060016080820152611969846122d3565b60a0820152611910565b60068103611993575060018152611989846122d3565b6020820152611910565b60080361194b576001610100820152611910565b610a436113a791610f706119bb8251611b8c565b91604051948593602085019061107a565b6119d4610abd565b505f5f925f5f5b6080850180518051831015611b055760406119f984611a0193610d89565b510151151590565b80611add575b611a94575b611a1c60406119f9848451610d89565b80611a5b575b611a30575b506001016119db565b819250611a5360206060611a4960809560019551610d89565b510151015161248f565b929150611a27565b50634953544d60e01b63ffffffff60e01b611a8d6060611a7c868651610d89565b510151516001600160e01b03191690565b1614611a22565b95509250611ab761188160206060611aad878a51610d89565b51015101516123af565b92611ad761188160206060611acd858b51610d89565b5101510151612429565b95611a0c565b5063049534d560e41b63ffffffff60e01b611afe6060611a7c868651610d89565b1614611a07565b505050925092908215611b2a57611b1a610182565b9283526020830152604082015290565b633eba99d160e21b5f5260045ffd5b6003198101919082116112bc57565b5f198101919082116112bc57565b60200390602082116112bc57565b919082039182116112bc57565b600190610a43939260ff60f81b9060f81b168152019061107a565b6040811015611bd057610a43611bae611ba8610f709360021b90565b60ff1690565b60405160f89190911b6001600160f81b03191660208201529182906021820190565b614000811015611c2f57610a43611c0d611bfa611bf3611708610f709560021b90565b61ffff1690565b60ff61ff008260081b169160081c161790565b60405160f09190911b6001600160f01b03191660208201529182906022820190565b6340000000811015611cac57610a43611c8a611c59610bcd611c54610f709560021b90565b6112cf565b63ffffff0062ff00ff62ffffff818460081c1616921660081b161763ffff000061ffff8260101c169160101b161790565b60405160e09190911b6001600160e01b03191660208201529182906024820190565b610f70611cd1611cbe611cd693612627565b6040519283916020830160209181520190565b612740565b610a43611cf7611ba8611cf2611cec8551611b39565b60021b90565b6112c1565b610f7060405193849260208401611b71565b8151919060418303611d3957611d329250602082015190606060408401519301515f1a90612789565b9192909190565b50505f9160029190565b60041115611d4d57565b634e487b7160e01b5f52602160045260245ffd5b611d6a81611d43565b80611d73575050565b611d7c81611d43565b60018103611d935763f645eedf60e01b5f5260045ffd5b611d9c81611d43565b60028103611db7575063fce698f760e01b5f5260045260245ffd5b80611dc3600392611d43565b14611dcb5750565b6335e2f38360e21b5f5260045260245ffd5b90611de782610226565b611df46040519182610141565b8281528092611e05601f1991610226565b0190602036910137565b949291969593909660405197889660208801611e2a9161107a565b9063ffffffff60e01b168152600401611e429161107a565b916001600160401b0360c01b16825263ffffffff60e01b166008820152600c01611e6b9161107a565b611e749161107a565b03601f19810183526101719083610141565b611f67611f44610cef610a4393611ec3611ea1825160ff1690565b60405160f89190911b6001600160f81b03191660208201529283906021820190565b610cef611eda611164602084015163ffffffff1690565b611ef560408401516040519384916020830160209181520190565b606083015193611f756080611f1461144a88516001600160401b031690565b95611f526040611f2e61116460208c015163ffffffff1690565b9901516040519a8b916020830160209181520190565b03601f1981018b528a610141565b01516040519889916020830160209181520190565b03601f198101895288610141565b611e0f565b80611f83575090565b81039081116112bc5790565b929091611593926129b6565b60208101908151602081018091116112bc57815151106100b8576020905181835182010191829101116112bc576020611fd391612bee565b90805190602082018092116112bc575290565b90602082019182518281018091116112bc57815151106100b8578115612034576020905181845182010191829101116112bc578161202391612bee565b9180519182018092116112bc575290565b505050604051612045602082610141565b5f815290565b60ff60049116019060ff82116112bc57565b1561206457565b60405162461bcd60e51b815260206004820152602860248201527f756e657870656374656420707265666978206465636f64696e6720436f6d706160448201526731ba1e2ab4b73a1f60c11b6064820152608490fd5b906001600160401b03809116911601906001600160401b0382116112bc57565b6120e381612259565b60038116806120fd5750610a43915060021c603f16611ba8565b6001810361214e57506121429061213c611ba8612132612122611ba8610a4397612259565b60061b67ffffffffffffffc01690565b9260021c603f1690565b906120ba565b6001600160401b031690565b600281036121c45750610bcd906121b760ff846121a882612171610a4398612259565b95816121978161218961218388612259565b97612259565b991660081b63ffffff001690565b911617921660101b63ffff00001690565b17921660181b63ff0000001690565b1760021c633fffffff1690565b60030361220357610a439160ff6121ea6121e56121fe94603f9060021c1690565b61204b565b16906121f9600883111561205d565b611fe6565b61248f565b60405162461bcd60e51b815260206004820152601a60248201527f436f64652073686f756c6420626520756e726561636861626c650000000000006044820152606490fd5b908151811015610d84570160200190565b60208101908151600181018091116112bc578151511061229f575181516001600160f81b03199161228a9190612248565b511660f81c9061229a81516112ae565b905290565b60405162461bcd60e51b815260206004820152600c60248201526b4f7574206f662072616e676560a01b6044820152606490fd5b6122db611789565b5060208101908151600481018091116112bc57815151106100b8576020815181845182010191829101116112bc57600461231491612bee565b91805190600482018092116112bc57525f915f905b6004821061236957505080612340612346926120da565b90611fe6565b612361612351610173565b6001600160e01b03199093168352565b602082015290565b90926001600160f81b031961237e8584612248565b5116908460031b91858304600814861517156112bc576001926001600160e01b0319918216901c1617930190612329565b80516020116100b857602080610a439201612bee565b80516008116100b85760086020610a439201612bee565b8051600c116100b857602881019060200181106112bc576004610a4391612bee565b90815181116100b8578015612419576020610a439201612bee565b5050604051612045602082610141565b8051806020116100b857601f1981019081116112bc57604082019160200182106112bc57610a4391612bee565b805180600c116100b857600b1981019081116112bc57602c82019160200182106112bc57610a4391612bee565b80156112bc575f190190565b80515f91815b61249e57505090565b90915f198301908382116112bc576124b68284612248565b5160f81c91600381901b906001600160fd1b038116036112bc5760ff81116112bc576001901b918281029281840414901517156112bc57612500916124fa916112dd565b92612483565b9081612495565b61250f610abd565b5f915b608081018051805185101561261f57610e9360406119f98761253394610d89565b612613576341504b4360e01b61255f6125526060611a7c888651610d89565b6001600160e01b03191690565b036126135760606125738560209351610d89565b510151015191602c835103612606576125916121426121fe856123c5565b936001600160401b038516156125f2575050506125ea6125c26118816125bc610bcd6121fe866123dc565b93612456565b916125dd6125ce610182565b6001600160401b039095168552565b63ffffffff166020840152565b604082015290565b608092945060019193505b01929050612512565b91509160016080916125fd565b509160016080916125fd565b505050919050565b8060081c9060081b907cff000000ff000000ff000000ff000000ff000000ff000000ff000000ff7dff000000ff000000ff000000ff000000ff000000ff000000ff000000ff007fff000000ff000000ff000000ff000000ff000000ff000000ff000000ff00000084167eff000000ff000000ff000000ff000000ff000000ff000000ff000000ff000084161760101c931691161760101b177bffffffff00000000ffffffff00000000ffffffff00000000ffffffff7fffffffff00000000ffffffff00000000ffffffff00000000ffffffff00000000821660201c911660201b1777ffffffffffffffff0000000000000000ffffffffffffffff8019821660401c911660401b1761273c8160801c9160801b90565b1790565b80515f1981019081116112bc575b6001600160f81b03196127618284612248565b51166127755761277090612483565b61274e565b600181018091116112bc57610a43916123fe565b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841161280b579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15612800575f516001600160a01b038116156127f657905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f9160039190565b6001811115611283575f1981019081116112bc5761283390612c8f565b600181018091116112bc5790565b9192905f83515b600161285386610d77565b51146129a35761287f61287a85600161287461286e8a610d77565b51612c8f565b1b6112dd565b611b48565b905f915f915b8083106128a85750505061289c6128a291946112ae565b60011c90565b92612848565b9091926128b58489610d89565b51826128c0866112ae565b1080612985575b15612926579061291260019260026128fd6128e2898c610d89565b516128f56128ef8b6112ae565b8d610d89565b519084612cf5565b9701965b61290b848b610d89565b5260011c90565b61291c828b610d89565b5201929190612885565b8987600183188610612968579160018061295e612912946129568c61294f8d9e9d869b9a610d89565b5192610d89565b519085612cf5565b9801980196612901565b612912915091600161297c88829695610d89565b51970196612901565b50612998612992866112ae565b8a610d89565b5160018218146128c7565b50509150506129b29150610d77565b5190565b9290928215611617578351938415612bdf5760015b858110612bb157506001841480612ba7575b80612b95575b612b7a576129f86129f385612d15565b611ddd565b94612a01610173565b945f865260208601968752612a14610173565b925f845260208401948552612a27610182565b905f82526020820193845260408201525f91805b612ac6575b50505051612ab7575190515103612aa85781515f190182525b815115612a9c57612a6982612fb7565b612a7283612fb7565b90612a7e845160010190565b84525f5260205260405f20612a968451845190610d89565b52612a59565b916129b2915051610d77565b637227423160e11b5f5260045ffd5b63072afb8760e51b5f5260045ffd5b612acf81612c8f565b92612ae9612ae16001861b8094611b64565b9283926112dd565b938685612af68187612d5a565b92602084015180155f14612b2e575050505050508551518551145f03612a405780612b29612b2387612f9b565b8a612f80565b612a3b565b60011480612b72575b15612b5e575050506020612b55826040612b29940151905190610d89565b5101518a612f80565b91612b2993916002612b23941b0391612de3565b508015612b37565b925090925051612aa857612b8f602091610d77565b51015190565b50612b9f81610d77565b5151156129e3565b50600185146129dd565b612bbb8183610d89565b5151612bcf612bc983611b48565b84610d89565b515110156115ee576001016129cb565b631a14a47760e31b5f5260045ffd5b919091612bfa8361023d565b612c076040519182610141565b838152612c138461023d565b602082019190601f1901368337939091905b6020811015612c5f5780612c4557505f19905b5182518216911916179052565b61287a612c54612c5992611b56565b612fd3565b90612c38565b909182518152602081018091116112bc5791602081018091116112bc5790601f19810190811115612c255761129a565b806fffffffffffffffffffffffffffffffff1060071b81811c6001600160401b031060061b1781811c63ffffffff1060051b1781811c61ffff1060041b1781811c60ff1060031b1781811c600f1060021b1781811c60031060011b1790811c6001101790565b600116612d08575f5260205260405f2090565b905f5260205260405f2090565b90815f925b612d215750565b915f1983018381116112bc57600193169283910192612d1a565b60405190612d488261010b565b60606040835f81525f60208201520152565b612d62612d3b565b508051612d7560208301918251906112dd565b928251905b848210612dbc575b50829350612d9561229a92935182611b64565b908451946040810151612da6610182565b9687528360208801526040870152528251611b64565b90612dcb816040860151610d89565b5151821115612ddd5760010190612d7a565b90612d82565b91906020830151835193612df682611ddd565b93612e0083611ddd565b955f5b848110612f325750505050935b6001612e1b84610d77565b5114612f25575f945f905b808210612e3a575050848084528452612e10565b9095612e468786610d89565b51878784612e53836112ae565b1080612f07575b15612eae57600192612e8c83612e846128ef612e7d612ea89997612e9697610d89565b51926112ae565b519083612cf5565b61290b848c610d89565b612ea08289610d89565b5201966112cf565b90612e26565b505084516020860151511115612ef857600191612ee48392612e8c612ed38c8c610d89565b51612edd8a612f9b565b9083612cf5565b612eee8289610d89565b5201960190612e26565b63d8f29a1560e01b5f5260045ffd5b50612f1a612f14836112ae565b89610d89565b516001841814612e5a565b935050506129b290610d77565b600190612f6b604086016020612f538251612f4d86896112dd565b90610d89565b510151612f60848d610d89565b525182850190610d89565b51518401612f79828a610d89565b5201612e03565b90612f916020830151835190610d89565b5260018151019052565b612fab6020820151825190610d89565b51906001815101905290565b612fc76020820151825190610d89565b5181515f190190915290565b601f81116112bc576101000a9056fea2646970667358221220b54cab27f9309d22218000ba9b81a2beac680cddb6189b628244d20391b8b15264736f6c634300081e0033","sourceMap":"2797:9411:121:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;:::i;:::-;;:::i;:::-;;;;;;-1:-1:-1;;2797:9411:121;;;;;;;;;;;;;;;;-1:-1:-1;;;3997:45:121;;;:85;;;;2797:9411;;;;;;;;;3997:85;-1:-1:-1;;;829:40:49;;-1:-1:-1;3997:85:121;;;2797:9411;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;:::o;:::-;;:::i;:::-;;;;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;:::o;:::-;;;;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;:::o;:::-;;;;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;:::o;:::-;;;;;;;;;;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;:::o;:::-;;;;;;;;:::i;:::-;:::o;:::-;7134:25;2797:9411;;;7134:25;2797:9411;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;-1:-1:-1;;2797:9411:121;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;:::o;:::-;-1:-1:-1;;;;;2797:9411:121;;;;;;;;;:::o;:::-;-1:-1:-1;;;;;2797:9411:121;;;;;;-1:-1:-1;;2797:9411:121;;;;:::o;:::-;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;-1:-1:-1;2797:9411:121;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;:::o;:::-;;;-1:-1:-1;;;;;2797:9411:121;;;;;;:::o;:::-;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;;;;-1:-1:-1;;2797:9411:121;;;;;;;;;;:::i;:::-;;;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;;;;;;;;;;;;;:::i;:::-;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;;;;:::i;:::-;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;;;;;-1:-1:-1;;2797:9411:121;;;;;;;;;;:::i;:::-;;;;;-1:-1:-1;;;;;;2797:9411:121;;;;;;;;;;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;;;;:::i;:::-;;;;;;;;;:::i;:::-;;;;;;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;;;;:::i;:::-;;;;;;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;;:::i;:::-;;;;;:::o;:::-;;;;;;;;;;;;;;;;;:::i;:::-;;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;;;;;;;:::i;:::-;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;;;;;-1:-1:-1;;2797:9411:121;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;-1:-1:-1;;2797:9411:121;;;;;;;:::i;:::-;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;;;;;;;:::i;:::-;;;;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;-1:-1:-1;;2797:9411:121;;;;;;-1:-1:-1;;;2797:9411:121;;;;;;;;;;;-1:-1:-1;;2797:9411:121;;;;;;;3131:38;2797:9411;;;;;;;;;;;;;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;;;;;;;;;:::o;:::-;;;;;;-1:-1:-1;;2797:9411:121;;;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;;;;;:::i;:::-;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;;;;;;:::i;:::-;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;2797:9411:121;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;2797:9411:121;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;:::o;:::-;;;;;;;;;;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;;;;:::i;:::-;;;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;;:::i;:::-;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;2797:9411:121;;;;;;;;;;;;:::o;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;;;;:::i;:::-;;;-1:-1:-1;2797:9411:121;;;;;;;;;:::o;:::-;;;;;;;;:::i;:::-;-1:-1:-1;2797:9411:121;;-1:-1:-1;2797:9411:121;;;;;;:::i;:::-;;;;;;;;;;;;;;4251:1982;4475:48;4251:1982;4611:52;4251:1982;;;;;4475:48;;;;:::i;:::-;4611:52;;;;;:::i;:::-;2797:9411;;;;;4865:76;2797:9411;4896:45;:22;;:33;:45;2797:9411;;;;;;;;;;4865:76;-1:-1:-1;4865:76:121;4861:206;;6160:20;5135:43;5675:36;4896:45;5231:48;5135:43;;6160:20;5135:43;;;:::i;:::-;5231:48;;;;:::i;:::-;5675:19;;;:36;;2797:9411;;-1:-1:-1;;;;;2797:9411:121;5739:25;5675:36;5739:25;;;;;2797:9411;-1:-1:-1;;;;;2797:9411:121;;;;5725:42;5721:421;;4251:1982;2797:9411;;;;;6160:20;;;4896:45;6160:20;;;:::i;:::-;;2797:9411;;6160:20;;;;;;:::i;:::-;6197:25;2797:9411;6152:74;;;4251:1982;:::o;5721:421::-;5783:28;;;:56;2797:9411;;-1:-1:-1;;;;;2797:9411:121;5950:12;2797:9411;5783:28;5997:71;2797:9411;4896:45;5950:12;;2797:9411;;;;;;6019:20;;6054:13;;5997:71;;:::i;:::-;6103:13;;2797:9411;;-1:-1:-1;;;;;2797:9411:121;;:::i;:::-;;;;;;4896:45;5881:250;;2797:9411;5783:28;5881:250;;2797:9411;5675:36;5881:250;;2797:9411;5853:278;;5721:421;;;;;4861:206;-1:-1:-1;2797:9411:121;;;-1:-1:-1;4965:26:121;2797:9411;4965:26;;4896:45;4965:26;;;:::i;:::-;;2797:9411;;4965:26;;;;;;:::i;:::-;5021:31;4993:26;;:::i;:::-;5021:31;;;2797:9411;4957:99;;;;:::o;2797:9411::-;;;;;;;:::i;:::-;-1:-1:-1;2797:9411:121;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;:::i;:::-;;-1:-1:-1;2797:9411:121;;-1:-1:-1;2797:9411:121;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;:::i;:::-;;;;;;;;;;;;;;;:::o;:::-;;;;;;;:::i;:::-;-1:-1:-1;2797:9411:121;;;;;;;:::o;:::-;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;;;;:::i;:::-;;;-1:-1:-1;2797:9411:121;;;;;;;;;:::o;:::-;;;;;:::i;:::-;;;;;;;;;;6684:2343;;;2797:9411;;:::i;:::-;;6905:27;;:33;;;;;2797:9411;6978:38;;:50;6955:73;6978:50;6905:33;6978:50;;2797:9411;;;;;;6955:73;7134:25;;;;2797:9411;;;-1:-1:-1;;;;;2797:9411:121;;;;7163:32;7134:25;7163:32;;;-1:-1:-1;;;;;7163:32:121;;2797:9411;;;7134:64;;;;:145;;;;6684:2343;7117:226;;;2797:9411;7410:32;;2797:9411;;7410:32;;-1:-1:-1;;;;;2797:9411:121;7381:64;7502:87;;;;6905:33;7640:16;;2797:9411;7603:54;7604:53;2797:9411;;7604:53;;:::i;:::-;7603:54;;2797:9411;7603:54;7599:90;;7724:18;;2797:9411;7759:15;2797:9411;7789:13;2797:9411;7804:17;;;;;;8047:21;;;;8043:54;;8176:24;;;:::i;:::-;6905:33;2797:9411;;;;8166:35;8256;;;;:::i;:::-;8306:13;2797:9411;8321:10;;;;;;8706:28;;;;8664:129;8706:28;;8807:6;8706:28;;2797:9411;8736:16;8706:28;8736:16;;;2797:9411;;8664:129;;:::i;8807:6::-;8803:44;;8989:30;8898:7;;6905:33;8898:7;;;;:::i;:::-;2797:9411;;8989:24;;:30;2797:9411;6684:2343;:::o;8803:44::-;8822:25;;;2797:9411;8822:25;;2797:9411;8822:25;8333:3;8371:27;:36;2797:9411;8371:27;6905:33;8371:27;;:33;;:36;:::i;:::-;;6905:33;8441:45;8471:14;;8441:45;;:::i;:::-;8563:19;;2797:9411;;7134:25;2797:9411;8600:27;;;6905:33;8600:27;;;;2797:9411;;;;;;;;;;;;;8600:27;;2797:9411;;8600:27;;;;;;:::i;:::-;2797:9411;8590:38;;2797:9411;;:::i;:::-;;;;6905:33;8533:97;;2797:9411;8500:130;;;;:::i;:::-;;;;;;:::i;:::-;;2797:9411;8306:13;;8043:54;8077:20;;;2797:9411;8077:20;;2797:9411;8077:20;7823:3;2797:9411;;;7846:47;:24;:21;:18;;;:21;:::i;:::-;;2797:9411;-1:-1:-1;;;;;;2797:9411:121;;;7846:24;-1:-1:-1;;;;;;2797:9411:121;;;7846:47;;:90;;;7823:3;7842:182;;7823:3;2797:9411;;7789:13;;7842:182;7982:18;;2797:9411;7966:43;6905:33;7982:21;:18;;;:21;:::i;:::-;;:26;;7966:43;:::i;:::-;7842:182;;;;;7846:90;7897:18;6905:33;7897:18;:21;:18;;;:21;:::i;:::-;;:26;;2797:9411;7897:39;7846:90;;7599;7666:23;;;2797:9411;7666:23;;2797:9411;7666:23;7502:87;7560:29;;;;;7502:87;;;7117:226;7311:21;;;2797:9411;7311:21;;2797:9411;7311:21;7134:145;7247:29;;;;;;2797:9411;7218:61;;7134:145;;;2797:9411;;;;;;;;;;;;;;;;:::o;:::-;;;-1:-1:-1;;;;;;2797:9411:121;;;;;;;;;;;;;;;;;;;;:::i;10246:1266::-;10433:16;;2797:9411;10506:32;;;;:::i;:::-;10591:28;;;;:::i;:::-;10635:13;-1:-1:-1;10650:7:121;;;;;;11301;11297:178;;10630:657;11485:20;;;10246:1266;:::o;11297:178::-;11432:6;11377:11;;10777;10807:13;11337:77;11377:11;;;11398:15;;2797:9411;11337:77;;:::i;11432:6::-;11428:36;;11297:178;;;;;11428:36;11447:17;;;-1:-1:-1;11447:17:121;;-1:-1:-1;11447:17:121;10659:3;10702:19;:16;;;:19;:::i;:::-;;10777:11;;;;;10758:31;10777:11;;10758:31;:::i;:::-;10807:13;;;;2797:9411;;;10807:18;10803:52;;2797:9411;;;10959:87;10807:13;11112:24;2797:9411;;10999:7;;2797:9411;11010:35;10972:36;10992:15;2797:9411;;;;;;10992:15;-1:-1:-1;;;;;;2797:9411:121;;;;;;;;;;;;;8200:10:65;2797:9411:121;8168:49:65;2797:9411:121;;;;;;;;;;;8266:21:65;2797:9411:121;;;8815:111:65;;10972:36:121;11033:11;;11010:35;:::i;:::-;10959:87;;:::i;:::-;10807:13;2797:9411;;;;10949:98;2797:9411;;:::i;:::-;;;;10807:13;10882:179;;2797:9411;10870:191;;;;:::i;:::-;;;;;;:::i;:::-;;11112:24;:::i;:::-;2797:9411;;;;;;:::i;:::-;;;;10807:13;11185:91;;2797:9411;10777:11;11185:91;;2797:9411;11150:126;;;;:::i;:::-;;;;;;:::i;:::-;;2797:9411;10635:13;;10803:52;10834:21;;;-1:-1:-1;10834:21:121;;-1:-1:-1;10834:21:121;7946:368:119;;;;2797:9411:121;8108:182:119;8151:3;2797:9411:121;;8128:21:119;;;;;8174:13;:16;:13;;;;:::i;:::-;;:16;2797:9411:121;8174:32:119;8170:46;;8251:13;:20;:13;8445:36;8251:13;8445:20;8251:13;;:::i;:::-;;:20;;8445;:::i;:::-;:36;:::i;:::-;2797:9411:121;-1:-1:-1;;;;;2797:9411:121;;;;8498:45:119;2797:9411:121;8498:21:119;:45;2797:9411:121;;;8251:20:119;8522:17;2797:9411:121;8230:49:119;:::o;8498:45::-;;2797:9411:121;8230:49:119;:::o;8170:46::-;8208:8;2797:9411:121;;8113:13:119;;;8128:21;;;;;2797:9411:121;7946:368:119;:::o;2797:9411:121:-;;;;;;;;;;;;;;9792:1;2797:9411;;;;;;;:::o;:::-;;:::i;:::-;;5093:1:65;2797:9411:121;;;;;;;:::o;:::-;;4835:1:65;2797:9411:121;;;;;;;:::o;:::-;;;;;;;;;;:::o;11911:146::-;;-1:-1:-1;;;;;2797:9411:121;;;;;;12044:1;2797:9411;;;;;;;;;;;;;12022:28;;11911:146;:::o;4543:226:58:-;4650:2;2797:9411:121;;4635:17:58;2797:9411:121;;4650:2:58;4703:60;;4543:226;:::o;2797:9411:121:-;;;-1:-1:-1;;;2797:9411:121;;4650:2:58;2797:9411:121;;;;;;;;;;;;;;-1:-1:-1;;;2797:9411:121;;;;;;;;;;;;;;;:::i;:::-;;;;:::o;:::-;;;;;;;;;;;;;;;;;;:::i;:::-;;;:::i;:::-;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;;2797:9411:121;;;;;;-1:-1:-1;;;;;;2797:9411:121;;;;;;-1:-1:-1;;2797:9411:121;;;;;;;:::i;1604:684:119:-;;1718:18;;2797:9411:121;;;:::i;:::-;1800:13:119;-1:-1:-1;1815:14:119;;;;;;2093:40;;;2067:214;2093:40;;;:::i;:::-;2188:22;2225:46;2797:9411:121;;2168:43:119;2797:9411:121;1915:42:119;2188:22;;2797:9411:121;;;;;2168:43:119;2245:25;;2797:9411:121;-1:-1:-1;;;;;2797:9411:121;;;;;7788:18:65;2797:9411:121;;;;;;;;;;7937:18:65;7933:22;2797:9411:121;7902:18:65;7898:22;;;;;;2797:9411:121;;;7932:30:65;7933:22;;;;2797:9411:121;;;7896:67:65;2797:9411:121;;;;;;8025:7:65;2797:9411:121;;;-1:-1:-1;;;;;2797:9411:121;;8012:21:65;;2797:9411:121;;;8698:111:65;;2225:46:119;2067:214;;:::i;1831:3::-;1932:18;1860:179;2797:9411:121;1932:18:119;1915:42;;1932:24;:21;:18;;;:21;:::i;:24::-;2797:9411:121;;-1:-1:-1;;;;;;2797:9411:121;;;1915:42:119;;;2797:9411:121;;;;;;;;;1915:42:119;1975:50;1915:42;1998:21;:18;;;:21;:::i;:::-;;:26;;1975:50;:::i;:::-;1860:179;;:::i;:::-;1831:3;2797:9411:121;1800:13:119;;3714:255:48;3927:8;3714:255;3871:27;3714:255;3871:27;:::i;:::-;3927:8;;;;;:::i;1990:238:56:-;;;;;3884:14;;3880:38;;2797:9411:121;;3995:18:56;;;:::i;:::-;4049;;;:::i;:::-;4106:20;4101:1;4106:20;;;:::i;:::-;2797:9411:121;-1:-1:-1;;4179:7:56;;;;;;4577:42;;;;;;;;;;:::i;:::-;2174:47;1990:238;:::o;4168:9::-;4217;;;;:::i;:::-;;2797:9411:121;4250:16:56;;;;;4246:51;;4315:6;;;:26;;;;4168:9;4311:55;;;4101:1;4392:9;:14;:9;;;;:::i;:::-;;:14;2797:9411:121;4380:26:56;;;;:::i;:::-;2797:9411:121;;;;4448:33:56;;;;:::i;:::-;2797:9411:121;;;4168:9:56;;;4311:55;4350:16;;;-1:-1:-1;4350:16:56;;-1:-1:-1;4350:16:56;4315:26;4325:16;;;;;4315:26;;;4246:51;4275:22;;;-1:-1:-1;4275:22:56;;-1:-1:-1;4275:22:56;3880:38;3907:11;;;-1:-1:-1;3907:11:56;;-1:-1:-1;3907:11:56;2797:9411:121;;;;;;;;;;;:::i;:::-;9792:1;2797:9411;;;-1:-1:-1;;2797:9411:121;;-1:-1:-1;2797:9411:121;;;;;;;;;:::o;:::-;;;;;:::i;:::-;;;;;;;;;;9075:1054;10000:75;9075:1054;10090:6;9075:1054;9478:30;9349:19;;;;9711:82;:78;9349:19;9268:403;9349:19;;2797:9411;;;;;;;;9412:32;2797:9411;9412:32;;;2797:9411;;;;;;9478:30;;;;2797:9411;9298:359;9613:25;9548:36;;;;9613:25;;2797:9411;;9298:359;2797:9411;;:::i;:::-;;;;;;;;9298:359;2797:9411;;9298:359;;;2797:9411;;9298:359;;;;2797:9411;9548:36;9298:359;;2797:9411;9613:25;9298:359;;2797:9411;9268:403;:::i;:::-;2797:9411;;;;;9245:436;9721:33;;2797:9411;9756:19;;9349;9756:32;2797:9411;9711:78;;2797:9411;;9756:32;2797:9411;9711:78;;;:::i;:::-;:82;:::i;:::-;9846:33;2797:9411;9846:33;;:::i;:::-;9934:19;;:29;2797:9411;;;;:::i;:::-;;;;9349:19;9901:76;;2797:9411;9889:88;;;:::i;:::-;;;;;:::i;:::-;;10041:14;;10000:75;;:::i;10090:6::-;10086:36;;9075:1054::o;2797:9411::-;;;;;;;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;;;;-1:-1:-1;2797:9411:121;;-1:-1:-1;2797:9411:121;;;;-1:-1:-1;2797:9411:121;;;;-1:-1:-1;2797:9411:121;;;;;;:::o;:::-;;;;;;;:::i;:::-;;;;-1:-1:-1;2797:9411:121;;;;:::o;:::-;;;;;;;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;-1:-1:-1;2797:9411:121;;;;;;;:::i;:::-;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;:::o;:::-;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;;;;:::i;:::-;;;-1:-1:-1;2797:9411:121;;;;;;;;;:::o;:::-;;;;;:::i;:::-;;;;;;;;;;2883:1495:119;2797:9411:121;;:::i;:::-;;;;:::i;:::-;;;;3019:1:119;3000:21;;;2797:9411:121;3052:38:119;3068:21;;;:::i;:::-;3052:38;:::i;:::-;3122:35;;;;:::i;:::-;3187:38;3203:21;;;:::i;3187:38::-;3260;3276:21;;;:::i;3260:38::-;3326:35;;;;:::i;:::-;3397:20;;;:::i;:::-;3433:13;3019:1;3448:10;;;;;;2797:9411:121;;;;;:::i;:::-;;;;3000:21:119;4304:67;;2797:9411:121;;4304:67:119;;2797:9411:121;4304:67:119;;;2797:9411:121;4304:67:119;;;2797:9411:121;2883:1495:119;:::o;3460:3::-;2797:9411:121;3492:21:119;;;;:::i;:::-;2797:9411:121;;;:::i;:::-;;;3565:25:119;;;-1:-1:-1;2797:9411:121;3610:14:119;;;1540:1;3610:21;4257:19;;;;:::i;:::-;;;;;;:::i;:::-;;2797:9411:121;3433:13:119;;3561:683;1361:1;3656:29;;1361:1;;-1:-1:-1;2797:9411:121;;3705:18:119;;1540:1;3767:23;;;:::i;:::-;3748:16;;;:42;3652:592;3561:683;;3652:592;1411:1;3815:24;;1411:1;;-1:-1:-1;2797:9411:121;3859:13:119;;;1540:1;3911:23;;;:::i;:::-;3897:11;;;:37;3561:683;;3811:433;1467:1;3959:30;;1467:1;;-1:-1:-1;2797:9411:121;1540:1:119;;4073:23;;;:::i;:::-;3000:21;4053:17;;:43;3561:683;;3955:289;1540:1;4121:47;3811:433;4117:127;2797:9411:121;4188:34:119;;;1540:1;3561:683;;9049:172:65;9158:56;2797:9411:121;9049:172:65;2797:9411:121;9175:31:65;2797:9411:121;;9175:31:65;:::i;:::-;2797:9411:121;;;9158:56:65;;;;;;2797:9411:121;;:::i;8762:967:123:-;2797:9411:121;;:::i;:::-;;-1:-1:-1;;8920:17:123;-1:-1:-1;;8993:3:123;8972:12;;;;;2797:9411:121;;8968:23:123;;;;;9016:27;:15;;:27;:15;;:::i;:::-;;:27;2797:9411:121;;;;;9016:27:123;:89;;;8993:3;9012:305;;8993:3;9335:27;9016;9335:15;:12;;;:15;:::i;:27::-;:89;;;8993:3;9331:196;;8993:3;-1:-1:-1;2797:9411:121;;8953:13:123;;9331:196;9481:12;;;9456:56;9481:30;:25;:15;8972:12;9481;2797:9411:121;9481:12:123;;:15;:::i;:::-;;:25;;:30;;9456:56;:::i;:::-;9331:196;;;;;9335:89;8237:14;;;;2797:9411:121;;;9366:37:123;:25;:15;:12;;;:15;:::i;:::-;;:25;;2797:9411:121;-1:-1:-1;;;;;;2797:9411:121;;;9366:37:123;2797:9411:121;9366:58:123;9335:89;;9012:305;9164:12;;;;9135:68;9151:51;9164:30;:25;:15;:12;;;:15;:::i;:::-;;:25;;:30;;9151:51;:::i;9135:68::-;9266:12;9237:65;9253:48;9164:30;:25;9266:15;:12;;;:15;:::i;:::-;;:25;;:30;;9253:48;:::i;9237:65::-;9012:305;;;9016:89;2797:9411:121;;;;;;;9047:37:123;:25;:15;:12;;;:15;:::i;:37::-;2797:9411:121;9047:58:123;9016:89;;8968:23;;;;;;;;9575:14;;9571:46;;2797:9411:121;;:::i;:::-;;;;9635:87:123;;;2797:9411:121;9016:27:123;9635:87;;2797:9411:121;8762:967:123;:::o;9571:46::-;9598:19;;;-1:-1:-1;9598:19:123;;-1:-1:-1;9598:19:123;2797:9411:121;-1:-1:-1;;2797:9411:121;;;;;;;;:::o;:::-;-1:-1:-1;;2797:9411:121;;;;;;;;:::o;:::-;;;;;;;;;:::o;:::-;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;:::i;4484:686:65:-;4577:2;4573:6;;4577:2;;;4602:31;4619:13;4625:6;4602:31;4625:6;2797:9411:121;;;;4625:6:65;2797:9411:121;;;;4619:13:65;4577:2;2797:9411:121;;;;;;-1:-1:-1;;;;;;2797:9411:121;4602:31:65;;;2797:9411:121;;;;;;;;;4569:595:65;4658:7;4654:11;;4658:7;;;4688:51;4705:33;4715:22;4723:12;4724:6;4688:51;4724:6;2797:9411:121;;;;4723:12:65;2797:9411:121;;;;4715:22:65;2797:9411:121;;;8428:1:65;2797:9411:121;;;8428:1:65;2797:9411:121;;8422:19:65;8300:148;;4705:33;4577:2;2797:9411:121;;;;;;-1:-1:-1;;;;;;2797:9411:121;4688:51:65;;;2797:9411:121;;;;;;;;;4650:514:65;4764:7;4760:11;;4764:7;;;4794:51;4811:33;4821:22;4829:12;4830:6;4794:51;4830:6;2797:9411:121;;;;4830:6:65;4829:12;:::i;4821:22::-;2797:9411:121;8200:10:65;2797:9411:121;;;;;;;8195:21:65;8196:14;2797:9411:121;;;8168:49:65;2797:9411:121;;;;;;8279:7:65;2797:9411:121;;;8266:21:65;8046:248;;4811:33;4577:2;2797:9411:121;;;;;;-1:-1:-1;;;;;;2797:9411:121;4794:51:65;;;2797:9411:121;;;;;;;;;4756:408:65;4942:31;;4959:13;4902:85;4959:13;;:::i;:::-;4577:2;2797:9411:121;4942:31:65;;;;;;2797:9411:121;;;;;;;4942:31:65;4902:85;:::i;:::-;5117:36;5065:30;5071:23;5072:17;5073:10;2797:9411:121;;5073:10:65;:::i;:::-;2797:9411:121;;;;5072:17:65;5071:23;:::i;5065:30::-;5117:36;4577:2;2797:9411:121;5117:36:65;;;4942:31;5117:36;;;:::i;2129:778:48:-;2797:9411:121;;;2129:778:48;2319:2;2299:22;;2319:2;;2751:25;2535:196;;;;;;;;;;;;;;;-1:-1:-1;2535:196:48;2751:25;;:::i;:::-;2744:32;;;;;:::o;2295:606::-;2807:83;;2823:1;2807:83;2827:35;2807:83;;:::o;2797:9411:121:-;;-1:-1:-1;2797:9411:121;;;:::o;:::-;;;;;;;;;;;;7280:532:48;2797:9411:121;;;:::i;:::-;7366:29:48;;;7411:7;;:::o;7362:444::-;2797:9411:121;;;:::i;:::-;7471:29:48;7462:38;;7471:29;;7523:23;;;7375:20;7523:23;;7375:20;7523:23;7458:348;2797:9411:121;;;:::i;:::-;7576:35:48;7567:44;;7576:35;;7634:46;;;;7375:20;7634:46;;2797:9411:121;;7375:20:48;7634:46;7563:243;2797:9411:121;;7710:30:48;2797:9411:121;;:::i;:::-;7701:39:48;7697:109;;7563:243;7280:532::o;7697:109::-;7763:32;;;7375:20;7763:32;7634:46;2797:9411:121;;7375:20:48;7763:32;2797:9411:121;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;;;;:::i;:::-;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;:::i;:::-;;-1:-1:-1;;;;;2797:9411:121;;;;;;;;;;;;;;;;;;:::i;:::-;;;;:::i;:::-;;;;;;;;;;;;:::i;2339:494:119:-;2788:28;2730:44;2468:30;2442:384;2339:494;2468:30;2797:9411:121;;;;;;;;;;;;;;;-1:-1:-1;;;;;;2797:9411:121;2468:30:119;;;2797:9411:121;;;;;;;;;2468:30:119;2564:33;2512:38;2797:9411:121;2468:30:119;2532:17;;2797:9411:121;;;;;2512:38:119;2564:33;2797:9411:121;2581:15:119;;2797:9411:121;;;2564:33:119;;;2468:30;2564:33;;2797:9411:121;;;;;;;2564:33:119;2631:21;;;;2797:9411:121;2788:28:119;2805:10;2611:45;2797:9411:121;;;-1:-1:-1;;;;;2797:9411:121;;;2611:45:119;2690:25;2730:44;2797:9411:121;2670:46:119;2797:9411:121;2468:30:119;2690:25;;2797:9411:121;;;;;2670:46:119;2747:26;;2797:9411:121;;;2730:44:119;;;2468:30;2730:44;;2797:9411:121;;;;;;;2730:44:119;;2797:9411:121;;2730:44:119;;;;;;:::i;:::-;2805:10;2797:9411:121;;;2788:28:119;;;2468:30;2788:28;;2797:9411:121;;;;;;;2788:28:119;;2797:9411:121;;2788:28:119;;;;;;:::i;:::-;2442:384;:::i;11602:252:121:-;11714:20;;;11750:19;;:::o;11710:138::-;2797:9411;;;;;;;11800:37;:::o;3366:228:55:-;;;;3548:39;3366:228;3548:39;:::i;2089:399:58:-;3000:21:119;2216:11:58;;2797:9411:121;;;3000:21:119;2797:9411:121;;;;;;;2237:9:58;;2797:9411:121;-1:-1:-1;2797:9411:121;;3000:21:119;2264:48:58;2351:9;2797:9411:121;;;;;;1938:75:59;;;;2797:9411:121;;;3000:21:119;2392:39:58;;;:::i;:::-;2797:9411:121;;;;3000:21:119;2797:9411:121;;;;;;;;2089:399:58;:::o;:::-;;2216:11;;;2797:9411:121;;;;;;;;;;;2237:9:58;;2797:9411:121;-1:-1:-1;2797:9411:121;;2268:8:58;;2264:48;;2216:11;2351:9;;2797:9411:121;;;;;;1938:75:59;;;;2797:9411:121;;;2392:39:58;;;;:::i;:::-;2797:9411:121;;;;;;;;;;;;2089:399:58;:::o;2264:48::-;2797:9411:121;;;;;;;;;:::i;:::-;-1:-1:-1;2797:9411:121;;2292:9:58;:::o;2797:9411:121:-;;;;;;;;;;;;:::o;:::-;;;;:::o;:::-;;;-1:-1:-1;;;2797:9411:121;;;;;;;;;;;;;;;;;-1:-1:-1;;;2797:9411:121;;;;;;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;-1:-1:-1;;;;;2797:9411:121;;;;:::o;1205:1510:65:-;1323:20;;;:::i;:::-;2797:9411:121;;;;1453:9:65;;-1:-1:-1;1501:14:65;;-1:-1:-1;2797:9411:121;;;;1509:6:65;2797:9411:121;1449:1238:65;1579:1;1571:9;;1579:1;;1634:20;1782:11;1634:20;1782:11;1787:6;1740:7;1692:13;1634:20;1842:9;1634:20;;:::i;1692:13::-;2797:9411:121;;;;;;1740:7:65;1787:6;2797:9411:121;;;;;;1782:11:65;;;:::i;:::-;-1:-1:-1;;;;;2797:9411:121;;;1567:1120:65;1880:1;1872:9;;1880:1;;1943:20;2275:8;1943:20;2243:16;2797:9411:121;1943:20:65;2194:16;1943:20;;2336:11;1943:20;;:::i;:::-;2013;;2118:15;2013:20;2058;2013;;;:::i;:::-;2058;;:::i;:::-;2797:9411:121;;;;;;;;2118:15:65;2797:9411:121;;2105:29:65;;2797:9411:121;;;;;;;2194:16:65;2188:23;;2797:9411:121;;;;;;;2243:16:65;2237:23;2797:9411:121;;;;;;1868:819:65;2376:1;2368:9;2376:1;;2575:34;2450:6;2797:9411:121;2449:12:65;2450:6;2589:19;2450:6;2797:9411:121;;;;;;;2450:6:65;2449:12;:::i;:::-;2797:9411:121;2503:6:65;2495:59;2508:1;2503:6;;;2495:59;:::i;:::-;2589:19;:::i;:::-;2575:34;:::i;2364:323::-;2797:9411:121;;-1:-1:-1;;;2640:36:65;;2797:9411:121;1393:1:65;2640:36;;2797:9411:121;;;;;;;;;;;;;2640:36:65;2797:9411:121;;;;;;;;;;;;;:::o;1564:269:58:-;1649:11;;;2797:9411:121;;;1663:1:58;2797:9411:121;;;;;;;1667:9:58;;2797:9411:121;-1:-1:-1;1645:87:58;;1758:9;2797:9411:121;;-1:-1:-1;;;;;;2797:9411:121;1758:22:58;;2797:9411:121;1758:22:58;:::i;:::-;2797:9411:121;;;;;1791:16:58;2797:9411:121;;1791:16:58;:::i;:::-;2797:9411:121;;1564:269:58;:::o;1645:87::-;2797:9411:121;;-1:-1:-1;;;1699:22:58;;1649:11;1699:22;;;2797:9411:121;;;;;;-1:-1:-1;;;2797:9411:121;;;;1699:22:58;;;4479:308:119;2797:9411:121;;:::i;:::-;;2216:11:58;;;2797:9411:121;;;4620:1:119;2797:9411:121;;;;;;;2237:9:58;;2797:9411:121;-1:-1:-1;2797:9411:121;;2216:11:58;2351:9;;2797:9411:121;;;;;;1938:75:59;;;;2797:9411:121;;;4620:1:119;2392:39:58;;;:::i;:::-;2797:9411:121;;;;4620:1:119;2797:9411:121;;;;;;;;4624:1:119;5413:13:58;4624:1:119;5408:106:58;5428:5;4620:1:119;5428:5:58;;;;4653:35:119;;;;4718:25;4653:35;;:::i;:::-;4718:25;;:::i;:::-;4760:20;2797:9411:121;;:::i;:::-;-1:-1:-1;;;;;;2797:9411:121;;;;;;4760:20:119;2216:11:58;4760:20:119;;2797:9411:121;4479:308:119;:::o;5435:3:58:-;2797:9411:121;;-1:-1:-1;;;;;;5468:16:58;2797:9411:121;;5468:16:58;:::i;:::-;2797:9411:121;;;;;;;;;;5501:1:58;2797:9411:121;;;;;;;;;-1:-1:-1;;;;;;2797:9411:121;;;;;;5454:49:58;5435:3;2797:9411:121;5413:13:58;;;3313:349;2797:9411:121;;9164:30:123;3466:31:58;2797:9411:121;;9164:30:123;3512:8:58;3617:38;3512:8;1938:75:59;3617:38:58;:::i;3313:349::-;2797:9411:121;;11258:1:123;3466:31:58;2797:9411:121;;11258:1:123;1938:75:59;3617:38:58;3512:8;1938:75:59;3617:38:58;:::i;3313:349::-;2797:9411:121;;;3466:31:58;2797:9411:121;;;;;;1938:75:59;;2797:9411:121;-1:-1:-1;2797:9411:121;;11439:1:123;3617:38:58;;;:::i;3313:349::-;;2797:9411:121;;3466:31:58;;2797:9411:121;;3512:8:58;;3508:48;;1938:75:59;3617:38:58;2797:9411:121;1938:75:59;3617:38:58;:::i;3508:48::-;2797:9411:121;;;;;;;;:::i;2744:313:58:-;2797:9411:121;;2876:25:58;9164:30:123;2876:25:58;2797:9411:121;;-1:-1:-1;;2797:9411:121;;;;;;;;;;;9164:30:123;1938:75:59;2797:9411:121;-1:-1:-1;2797:9411:121;;3012:38:58;;;:::i;2744:313::-;2797:9411:121;;2876:25:58;11516:2:123;2876:25:58;2797:9411:121;;-1:-1:-1;;2797:9411:121;;;;;;;;;;;1938:75:59;;2797:9411:121;-1:-1:-1;2797:9411:121;;3012:38:58;;;:::i;2797:9411:121:-;;;;;-1:-1:-1;;2797:9411:121;;:::o;823:320:65:-;2797:9411:121;;;;;961:5:65;;;1123:13;;823:320;:::o;968:3::-;2797:9411:121;;-1:-1:-1;;2797:9411:121;;;;;;;;1051:11:65;;;;:::i;:::-;2797:9411:121;;;;;;;;;-1:-1:-1;;;;;2797:9411:121;;;;;;;;;;1060:1:65;2797:9411:121;;1037:66:65;2797:9411:121;;;;;;;;;;;;;;968:3:65;1012:91;;;;:::i;:::-;968:3;;:::i;:::-;936:23;;;;10579:974:123;2797:9411:121;;:::i;:::-;10697:1:123;10680:867;10725:3;10704:12;;;;;2797:9411:121;;10700:23:123;;;;;10749:27;;:15;;10748:28;10749:15;;:::i;10748:28::-;10744:42;;2797:9411:121;;;10804:58:123;:37;:25;:15;:12;;;:15;:::i;:37::-;-1:-1:-1;;;;;;2797:9411:121;;;10804:58:123;;10800:72;;10804:25;10907:15;:12;:30;:12;;:15;:::i;:::-;;:25;;:30;;2797:9411:121;11162:2:123;2797:9411:121;;11147:17:123;11143:31;;11204:58;11211:50;11236:24;;;:::i;11204:58::-;2797:9411:121;-1:-1:-1;;;;;2797:9411:121;;11280:10:123;11276:24;;11417;;;11322:214;11481:39;11497:22;11385:58;11392:50;11417:24;;;:::i;11385:58::-;11497:22;;:::i;11481:39::-;2797:9411:121;11322:214:123;2797:9411:121;;:::i;:::-;-1:-1:-1;;;;;2797:9411:121;;;8525:14:123;;;11322:214;2797:9411:121;;10907:30:123;11322:214;;2797:9411:121;;11322:214:123;10749:27;11322:214;;2797:9411:121;11315:221:123;:::o;11276:24::-;10704:12;11292:8;;;2797:9411:121;11292:8:123;;;10685:13;2797:9411:121;10685:13:123;;;;;11143:31;11166:8;;;2797:9411:121;10704:12:123;11166:8;;;10800:72;10864:8;;2797:9411:121;10704:12:123;10864:8;;;10700:23;;;;;;;10579:974::o;5627:1354:65:-;2797:9411:121;5873:1:65;2797:9411:121;;5873:1:65;2797:9411:121;6063:110:65;6191:86;;6064;;;;;;;2797:9411:121;;6190:110:65;6191:86;;;;2797:9411:121;;6062:239:65;6511:66;6384;6364:86;;2797:9411:121;;6490:110:65;6491:86;2797:9411:121;;6362:239:65;6811:66;6684;;6664:86;;2797:9411:121;;6790:110:65;6791:86;2797:9411:121;;6662:239:65;6965:8;2797:9411:121;;;6965:8:65;2797:9411:121;;;;6965:8:65;6951:23;5627:1354;:::o;6202:380:58:-;2797:9411:121;;-1:-1:-1;;2797:9411:121;;;;;;;6414:3:58;-1:-1:-1;;;;;;6437:7:58;;;;:::i;:::-;2797:9411:121;;6433:86:58;;6414:3;;;:::i;:::-;6382:22;;6433:86;6403:1;2797:9411:121;;;;;;;6546:29:58;;;:::i;5203:1551:48:-;;;6283:66;6270:79;;6266:164;;2797:9411:121;;;;;;-1:-1:-1;2797:9411:121;;;;;;;;;;;;;;;;;;;6541:24:48;;;;;;;;;-1:-1:-1;6541:24:48;-1:-1:-1;;;;;2797:9411:121;;6579:20:48;6575:113;;6698:49;-1:-1:-1;6698:49:48;-1:-1:-1;5203:1551:48;:::o;6575:113::-;6615:62;-1:-1:-1;6615:62:48;6541:24;6615:62;-1:-1:-1;6615:62:48;:::o;6541:24::-;2797:9411:121;;;;;;;;;6266:164:48;6365:54;;;6381:1;6365:54;6385:30;6365:54;;:::o;7197:131:56:-;7277:1;7272:6;;;7268:20;;-1:-1:-1;;2797:9411:121;;;;;;;7305:12:56;;;:::i;:::-;7277:1;2797:9411:121;;;;;;;7197:131:56;:::o;5056:1349::-;;;;2797:9411:121;;;5309:1063:56;5332:1;5316:12;;;:::i;:::-;2797:9411:121;5316:17:56;;;5369:45;:41;5381:12;5332:1;5375:19;5381:12;;;:::i;:::-;2797:9411:121;5375:19:56;:::i;:::-;2797:9411:121;5369:41:56;:::i;:::-;:45;:::i;:::-;5428:9;2797:9411:121;5457:9:56;2797:9411:121;5452:836:56;5468:7;;;;;;6302;;;6339:16;6338:23;6302:7;6339:16;;:::i;:::-;2797:9411:121;;;;6338:23:56;5309:1063;;;5457:9;5510:12;;;;;;;:::i;:::-;2797:9411:121;5563:5:56;;;;:::i;:::-;:11;:44;;;5457:9;5719:442;;;5786:9;6230:8;5332:1;5786:9;5850:1;5771:40;5786:9;;;;:::i;:::-;2797:9411:121;5797:13:56;5804:5;;;:::i;:::-;5797:13;;:::i;:::-;2797:9411:121;5771:40:56;;;:::i;:::-;2797:9411:121;;5719:442:56;;6179:18;;;;:::i;:::-;2797:9411:121;;;;;6230:8:56;6215:23;;;;:::i;:::-;2797:9411:121;;5457:9:56;;;;;5719:442;5647:7;;5332:1;5647:7;;5646:22;-1:-1:-1;5646:22:56;;5943:9;5332:1;5943:9;5928:35;6230:8;5943:9;5954:8;5943:9;;;;;;;;;:::i;:::-;2797:9411:121;5954:8:56;;:::i;:::-;2797:9411:121;5928:35:56;;;:::i;:::-;2797:9411:121;;;;5878:283:56;5719:442;;5878:283;6230:8;6063:9;;;5332:1;6063:9;;;;;;:::i;:::-;2797:9411:121;;;5878:283:56;5719:442;;5563:44;5588:5;5578:16;5588:5;;;:::i;:::-;5578:16;;:::i;:::-;2797:9411:121;5332:1:56;5599:7;;5578:29;5563:44;;5316:17;;;;;;6389:9;5316:17;;6389:9;:::i;:::-;2797:9411:121;5056:1349:56;:::o;4448:2801:55:-;;;;4610:14;;4606:38;;2797:9411:121;;4702:14:55;;;4698:40;;4810:1;4813:13;;;;;;5012:14;4810:1;5012:14;;:32;;;4793:159;5012:56;;;4793:159;5008:169;;5235:35;5249:20;;;:::i;:::-;5235:35;:::i;:::-;2797:9411:121;;;:::i;:::-;;;;;5219:52:55;;;2797:9411:121;;;;;:::i;:::-;;;;;5219:52:55;5313:22;;2797:9411:121;;;;;:::i;:::-;;;;;5219:52:55;5376:34;;2797:9411:121;;;;5376:34:55;;2797:9411:121;;5455:29:55;5494:940;5501:14;;;5494:940;2797:9411:121;;;;6490:52:55;;2797:9411:121;6642:14:55;;2797:9411:121;6622:41:55;6618:71;;2797:9411:121;;-1:-1:-1;;2797:9411:121;;;6763:445:55;2797:9411:121;;6770:21:55;;;6823:20;;;:::i;:::-;6872;;;:::i;:::-;2797:9411:121;6934:18:55;2797:9411:121;;;;;;6934:18:55;2797:9411:121;;;7006:139:55;5219:52;7006:139;2797:9411:121;;7006:139:55;7158:39;:14;;2797:9411:121;;7158:39:55;;:::i;:::-;2797:9411:121;6763:445:55;;6770:21;;7225:17;6770:21;;7225:14;:17;:::i;6618:71::-;5114:17;;;2797:9411:121;6672:17:55;;2797:9411:121;6672:17:55;6490:52;6523:19;;;2797:9411:121;6523:19:55;;2797:9411:121;6523:19:55;5494:940;5548:16;;;:::i;:::-;2797:9411:121;5663:31:55;5625:24;4810:1;2797:9411:121;;5625:24:55;;;:::i;:::-;5663:31;;;;:::i;:::-;5745:42;;;;;;;:::i;:::-;5806:20;5219:52;5806:20;;2797:9411:121;5806:25:55;;5802:622;5806:25;;;5855:14;;;;;;;;2797:9411:121;;;5855:41:55;5851:174;5880:16;5920:5;5880:16;5989;;;;;:::i;:::-;;;:::i;:::-;5494:940;;5802:622;4810:1;6049:25;:40;;;5802:622;6045:379;;;6126:18;;;5219:52;6126:40;:18;2797:9411:121;6126:45:55;:18;;;2797:9411:121;;6126:40:55;;:::i;:::-;;:45;2797:9411:121;6126:45:55;;:::i;6045:379::-;2797:9411:121;6353:55:55;2797:9411:121;;6282:1:55;6353:55;2797:9411:121;;;6353:55:55;;:::i;6049:40::-;6078:11;;;6049:40;;5008:169;2797:9411:121;;;;;;5084:47:55;;5152:9;:14;:9;;:::i;:::-;;:14;2797:9411:121;5145:21:55;:::o;5012:56::-;5048:9;;;;:::i;:::-;;2797:9411:121;5048:20:55;5012:56;;:32;5030:14;4810:1;5030:14;;5012:32;;4798:13;4847:9;;;;:::i;:::-;;2797:9411:121;4866:13:55;4873:5;;;:::i;:::-;4866:13;;:::i;:::-;;2797:9411:121;-1:-1:-1;4847:38:55;4843:67;;4810:1;2797:9411:121;4798:13:55;;4698:40;4725:13;;;2797:9411:121;4725:13:55;;2797:9411:121;4725:13:55;2284:287:59;;;;2797:9411:121;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;-1:-1:-1;;2797:9411:121;;;;2560:3:59;;;3244:193;3251:16;2797:9411:121;3251:16:59;;;;;3494:8;;;-1:-1:-1;;;2797:9411:121;3494:132:59;3636:173;;;;;;;;;;;2284:287::o;3494:132::-;3598:24;3606:15;3598:28;3606:15;;:::i;:::-;3598:24;:::i;:28::-;3494:132;;;3269:16;3301:65;;;;;;2797:9411:121;;;;;;;;;;;;;;;;;3269:16:59;-1:-1:-1;;2797:9411:121;;;;;;3244:193:59;2797:9411:121;;:::i;7390:537:56:-;7459:462;;;;;;;;-1:-1:-1;;;;;7459:462:56;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7390:537;:::o;6686:471::-;6806:1;6800:7;6806:1;;2797:9411:121;6829:141:56;;;;2797:9411:121;6829:141:56;6795:356;6686:471::o;6795:356::-;7000:141;2797:9411:121;7000:141:56;;;;2797:9411:121;7000:141:56;6795:356;6686:471::o;13095:196:55:-;;13147:13;2797:9411:121;13172:113:55;13179:6;;;13095:196;:::o;13172:113::-;2797:9411:121;-1:-1:-1;;2797:9411:121;;;;;;;13210:1:55;13201:10;;2797:9411:121;;;;13172:113:55;;;2797:9411:121;;;;;;;:::i;:::-;;;;-1:-1:-1;2797:9411:121;;-1:-1:-1;2797:9411:121;;;;;;:::o;7670:742:55:-;2797:9411:121;;:::i;:::-;;;;7846:33:55;7864:15;;;2797:9411:121;;;7846:33:55;;:::i;:::-;2797:9411:121;;;7935:210:55;7942:15;;;;;;7935:210;2797:9411:121;;;;8175:27:55;8348:28;2797:9411:121;;;8175:27:55;;:::i;:::-;2797:9411:121;;;8287:13:55;7998;8287;;;2797:9411:121;;:::i;:::-;;;;8246:55:55;7864:15;8246:55;;2797:9411:121;7998:13:55;8246:55;;2797:9411:121;;;;8348:28:55;:::i;7935:210::-;7998:13;:24;:13;;;;;:24;:::i;:::-;;2797:9411:121;7978:50:55;;;7974:94;;2797:9411:121;;7935:210:55;;;7974:94;8048:5;;;9445:1927;;;9640:15;;;2797:9411:121;;;9737:21:55;;;;:::i;:::-;9794;;;;:::i;:::-;9830:9;2797:9411:121;9841:10:55;;;;;;10071:20;;;;10182:1157;;2797:9411:121;10189:12:55;;;:::i;:::-;2797:9411:121;10189:17:55;;;2797:9411:121;10248:9:55;2797:9411:121;10272:851:55;10279:7;;;;;;11204:10;;;11228:101;;;;;10182:1157;;10272:851;10320:12;;;;;;:::i;:::-;2797:9411:121;10355:5:55;;;;;;:::i;:::-;:11;:44;;;10272:851;10351:758;;;2797:9411:121;10496:9:55;10481:40;10496:9;10507:13;10514:5;10496:9;10677:6;10496:9;;10561:8;10496:9;;:::i;:::-;2797:9411:121;10514:5:55;;:::i;10507:13::-;2797:9411:121;10481:40:55;;;:::i;:::-;10466:55;;;;:::i;10561:8::-;10543:26;;;;:::i;:::-;2797:9411:121;;10677:6:55;;:::i;:::-;10351:758;10272:851;;10351:758;2797:9411:121;;;;9640:15:55;10801:14;;;2797:9411:121;-1:-1:-1;10781:41:55;10777:70;;2797:9411:121;10899:9:55;10967:8;10899:9;;10884:43;10899:9;;;;:::i;:::-;2797:9411:121;10910:16:55;;;:::i;:::-;10884:43;;;:::i;10967:8::-;10949:26;;;;:::i;:::-;2797:9411:121;;;;10351:758:55;10272:851;;10777:70;10831:16;;;2797:9411:121;10831:16:55;;2797:9411:121;10831:16:55;10355:44;10380:5;10370:16;10380:5;;;:::i;:::-;10370:16;;:::i;:::-;2797:9411:121;;10391:7:55;;10370:29;10355:44;;10189:17;;;;;11356:9;10189:17;11356:9;:::i;9830:::-;2797:9411:121;9880:13:55;9985:25;9880:13;;;9640:15;9880:25;:13;;9894:10;;;;:::i;:::-;9880:25;;:::i;:::-;;:30;2797:9411:121;9868:42:55;;;;:::i;:::-;2797:9411:121;9985:13:55;2797:9411:121;;;9985:25:55;;:::i;:::-;;2797:9411:121;;;9952:64:55;;;;:::i;:::-;2797:9411:121;;9830:9:55;;11444:188;;11527:37;:13;;;;2797:9411:121;;11527:37:55;;:::i;:::-;2797:9411:121;;;;;;;11444:188:55:o;11706:222::-;11808:30;:13;;;;2797:9411:121;;11808:30:55;;:::i;:::-;2797:9411:121;;;;;;;;11706:222:55;:::o;12000:226::-;12106:30;:13;;;;2797:9411:121;;12106:30:55;;:::i;:::-;2797:9411:121;;;-1:-1:-1;;2797:9411:121;;;;;12000:226:55:o;713:2:59:-;;;;;;;;;:::o","linkReferences":{},"immutableReferences":{"77940":[{"start":2148,"length":32},{"start":3201,"length":32}]}},"methodIdentifiers":{"MMR_ROOT_PAYLOAD_ID()":"af8b91d6","_digestParaId()":"e455995b","noOp((uint256,uint256,(uint256,uint256,uint256,bytes32),(uint256,uint256,uint256,bytes32)),(((((bytes2,bytes)[],uint32,uint64),(bytes,uint256)[]),(uint8,uint32,bytes32,(uint64,uint32,bytes32),bytes32,uint256),bytes32[],bytes32[]),((uint256,uint256,bytes)[],bytes32[],uint256)))":"9442d9fc","supportsInterface(bytes4)":"01ffc9a7","verify(bytes,bytes)":"f7e83aee"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.30+commit.73712a01\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"digestParaId\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"ECDSAInvalidSignature\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"length\",\"type\":\"uint256\"}],\"name\":\"ECDSAInvalidSignatureLength\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"ECDSAInvalidSignatureS\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"EmptyLeaves\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"EmptyTree\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"EmptyTree\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"IllegalGenesisBlock\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidAuthoritiesProof\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidMmrProof\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"LeafIndexOutOfBounds\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MmrRootHashMissing\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OutOfBoundsLeaves\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ProofExhausted\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"SuperMajorityRequired\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"TimestampNotFound\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnconsumedProof\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnknownAuthoritySet\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnsortedLeaves\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnsortedLeaves\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MMR_ROOT_PAYLOAD_ID\",\"outputs\":[{\"internalType\":\"bytes2\",\"name\":\"\",\"type\":\"bytes2\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_digestParaId\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"latestHeight\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"beefyActivationBlock\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"len\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"blsPoseidonHash\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"ecdsaMerkleRoot\",\"type\":\"bytes32\"}],\"internalType\":\"struct AuthoritySet\",\"name\":\"currentAuthoritySet\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"len\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"blsPoseidonHash\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"ecdsaMerkleRoot\",\"type\":\"bytes32\"}],\"internalType\":\"struct AuthoritySet\",\"name\":\"nextAuthoritySet\",\"type\":\"tuple\"}],\"internalType\":\"struct BeefyConsensusState\",\"name\":\"s\",\"type\":\"tuple\"},{\"components\":[{\"components\":[{\"components\":[{\"components\":[{\"components\":[{\"internalType\":\"bytes2\",\"name\":\"id\",\"type\":\"bytes2\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"internalType\":\"struct Payload[]\",\"name\":\"payload\",\"type\":\"tuple[]\"},{\"internalType\":\"uint32\",\"name\":\"blockNumber\",\"type\":\"uint32\"},{\"internalType\":\"uint64\",\"name\":\"validatorSetId\",\"type\":\"uint64\"}],\"internalType\":\"struct Commitment\",\"name\":\"commitment\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"authorityIndex\",\"type\":\"uint256\"}],\"internalType\":\"struct Vote[]\",\"name\":\"votes\",\"type\":\"tuple[]\"}],\"internalType\":\"struct SignedCommitment\",\"name\":\"signedCommitment\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint8\",\"name\":\"version\",\"type\":\"uint8\"},{\"internalType\":\"uint32\",\"name\":\"parentNumber\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"parentHash\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"uint64\",\"name\":\"id\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"len\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"}],\"internalType\":\"struct AuthoritySetCommitment\",\"name\":\"nextAuthoritySet\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"extra\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"leafIndex\",\"type\":\"uint256\"}],\"internalType\":\"struct BeefyMmrLeaf\",\"name\":\"latestMmrLeaf\",\"type\":\"tuple\"},{\"internalType\":\"bytes32[]\",\"name\":\"mmrProof\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"proof\",\"type\":\"bytes32[]\"}],\"internalType\":\"struct RelayChainProof\",\"name\":\"relay\",\"type\":\"tuple\"},{\"components\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"header\",\"type\":\"bytes\"}],\"internalType\":\"struct Parachain[]\",\"name\":\"parachains\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"proof\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint256\",\"name\":\"leafCount\",\"type\":\"uint256\"}],\"internalType\":\"struct ParachainProof\",\"name\":\"parachain\",\"type\":\"tuple\"}],\"internalType\":\"struct BeefyConsensusProof\",\"name\":\"p\",\"type\":\"tuple\"}],\"name\":\"noOp\",\"outputs\":[],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"previousState\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"proof\",\"type\":\"bytes\"}],\"name\":\"verify\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"stateMachineId\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"height\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"overlayRoot\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"stateRoot\",\"type\":\"bytes32\"}],\"internalType\":\"struct StateCommitment\",\"name\":\"commitment\",\"type\":\"tuple\"}],\"internalType\":\"struct IntermediateState[]\",\"name\":\"\",\"type\":\"tuple[]\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"author\":\"Polytope Labs (hello@polytope.technology)\",\"details\":\"The verification flow is: 1. Confirm the commitment's validator set id matches a known authority set. 2. Verify that enough signatures are present to meet the supermajority threshold. 3. Recover signer addresses via ecrecover and verify their membership in the authority set via a merkle multi-proof against the authority set root. 4. Extract the MMR root from the commitment payload and verify the latest MMR leaf inclusion via a merkle mountain range proof. 5. Verify parachain header inclusion in the MMR leaf's parachain heads root. 6. Decode each parachain header to extract finalized state commitments. Stale proofs (commitment block number <= trusted latest height) are treated as no-ops.\",\"errors\":{\"ECDSAInvalidSignature()\":[{\"details\":\"The signature derives the `address(0)`.\"}],\"ECDSAInvalidSignatureLength(uint256)\":[{\"details\":\"The signature has an invalid length.\"}],\"ECDSAInvalidSignatureS(bytes32)\":[{\"details\":\"The signature has an S value that is in the upper half order.\"}]},\"kind\":\"dev\",\"methods\":{\"supportsInterface(bytes4)\":{\"details\":\"See {IERC165-supportsInterface}.\"},\"verify(bytes,bytes)\":{\"details\":\"IConsensusV2 entry point. Decodes the proof, verifies consensus, and returns the updated state along with the latest authority set id.\"}},\"title\":\"The ECDSA BEEFY Consensus Client.\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"_digestParaId()\":{\"notice\":\"The parachain whose header digests carry the bls commitment, which is hyperbridge. Only its headers are read for one, since every parachain in a proof is equally authentic and only this one speaks for the relay's authorities.\"}},\"notice\":\"Verifies BEEFY consensus proofs by checking a 2/3+1 supermajority of secp256k1 signatures on-chain, along with merkle multi-proofs of authority set membership. This is the most gas-expensive verifier but requires no off-chain proving infrastructure.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/consensus/EcdsaBeefy.sol\":\"EcdsaBeefy\"},\"evmVersion\":\"prague\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@gnark-apk-proofs/=lib/gnark-apk-proofs/solidity/contracts/\",\":@hyperbridge/core/=node_modules/@hyperbridge/core/contracts/\",\":@openzeppelin/=node_modules/@openzeppelin/\",\":@polytope-labs/=node_modules/@polytope-labs/\",\":@sp1-contracts/=lib/sp1-contracts/contracts/src/\",\":@uniswap/=node_modules/@uniswap/\",\":ds-test/=lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/sp1-contracts/contracts/lib/openzeppelin-contracts/lib/erc4626-tests/\",\":forge-std/=node_modules/forge-std/src/\",\":gnark-apk-proofs/=lib/gnark-apk-proofs/\",\":openzeppelin-contracts/=lib/sp1-contracts/contracts/lib/openzeppelin-contracts/\",\":solidity-stringutils/=lib/solidity-stringutils/\",\":sp1-contracts/=lib/sp1-contracts/contracts/\",\":stringutils/=lib/solidity-stringutils/src/\"],\"viaIR\":true},\"sources\":{\"node_modules/@hyperbridge/core/contracts/interfaces/IConsensusV2.sol\":{\"keccak256\":\"0x71dcb5168f8f0f95effac221bdc49e0f662011c1bf86a9369cc0db183b8ac4c3\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://d84388af7b50f5f31110fe3d711930c816432f8c2ebcc7417639a727760544ff\",\"dweb:/ipfs/QmWp89jgUMhCqrGVDphLAEhJm5kPP6q1y9yid3D2a3JFZ9\"]},\"node_modules/@hyperbridge/core/contracts/libraries/StateMachine.sol\":{\"keccak256\":\"0x860289ae856ea354df5cca2131612da5c15a129fc14ae5ac0d528e0ce7c809c3\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://2ab5a7d6463b634da1e05e2b06818416a6f2d59b5c10815cc74bf26d003c2b95\",\"dweb:/ipfs/QmZ4dpEoDhNefAcKwKBXumLfSHqxhcBctErTzMvvPHXY5G\"]},\"node_modules/@openzeppelin/contracts/utils/Panic.sol\":{\"keccak256\":\"0xf7fe324703a64fc51702311dc51562d5cb1497734f074e4f483bfb6717572d7a\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://c6a5ff4f9fd8649b7ee20800b7fa387d3465bd77cf20c2d1068cd5c98e1ed57a\",\"dweb:/ipfs/QmVSaVJf9FXFhdYEYeCEfjMVHrxDh5qL4CGkxdMWpQCrqG\"]},\"node_modules/@openzeppelin/contracts/utils/Strings.sol\":{\"keccak256\":\"0xad148d59f05165f9217d0a9e1ac8f772abb02ea6aaad8a756315c532bf79f9f4\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://15e3599867c2182f5831e9268b274b2ef2047825837df6b4d81c9e89254b093e\",\"dweb:/ipfs/QmZbL7XAYr5RmaNaooPgZRmcDXaudfsYQfYD9y5iAECvpS\"]},\"node_modules/@openzeppelin/contracts/utils/cryptography/ECDSA.sol\":{\"keccak256\":\"0x69f54c02b7d81d505910ec198c11ed4c6a728418a868b906b4a0cf29946fda84\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://8e25e4bdb7ae1f21d23bfee996e22736fc0ab44cfabedac82a757b1edc5623b9\",\"dweb:/ipfs/QmQdWQvB6JCP9ZMbzi8EvQ1PTETqkcTWrbcVurS7DKpa5n\"]},\"node_modules/@openzeppelin/contracts/utils/introspection/ERC165.sol\":{\"keccak256\":\"0x2d9dc2fe26180f74c11c13663647d38e259e45f95eb88f57b61d2160b0109d3e\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://81233d1f98060113d9922180bb0f14f8335856fe9f339134b09335e9f678c377\",\"dweb:/ipfs/QmWh6R35SarhAn4z2wH8SU456jJSYL2FgucfTFgbHJJN4E\"]},\"node_modules/@openzeppelin/contracts/utils/introspection/IERC165.sol\":{\"keccak256\":\"0x8891738ffe910f0cf2da09566928589bf5d63f4524dd734fd9cedbac3274dd5c\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://971f954442df5c2ef5b5ebf1eb245d7105d9fbacc7386ee5c796df1d45b21617\",\"dweb:/ipfs/QmadRjHbkicwqwwh61raUEapaVEtaLMcYbQZWs9gUkgj3u\"]},\"node_modules/@openzeppelin/contracts/utils/math/Math.sol\":{\"keccak256\":\"0x1225214420c83ebcca88f2ae2b50f053aaa7df7bd684c3e878d334627f2edfc6\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://6c5fab4970634f9ab9a620983dc1c8a30153981a0b1a521666e269d0a11399d3\",\"dweb:/ipfs/QmVRnBC575MESGkEHndjujtR7qub2FzU9RWy9eKLp4hPZB\"]},\"node_modules/@openzeppelin/contracts/utils/math/SafeCast.sol\":{\"keccak256\":\"0x195533c86d0ef72bcc06456a4f66a9b941f38eb403739b00f21fd7c1abd1ae54\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://b1d578337048cad08c1c03041cca5978eff5428aa130c781b271ad9e5566e1f8\",\"dweb:/ipfs/QmPFKL2r9CBsMwmUqqdcFPfHZB2qcs9g1HDrPxzWSxomvy\"]},\"node_modules/@openzeppelin/contracts/utils/math/SignedMath.sol\":{\"keccak256\":\"0xb1970fac7b64e6c09611e6691791e848d5e3fe410fa5899e7df2e0afd77a99e3\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://db5fbb3dddd8b7047465b62575d96231ba8a2774d37fb4737fbf23340fabbb03\",\"dweb:/ipfs/QmVUSvooZKEdEdap619tcJjTLcAuH6QBdZqAzWwnAXZAWJ\"]},\"node_modules/@polytope-labs/solidity-merkle-trees/src/MerkleMountainRange.sol\":{\"keccak256\":\"0x014237038bb77bdf371b50c1268d02bf7eeaf7068c35483d5d0684ca7c30d544\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://beb3ae60094f49716a2b0cfa6229ee27af1778934cf5f2dfc704433fda5a687b\",\"dweb:/ipfs/QmQ339wEFT9X1woHyFTDbbhUCx8ekD9MGLRnhS3PpYp1pm\"]},\"node_modules/@polytope-labs/solidity-merkle-trees/src/MerkleMultiProof.sol\":{\"keccak256\":\"0xd4f6e6a9eceaa7d1cdf9684bfe7f3f552adf21dfcf7f1372f7943a4c2f15deb7\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://ec900b3e79ea8ef9d275697937d1984e994d10117aa2be066bd73f6a31aedf10\",\"dweb:/ipfs/QmZQGLiJtbvUSEW5H2HEenzxp58fN43XHRY3SoNTC9Hobg\"]},\"node_modules/@polytope-labs/solidity-merkle-trees/src/trie/Bytes.sol\":{\"keccak256\":\"0xd305383358b93285d8fcee512795487484eadfd7b602df16ff4e9b01afbefec7\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://57c86cd2fe6ab591264c5632d503d77f04748d722e4defa9c2badb238ee1f9bd\",\"dweb:/ipfs/QmTZ6xTcaYkRBshq6YZVMx2kkk94ZtJsRM6xLpC5qWT3oA\"]},\"node_modules/@polytope-labs/solidity-merkle-trees/src/trie/Memory.sol\":{\"keccak256\":\"0x59e3a56caa42c1aac30231173439817d38c7f359e40bd36e9bb418d3f82ceab7\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://e060fed46c6b420624166ea02a326f0f566897941bdc322257e690ae134b8179\",\"dweb:/ipfs/QmbXW8yG2ZntjLMyUEmQGcRZroZj4dVZkBhHxK2PFKfUKB\"]},\"node_modules/@polytope-labs/solidity-merkle-trees/src/trie/Node.sol\":{\"keccak256\":\"0xca611969a68f7fe63dcdc742c9caf9bc1b26495561b68f4676c209279a4576ba\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://17b7ec2cf65d484f0a2c188a7ae33430b015b3a55bf10393167f53839e2dde56\",\"dweb:/ipfs/QmTQbT8J7rBRNHFXSR7S4KbpwCteMrFt8mvrzCD1vYYTwX\"]},\"node_modules/@polytope-labs/solidity-merkle-trees/src/trie/polkadot/ScaleCodec.sol\":{\"keccak256\":\"0x9ac4df46e68718f7deaaa5b7443778533f53dc0ff3736cc386cf4991099da2aa\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://85d08b00d9173358323105be975ce85b7e206ba52df2d80a14d2581dec1ddd11\",\"dweb:/ipfs/QmQCqVvSdJUqfhLH8TRBGNDGiEzkGduhRacnYhoKcm7e2a\"]},\"src/consensus/Codec.sol\":{\"keccak256\":\"0x477b62396db5a5c1d89d0388b0724dde80e0a0423b89c07f7b3b5622ff03b5d4\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://6a584c6500ae347c6f9d76d06a96757b024dcb892601c6d9c1a945b9d76811d6\",\"dweb:/ipfs/QmduKRb2Zr9eqeZbi5TFRw5Dyig3XVY3rMvPkxLktLFYrN\"]},\"src/consensus/EcdsaBeefy.sol\":{\"keccak256\":\"0x1fd6824212d0c42ecfb107d3599ff75d280a956dbaa212707eaa83b6e667aca7\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://0471c92a4bf06528e9826ace0f48c32a17702ecbad93fb8e015123444c002448\",\"dweb:/ipfs/QmaZ47vQbHFHNBzFpupqn66PdVigLYv7P363EmgsyXk2ED\"]},\"src/consensus/Types.sol\":{\"keccak256\":\"0xe4b169eefb4afd0f83f09335f4460705fe96e7736314c2030235720cf976e76f\",\"license\":\"Apache-2.0\",\"urls\":[\"bzz-raw://4ad510e26689491bcba165fd73c08e1b80302148804e27a184861712d966540b\",\"dweb:/ipfs/QmVuaZ3yB3eBkHRh2DS6Cy1E6B3oige6fDtvCrcYssc3qi\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.30+commit.73712a01"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"uint256","name":"digestParaId","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"type":"error","name":"ECDSAInvalidSignature"},{"inputs":[{"internalType":"uint256","name":"length","type":"uint256"}],"type":"error","name":"ECDSAInvalidSignatureLength"},{"inputs":[{"internalType":"bytes32","name":"s","type":"bytes32"}],"type":"error","name":"ECDSAInvalidSignatureS"},{"inputs":[],"type":"error","name":"EmptyLeaves"},{"inputs":[],"type":"error","name":"EmptyTree"},{"inputs":[],"type":"error","name":"EmptyTree"},{"inputs":[],"type":"error","name":"IllegalGenesisBlock"},{"inputs":[],"type":"error","name":"InvalidAuthoritiesProof"},{"inputs":[],"type":"error","name":"InvalidMmrProof"},{"inputs":[],"type":"error","name":"LeafIndexOutOfBounds"},{"inputs":[],"type":"error","name":"MmrRootHashMissing"},{"inputs":[],"type":"error","name":"OutOfBoundsLeaves"},{"inputs":[],"type":"error","name":"ProofExhausted"},{"inputs":[],"type":"error","name":"SuperMajorityRequired"},{"inputs":[],"type":"error","name":"TimestampNotFound"},{"inputs":[],"type":"error","name":"UnconsumedProof"},{"inputs":[],"type":"error","name":"UnknownAuthoritySet"},{"inputs":[],"type":"error","name":"UnsortedLeaves"},{"inputs":[],"type":"error","name":"UnsortedLeaves"},{"inputs":[],"stateMutability":"view","type":"function","name":"MMR_ROOT_PAYLOAD_ID","outputs":[{"internalType":"bytes2","name":"","type":"bytes2"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"_digestParaId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"struct BeefyConsensusState","name":"s","type":"tuple","components":[{"internalType":"uint256","name":"latestHeight","type":"uint256"},{"internalType":"uint256","name":"beefyActivationBlock","type":"uint256"},{"internalType":"struct AuthoritySet","name":"currentAuthoritySet","type":"tuple","components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"len","type":"uint256"},{"internalType":"uint256","name":"blsPoseidonHash","type":"uint256"},{"internalType":"bytes32","name":"ecdsaMerkleRoot","type":"bytes32"}]},{"internalType":"struct AuthoritySet","name":"nextAuthoritySet","type":"tuple","components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"len","type":"uint256"},{"internalType":"uint256","name":"blsPoseidonHash","type":"uint256"},{"internalType":"bytes32","name":"ecdsaMerkleRoot","type":"bytes32"}]}]},{"internalType":"struct BeefyConsensusProof","name":"p","type":"tuple","components":[{"internalType":"struct RelayChainProof","name":"relay","type":"tuple","components":[{"internalType":"struct SignedCommitment","name":"signedCommitment","type":"tuple","components":[{"internalType":"struct Commitment","name":"commitment","type":"tuple","components":[{"internalType":"struct Payload[]","name":"payload","type":"tuple[]","components":[{"internalType":"bytes2","name":"id","type":"bytes2"},{"internalType":"bytes","name":"data","type":"bytes"}]},{"internalType":"uint32","name":"blockNumber","type":"uint32"},{"internalType":"uint64","name":"validatorSetId","type":"uint64"}]},{"internalType":"struct Vote[]","name":"votes","type":"tuple[]","components":[{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"uint256","name":"authorityIndex","type":"uint256"}]}]},{"internalType":"struct BeefyMmrLeaf","name":"latestMmrLeaf","type":"tuple","components":[{"internalType":"uint8","name":"version","type":"uint8"},{"internalType":"uint32","name":"parentNumber","type":"uint32"},{"internalType":"bytes32","name":"parentHash","type":"bytes32"},{"internalType":"struct AuthoritySetCommitment","name":"nextAuthoritySet","type":"tuple","components":[{"internalType":"uint64","name":"id","type":"uint64"},{"internalType":"uint32","name":"len","type":"uint32"},{"internalType":"bytes32","name":"root","type":"bytes32"}]},{"internalType":"bytes32","name":"extra","type":"bytes32"},{"internalType":"uint256","name":"leafIndex","type":"uint256"}]},{"internalType":"bytes32[]","name":"mmrProof","type":"bytes32[]"},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"}]},{"internalType":"struct ParachainProof","name":"parachain","type":"tuple","components":[{"internalType":"struct Parachain[]","name":"parachains","type":"tuple[]","components":[{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bytes","name":"header","type":"bytes"}]},{"internalType":"bytes32[]","name":"proof","type":"bytes32[]"},{"internalType":"uint256","name":"leafCount","type":"uint256"}]}]}],"stateMutability":"pure","type":"function","name":"noOp"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"stateMutability":"view","type":"function","name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}]},{"inputs":[{"internalType":"bytes","name":"previousState","type":"bytes"},{"internalType":"bytes","name":"proof","type":"bytes"}],"stateMutability":"view","type":"function","name":"verify","outputs":[{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"struct IntermediateState[]","name":"","type":"tuple[]","components":[{"internalType":"uint256","name":"stateMachineId","type":"uint256"},{"internalType":"uint256","name":"height","type":"uint256"},{"internalType":"struct StateCommitment","name":"commitment","type":"tuple","components":[{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bytes32","name":"overlayRoot","type":"bytes32"},{"internalType":"bytes32","name":"stateRoot","type":"bytes32"}]}]},{"internalType":"uint256","name":"","type":"uint256"}]}],"devdoc":{"kind":"dev","methods":{"supportsInterface(bytes4)":{"details":"See {IERC165-supportsInterface}."},"verify(bytes,bytes)":{"details":"IConsensusV2 entry point. Decodes the proof, verifies consensus, and returns the updated state along with the latest authority set id."}},"version":1},"userdoc":{"kind":"user","methods":{"_digestParaId()":{"notice":"The parachain whose header digests carry the bls commitment, which is hyperbridge. Only its headers are read for one, since every parachain in a proof is equally authentic and only this one speaks for the relay's authorities."}},"version":1}},"settings":{"remappings":["@gnark-apk-proofs/=lib/gnark-apk-proofs/solidity/contracts/","@hyperbridge/core/=node_modules/@hyperbridge/core/contracts/","@openzeppelin/=node_modules/@openzeppelin/","@polytope-labs/=node_modules/@polytope-labs/","@sp1-contracts/=lib/sp1-contracts/contracts/src/","@uniswap/=node_modules/@uniswap/","ds-test/=lib/forge-std/lib/ds-test/src/","erc4626-tests/=lib/sp1-contracts/contracts/lib/openzeppelin-contracts/lib/erc4626-tests/","forge-std/=node_modules/forge-std/src/","gnark-apk-proofs/=lib/gnark-apk-proofs/","openzeppelin-contracts/=lib/sp1-contracts/contracts/lib/openzeppelin-contracts/","solidity-stringutils/=lib/solidity-stringutils/","sp1-contracts/=lib/sp1-contracts/contracts/","stringutils/=lib/solidity-stringutils/src/"],"optimizer":{"enabled":true,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"src/consensus/EcdsaBeefy.sol":"EcdsaBeefy"},"evmVersion":"prague","libraries":{},"viaIR":true},"sources":{"node_modules/@hyperbridge/core/contracts/interfaces/IConsensusV2.sol":{"keccak256":"0x71dcb5168f8f0f95effac221bdc49e0f662011c1bf86a9369cc0db183b8ac4c3","urls":["bzz-raw://d84388af7b50f5f31110fe3d711930c816432f8c2ebcc7417639a727760544ff","dweb:/ipfs/QmWp89jgUMhCqrGVDphLAEhJm5kPP6q1y9yid3D2a3JFZ9"],"license":"Apache-2.0"},"node_modules/@hyperbridge/core/contracts/libraries/StateMachine.sol":{"keccak256":"0x860289ae856ea354df5cca2131612da5c15a129fc14ae5ac0d528e0ce7c809c3","urls":["bzz-raw://2ab5a7d6463b634da1e05e2b06818416a6f2d59b5c10815cc74bf26d003c2b95","dweb:/ipfs/QmZ4dpEoDhNefAcKwKBXumLfSHqxhcBctErTzMvvPHXY5G"],"license":"Apache-2.0"},"node_modules/@openzeppelin/contracts/utils/Panic.sol":{"keccak256":"0xf7fe324703a64fc51702311dc51562d5cb1497734f074e4f483bfb6717572d7a","urls":["bzz-raw://c6a5ff4f9fd8649b7ee20800b7fa387d3465bd77cf20c2d1068cd5c98e1ed57a","dweb:/ipfs/QmVSaVJf9FXFhdYEYeCEfjMVHrxDh5qL4CGkxdMWpQCrqG"],"license":"MIT"},"node_modules/@openzeppelin/contracts/utils/Strings.sol":{"keccak256":"0xad148d59f05165f9217d0a9e1ac8f772abb02ea6aaad8a756315c532bf79f9f4","urls":["bzz-raw://15e3599867c2182f5831e9268b274b2ef2047825837df6b4d81c9e89254b093e","dweb:/ipfs/QmZbL7XAYr5RmaNaooPgZRmcDXaudfsYQfYD9y5iAECvpS"],"license":"MIT"},"node_modules/@openzeppelin/contracts/utils/cryptography/ECDSA.sol":{"keccak256":"0x69f54c02b7d81d505910ec198c11ed4c6a728418a868b906b4a0cf29946fda84","urls":["bzz-raw://8e25e4bdb7ae1f21d23bfee996e22736fc0ab44cfabedac82a757b1edc5623b9","dweb:/ipfs/QmQdWQvB6JCP9ZMbzi8EvQ1PTETqkcTWrbcVurS7DKpa5n"],"license":"MIT"},"node_modules/@openzeppelin/contracts/utils/introspection/ERC165.sol":{"keccak256":"0x2d9dc2fe26180f74c11c13663647d38e259e45f95eb88f57b61d2160b0109d3e","urls":["bzz-raw://81233d1f98060113d9922180bb0f14f8335856fe9f339134b09335e9f678c377","dweb:/ipfs/QmWh6R35SarhAn4z2wH8SU456jJSYL2FgucfTFgbHJJN4E"],"license":"MIT"},"node_modules/@openzeppelin/contracts/utils/introspection/IERC165.sol":{"keccak256":"0x8891738ffe910f0cf2da09566928589bf5d63f4524dd734fd9cedbac3274dd5c","urls":["bzz-raw://971f954442df5c2ef5b5ebf1eb245d7105d9fbacc7386ee5c796df1d45b21617","dweb:/ipfs/QmadRjHbkicwqwwh61raUEapaVEtaLMcYbQZWs9gUkgj3u"],"license":"MIT"},"node_modules/@openzeppelin/contracts/utils/math/Math.sol":{"keccak256":"0x1225214420c83ebcca88f2ae2b50f053aaa7df7bd684c3e878d334627f2edfc6","urls":["bzz-raw://6c5fab4970634f9ab9a620983dc1c8a30153981a0b1a521666e269d0a11399d3","dweb:/ipfs/QmVRnBC575MESGkEHndjujtR7qub2FzU9RWy9eKLp4hPZB"],"license":"MIT"},"node_modules/@openzeppelin/contracts/utils/math/SafeCast.sol":{"keccak256":"0x195533c86d0ef72bcc06456a4f66a9b941f38eb403739b00f21fd7c1abd1ae54","urls":["bzz-raw://b1d578337048cad08c1c03041cca5978eff5428aa130c781b271ad9e5566e1f8","dweb:/ipfs/QmPFKL2r9CBsMwmUqqdcFPfHZB2qcs9g1HDrPxzWSxomvy"],"license":"MIT"},"node_modules/@openzeppelin/contracts/utils/math/SignedMath.sol":{"keccak256":"0xb1970fac7b64e6c09611e6691791e848d5e3fe410fa5899e7df2e0afd77a99e3","urls":["bzz-raw://db5fbb3dddd8b7047465b62575d96231ba8a2774d37fb4737fbf23340fabbb03","dweb:/ipfs/QmVUSvooZKEdEdap619tcJjTLcAuH6QBdZqAzWwnAXZAWJ"],"license":"MIT"},"node_modules/@polytope-labs/solidity-merkle-trees/src/MerkleMountainRange.sol":{"keccak256":"0x014237038bb77bdf371b50c1268d02bf7eeaf7068c35483d5d0684ca7c30d544","urls":["bzz-raw://beb3ae60094f49716a2b0cfa6229ee27af1778934cf5f2dfc704433fda5a687b","dweb:/ipfs/QmQ339wEFT9X1woHyFTDbbhUCx8ekD9MGLRnhS3PpYp1pm"],"license":"Apache-2.0"},"node_modules/@polytope-labs/solidity-merkle-trees/src/MerkleMultiProof.sol":{"keccak256":"0xd4f6e6a9eceaa7d1cdf9684bfe7f3f552adf21dfcf7f1372f7943a4c2f15deb7","urls":["bzz-raw://ec900b3e79ea8ef9d275697937d1984e994d10117aa2be066bd73f6a31aedf10","dweb:/ipfs/QmZQGLiJtbvUSEW5H2HEenzxp58fN43XHRY3SoNTC9Hobg"],"license":"Apache-2.0"},"node_modules/@polytope-labs/solidity-merkle-trees/src/trie/Bytes.sol":{"keccak256":"0xd305383358b93285d8fcee512795487484eadfd7b602df16ff4e9b01afbefec7","urls":["bzz-raw://57c86cd2fe6ab591264c5632d503d77f04748d722e4defa9c2badb238ee1f9bd","dweb:/ipfs/QmTZ6xTcaYkRBshq6YZVMx2kkk94ZtJsRM6xLpC5qWT3oA"],"license":"Apache-2.0"},"node_modules/@polytope-labs/solidity-merkle-trees/src/trie/Memory.sol":{"keccak256":"0x59e3a56caa42c1aac30231173439817d38c7f359e40bd36e9bb418d3f82ceab7","urls":["bzz-raw://e060fed46c6b420624166ea02a326f0f566897941bdc322257e690ae134b8179","dweb:/ipfs/QmbXW8yG2ZntjLMyUEmQGcRZroZj4dVZkBhHxK2PFKfUKB"],"license":"Apache-2.0"},"node_modules/@polytope-labs/solidity-merkle-trees/src/trie/Node.sol":{"keccak256":"0xca611969a68f7fe63dcdc742c9caf9bc1b26495561b68f4676c209279a4576ba","urls":["bzz-raw://17b7ec2cf65d484f0a2c188a7ae33430b015b3a55bf10393167f53839e2dde56","dweb:/ipfs/QmTQbT8J7rBRNHFXSR7S4KbpwCteMrFt8mvrzCD1vYYTwX"],"license":"Apache-2.0"},"node_modules/@polytope-labs/solidity-merkle-trees/src/trie/polkadot/ScaleCodec.sol":{"keccak256":"0x9ac4df46e68718f7deaaa5b7443778533f53dc0ff3736cc386cf4991099da2aa","urls":["bzz-raw://85d08b00d9173358323105be975ce85b7e206ba52df2d80a14d2581dec1ddd11","dweb:/ipfs/QmQCqVvSdJUqfhLH8TRBGNDGiEzkGduhRacnYhoKcm7e2a"],"license":"Apache-2.0"},"src/consensus/Codec.sol":{"keccak256":"0x477b62396db5a5c1d89d0388b0724dde80e0a0423b89c07f7b3b5622ff03b5d4","urls":["bzz-raw://6a584c6500ae347c6f9d76d06a96757b024dcb892601c6d9c1a945b9d76811d6","dweb:/ipfs/QmduKRb2Zr9eqeZbi5TFRw5Dyig3XVY3rMvPkxLktLFYrN"],"license":"Apache-2.0"},"src/consensus/EcdsaBeefy.sol":{"keccak256":"0x1fd6824212d0c42ecfb107d3599ff75d280a956dbaa212707eaa83b6e667aca7","urls":["bzz-raw://0471c92a4bf06528e9826ace0f48c32a17702ecbad93fb8e015123444c002448","dweb:/ipfs/QmaZ47vQbHFHNBzFpupqn66PdVigLYv7P363EmgsyXk2ED"],"license":"Apache-2.0"},"src/consensus/Types.sol":{"keccak256":"0xe4b169eefb4afd0f83f09335f4460705fe96e7736314c2030235720cf976e76f","urls":["bzz-raw://4ad510e26689491bcba165fd73c08e1b80302148804e27a184861712d966540b","dweb:/ipfs/QmVuaZ3yB3eBkHRh2DS6Cy1E6B3oige6fDtvCrcYssc3qi"],"license":"Apache-2.0"}},"version":1},"id":121} \ No newline at end of file diff --git a/evm/rust/src/conversions.rs b/evm/rust/src/conversions.rs index 312c96201..1e46eb896 100644 --- a/evm/rust/src/conversions.rs +++ b/evm/rust/src/conversions.rs @@ -47,9 +47,9 @@ mod beefy { use super::ToU256; use crate::{ ecdsa_beefy::Beefy::{ - AuthoritySetCommitment, BeefyConsensusProof, BeefyConsensusState, BeefyMmrLeaf, - Commitment, Parachain, ParachainProof, Payload, RelayChainProof, SignedCommitment, - Vote, + AuthoritySet, AuthoritySetCommitment, BeefyConsensusProof, BeefyConsensusState, + BeefyMmrLeaf, Commitment, Parachain, ParachainProof, Payload, RelayChainProof, + SignedCommitment, Vote, }, sp1_beefy::SP1Beefy::{MiniCommitment, ParachainHeader, PartialBeefyMmrLeaf}, }; @@ -192,6 +192,17 @@ mod beefy { } } + impl From for AuthoritySet { + fn from(value: beefy_verifier_primitives::AuthoritySet) -> Self { + AuthoritySet { + id: value.id.to_u256(), + len: (value.len as u64).to_u256(), + blsPoseidonHash: alloy_primitives::U256::from_be_bytes(value.bls_poseidon_hash.0), + ecdsaMerkleRoot: FixedBytes::from(value.ecdsa_merkle_root.0), + } + } + } + impl From for BeefyConsensusState { fn from(value: ConsensusState) -> Self { BeefyConsensusState { @@ -205,6 +216,13 @@ mod beefy { impl From for ConsensusState { fn from(value: BeefyConsensusState) -> Self { + let authority_set = |set: AuthoritySet| beefy_verifier_primitives::AuthoritySet { + id: set.id.try_into().expect("authority set id out of bounds"), + len: set.len.try_into().expect("authority set length out of bounds"), + bls_poseidon_hash: H256(set.blsPoseidonHash.to_be_bytes()), + ecdsa_merkle_root: H256(set.ecdsaMerkleRoot.0), + }; + ConsensusState { beefy_activation_block: value .beefyActivationBlock @@ -215,59 +233,8 @@ mod beefy { .try_into() .expect("Beefy latest height out of bounds"), mmr_root_hash: Default::default(), - current_authorities: BeefyNextAuthoritySet { - id: value - .currentAuthoritySet - .id - .try_into() - .expect("current authority set id out of bounds"), - len: value - .currentAuthoritySet - .len - .try_into() - .expect("current authority set length out of bounds"), - keyset_commitment: H256(value.currentAuthoritySet.root.0), - }, - next_authorities: BeefyNextAuthoritySet { - id: value - .nextAuthoritySet - .id - .try_into() - .expect("next authority set out of bounds"), - len: value - .nextAuthoritySet - .len - .try_into() - .expect("next authority set length out of bounds"), - keyset_commitment: H256(value.nextAuthoritySet.root.0), - }, - } - } - } - - impl From for sp_consensus_beefy::mmr::MmrLeaf { - fn from(value: PartialBeefyMmrLeaf) -> Self { - let version: u8 = value.version.try_into().expect("mmr leaf version out of bounds"); - sp_consensus_beefy::mmr::MmrLeaf { - version: MmrLeafVersion::new(version >> 5, version & 0b11111), - parent_number_and_hash: ( - value.parentNumber.try_into().expect("parent number out of bounds"), - H256(value.parentHash.0), - ), - beefy_next_authority_set: BeefyNextAuthoritySet { - id: value - .nextAuthoritySet - .id - .try_into() - .expect("next authority set id out of bounds"), - len: value - .nextAuthoritySet - .len - .try_into() - .expect("next authority set len out of bounds"), - keyset_commitment: H256(value.nextAuthoritySet.root.0), - }, - leaf_extra: H256(value.extra.0), + current_authorities: authority_set(value.currentAuthoritySet), + next_authorities: authority_set(value.nextAuthoritySet), } } } @@ -303,6 +270,33 @@ mod beefy { } } + impl From for sp_consensus_beefy::mmr::MmrLeaf { + fn from(value: PartialBeefyMmrLeaf) -> Self { + let version: u8 = value.version.try_into().expect("mmr leaf version out of bounds"); + sp_consensus_beefy::mmr::MmrLeaf { + version: MmrLeafVersion::new(version >> 5, version & 0b11111), + parent_number_and_hash: ( + value.parentNumber.try_into().expect("parent number out of bounds"), + H256(value.parentHash.0), + ), + beefy_next_authority_set: BeefyNextAuthoritySet { + id: value + .nextAuthoritySet + .id + .try_into() + .expect("next authority set id out of bounds"), + len: value + .nextAuthoritySet + .len + .try_into() + .expect("next authority set len out of bounds"), + keyset_commitment: H256(value.nextAuthoritySet.root.0), + }, + leaf_extra: H256(value.extra.0), + } + } + } + impl From for SpMmrLeaf { fn from(value: BeefyMmrLeaf) -> Self { let version: u8 = value.version.try_into().expect("mmr leaf version out of bounds"); @@ -393,6 +387,180 @@ mod beefy { } } + // `sol!` emits a distinct set of Rust types per binding, so the shared BEEFY structs appear + // again under `BlsApkBeefy` even though the Solidity definitions are the same ones. These + // bridge those duplicates onto the `Beefy` types so the conversions to the SCALE primitives + // stay single-sourced above. + mod apk_bridge { + use super::*; + use crate::bls_beefy::BlsBeefy; + + impl From for Payload { + fn from(value: BlsBeefy::Payload) -> Self { + Payload { id: value.id, data: value.data } + } + } + + impl From for Commitment { + fn from(value: BlsBeefy::Commitment) -> Self { + Commitment { + payload: value.payload.into_iter().map(Into::into).collect(), + blockNumber: value.blockNumber, + validatorSetId: value.validatorSetId, + } + } + } + + impl From for AuthoritySetCommitment { + fn from(value: BlsBeefy::AuthoritySetCommitment) -> Self { + AuthoritySetCommitment { id: value.id, len: value.len, root: value.root } + } + } + + impl From for BeefyMmrLeaf { + fn from(value: BlsBeefy::BeefyMmrLeaf) -> Self { + BeefyMmrLeaf { + version: value.version, + parentNumber: value.parentNumber, + parentHash: value.parentHash, + nextAuthoritySet: value.nextAuthoritySet.into(), + extra: value.extra, + leafIndex: value.leafIndex, + } + } + } + + impl From for Parachain { + fn from(value: BlsBeefy::Parachain) -> Self { + Parachain { index: value.index, id: value.id, header: value.header } + } + } + + impl From for ParachainProof { + fn from(value: BlsBeefy::ParachainProof) -> Self { + ParachainProof { + parachains: value.parachains.into_iter().map(Into::into).collect(), + proof: value.proof, + leafCount: value.leafCount, + } + } + } + } + + /// Decoded from calldata a relayer supplied, so every width is checked rather than assumed. + impl TryFrom + for beefy_verifier_primitives::ApkConsensusMessage + { + type Error = &'static str; + + fn try_from( + value: crate::bls_beefy::BlsBeefy::BlsApkBeefyConsensusProof, + ) -> Result { + let relay = value.relay; + let leaf: BeefyMmrLeaf = relay.latestMmrLeaf.into(); + let leaf_index: u64 = + leaf.leafIndex.try_into().map_err(|_| "mmr leaf index out of bounds")?; + + let commitment: Commitment = relay.commitment.into(); + let parachain: ParachainProof = value.parachain.into(); + + let mut bitlist = [[0u8; 32]; beefy_verifier_primitives::APK_BITLIST_WORDS]; + for (word, out) in relay.bitlist.iter().zip(bitlist.iter_mut()) { + *out = word.to_be_bytes(); + } + + Ok(beefy_verifier_primitives::ApkMmrProof { + commitment: commitment.into(), + bitlist, + apk: flatten(&relay.apk), + apk2: flatten(&relay.apk2), + apk_proof: relay.apkProof.to_vec(), + signature: flatten(&relay.signature), + latest_mmr_leaf: leaf.into(), + mmr_proof: LeafProof { + leaf_indices: vec![leaf_index], + leaf_count: leaf_index.saturating_add(1), + items: relay.mmrProof.into_iter().map(|h| H256(h.0)).collect(), + }, + }) + .map(|mmr| beefy_verifier_primitives::ApkConsensusMessage { + mmr, + parachain: parachain.into(), + }) + } + } + + /// The direction tooling needs when bootstrapping a chain: the starting set's commitment has + /// to be handed to `initialize_apk_state` in this encoding, since it is otherwise only ever + /// learned from a header digest. + impl From + for crate::bls_beefy::BlsBeefy::BeefyConsensusState + { + fn from(value: beefy_verifier_primitives::ConsensusState) -> Self { + let authority_set = |set: beefy_verifier_primitives::AuthoritySet| { + crate::bls_beefy::BlsBeefy::AuthoritySet { + id: set.id.to_u256(), + len: (set.len as u64).to_u256(), + blsPoseidonHash: alloy_primitives::U256::from_be_bytes(set.bls_poseidon_hash.0), + ecdsaMerkleRoot: FixedBytes(set.ecdsa_merkle_root.0), + } + }; + + crate::bls_beefy::BlsBeefy::BeefyConsensusState { + latestHeight: value.latest_beefy_height.to_u256(), + beefyActivationBlock: value.beefy_activation_block.to_u256(), + currentAuthoritySet: authority_set(value.current_authorities), + nextAuthoritySet: authority_set(value.next_authorities), + } + } + } + + /// The mmr root is not part of the initial state, since nothing has been proven yet. It is + /// filled by the first update that verifies. + impl TryFrom + for beefy_verifier_primitives::ConsensusState + { + type Error = &'static str; + + fn try_from( + value: crate::bls_beefy::BlsBeefy::BeefyConsensusState, + ) -> Result { + let authority_set = |set: crate::bls_beefy::BlsBeefy::AuthoritySet| { + Ok::<_, &'static str>(beefy_verifier_primitives::AuthoritySet { + id: set.id.try_into().map_err(|_| "authority set id out of bounds")?, + len: set.len.try_into().map_err(|_| "authority set size out of bounds")?, + bls_poseidon_hash: H256(set.blsPoseidonHash.to_be_bytes()), + ecdsa_merkle_root: H256(set.ecdsaMerkleRoot.0), + }) + }; + + Ok(beefy_verifier_primitives::ConsensusState { + latest_beefy_height: value + .latestHeight + .try_into() + .map_err(|_| "latest height out of bounds")?, + beefy_activation_block: value + .beefyActivationBlock + .try_into() + .map_err(|_| "beefy activation block out of bounds")?, + mmr_root_hash: H256::zero(), + current_authorities: authority_set(value.currentAuthoritySet)?, + next_authorities: authority_set(value.nextAuthoritySet)?, + }) + } + } + + /// Curve points reach the circuit's verifier as raw coordinates packed into 32 byte words. + fn flatten( + words: &[FixedBytes<32>; WORDS], + ) -> [u8; BYTES] { + let mut out = [0u8; BYTES]; + for (word, chunk) in words.iter().zip(out.chunks_mut(32)) { + chunk.copy_from_slice(&word.0); + } + out + } + impl From for Sp1BeefyProof { fn from(value: crate::sp1_beefy::SP1Beefy::SP1BeefyProof) -> Self { Sp1BeefyProof { diff --git a/evm/rust/src/generated/bls_beefy.rs b/evm/rust/src/generated/bls_beefy.rs new file mode 100644 index 000000000..216d4ca5e --- /dev/null +++ b/evm/rust/src/generated/bls_beefy.rs @@ -0,0 +1,24 @@ +//! Aggregate public key BLS BEEFY contract bindings generated with alloy sol! macro. +//! +//! See [`crate::generated::ecdsa_beefy`] for why the two `sol!` invocations are picked between +//! with `#[cfg]` rather than `cfg_attr`. + +use alloy_sol_macro::sol; + +#[cfg(feature = "std")] +sol!( + #[allow(missing_docs)] + #[sol(rpc, ignore_unlinked)] + #[derive(Debug, PartialEq, Eq)] + BlsBeefy, + "abi/BlsBeefy.json" +); + +#[cfg(not(feature = "std"))] +sol!( + #[allow(missing_docs)] + #[sol(ignore_unlinked)] + #[derive(Debug, PartialEq, Eq)] + BlsBeefy, + "abi/BlsBeefy.json" +); diff --git a/evm/rust/src/generated/mod.rs b/evm/rust/src/generated/mod.rs index 6f8168c38..eeec93e82 100644 --- a/evm/rust/src/generated/mod.rs +++ b/evm/rust/src/generated/mod.rs @@ -10,6 +10,7 @@ //! which is what substrate pallets consume. pub mod bandwidth_manager; +pub mod bls_beefy; pub mod ecdsa_beefy; pub mod erc20; pub mod evm_host; diff --git a/evm/script/DeployConsensusRouter.s.sol b/evm/script/DeployConsensusRouter.s.sol index ee244cf9e..89b1b338e 100644 --- a/evm/script/DeployConsensusRouter.s.sol +++ b/evm/script/DeployConsensusRouter.s.sol @@ -31,12 +31,14 @@ contract DeployScript is BaseScript { function deploy() internal override { address ecdsaBeefy = config.get("ECDSA_BEEFY").toAddress(); address sp1Verifier = config.get("SP1_VERIFIER").toAddress(); + // Hyperbridge's own para id, whose headers carry the bls commitment every client records. + uint256 hyperbridgeParaId = config.get("HYPERBRIDGE_PARA_ID").toUint256(); // Guard against wiring the router to an address that holds no code on this chain. require(ecdsaBeefy.code.length != 0, "ECDSA_BEEFY has no code on this chain"); require(sp1Verifier.code.length != 0, "SP1_VERIFIER has no code on this chain"); - SP1Beefy sp1Beefy = new SP1Beefy{salt: salt}(ISP1Verifier(sp1Verifier), sp1VerificationKey); + SP1Beefy sp1Beefy = new SP1Beefy{salt: salt}(ISP1Verifier(sp1Verifier), sp1VerificationKey, hyperbridgeParaId); ConsensusRouter consensusRouter = new ConsensusRouter{salt: salt}(IConsensusV2(address(sp1Beefy)), IConsensusV2(ecdsaBeefy)); diff --git a/evm/script/DeployHostUpdates.s.sol b/evm/script/DeployHostUpdates.s.sol index be534425c..854e2cff6 100644 --- a/evm/script/DeployHostUpdates.s.sol +++ b/evm/script/DeployHostUpdates.s.sol @@ -22,10 +22,12 @@ contract DeployScript is BaseScript { /// @notice Main deployment logic - called by BaseScript's run() functions /// @dev This function is called within a broadcast context function deploy() internal override { - // Deploy consensus clients - EcdsaBeefy ecdsaBeefy = new EcdsaBeefy{salt: salt}(); + // Deploy consensus clients. Hyperbridge's own para id is the one whose headers carry the + // bls commitment, which every client records whether or not it checks proofs with it. + uint256 hyperbridgeParaId = config.get("HYPERBRIDGE_PARA_ID").toUint256(); + EcdsaBeefy ecdsaBeefy = new EcdsaBeefy{salt: salt}(hyperbridgeParaId); SP1Verifier verifier = new SP1Verifier{salt: salt}(); - SP1Beefy sp1 = new SP1Beefy{salt: salt}(verifier, sp1VerificationKey); + SP1Beefy sp1 = new SP1Beefy{salt: salt}(verifier, sp1VerificationKey, hyperbridgeParaId); ConsensusRouter consensusClient = new ConsensusRouter{salt: salt}( IConsensusV2(sp1), IConsensusV2(ecdsaBeefy) diff --git a/evm/script/DeployIsmp.s.sol b/evm/script/DeployIsmp.s.sol index b6588e54c..242a91f5f 100644 --- a/evm/script/DeployIsmp.s.sol +++ b/evm/script/DeployIsmp.s.sol @@ -53,9 +53,10 @@ contract DeployScript is BaseScript { // Deploy SP1 ZK consensus client SP1Verifier verifier = new SP1Verifier{salt: salt}(); - SP1Beefy sp1Beefy = new SP1Beefy{salt: salt}(verifier, sp1VerificationKey); + uint256 hyperbridgeParaId = config.get("HYPERBRIDGE_PARA_ID").toUint256(); + SP1Beefy sp1Beefy = new SP1Beefy{salt: salt}(verifier, sp1VerificationKey, hyperbridgeParaId); // Deploy EcdsaBeefy naive consensus client - EcdsaBeefy ecdsaBeefy = new EcdsaBeefy{salt: salt}(); + EcdsaBeefy ecdsaBeefy = new EcdsaBeefy{salt: salt}(hyperbridgeParaId); // Deploy ConsensusRouter wrapping both consensus clients ConsensusRouter consensusRouter = new ConsensusRouter{salt: salt}( IConsensusV2(address(sp1Beefy)), IConsensusV2(address(ecdsaBeefy)) diff --git a/evm/src/consensus/BlsBeefy.sol b/evm/src/consensus/BlsBeefy.sol new file mode 100644 index 000000000..b06226c08 --- /dev/null +++ b/evm/src/consensus/BlsBeefy.sol @@ -0,0 +1,329 @@ +// Copyright (C) Polytope Labs Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. +pragma solidity ^0.8.17; + +import {IConsensusV2, IntermediateState, StateCommitment} from "@hyperbridge/core/interfaces/IConsensusV2.sol"; +import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import {Bytes} from "@polytope-labs/solidity-merkle-trees/src/trie/Bytes.sol"; +import {MerkleMountainRange} from "@polytope-labs/solidity-merkle-trees/src/MerkleMountainRange.sol"; +import {MerkleMultiProof} from "@polytope-labs/solidity-merkle-trees/src/MerkleMultiProof.sol"; +import {ScaleCodec} from "@polytope-labs/solidity-merkle-trees/src/trie/polkadot/ScaleCodec.sol"; + +import {Codec} from "./Codec.sol"; +import { + AuthoritySet, + AuthoritySetCommitment, + ApkDigest, + BlsApkBeefyConsensusProof, + BeefyConsensusState, + BlsApkRelayChainProof, + BeefyMmrLeaf, + Commitment, + Digest, + Header, + HeaderImpl, + Parachain, + ParachainProof, + PartialBeefyMmrLeaf +} from "./Types.sol"; + +interface IApkProof { + function verify( + uint256 publicKeysCommitment, + uint256[5] calldata bitlist, + bytes32[3] calldata apk, + bytes calldata apkProof, + bytes32[3] calldata message, + bytes32[3] calldata signature, + bytes32[6] calldata apk2 + ) external view; + + function hashToG1(bytes memory message) external view returns (bytes32[3] memory); +} + +/** + * @title BEEFY consensus verified by an aggregate public key proof + * @notice Verifies BEEFY finality without touching individual validator keys. + * + * @dev Naming each signer and proving their public key against the authority set's keyset root + * costs roughly 17k gas per signer, for the G2 addition and the compression needed to rebuild the + * leaf. At a few hundred validators that dominates everything else. + * + * Here a SNARK does that work instead. The prover shows that an aggregate public key corresponds + * to exactly the validators named in a bitlist, against a commitment to the whole set, and the + * contract checks one proof and one pairing. Nothing in the calldata or the verification grows + * with the number of signers. + * + * The commitment the proof is checked against does not come from the relay chain's MMR leaf, the + * way the keyset root does. Hyperbridge computes it over the relay's next authority set and + * publishes it in a header digest, so a client picks it up from a header it has already verified + * and carries it in its consensus state. That is what `AuthoritySet.blsPoseidonHash` holds, and + * why the state has to be seeded with the starting set's commitment at initialisation. + * + * Requires Prague for the EIP-2537 precompiles. + */ +contract BlsBeefy is IConsensusV2, ERC165 { + /// The payload id for the mmr root in a BEEFY commitment, "mh" + bytes2 public constant MMR_ROOT_PAYLOAD_ID = bytes2("mh"); + + /// The APK proof verifier, holding the circuit's verifying key. + IApkProof public immutable _apk; + + /// The parachain whose header digests carry apk commitments, which is hyperbridge. + /// + /// Every parachain in a proof is proven against the heads root, so a digest from any of them + /// is authentic; only this one's says anything about the relay chain's authorities. + uint32 public immutable _digestParaId; + + /// The commitment was signed by a set this client does not know. + error UnknownAuthoritySet(); + /// Fewer than two thirds of the set signed. + error SuperMajorityRequired(); + /// The APK proof or the aggregate signature did not verify. + error InvalidAggregateProof(); + /// The commitment carried no mmr root payload. + error MmrRootHashMissing(); + /// The mmr leaf was not in the tree the commitment attests to. + error InvalidMmrProof(); + /// A parachain header was not in the heads root. + error InvalidParachainHeaderProof(); + /// The authority set has no APK commitment yet, so no proof against it can be checked. + error MissingApkCommitment(); + + constructor(address apkProof, uint32 digestParaId) { + _apk = IApkProof(apkProof); + _digestParaId = digestParaId; + } + + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165) returns (bool) { + return interfaceId == type(IConsensusV2).interfaceId || super.supportsInterface(interfaceId); + } + + /// @dev IConsensusV2 entry point. + function verify(bytes calldata previousState, bytes calldata proof) + external + view + returns (bytes memory, IntermediateState[] memory, uint256) + { + BeefyConsensusState memory consensusState = abi.decode(previousState, (BeefyConsensusState)); + (BlsApkRelayChainProof memory relay, ParachainProof memory parachain) = + abi.decode(proof, (BlsApkRelayChainProof, ParachainProof)); + + // Replays are idempotent rather than reverting, matching the ecdsa client. + if (consensusState.latestHeight >= relay.commitment.blockNumber) { + return (abi.encode(consensusState), new IntermediateState[](0), consensusState.nextAuthoritySet.id); + } + + (BeefyConsensusState memory newState, bytes32 headsRoot) = verifyMmrUpdateProof(consensusState, relay); + (IntermediateState[] memory intermediates, ApkDigest memory digest) = + verifyParachainHeaderProof(headsRoot, parachain, _digestParaId); + + // Forward chaining: everything this client believes about the incoming set comes from + // here, its id, its size and its commitment together. A header naming the set after the + // one being waited on rolls the sets forward, since the relay has moved on. + // Both roots are filled in, not just the one this client checks against. The mmr leaf + // names the incoming set's ecdsa root and the digest names its poseidon hash, so a state + // this client advances stays usable by the ecdsa and sp1 clients too. + if (digest.setId > newState.nextAuthoritySet.id) { + newState.currentAuthoritySet = newState.nextAuthoritySet; + newState.nextAuthoritySet = AuthoritySet({ + id: digest.setId, + len: digest.len, + blsPoseidonHash: digest.commitment, + ecdsaMerkleRoot: relay.latestMmrLeaf.nextAuthoritySet.id == digest.setId + ? relay.latestMmrLeaf.nextAuthoritySet.root + : bytes32(0) + }); + } else if (digest.setId == newState.nextAuthoritySet.id && newState.nextAuthoritySet.blsPoseidonHash == 0) { + newState.nextAuthoritySet.len = digest.len; + newState.nextAuthoritySet.blsPoseidonHash = digest.commitment; + } + + return (abi.encode(newState), intermediates, newState.nextAuthoritySet.id); + } + + /// @dev Verify the signed mmr root, then roll the authority sets forward. + function verifyMmrUpdateProof(BeefyConsensusState memory trustedState, BlsApkRelayChainProof memory relayProof) + internal + view + returns (BeefyConsensusState memory, bytes32) + { + Commitment memory commitment = relayProof.commitment; + if ( + commitment.validatorSetId != trustedState.currentAuthoritySet.id + && commitment.validatorSetId != trustedState.nextAuthoritySet.id + ) { + revert UnknownAuthoritySet(); + } + + bool isCurrent = commitment.validatorSetId == trustedState.currentAuthoritySet.id; + AuthoritySet memory authoritySet = isCurrent ? trustedState.currentAuthoritySet : trustedState.nextAuthoritySet; + + // A set whose commitment has not been learned from a digest yet cannot be verified against. + // Reverting here is deliberate: silently accepting would mean checking the proof against a + // zero commitment. + if (authoritySet.blsPoseidonHash == 0) revert MissingApkCommitment(); + + verifySignedByApk(Codec.Encode(commitment), relayProof, authoritySet); + + uint256 payloadLength = commitment.payload.length; + bytes32 mmrRoot; + for (uint256 i = 0; i < payloadLength; i++) { + if (commitment.payload[i].id == MMR_ROOT_PAYLOAD_ID && commitment.payload[i].data.length == 32) { + mmrRoot = Bytes.toBytes32(commitment.payload[i].data); + } + } + if (mmrRoot == bytes32(0)) revert MmrRootHashMissing(); + + verifyMmrLeaf(trustedState, relayProof, mmrRoot); + + trustedState.latestHeight = commitment.blockNumber; + + return (trustedState, relayProof.latestMmrLeaf.extra); + } + + /** + * @notice Establish that a supermajority of `authoritySet` signed `encodedCommitment`. + * @dev Two things are checked, and the split matters. The SNARK proves that `apk` is the + * aggregate of exactly the validators set in `bitlist`, against the set's commitment. The + * pairing inside `ApkProof` then checks the aggregate signature against that key, and binds + * `apk2` to `apk` so a caller cannot supply an unrelated G2 point. The threshold is counted + * here, because the circuit proves who signed but has no opinion on whether that is enough. + */ + function verifySignedByApk( + bytes memory encodedCommitment, + BlsApkRelayChainProof memory relayProof, + AuthoritySet memory authoritySet + ) internal view { + uint256 signed = countSigners(relayProof.bitlist); + if (!checkParticipationThreshold(signed, authoritySet.len)) revert SuperMajorityRequired(); + + bytes32[3] memory message = _apk.hashToG1(encodedCommitment); + + // `verify` reverts on failure rather than returning false, so a successful call is the + // whole result. Wrapped so the reason surfaces as this contract's error. + try _apk.verify( + authoritySet.blsPoseidonHash, + relayProof.bitlist, + relayProof.apk, + relayProof.apkProof, + message, + relayProof.signature, + relayProof.apk2 + ) {} catch { + revert InvalidAggregateProof(); + } + } + + /// @dev Population count over the bitlist. Fixed cost regardless of how many signed, which is + /// the point of the whole scheme. + /// + /// The 1024 slots are packed 250 to a word across the first four and 24 into the last, which + /// is what the circuit decomposes with `ToBinary`. Bits above those ranges are constrained to + /// zero there, so a proof carrying any would not verify; masking them off here keeps the count + /// honest on its own terms rather than relying on that. + function countSigners(uint256[5] memory bitlist) internal pure returns (uint256 count) { + for (uint256 w = 0; w < 5; w++) { + uint256 width = w == 4 ? 24 : 250; + uint256 word = bitlist[w] & ((uint256(1) << width) - 1); + while (word != 0) { + word &= word - 1; + count++; + } + } + } + + /// @dev Two thirds plus one, matching the ecdsa client and substrate's own rule. + function checkParticipationThreshold(uint256 signed, uint256 total) internal pure returns (bool) { + return total > 0 && signed * 3 > total * 2; + } + + /// @dev The signed mmr root must attest to the leaf carrying the parachain heads. + function verifyMmrLeaf( + BeefyConsensusState memory trustedState, + BlsApkRelayChainProof memory relay, + bytes32 mmrRoot + ) internal pure { + bytes32 hash = keccak256( + Codec.Encode( + PartialBeefyMmrLeaf({ + version: relay.latestMmrLeaf.version, + parentNumber: relay.latestMmrLeaf.parentNumber, + parentHash: relay.latestMmrLeaf.parentHash, + nextAuthoritySet: relay.latestMmrLeaf.nextAuthoritySet, + extra: relay.latestMmrLeaf.extra + }) + ) + ); + uint256 leafCount = leafIndex(trustedState.beefyActivationBlock, relay.latestMmrLeaf.parentNumber) + 1; + + MerkleMountainRange.Leaf[] memory leaves = new MerkleMountainRange.Leaf[](1); + leaves[0] = MerkleMountainRange.Leaf({index: relay.latestMmrLeaf.leafIndex, hash: hash}); + + bool valid = MerkleMountainRange.VerifyProof(mmrRoot, relay.mmrProof, leaves, leafCount); + if (!valid) revert InvalidMmrProof(); + } + + /// @dev Verify the parachain headers against the heads root, and surface any APK commitment + /// they carry so the caller can roll it into the consensus state. + function verifyParachainHeaderProof(bytes32 headsRoot, ParachainProof memory proof, uint32 digestParaId) + internal + pure + returns (IntermediateState[] memory, ApkDigest memory digest) + { + uint256 len = proof.parachains.length; + MerkleMultiProof.Leaf[] memory leaves = new MerkleMultiProof.Leaf[](len); + IntermediateState[] memory intermediates = new IntermediateState[](len); + + for (uint256 i = 0; i < len; i++) { + Parachain memory para = proof.parachains[i]; + Header memory header = Codec.DecodeHeader(para.header); + + leaves[i] = MerkleMultiProof.Leaf( + para.index, + keccak256(bytes.concat(ScaleCodec.encode32(uint32(para.id)), ScaleCodec.encodeBytes(para.header))) + ); + + intermediates[i] = IntermediateState({ + stateMachineId: para.id, + height: header.number, + commitment: HeaderImpl.stateCommitment(header) + }); + + // Only the first commitment found is used; the pallet writes at most one per block. + // Read it from hyperbridge's header alone, or any parachain in the proof could name + // the keys this client trusts next. + if (digest.setId == 0 && para.id == digestParaId) { + digest = HeaderImpl.apkCommitment(header); + } + } + + if (len > 0) { + bool valid = MerkleMultiProof.VerifyProof(headsRoot, proof.proof, leaves, proof.leafCount); + if (!valid) revert InvalidParachainHeaderProof(); + } + + return (intermediates, digest); + } + + /// @dev Leaf index for a relay chain block, given where beefy was activated. + function leafIndex(uint256 activationBlock, uint256 parentNumber) internal pure returns (uint256) { + return activationBlock == 0 ? parentNumber : parentNumber - activationBlock; + } + + /// @dev Only here so the structs appear in the ABI, which is what the Rust bindings are + /// generated from. `verify` takes bytes, so without this they would be invisible. + function noOp(BeefyConsensusState memory s, BlsApkBeefyConsensusProof memory p) external pure {} +} diff --git a/evm/src/consensus/Codec.sol b/evm/src/consensus/Codec.sol index f402fdf66..aea30a06e 100644 --- a/evm/src/consensus/Codec.sol +++ b/evm/src/consensus/Codec.sol @@ -28,6 +28,8 @@ import "./Types.sol"; * and decode SCALE compact unsigned integers. */ library Codec { + using HeaderImpl for Header; + uint8 internal constant DIGEST_ITEM_OTHER = 0; uint8 internal constant DIGEST_ITEM_CONSENSUS = 4; uint8 internal constant DIGEST_ITEM_SEAL = 5; @@ -169,4 +171,39 @@ library Codec { } return (value); } + + /// The commitment hyperbridge published for `setId`, taken from whichever of these headers is + /// hyperbridge's own. Zero when none of them carries one for that set. + /// + /// Every client keeps both roots of an authority set, so a client that has no use for this one + /// still has to record it, or it strands whichever client does. + function blsPoseidonHash(ParachainHeader[] memory headers, uint64 setId, uint256 digestParaId) + internal + pure + returns (uint256) + { + for (uint256 i = 0; i < headers.length; i++) { + if (headers[i].id != digestParaId) continue; + return commitmentFor(headers[i].header, setId); + } + return 0; + } + + /// The same, for the proof shape that carries a merkle index alongside each header. + function blsPoseidonHash(Parachain[] memory parachains, uint64 setId, uint256 digestParaId) + internal + pure + returns (uint256) + { + for (uint256 i = 0; i < parachains.length; i++) { + if (parachains[i].id != digestParaId) continue; + return commitmentFor(parachains[i].header, setId); + } + return 0; + } + + function commitmentFor(bytes memory header, uint64 setId) private pure returns (uint256) { + ApkDigest memory digest = DecodeHeader(header).apkCommitment(); + return digest.setId == setId ? digest.commitment : 0; + } } diff --git a/evm/src/consensus/EcdsaBeefy.sol b/evm/src/consensus/EcdsaBeefy.sol index e0fb7d52c..a99e79ccd 100644 --- a/evm/src/consensus/EcdsaBeefy.sol +++ b/evm/src/consensus/EcdsaBeefy.sol @@ -29,6 +29,7 @@ import {Codec} from "./Codec.sol"; import { Header, HeaderImpl, + AuthoritySet, AuthoritySetCommitment, Vote, RelayChainProof, @@ -63,6 +64,15 @@ import { contract EcdsaBeefy is IConsensusV2, ERC165 { using HeaderImpl for Header; + /// The parachain whose header digests carry the bls commitment, which is hyperbridge. Only its + /// headers are read for one, since every parachain in a proof is equally authentic and only + /// this one speaks for the relay's authorities. + uint256 public immutable _digestParaId; + + constructor(uint256 digestParaId) { + _digestParaId = digestParaId; + } + // The PayloadId for the mmr root. bytes2 public constant MMR_ROOT_PAYLOAD_ID = bytes2("mh"); @@ -95,7 +105,7 @@ contract EcdsaBeefy is IConsensusV2, ERC165 { /// the updated state along with the latest authority set id. function verify(bytes calldata previousState, bytes calldata proof) external - pure + view returns (bytes memory, IntermediateState[] memory, uint256) { BeefyConsensusState memory consensusState = abi.decode(previousState, (BeefyConsensusState)); @@ -110,6 +120,21 @@ contract EcdsaBeefy is IConsensusV2, ERC165 { (BeefyConsensusState memory newState, bytes32 headsRoot) = verifyMmrUpdateProof(consensusState, relay); IntermediateState[] memory intermediates = verifyParachainHeaderProof(headsRoot, parachain); + // Rotating here rather than inside the mmr check, because the bls half of the set comes + // from a hyperbridge header and those are only trustworthy once proven against the heads + // root above. Both halves are recorded even though this client only uses one, so the + // state stays usable by the aggregate client. + AuthoritySetCommitment memory incoming = relay.latestMmrLeaf.nextAuthoritySet; + if (incoming.id > newState.nextAuthoritySet.id) { + newState.currentAuthoritySet = newState.nextAuthoritySet; + newState.nextAuthoritySet = AuthoritySet({ + id: incoming.id, + len: incoming.len, + blsPoseidonHash: Codec.blsPoseidonHash(parachain.parachains, incoming.id, _digestParaId), + ecdsaMerkleRoot: incoming.root + }); + } + return (abi.encode(newState), intermediates, newState.nextAuthoritySet.id); } @@ -135,7 +160,7 @@ contract EcdsaBeefy is IConsensusV2, ERC165 { } bool isCurrentAuthorities = commitment.validatorSetId == trustedState.currentAuthoritySet.id; - AuthoritySetCommitment memory authoritySet = + AuthoritySet memory authoritySet = isCurrentAuthorities ? trustedState.currentAuthoritySet : trustedState.nextAuthoritySet; if (!checkParticipationThreshold(sigLen, authoritySet.len)) revert SuperMajorityRequired(); @@ -158,14 +183,12 @@ contract EcdsaBeefy is IConsensusV2, ERC165 { MerkleMultiProof.Leaf({index: vote.authorityIndex, hash: keccak256(abi.encodePacked(authority))}); } - bool valid = MerkleMultiProof.VerifyProof(authoritySet.root, relayProof.proof, authorities, authoritySet.len); + bool valid = MerkleMultiProof.VerifyProof( + authoritySet.ecdsaMerkleRoot, relayProof.proof, authorities, authoritySet.len + ); if (!valid) revert InvalidAuthoritiesProof(); verifyMmrLeaf(trustedState, relayProof, mmrRoot); - if (relayProof.latestMmrLeaf.nextAuthoritySet.id > trustedState.nextAuthoritySet.id) { - trustedState.currentAuthoritySet = trustedState.nextAuthoritySet; - trustedState.nextAuthoritySet = relayProof.latestMmrLeaf.nextAuthoritySet; - } trustedState.latestHeight = latestHeight; return (trustedState, relayProof.latestMmrLeaf.extra); diff --git a/evm/src/consensus/SP1Beefy.sol b/evm/src/consensus/SP1Beefy.sol index d4266bcda..d25fea4f3 100644 --- a/evm/src/consensus/SP1Beefy.sol +++ b/evm/src/consensus/SP1Beefy.sol @@ -22,6 +22,7 @@ import {Codec} from "./Codec.sol"; import { Header, HeaderImpl, + AuthoritySet, AuthoritySetCommitment, BeefyConsensusState, MiniCommitment, @@ -66,9 +67,15 @@ contract SP1Beefy is IConsensusV2, ERC165 { // Genesis block should not be provided error IllegalGenesisBlock(); - constructor(ISP1Verifier v, bytes32 vk) { + /// The parachain whose header digests carry the bls commitment, which is hyperbridge. Only + /// its headers are read for one, since every parachain in a proof is equally authentic and + /// only this one speaks for the relay's authorities. + uint256 public immutable _digestParaId; + + constructor(ISP1Verifier v, bytes32 vk, uint256 digestParaId) { verifier = v; verificationKey = vk; + _digestParaId = digestParaId; } /** @@ -124,7 +131,7 @@ contract SP1Beefy is IConsensusV2, ERC165 { if (uint256(proof.mmrLeaf.parentNumber) + 1 != commitment.blockNumber) revert StaleMmrLeaf(); - AuthoritySetCommitment memory authority; + AuthoritySet memory authority; if (commitment.validatorSetId == trustedState.nextAuthoritySet.id) { authority = trustedState.nextAuthoritySet; } else if (commitment.validatorSetId == trustedState.currentAuthoritySet.id) { @@ -145,7 +152,7 @@ contract SP1Beefy is IConsensusV2, ERC165 { bytes memory publicInputs = abi.encode( PublicInputs({ authorities_len: authority.len, - authorities_root: authority.root, + authorities_root: authority.ecdsaMerkleRoot, headers: headers, block_number: commitment.blockNumber, leaf_hash: keccak256(Codec.Encode(proof.mmrLeaf)), @@ -169,7 +176,14 @@ contract SP1Beefy is IConsensusV2, ERC165 { if (proof.mmrLeaf.nextAuthoritySet.id > trustedState.nextAuthoritySet.id) { trustedState.currentAuthoritySet = trustedState.nextAuthoritySet; - trustedState.nextAuthoritySet = proof.mmrLeaf.nextAuthoritySet; + trustedState.nextAuthoritySet = AuthoritySet({ + id: proof.mmrLeaf.nextAuthoritySet.id, + len: proof.mmrLeaf.nextAuthoritySet.len, + blsPoseidonHash: Codec.blsPoseidonHash( + proof.headers, proof.mmrLeaf.nextAuthoritySet.id, _digestParaId + ), + ecdsaMerkleRoot: proof.mmrLeaf.nextAuthoritySet.root + }); } trustedState.latestHeight = commitment.blockNumber; diff --git a/evm/src/consensus/Types.sol b/evm/src/consensus/Types.sol index fcb3d025e..cc5800a55 100644 --- a/evm/src/consensus/Types.sol +++ b/evm/src/consensus/Types.sol @@ -85,6 +85,10 @@ struct Commitment { uint64 validatorSetId; } +/// A validator set as the relay chain describes it in an mmr leaf. +/// +/// The field widths are part of how the leaf is hashed into the mmr, so they follow the relay +/// rather than our own preference. [`AuthoritySet`] is the shape a client keeps in its state. struct AuthoritySetCommitment { /// Id of the set. uint64 id; @@ -94,6 +98,23 @@ struct AuthoritySetCommitment { bytes32 root; } +/// A validator set as a client keeps it, holding what every proof format needs to check a +/// signature from that set. +/// +/// One state serves all of them, so a client that advances the set fills in both roots rather than +/// only the one it uses. Leaving the other empty would strand whichever client relies on it until +/// something supplies it again. +struct AuthoritySet { + /// Id of the set. + uint256 id; + /// Number of validators in the set, which the threshold is taken against. + uint256 len; + /// Poseidon2 over the set's G1 keys, which an aggregate proof is checked against. + uint256 blsPoseidonHash; + /// Merkle root over the set's ecdsa keys, which a per signer proof is checked against. + bytes32 ecdsaMerkleRoot; +} + struct BeefyMmrLeaf { uint8 version; uint32 parentNumber; @@ -110,9 +131,9 @@ struct BeefyConsensusState { /// This should be the first block in the merkle-mountain-range tree. uint256 beefyActivationBlock; /// authorities for the current round - AuthoritySetCommitment currentAuthoritySet; + AuthoritySet currentAuthoritySet; /// authorities for the next round - AuthoritySetCommitment nextAuthoritySet; + AuthoritySet nextAuthoritySet; } struct PartialBeefyMmrLeaf { @@ -173,6 +194,48 @@ struct BeefyConsensusProof { ParachainProof parachain; } +// An authority set identified by its APK commitment rather than by a merkle root over keys. +// +// The commitment is Poseidon2 over the validators' BLS12-381 G1 keys, padded to the circuit's +// fixed width. Unlike the keyset root it does not come from the MMR leaf: hyperbridge publishes it +// in a header digest, and a client picks it up from a header it has already verified. That is why +// it is carried in the consensus state rather than supplied with each proof. +/// An apk commitment read off a header, with `setId` zero meaning the header carried none. +struct ApkDigest { + /// The authority set the commitment describes. + uint64 setId; + /// How many validators are in it, which is what the threshold is taken against. + uint32 len; + /// Poseidon2 commitment over that set's G1 public keys. + uint256 commitment; +} + +struct BlsApkRelayChainProof { + // A commitment to the finalized state + Commitment commitment; + // Which validators signed, one bit each, 1024 slots over five words + uint256[5] bitlist; + // Aggregate public key in G1, proven correct against the set's APK commitment + bytes32[3] apk; + // The same aggregate in G2, bound to `apk` by the pairing check inside ApkProof + bytes32[6] apk2; + // PLONK proof that `apk` is the aggregate of exactly the validators in `bitlist` + bytes apkProof; + // Sum of the signers' BLS signatures, a G1 point + bytes32[3] signature; + // Latest leaf added to mmr + BeefyMmrLeaf latestMmrLeaf; + // Proof for the latest mmr leaf + bytes32[] mmrProof; +} + +struct BlsApkBeefyConsensusProof { + // The proof items for the relay chain consensus + BlsApkRelayChainProof relay; + // Proof items for parachain headers + ParachainProof parachain; +} + struct DigestItem { bytes4 consensusId; bytes data; @@ -203,6 +266,8 @@ library HeaderImpl { bytes4 public constant ISMP_CONSENSUS_ID = bytes4("ISMP"); /// ConsensusID for the ISMP timestamp digest deposited by pallet-ismp bytes4 public constant ISMP_TIMESTAMP_ID = bytes4("ISTM"); + /// ConsensusID for the APK commitment digest deposited by pallet-beefy-apk-digest + bytes4 public constant APK_COMMITMENT_ID = bytes4("APKC"); error TimestampNotFound(); @@ -229,4 +294,37 @@ library HeaderImpl { return StateCommitment({timestamp: timestamp, overlayRoot: mmrRoot, stateRoot: childTrieRoot}); } + + /// @dev The commitment to the relay chain's next BEEFY authority set, if this header carries + /// one. Written by `pallet-beefy-apk-digest` on the block a set finishes being absorbed, so + /// most headers do not have it and `found` is false for those. + /// + /// The header itself is already authenticated, through the parachain heads root in the BEEFY + /// MMR leaf, so no further proof is needed: `commitment` can go straight to `ApkProof.verify` + /// as `publicKeysCommitment`, and `setId` says which authority set it describes. + /// + /// Payload is SCALE: a u64 set id little-endian, a u32 size, then the 32 byte commitment. + /// + /// A zero `setId` reads as absent. BEEFY numbers its sets from one, so nothing legitimate + /// names set zero, and the caller then has one thing to check rather than two. + function apkCommitment(Header memory self) internal pure returns (ApkDigest memory digest) { + for (uint256 j = 0; j < self.digests.length; j++) { + if (!self.digests[j].isConsensus) continue; + if (self.digests[j].consensus.consensusId != APK_COMMITMENT_ID) continue; + + bytes memory data = self.digests[j].consensus.data; + // Ignore a malformed item rather than reverting: a wrong length means some other + // producer wrote under this engine id, and the caller should see "absent", not fail. + if (data.length != 44) continue; + + uint64 setId = uint64(ScaleCodec.decodeUint256(Bytes.substr(data, 0, 8))); + if (setId == 0) continue; + + return ApkDigest({ + setId: setId, + len: uint32(ScaleCodec.decodeUint256(Bytes.substr(data, 8, 4))), + commitment: uint256(Bytes.toBytes32(Bytes.substr(data, 12))) + }); + } + } } diff --git a/evm/tests/foundry/ApkCommitmentDigest.t.sol b/evm/tests/foundry/ApkCommitmentDigest.t.sol new file mode 100644 index 000000000..6a9c397ca --- /dev/null +++ b/evm/tests/foundry/ApkCommitmentDigest.t.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.17; + +import {Test} from "forge-std/Test.sol"; +import {ApkDigest, Header, Digest, DigestItem, HeaderImpl} from "../../src/consensus/Types.sol"; + +/// The client side of `pallet-beefy-apk-digest`: reading the APK commitment out of a hyperbridge +/// header that a verifier has already authenticated through the BEEFY MMR's parachain heads root. +/// +/// The payload bytes here are the SCALE encoding the pallet produces, a little-endian u64 set id +/// followed by the 32 byte commitment. Both sides are pinned: `digest_payload_round_trips` in the +/// pallet asserts the same 8 + 32 layout. +contract ApkCommitmentDigestTest is Test { + using HeaderImpl for Header; + + bytes constant PAYLOAD_577 = + hex"4102000000000000020000000303030303030303030303030303030303030303030303030303030303030303"; + + function _header(Digest[] memory digests) internal pure returns (Header memory) { + return Header({ + parentHash: bytes32(0), + number: 1, + stateRoot: bytes32(0), + extrinsicRoot: bytes32(0), + digests: digests + }); + } + + function _consensus(bytes4 id, bytes memory data) internal pure returns (Digest memory d) { + d.isConsensus = true; + d.consensus = DigestItem({consensusId: id, data: data}); + } + + function _preRuntime(bytes4 id) internal pure returns (Digest memory d) { + d.isPreRuntime = true; + d.preruntime = DigestItem({consensusId: id, data: hex"0102"}); + } + + function test_reads_the_commitment_the_pallet_wrote() public view { + Digest[] memory digests = new Digest[](1); + digests[0] = _consensus(HeaderImpl.APK_COMMITMENT_ID, PAYLOAD_577); + + ApkDigest memory digest = _header(digests).apkCommitment(); + assertEq(digest.setId, 577, "set id decoded wrong; check little-endian"); + assertEq(digest.len, 2, "set size decoded wrong"); + assertEq(digest.commitment, uint256(0x0303030303030303030303030303030303030303030303030303030303030303)); + } + + /// The normal case: a header carrying aura's items alongside ours. + function test_finds_it_among_other_digests() public view { + Digest[] memory digests = new Digest[](3); + digests[0] = _preRuntime(bytes4("aura")); + digests[1] = _consensus(HeaderImpl.APK_COMMITMENT_ID, PAYLOAD_577); + digests[2] = _preRuntime(bytes4("aura")); + + ApkDigest memory digest = _header(digests).apkCommitment(); + assertEq(digest.setId, 577); + } + + /// Most headers do not complete a set, and that must read as absent rather than revert. + function test_absent_on_a_header_without_one() public view { + Digest[] memory digests = new Digest[](1); + digests[0] = _preRuntime(bytes4("aura")); + + ApkDigest memory digest = _header(digests).apkCommitment(); + assertEq(digest.setId, 0, "no digest, so the set id stays zero"); + assertEq(digest.commitment, 0); + } + + /// Another engine's consensus item must not be mistaken for ours. Same digest variant, so the + /// engine id is the only thing separating them. + function test_ignores_another_engines_consensus_item() public view { + Digest[] memory digests = new Digest[](1); + digests[0] = _consensus(bytes4("ISMP"), PAYLOAD_577); + + assertEq(_header(digests).apkCommitment().setId, 0, "an ISMP digest was read as an APK commitment"); + } + + /// A wrong-length payload under our engine id is skipped rather than decoded into garbage. + function test_malformed_payload_reads_as_absent() public view { + Digest[] memory digests = new Digest[](1); + digests[0] = _consensus(HeaderImpl.APK_COMMITMENT_ID, hex"4102000000000000"); + + assertEq(_header(digests).apkCommitment().setId, 0, "a truncated payload should not decode"); + } +} diff --git a/evm/tests/foundry/Beefy.sol b/evm/tests/foundry/Beefy.sol index ba41b03a1..81df94347 100644 --- a/evm/tests/foundry/Beefy.sol +++ b/evm/tests/foundry/Beefy.sol @@ -24,7 +24,8 @@ contract BeefyConsensusClientTest is Test { EcdsaBeefy internal beefy; function setUp() public virtual { - beefy = new EcdsaBeefy(); + // Hyperbridge is para 4009 on the chain these fixtures came from. + beefy = new EcdsaBeefy(4009); } function VerifyV2(bytes calldata trustedConsensusState, bytes calldata proof) diff --git a/evm/tests/foundry/BlsBeefy.t.sol b/evm/tests/foundry/BlsBeefy.t.sol new file mode 100644 index 000000000..9fe960a8f --- /dev/null +++ b/evm/tests/foundry/BlsBeefy.t.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.30; + +import {Test, console} from "forge-std/Test.sol"; +import {IntermediateState} from "@hyperbridge/core/interfaces/IConsensusV2.sol"; + +import {BlsBeefy} from "../../src/consensus/BlsBeefy.sol"; +import {BeefyConsensusState} from "../../src/consensus/Types.sol"; +import {ApkProof} from "@gnark-apk-proofs/ApkProof.sol"; +import {PlonkVerifier} from "@gnark-apk-proofs/PlonkVerifier.sol"; + +/** + * @title BEEFY verified through an aggregate public key proof. + * + * @notice The fixtures come from `bls_apk_live_inputs` and `bls_apk_live_fixture` in the Rust + * verifier, plus a PLONK proof generated by gnark-apk-proofs for the same validator set. Every + * part is real: the commitment was signed by a running relay's paired ecdsa_bls381 validators, the + * MMR proof has actual depth, and the parachain header is a registered para's. + * + * The point of this test is the gas number, and it is only comparable to `BlsBeefyTest`'s + * `test_verify_live_proof` because both run the whole client over live data in the same harness. + * + * Needs EIP-2537: + * FOUNDRY_PROFILE=bls forge test --match-contract BlsBeefyTest -vv --gas-report + */ +contract BlsBeefyTest is Test { + BlsBeefy internal client; + + function setUp() public { + // False selects the basic ciphersuite, which is what substrate's BEEFY signs with. PoP + // would hash the same message to a different point and fail with nothing to explain why. + ApkProof apk = new ApkProof(address(new PlonkVerifier())); + // The fixture's proof carries para 4009's header, which is where its digest comes from. + client = new BlsBeefy(address(apk), 4009); + } + + function _state() internal view returns (bytes memory) { + return vm.parseBytes(vm.readFile("tests/foundry/fixtures/bls-apk-beefy-state.hex")); + } + + function _proof() internal view returns (bytes memory) { + return vm.parseBytes(vm.readFile("tests/foundry/fixtures/bls-apk-beefy-proof.hex")); + } + + /// A complete consensus update: SNARK over the aggregate key, the batched pairing that binds + /// apk2 and checks the signature, MMR leaf inclusion, and the parachain header proof. + function test_verify_live_proof() public view { + (bytes memory newStateBytes, IntermediateState[] memory intermediates,) = client.verify(_state(), _proof()); + + BeefyConsensusState memory newState = abi.decode(newStateBytes, (BeefyConsensusState)); + BeefyConsensusState memory oldState = abi.decode(_state(), (BeefyConsensusState)); + + assertGt(newState.latestHeight, oldState.latestHeight, "height should advance"); + assertEq(intermediates.length, 1, "should finalize the registered parachain"); + assertEq(intermediates[0].stateMachineId, 4009, "should be para 4009"); + assertGt(intermediates[0].height, 0, "parachain height should be non-zero"); + } + + /// Isolates the cost of the update itself, so the number can be compared with the merkle + /// client's without the calldata and fixture reading counted in. + function test_gas_verify_live_proof() public view { + bytes memory state = _state(); + bytes memory proof = _proof(); + + uint256 before = gasleft(); + client.verify(state, proof); + uint256 used = before - gasleft(); + + console.log("BlsBeefy.verify gas:", used); + } + + /// Replaying a proof the state has already passed is a no-op rather than a revert, matching the + /// merkle client. + function test_stale_proof_is_a_noop() public view { + (bytes memory advanced,,) = client.verify(_state(), _proof()); + + (bytes memory again, IntermediateState[] memory intermediates,) = client.verify(advanced, _proof()); + + assertEq(again, advanced, "state should be unchanged"); + assertEq(intermediates.length, 0, "a stale proof finalizes nothing"); + } +} diff --git a/evm/tests/foundry/SP1BeefyForkTest.sol b/evm/tests/foundry/SP1BeefyForkTest.sol index 05ee80aaf..a846b18c2 100644 --- a/evm/tests/foundry/SP1BeefyForkTest.sol +++ b/evm/tests/foundry/SP1BeefyForkTest.sol @@ -57,7 +57,7 @@ contract SP1BeefyForkTest is Test { // Reuse the live SP1 verifier from the production deployment, but with the new vkey. ISP1Verifier verifier = SP1Beefy(SP1_BEEFY).verifier(); - SP1Beefy beefy = new SP1Beefy(verifier, VKEY); + SP1Beefy beefy = new SP1Beefy(verifier, VKEY, 4009); (bytes memory newStateEnc, IntermediateState[] memory intermediates, uint256 nextAuthId) = beefy.verify(previousState, proof); diff --git a/evm/tests/foundry/SP1BeefyTest.sol b/evm/tests/foundry/SP1BeefyTest.sol index cfa18bd6b..0f09239bd 100644 --- a/evm/tests/foundry/SP1BeefyTest.sol +++ b/evm/tests/foundry/SP1BeefyTest.sol @@ -27,12 +27,12 @@ contract SP1BeefyTest is Test { function setUp() public virtual { sp1 = new SP1Verifier(); - beefy = new SP1Beefy(sp1, verificationKey); + beefy = new SP1Beefy(sp1, verificationKey, 4009); } function testDecodeConsensusState() public pure { bytes memory encodedState = - hex"0000000000000000000000000000000000000000000000000000000001771a6a00000000000000000000000000000000000000000000000000000000012a5318000000000000000000000000000000000000000000000000000000000000083a00000000000000000000000000000000000000000000000000000000000001f442c444bf993527f25cdeb8cca93b6632fbcacb30cf3e037748e5ca8f39ef9ade000000000000000000000000000000000000000000000000000000000000083b00000000000000000000000000000000000000000000000000000000000001f442c444bf993527f25cdeb8cca93b6632fbcacb30cf3e037748e5ca8f39ef9ade"; + hex"0000000000000000000000000000000000000000000000000000000001771a6a00000000000000000000000000000000000000000000000000000000012a5318000000000000000000000000000000000000000000000000000000000000083a00000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000000000000000000000000000000000000000000042c444bf993527f25cdeb8cca93b6632fbcacb30cf3e037748e5ca8f39ef9ade000000000000000000000000000000000000000000000000000000000000083b00000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000000000000000000000000000000000000000000042c444bf993527f25cdeb8cca93b6632fbcacb30cf3e037748e5ca8f39ef9ade"; BeefyConsensusState memory consensusState = abi.decode(encodedState, (BeefyConsensusState)); @@ -44,14 +44,14 @@ contract SP1BeefyTest is Test { console.log("currentAuthoritySet.len: "); console.log(consensusState.currentAuthoritySet.len); console.log("currentAuthoritySet.root: "); - console.logBytes32(consensusState.currentAuthoritySet.root); + console.logBytes32(consensusState.currentAuthoritySet.ecdsaMerkleRoot); console.log("nextAuthoritySet.id: "); console.log(consensusState.nextAuthoritySet.id); console.log("nextAuthoritySet.len: "); console.log(consensusState.nextAuthoritySet.len); console.log("nextAuthoritySet.root: "); - console.logBytes32(consensusState.nextAuthoritySet.root); + console.logBytes32(consensusState.nextAuthoritySet.ecdsaMerkleRoot); } function skip_testPolkadotVerifier() public view { @@ -79,7 +79,7 @@ contract SP1BeefyTest is Test { // proof was generated against sp1-beefy v1.0.0. The proof is an ABI-encoded SP1BeefyProof // (abi_encode_params output; no outer offset). function sp1BeefyFixture() internal pure returns (bytes memory state, bytes memory proof) { - state = hex"0000000000000000000000000000000000000000000000000000000001df6bd100000000000000000000000000000000000000000000000000000000012a5318000000000000000000000000000000000000000000000000000000000000136a00000000000000000000000000000000000000000000000000000000000002582cd28e2a83ddf10dbcc7da45533a44c70d5bc52be1868649ab8c30f7ec6dc741000000000000000000000000000000000000000000000000000000000000136b00000000000000000000000000000000000000000000000000000000000002582cd28e2a83ddf10dbcc7da45533a44c70d5bc52be1868649ab8c30f7ec6dc741"; + state = hex"0000000000000000000000000000000000000000000000000000000001df6bd100000000000000000000000000000000000000000000000000000000012a5318000000000000000000000000000000000000000000000000000000000000136a000000000000000000000000000000000000000000000000000000000000025800000000000000000000000000000000000000000000000000000000000000002cd28e2a83ddf10dbcc7da45533a44c70d5bc52be1868649ab8c30f7ec6dc741000000000000000000000000000000000000000000000000000000000000136b000000000000000000000000000000000000000000000000000000000000025800000000000000000000000000000000000000000000000000000000000000002cd28e2a83ddf10dbcc7da45533a44c70d5bc52be1868649ab8c30f7ec6dc741"; proof = hex"0000000000000000000000000000000000000000000000000000000001df6bd9000000000000000000000000000000000000000000000000000000000000136a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001df6bd8b06c82d25b39550a06ab64cf89004fce1f913b27190ab108320812295591fa89000000000000000000000000000000000000000000000000000000000000136b00000000000000000000000000000000000000000000000000000000000002582cd28e2a83ddf10dbcc7da45533a44c70d5bc52be1868649ab8c30f7ec6dc741ed96e512661b155ef81e590ca5ad1bacf2ccce06e7e822ca521daa71efb4ff91000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000003608eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000d2700000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000139557ed2657ce1e450327c6006e17e64425bb2154a7e6a55514e3d37fc7fd5d9884697790283bf3e632f74afab019365ed730a6deb0bc3e70bb229635fcf769d28febdf61f520234bde985bfdd18c0b04baf50ddae8f48860b9546184888ce393886265396140661757261209441d70800000000045250535290b43da1ab3f398f7008b0bd1374925ba70102ff77f33c1acce60e98a4e40fb8cf56af7d070449534d500101af5c78d7d0420a25ee6b68dc946d9919da3799b923cff420aa27ab1b646f355794a54ad343c04bc95bb013d63caecc98e97b65edb739cacc2c6e97f7d5aba5c9044953544d20f612176a000000000561757261010162f8da99803bec263b758f801ed06717d9af6178ac74e3c5ecba6e9cf6ab5c33e4daf9b75a18945bc3bb2221e1aceef06b67c87a0c6756872789dffbcd747b810000000000000000000000000000000000000000000000000000000000000000000000000001644388a21c0000000000000000000000000000000000000000000000000000000000000000002f850ee998974d6cc00e50cd0814b098c05bfade466d28573240d057f2535200000000000000000000000000000000000000000000000000000000000000002607774c88245bcad79f2414d5829f9b61771e86fb92366a5d224ba9a42cea9b16e91bc69ca90c8f455e8973ca2d522e460b371e95a8cdd298e00558bb25e39c1046ac2fb71dfc17f57e39ae5309c9522d97cc181e836aa679be1c168e25180b1556c8c21b6537ff21a57ecb73d497301f5fc9fe8f8d312d03720e401684da5e16440efe811bc61f2bfa210171efc4745d1b7461ce5593c8bcd9a9b2f6489f6e0c8cfbf59f1489e3e9a93143084cd57df0bf06cbf1fce9a7098154abcfb984a90fd4708053142c7043ce767492db2f5c0055f8791ef0cfb31173e9cac6ab47600e3953c2efe616bbd960b6048026dd1e0bb8bba4a29f3ccdb5ac21ce3899751300000000000000000000000000000000000000000000000000000000"; } diff --git a/evm/tests/foundry/fixtures/apk-verifying-key.bin b/evm/tests/foundry/fixtures/apk-verifying-key.bin new file mode 100644 index 000000000..0493d494d Binary files /dev/null and b/evm/tests/foundry/fixtures/apk-verifying-key.bin differ diff --git a/evm/tests/foundry/fixtures/bls-apk-beefy-proof.hex b/evm/tests/foundry/fixtures/bls-apk-beefy-proof.hex new file mode 100644 index 000000000..ac01ff318 --- /dev/null +++ b/evm/tests/foundry/fixtures/bls-apk-beefy-proof.hex @@ -0,0 +1 @@ +0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000ac00000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000478237b877864af98fe7a2d05f4cc0d78eb019d094da6d70c242f95609ee577c42ca62abc23bb3594dc8f5a3516e5c610cbe3f263a5dc33d9d8d0bc631567cf630c4df86e4ad78887f62f46e1e0daa52bd58eee1613cf211518e561b582b0990868ddaca3321f5ac0f7c57e4bf34b86726e874e5ae4476a08f4c74c21fcb4a9ee94ee8572a6fb82afba59f47745e9d51436fc8946294306c5448880df4c41f82a136985e8963070f035906bcc766f9e9125d3ea66cbc1b293ca2ef37a6c581812dd734fad4bd53feef8ac36b393bfd78ba789203a056f9cafcfc91e8e9afc5a64b724f54d18e983996397faa28bf63713d81c18fec214be9a4536400258d08c9f34acb58680d7b7150f23aadd0e10da0d1e7dfe476a2029a700545fe7a1001000000000000000000000000000000000000000000000000000000000000004a00cfc1b98cbe231e410d38586056db0380342d3924132f5558fca47f114b115ce069141a10715c776f6803816dda0aa3e0b19b325ee82a185ef5444cc550210e36a1a195b179246a74d6bba640579206f732b8204486f05f87794b58271f6854c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ddc0be2e7e8ce0baf653c480d6ed7849a0979b9aa841c21e6b6b0057a3eb44df4d460000000000000000000000000000000000000000000000000000000000000d600000000000000000000000000000000000000000000000000000000000000002d1cbb453472425036cef3caa539a0e81c6bd9fd9c3f24de7b38867380a19cafb5e8a8e4891aeb85777b580c2069c95bdc7a3f5630298d758f1ff6f9726741ebe000000000000000000000000000000000000000000000000000000000000ddc000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000ddc10000000000000000000000000000000000000000000000000000000000000d5f000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000206d68000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000201ef1c9fbd3f1f3a0ef08bab0caad7d04727dd985d5de2ba02e533d8e74fbbcc000000000000000000000000000000000000000000000000000000000000004a0070c46495a760b79d5461897a1aba3fc131f3e4b6fe427beaada6bf1615fae618da38715385099a33ac9ac90cc32b7bf008e3727f6b4efd1b6fded44a5ea4ab9ab001ab8beae65834ac7eb1bb154ce23c723bb4818ea2061fb3f37179d05b3061465de909840a93df73cddc8a01bc353f70137dd88f44d8586ea3bd346ca8606bfff2b143ecf874136e8b2ee6bb7ba1618cdfe20106e73de00e03ed4d77344d391da0990999c9e8e38a0f3c42be960246f7524a32abe6d0e4ddcd7699d29b9ac10b47bf8ca31ed34be97bd48c4b89c3011a85b41e04f2f606d46501991b06176f6b349c338c47168c37663aea1e5d6ff0b25721afc71e6624cdff91299d02d077877bdc08310ad6a3160ab34bbd55472c804096041375ca8474099303443ef5704004f1dd0f00b85667ca370e04e23c51cbcfd8a5adaeb674d363415d72669ecb182a65ebdc40c35daa0efc2f6e6a97309c84232e04e599be9c800717ba8b463c38dd1cf40bf9c8a745e86294e6cdb87757de4966ca2d11cf58c226231cdcfd604e95ec39478bd8d31e20be5bd70a7f0330909ea875c3a9bc4520d4656c5b2a798bf71bbf3ed9e6f002a147d9d2025310d6b1f539b3850bd1f93d3751123c671bdc7f204885a608ba53761c53dcf462042523681e05605bf62f03dbd4798fffd0831602cd7f808faea724b56c4273281ec0f5d12b7efd95d18e182393266c1d1e4ccc119c6d03628d7d162b23c5e58f7129a3f6c044d89674f4406ab51f7cdb030ce47b9d0f540e7150411802535308ac23188c1af4b05400932923e5648dc0b58cca4d61736bba4b64112c17fb1391bbaf97e971c2072eb9a840274a53eb30c39f14d302fc04d60e858130b34a33a123726a11e1ba15cf816a58a1c1dae7ddc41f75a4d6b0abef7d5a650f42758566fea8918096351372a0e5ecffce47840d20bc8ecd0cb520d70a52257bbc0d1e9142eefe3c7e8eab128b7ad82d60cdc75405f01ec4abfa624e6f8a14e781f1bfb76963183b56738595d8680b9cd18ad80cf08ba3614153b1d19669ab8611c5b87c0081711be76021834804a3d0aed23ce364e92535c74093ee74590aa25b31a5f691353d76a34dcefda72842fc8d6fdb097e3e93dc775ce13418fb687985473b87c90b4c7647e990c8d61e6b7924c835acb585f3148ad13a4d266c87d968dbc829ea55c746ade51e7bce0f512cf837b8dba145bc0f01ad88d7b4575fec9e334c4447bef5f1e380a6fa89b999552d741d244dec70ca4b0bc5c032a0f501786f35b1701eb96566e4bf0b9cc08494f16efe35fe84cda2eac02c0404706054140551c1f99e54cb68d6d0ddb6367529fb9e90d68107c3b7376472d65114bb62b7ff166e1d62e57224ffd50ecc0aaca92a2e295c7b94dbf1e008f8da78589f8f11361f71c0759506845a0d34dfd9587de815ab1987d61548a73ded3be5e34221d5e6d23adc92491144e05811c4903a679aa495fce087d6e6e68500c6d0a8596545f602e5f361a06ce737cd48d64a63f54a4aae14b0abf4e8b1b6dd71e74b7f5a97b866dc2326f62ecdc26ef5444f31a32ec0493c8455ea0e2f5a7a28a098bd39d2b1170b20801c3c3e3b426ebe02eaa30b2383fa28392258e75786995c9d7aa79c56385af76a749a327b0921d408881f50db824a200000000000000000000000000000000000000000000000000000000000000081f78d05de33bffdf649b225755be3ae579b9fe2c1ae2e73295ee3c1904749320ca25bfe03fb0fedfccfaf1f200f3b4e22a5ddbd0293584456e1a92ea1ec2ff4cd7eb776484949cc240142bd6508a5464a38c83ff4a633e7626b87468c17ed23803c783cc3af7700fc480092dadd7adeeb06516bd3b4a9b332af85ca9b43520015272786b444bcb3103b6d70a2d86207d873ba3798546f547fd5f7c8e5a2f730023eefd36e7bed259a0c0d81482a73634f7de9e05cfa7ec3a71a0bf51da2ff270e1083a4f191b947f14ae84cde920ef9b9dde8d51ed81e66f60392c46144424d2e4209d7f8db235d524eec7b11cee2e04060c1a6e246cf1e5a26add7604f15271000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fa90000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000016b5191bd2000ca420ee55c8aa61d09e62e33583c55f7e195638168831480525fcf46c60100693a7866d50c3a7048b2b8986cfd0b08cfa08bdec6822ed019afbaef8f8b79f71f3419409caf222892e56b0abc324eb232ec626e000df4cc1bc02bee948e933418066175726120b391c0110000000004525053529059f517784713bb59c71fc8b481358a2e92075ccb6e2827a0b91aebda0891f5f1ee7603000449534d5001013ebf160e4a9178592e46584385a6eaa86d06c7f4d6a1fe9ca8976fbe379286199781e23ba03a6c017ebe6dfd9d23459b90aa76e95d60837ab7ef0177f468bdd8044953544d20326a836a000000000441504b43b05f0d0000000000000200000013ba45ba08c2f9519d727dbefbc799cc1396a6c03f6438003c2c3950cab05e25056175726101019e9bdb3e47a71e2da61b0d7fd1f98794a56c3569d377162c21bbbc184de9c462390cdbbf03a8b3723a1cd5d98772f6d00226ea267209b664a8727b14627c25820000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 \ No newline at end of file diff --git a/evm/tests/foundry/fixtures/bls-apk-beefy-state.hex b/evm/tests/foundry/fixtures/bls-apk-beefy-state.hex new file mode 100644 index 000000000..1473b463e --- /dev/null +++ b/evm/tests/foundry/fixtures/bls-apk-beefy-state.hex @@ -0,0 +1 @@ +0x000000000000000000000000000000000000000000000000000000000000ddbd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d5e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d5f000000000000000000000000000000000000000000000000000000000000000213ba45ba08c2f9519d727dbefbc799cc1396a6c03f6438003c2c3950cab05e250000000000000000000000000000000000000000000000000000000000000000 \ No newline at end of file diff --git a/modules/consensus/beefy/primitives/Cargo.toml b/modules/consensus/beefy/primitives/Cargo.toml index e4d2f7deb..3bb0c556a 100644 --- a/modules/consensus/beefy/primitives/Cargo.toml +++ b/modules/consensus/beefy/primitives/Cargo.toml @@ -23,6 +23,7 @@ features = [ "sp-core", "sp-consensus-beefy", "sp-mmr-primitives", + "sp-runtime", ] [features] diff --git a/modules/consensus/beefy/primitives/src/lib.rs b/modules/consensus/beefy/primitives/src/lib.rs index cf0609d2d..9a495a1ee 100644 --- a/modules/consensus/beefy/primitives/src/lib.rs +++ b/modules/consensus/beefy/primitives/src/lib.rs @@ -35,9 +35,9 @@ pub struct ConsensusState { /// Latest mmr root hash pub mmr_root_hash: H256, /// Authorities for the current session - pub current_authorities: BeefyAuthoritySet, + pub current_authorities: AuthoritySet, /// Authorities for the next session - pub next_authorities: BeefyAuthoritySet, + pub next_authorities: AuthoritySet, } /// Hash length definition for hashing algorithms used @@ -126,6 +126,219 @@ pub const PROOF_TYPE_NAIVE: u8 = 0x00; /// Proof type identifier for SP1 ZK proofs pub const PROOF_TYPE_SP1: u8 = 0x01; +/// Proof type identifier for aggregate public key proofs +pub const PROOF_TYPE_APK: u8 = 0x02; + +/// Size of a compressed BLS12-381 G1 point, the group BEEFY signatures live in. +pub const BLS_G1_SIGNATURE_LEN: usize = 48; + +/// Size of a compressed BLS12-381 G2 point, the group BEEFY public keys live in. +pub const BLS_G2_PUBLIC_KEY_LEN: usize = 96; + +/// Wire size of a paired `ecdsa_bls_crypto` BEEFY key: `ecdsa(33) || G1(48) || G2(96)`. +pub const PAIRED_AUTHORITY_LEN: usize = 33 + BLS_G1_SIGNATURE_LEN + BLS_G2_PUBLIC_KEY_LEN; + +/// Where the BLS halves start, after the ECDSA key. +const PAIRED_G1_OFFSET: usize = 33; +const PAIRED_G2_OFFSET: usize = PAIRED_G1_OFFSET + BLS_G1_SIGNATURE_LEN; + +/// A BEEFY authority key on a relay chain that uses paired `ecdsa_bls_crypto`, exactly as the +/// relay stores it. +/// +/// `DoublePublicKey` publishes the same secret in both BLS groups, so a validator has a G1 and a +/// G2 half describing one key. BEEFY verifies signatures against the G2 half while an APK proof +/// aggregates the G1 halves, which is why both accessors exist. +#[derive(Clone, sp_std::fmt::Debug, PartialEq, Eq, Encode, Decode)] +pub struct PairedAuthority(pub [u8; PAIRED_AUTHORITY_LEN]); + +impl PairedAuthority { + /// The compressed G1 half. + pub fn g1(&self) -> [u8; BLS_G1_SIGNATURE_LEN] { + let mut out = [0u8; BLS_G1_SIGNATURE_LEN]; + out.copy_from_slice(&self.0[PAIRED_G1_OFFSET..PAIRED_G2_OFFSET]); + out + } + + /// The compressed G2 half. + pub fn g2(&self) -> [u8; BLS_G2_PUBLIC_KEY_LEN] { + let mut out = [0u8; BLS_G2_PUBLIC_KEY_LEN]; + out.copy_from_slice(&self.0[PAIRED_G2_OFFSET..PAIRED_AUTHORITY_LEN]); + out + } +} + +/// Size of a curve point as the APK circuit's verifier takes it: raw big-endian coordinates, 48 +/// bytes each, with no padding. Not the EIP-2537 layout. +pub const APK_G1_LEN: usize = 96; +/// The same for G2, four coordinates. +pub const APK_G2_LEN: usize = 192; +/// The participation bitlist, one bit per validator slot. +pub const APK_BITLIST_WORDS: usize = 5; + +/// `Beefy::NextAuthorities` on a relay chain. +/// +/// The *next* set, not the current one. A client verifying under set N learns the commitment for +/// set N+1, which is what lets it verify the update after a rotation. Note this is not +/// `well_known_keys::NEXT_AUTHORITIES`, which is Babe's: both end in `twox_128("NextAuthorities")` +/// and only the pallet prefix differs. +pub const RELAY_BEEFY_NEXT_AUTHORITIES: [u8; 32] = [ + 0x08, 0xc4, 0x19, 0x74, 0xa9, 0x7d, 0xbf, 0x15, 0xcf, 0xbe, 0xc2, 0x83, 0x65, 0xbe, 0xa2, 0xda, + 0xaa, 0xcf, 0x00, 0xb9, 0xb4, 0x1f, 0xda, 0x7a, 0x92, 0x68, 0x82, 0x1c, 0x2a, 0x2b, 0x3e, 0x4c, +]; + +/// Engine id of the digest item carrying an authority set's APK commitment. +pub const APK_ENGINE_ID: [u8; 4] = *b"APKC"; + +/// The payload hyperbridge writes into a header digest once it has hashed an authority set. +/// +/// This is a wire format shared by the runtime that writes it and every verifier that reads it. A +/// client that has already authenticated a hyperbridge header, through the parachain heads root in +/// the BEEFY MMR leaf, can take the commitment straight out of that header with no further proof. +#[derive(Clone, sp_std::fmt::Debug, PartialEq, Eq, Encode, Decode)] +pub struct ApkCommitmentDigest { + /// The BEEFY validator set the keys belong to, which is the relay's current set id plus one, + /// since the commitment describes the next set. + pub set_id: u64, + /// How many validators are in that set, which is what the threshold is taken against. + /// + /// Carried here rather than read from the mmr leaf so everything a client believes about the + /// incoming set comes from one authenticated place. Taking the size from the leaf and the + /// commitment from here would leave the two able to describe different sets. + pub len: u32, + /// Poseidon2 over the set's G1 keys, padded to the circuit width with the identity point. + pub commitment: [u8; 32], +} + +impl ApkCommitmentDigest { + /// Pull the commitment out of a header's digest logs, if one is there. + /// + /// Returns the first matching item, since at most one is ever written per block. A malformed + /// payload reads as absent rather than as an error, on the grounds that some other producer + /// wrote under this engine id and the honest answer is that there is no commitment here. + pub fn find_in(digest: &sp_runtime::generic::Digest) -> Option { + digest.logs().iter().find_map(|log| match log { + sp_runtime::DigestItem::Consensus(id, payload) if *id == APK_ENGINE_ID => + Self::decode(&mut &payload[..]).ok(), + _ => None, + }) + } +} +/// A validator set as a client keeps it, holding what every proof format needs to check a +/// signature from that set. +/// +/// One state serves all of them, so a client that advances the set fills in both roots rather than +/// only the one it uses. Leaving the other empty would strand whichever client relies on it until +/// something supplies it again. This is not the mmr leaf's authority set, whose shape belongs to +/// the relay chain. +#[derive(Clone, sp_std::fmt::Debug, PartialEq, Eq, Encode, Decode, Default)] +pub struct AuthoritySet { + /// Id of the set + pub id: u64, + /// Number of validators in the set, which the threshold is taken against + pub len: u32, + /// Poseidon2 over the set's G1 keys, zero until a header digest supplies it + pub bls_poseidon_hash: H256, + /// Merkle root over the set's ecdsa keys, as the mmr leaf names it + pub ecdsa_merkle_root: H256, +} + +impl ConsensusState { + /// What this state becomes once a verified header names an authority set, if anything. + /// + /// A header naming the set after the one being waited on rolls the sets forward: the relay has + /// moved on, so the current set is the one that was next. A header naming a set already held + /// fills in its commitment if it has none, which is what a client waiting on the next set is + /// looking for. Anything else, including a set already carrying one, returns `None`, so the + /// same digest arriving in several headers changes nothing. + pub fn with_bls_commitment( + &self, + set_id: u64, + len: u32, + commitment: H256, + ecdsa_merkle_root: H256, + ) -> Option<(AuthoritySet, AuthoritySet)> { + let incoming = + AuthoritySet { id: set_id, len, bls_poseidon_hash: commitment, ecdsa_merkle_root }; + if set_id > self.next_authorities.id { + return Some((self.next_authorities.clone(), incoming)); + } + if set_id == self.next_authorities.id && self.next_authorities.bls_poseidon_hash.is_zero() { + let mut next = self.next_authorities.clone(); + next.len = len; + next.bls_poseidon_hash = commitment; + return Some((self.current_authorities.clone(), next)); + } + if set_id == self.current_authorities.id && + self.current_authorities.bls_poseidon_hash.is_zero() + { + let mut current = self.current_authorities.clone(); + current.len = len; + current.bls_poseidon_hash = commitment; + return Some((current, self.next_authorities.clone())); + } + None + } +} + +/// The relay chain half of an update proven by an aggregate public key proof. +/// +/// Nothing here grows with the number of signers. The bitlist is fixed width, the aggregates are +/// one point each, and the proof is constant size. +#[derive(Clone, sp_std::fmt::Debug, PartialEq, Eq, Encode, Decode)] +pub struct ApkMmrProof { + /// The commitment that was signed + pub commitment: sp_consensus_beefy::Commitment, + /// Which validators signed, one bit each, big-endian words + pub bitlist: [[u8; 32]; APK_BITLIST_WORDS], + /// Aggregate of the signers' G1 keys, proven correct by [`Self::apk_proof`] + pub apk: [u8; APK_G1_LEN], + /// The same aggregate in G2, bound to `apk` by the pairing check + pub apk2: [u8; APK_G2_LEN], + /// PLONK proof that `apk` aggregates exactly the validators named in `bitlist` + pub apk_proof: Vec, + /// Sum of the signers' signatures, a G1 point + pub signature: [u8; APK_G1_LEN], + /// Latest leaf added to mmr + pub latest_mmr_leaf: MmrLeaf, + /// Proof for the latest mmr leaf + pub mmr_proof: sp_mmr_primitives::LeafProof, +} + +/// A BEEFY consensus update proven by an aggregate public key proof. +#[derive(Clone, sp_std::fmt::Debug, PartialEq, Eq, Encode, Decode)] +pub struct ApkConsensusMessage { + /// Parachain headers + pub parachain: ParachainProof, + /// Proof for the finalized mmr root + pub mmr: ApkMmrProof, +} + +/// The relay chain half of a BLS BEEFY update: the signed commitment, the aggregate signature, and +/// the MMR leaf it attests to. +/// +/// Which validators signed, and the proof that their aggregate key is the authority set's, are not +/// here. Those come from the APK proof, which is built and checked outside this crate. +#[derive(sp_std::fmt::Debug, Clone, PartialEq, Eq, Encode, Decode)] +pub struct BlsMmrProof { + /// The commitment that was signed + pub commitment: sp_consensus_beefy::Commitment, + /// Sum of the signers' G1 signatures, as a compressed G1 point + pub aggregate_signature: [u8; BLS_G1_SIGNATURE_LEN], + /// Latest leaf added to mmr + pub latest_mmr_leaf: MmrLeaf, + /// Proof for the latest mmr leaf + pub mmr_proof: sp_mmr_primitives::LeafProof, +} + +/// A BEEFY consensus update signed with aggregate BLS12-381. +#[derive(sp_std::fmt::Debug, Clone, PartialEq, Eq, Encode, Decode)] +pub struct BlsConsensusMessage { + /// Parachain headers + pub parachain: ParachainProof, + /// proof for finalized mmr root + pub mmr: BlsMmrProof, +} + /// SP1 BEEFY proof. The proof bytes are prefixed with [`PROOF_TYPE_SP1`] by the prover. #[derive(sp_std::fmt::Debug, Clone, PartialEq, Eq, Encode, Decode)] pub struct Sp1BeefyProof { diff --git a/modules/consensus/beefy/prover/Cargo.toml b/modules/consensus/beefy/prover/Cargo.toml index 763b870ae..1713f1e36 100644 --- a/modules/consensus/beefy/prover/Cargo.toml +++ b/modules/consensus/beefy/prover/Cargo.toml @@ -18,10 +18,19 @@ derive_more = { workspace = true, features = ["from"], default-features = true } rs_merkle = { workspace = true, default-features = true } hex-literal = "0.4.1" hex = { version = "0.4.3" } +w3f-bls = { version = "0.1.9", default-features = true, optional = true } + +ark-bls12-381 = { version = "0.4.0", features = ["curve"], default-features = false, optional = true } +ark-ec = { version = "0.4.0", default-features = false, optional = true } +ark-ff = { version = "0.4.0", default-features = false, optional = true } +ark-serialize = { version = "0.4.0", default-features = false, optional = true } subxt = { workspace = true, default-features = true } subxt-core = { workspace = true, default-features = true } beefy-verifier-primitives = { workspace = true } + +ismp-abi = { workspace = true, default-features = true, optional = true } +alloy-primitives = { workspace = true, default-features = true, optional = true } merkle-mountain-range = { workspace = true } indicatif = "0.18.0" futures = { workspace = true } @@ -50,3 +59,6 @@ features = [ [features] local = [] +# Prove the BLS half instead: `crate::bls`, which builds a proof the aggregate verifier checks in +# a single pairing. Purely additive, so it is safe to enable alongside plain-ECDSA proving. +bls-aggregate = ["dep:w3f-bls", "dep:ismp-abi", "dep:alloy-primitives", "dep:ark-bls12-381", "dep:ark-ec", "dep:ark-ff", "dep:ark-serialize"] diff --git a/modules/consensus/beefy/prover/src/bls.rs b/modules/consensus/beefy/prover/src/bls.rs new file mode 100644 index 000000000..21a0ac6d9 --- /dev/null +++ b/modules/consensus/beefy/prover/src/bls.rs @@ -0,0 +1,211 @@ +// Copyright (C) Polytope Labs Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +//! Proving BEEFY commitments signed with aggregate BLS12-381. +//! +//! A relay whose BEEFY authorities hold paired `ecdsa_bls_crypto` keys puts both signatures on the +//! wire. `crate::relay::decode_beefy_justification` keeps the ECDSA half so the existing verifier +//! works unchanged; this module keeps the BLS half instead, and builds a proof the aggregate +//! verifier can check in a single pairing. +//! +//! This only applies to a relay whose keyset commitment is over BLS public keys. A chain +//! committing Ethereum addresses cannot be proven this way, and vice versa. + +use anyhow::anyhow; +use codec::{Decode, Encode}; +use polkadot_sdk::*; +use sp_consensus_beefy::{SignedCommitment, VersionedFinalityProof, BEEFY_ENGINE_ID}; +use subxt::{backend::legacy::LegacyRpcMethods, Config}; +use subxt_core::config::HashFor; + +use beefy_verifier_primitives::{ + BlsConsensusMessage, BlsMmrProof, PairedAuthority, BLS_G1_SIGNATURE_LEN, BLS_G2_PUBLIC_KEY_LEN, +}; + +use crate::{ + build_parachain_proof, + relay::{fetch_mmr_proof, paras_parachains}, + Prover, BEEFY_AUTHORITIES, +}; + +/// Wire size of a paired (ECDSA, BLS12-381) BEEFY signature. +pub const PAIRED_LEN: usize = 177; + +/// Offset of the BLS G1 signature within a paired signature: the ECDSA half is 65 bytes, then the +/// `DoubleSignature` begins with its 48-byte G1 point. +const PAIRED_SIGNATURE_G1_OFFSET: usize = 65; + +/// A paired (ECDSA, BLS12-381) signature exactly as SCALE-encoded on the wire. +#[derive(Clone)] +pub struct PairedSignature(pub [u8; PAIRED_LEN]); + +impl Decode for PairedSignature { + fn decode(input: &mut I) -> Result { + let mut bytes = [0u8; PAIRED_LEN]; + input.read(&mut bytes)?; + Ok(PairedSignature(bytes)) + } +} + +impl PairedSignature { + /// The BLS12-381 G1 signature half. + pub fn g1_signature(&self) -> [u8; BLS_G1_SIGNATURE_LEN] { + let mut g1 = [0u8; BLS_G1_SIGNATURE_LEN]; + g1.copy_from_slice( + &self.0[PAIRED_SIGNATURE_G1_OFFSET..PAIRED_SIGNATURE_G1_OFFSET + BLS_G1_SIGNATURE_LEN], + ); + g1 + } +} + +/// Decode a BEEFY justification keeping the whole paired signature. +pub fn decode_paired_justification( + bytes: &[u8], +) -> Result, anyhow::Error> { + let VersionedFinalityProof::V1(signed_commitment) = + VersionedFinalityProof::::decode(&mut &*bytes)?; + Ok(signed_commitment) +} + +/// The justification at `at`, with both halves of each paired signature kept. +/// +/// `crate::relay::fetch_latest_beefy_justification` reads the same bytes but discards the BLS +/// half, which is the half this path needs. +pub async fn fetch_paired_justification( + rpc: &LegacyRpcMethods, + at: HashFor, +) -> Result, anyhow::Error> { + let block = rpc + .chain_get_block(Some(at)) + .await? + .ok_or_else(|| anyhow!("No block at {at:?}"))?; + + let justification = block + .justifications + .and_then(|justifications| { + justifications + .into_iter() + .find_map(|(id, encoded)| (id == BEEFY_ENGINE_ID).then_some(encoded)) + }) + .ok_or_else(|| anyhow!("Block {at:?} carries no beefy justification"))?; + + decode_paired_justification(&justification) +} + +/// The validators' BLS12-381 G2 public keys, in authority-set order. +pub async fn beefy_g2_authorities( + rpc: &LegacyRpcMethods, + at: Option>, +) -> Result, anyhow::Error> { + let data = rpc + .state_get_storage(BEEFY_AUTHORITIES.as_slice(), at) + .await? + .ok_or_else(|| anyhow!("No beefy authorities found!"))?; + + Ok(Vec::::decode(&mut data.as_ref())? + .iter() + .map(PairedAuthority::g2) + .collect()) +} + +/// The validators' BLS12-381 G1 public keys, in authority-set order. +/// +/// `DoublePublicKey` publishes the same secret in both groups, so these are the G1 counterparts of +/// [`beefy_g2_authorities`]. An APK proof consumes the G1 halves while BEEFY's own signature +/// verifies against the G2 halves. +pub async fn beefy_g1_authorities( + rpc: &LegacyRpcMethods, + at: Option>, +) -> Result, anyhow::Error> { + let data = rpc + .state_get_storage(BEEFY_AUTHORITIES.as_slice(), at) + .await? + .ok_or_else(|| anyhow!("No beefy authorities found!"))?; + + Ok(Vec::::decode(&mut data.as_ref())? + .iter() + .map(PairedAuthority::g1) + .collect()) +} + +/// Sum compressed G1 signatures into the single compressed G1 point the verifier checks. +pub fn aggregate_signatures( + signatures: &[[u8; BLS_G1_SIGNATURE_LEN]], +) -> Result<[u8; BLS_G1_SIGNATURE_LEN], anyhow::Error> { + use ark_ff::Zero; + use w3f_bls::{EngineBLS, SerializableToBytes, Signature, TinyBLS381}; + + // Aggregating nothing would give the identity, which is a well formed point and a meaningless + // signature, so an empty set is refused rather than summed. + if signatures.is_empty() { + Err(anyhow!("No signatures to aggregate"))? + } + + let aggregate = signatures.iter().try_fold( + ::SignatureGroup::zero(), + |sum, signature| { + let signature = Signature::::from_bytes(signature) + .map_err(|_| anyhow!("Invalid G1 signature encoding"))?; + Ok::<_, anyhow::Error>(sum + signature.0) + }, + )?; + Signature::(aggregate) + .to_bytes() + .try_into() + .map_err(|_| anyhow!("Aggregated signature was not {BLS_G1_SIGNATURE_LEN} bytes")) +} + +impl Prover { + /// Collect the relay chain half of a BLS BEEFY update: the signed commitment, the aggregate + /// signature, the MMR leaf and its proof, and the parachain headers. + /// + /// Which validators signed is left to the caller, which reads it from the justification's + /// bitfield and proves it with an APK proof. Nothing here grows with the number of signers. + pub async fn bls_consensus_proof( + &self, + signed_commitment: SignedCommitment, + ) -> Result { + let block_number: u32 = signed_commitment.commitment.block_number; + let (mmr_proof, latest_leaf) = + fetch_mmr_proof(&self.relay_rpc, block_number, self.query_batch_size).await?; + + // Only the signatures are needed here. The keys they belong to are the APK proof's + // business, and reading them is the caller's. + let g1_signatures = signed_commitment + .signatures + .iter() + .flatten() + .map(|signature| signature.g1_signature()) + .collect::>(); + let aggregate_signature = aggregate_signatures(&g1_signatures)?; + + let mmr = BlsMmrProof { + commitment: signed_commitment.commitment.clone(), + aggregate_signature, + latest_mmr_leaf: latest_leaf.clone(), + mmr_proof, + }; + + let heads = paras_parachains( + &self.relay_rpc, + Some(HashFor::::decode(&mut &*latest_leaf.parent_number_and_hash.1.encode())?), + ) + .await?; + + let parachain = build_parachain_proof(&self.para_ids, &heads); + + Ok(BlsConsensusMessage { mmr, parachain }) + } +} diff --git a/modules/consensus/beefy/prover/src/lib.rs b/modules/consensus/beefy/prover/src/lib.rs index dbefd5ccc..ebabcc86e 100644 --- a/modules/consensus/beefy/prover/src/lib.rs +++ b/modules/consensus/beefy/prover/src/lib.rs @@ -48,6 +48,15 @@ use relay::{ }; use util::hash_authority_addresses; +/// Size of a compressed secp256k1 BEEFY key, and of the ECDSA half of a paired one. +const ECDSA_PUBLIC_KEY_LEN: usize = 33; + +/// Wire size of a paired (ECDSA, BLS12-381) BEEFY key. +const PAIRED_AUTHORITY_LEN: usize = 177; + +/// Proving commitments signed with aggregate BLS12-381 +#[cfg(feature = "bls-aggregate")] +pub mod bls; /// Methods for querying the relay chain pub mod relay; /// Helper functions and types @@ -143,6 +152,19 @@ fn build_parachain_proof(para_ids: &[u32], heads: &[(u32, Vec)]) -> Parachai ParachainProof { parachains, proof, total_leaves: leaf_count as u32 } } +/// The client side shape of an authority set, built from what the relay chain names. +/// +/// The relay only knows the ecdsa merkle root. The poseidon hash reaches a client through a +/// hyperbridge header digest, so it starts empty here and is filled in once one is seen. +fn from_relay(set: BeefyAuthoritySet) -> beefy_verifier_primitives::AuthoritySet { + beefy_verifier_primitives::AuthoritySet { + id: set.id, + len: set.len, + bls_poseidon_hash: H256::zero(), + ecdsa_merkle_root: set.keyset_commitment, + } +} + impl Prover { /// Construct a beefy client state to be submitted to the counterparty chain pub async fn get_initial_consensus_state( @@ -167,14 +189,15 @@ impl Prover { mmr_root_hash, beefy_activation_block: self.beefy_activation_block, latest_beefy_height: signed_commitment.commitment.block_number as u32, - current_authorities: self - .mmr_leaf_current_authorities(Some(latest_beefy_finalized)) - .await?, - next_authorities: beefy_mmr_leaf_next_authorities( - &self.relay_rpc, - Some(latest_beefy_finalized), - ) - .await?, + // The relay only names the ecdsa root. A client learns the poseidon hash from a + // hyperbridge header digest, so a state starting here has that half empty. + current_authorities: from_relay( + self.mmr_leaf_current_authorities(Some(latest_beefy_finalized)).await?, + ), + next_authorities: from_relay( + beefy_mmr_leaf_next_authorities(&self.relay_rpc, Some(latest_beefy_finalized)) + .await?, + ), }; Ok(client_state) @@ -202,15 +225,35 @@ impl Prover { &self, at: Option>, ) -> Result, anyhow::Error> { - // Encoding and decoding to fix dependency version conflicts - let current_authorities = { - self.relay_rpc - .state_get_storage(BEEFY_AUTHORITIES.as_slice(), at) - .await? - .map(|data| Vec::<[u8; 33]>::decode(&mut data.as_ref())) - .transpose()? - .ok_or_else(|| anyhow!("No beefy authorities found!"))? + let data = self + .relay_rpc + .state_get_storage(BEEFY_AUTHORITIES.as_slice(), at) + .await? + .ok_or_else(|| anyhow!("No beefy authorities found!"))?; + + // A relay whose authorities hold paired `ecdsa_bls_crypto` keys stores 177 bytes each, + // where a plain ECDSA one stores 33. The item width follows from the length prefix and + // what is left after it, so the chain says which it is rather than the build. + let mut rest = data.as_ref(); + let count = codec::Compact::::decode(&mut rest)?.0 as usize; + let width = if count == 0 { ECDSA_PUBLIC_KEY_LEN } else { rest.len() / count }; + + // Either way keep the ECDSA half, a compressed secp256k1 key, so the derived address + // leaves match the on-chain keyset commitment that `BeefyEcdsaBlsToEthereum` builds from + // exactly those halves. + let current_authorities = match width { + ECDSA_PUBLIC_KEY_LEN => Vec::<[u8; ECDSA_PUBLIC_KEY_LEN]>::decode(&mut data.as_ref())?, + PAIRED_AUTHORITY_LEN => Vec::<[u8; PAIRED_AUTHORITY_LEN]>::decode(&mut data.as_ref())? + .into_iter() + .map(|key| { + let mut ecdsa_half = [0u8; ECDSA_PUBLIC_KEY_LEN]; + ecdsa_half.copy_from_slice(&key[..ECDSA_PUBLIC_KEY_LEN]); + ecdsa_half + }) + .collect(), + _ => Err(anyhow!("Beefy authorities are {width} bytes wide, which is neither an ecdsa nor a paired key"))?, }; + Ok(current_authorities) } diff --git a/modules/consensus/beefy/prover/src/relay.rs b/modules/consensus/beefy/prover/src/relay.rs index f391a9980..9d625fe8f 100644 --- a/modules/consensus/beefy/prover/src/relay.rs +++ b/modules/consensus/beefy/prover/src/relay.rs @@ -42,6 +42,12 @@ use crate::{ PARAS_PARACHAINS, }; +/// Wire size of a paired (ECDSA, BLS12-381) BEEFY signature. +const PAIRED_SIGNATURE_LEN: usize = 177; + +/// Wire size of the ECDSA half of one. +const ECDSA_SIGNATURE_LEN: usize = 65; + /// Storage key for mmr.numberOfLeaves pub const MMR_NUMBER_OF_LEAVES: [u8; 32] = hex!("a8c65209d47ee80f56b0011e8fd91f508156209906244f2341137c136774c91d"); @@ -73,13 +79,60 @@ pub async fn fetch_latest_beefy_justification( (justfication.0 == sp_consensus_beefy::BEEFY_ENGINE_ID).then(|| justfication.1) }) .expect("Should have valid beefy justification"); - let VersionedFinalityProof::V1(signed_commitment) = VersionedFinalityProof::< + let signed_commitment = decode_beefy_justification(&beefy_justification)?; + + Ok((signed_commitment, latest_beefy_finalized)) +} + +/// Decode a BEEFY justification into a `SignedCommitment` carrying 65-byte ECDSA signatures. +/// +/// A relay whose authorities hold paired `ecdsa_bls_crypto` keys puts 177-byte signatures on the +/// wire, where a plain ECDSA one puts 65. Which it is depends on the chain rather than on how this +/// was built, so both are attempted: a correct decode consumes the whole justification, and one +/// that leaves bytes behind read the wrong width. Either way only the ECDSA half survives, so +/// commitment hashing, signature recovery and the verifier are unaffected. +pub fn decode_beefy_justification( + bytes: &[u8], +) -> Result, anyhow::Error> { + let mut input = bytes; + if let Ok(VersionedFinalityProof::V1(signed_commitment)) = VersionedFinalityProof::< u32, sp_consensus_beefy::ecdsa_crypto::Signature, - >::decode(&mut &*beefy_justification) - .expect("Beefy justification should decode correctly"); + >::decode(&mut input) + { + if input.is_empty() { + return Ok(signed_commitment); + } + } - Ok((signed_commitment, latest_beefy_finalized)) + /// A paired (ECDSA, BLS12-381) signature exactly as SCALE-encoded on the wire. + struct PairedSignature([u8; PAIRED_SIGNATURE_LEN]); + impl codec::Decode for PairedSignature { + fn decode(input: &mut I) -> Result { + let mut bytes = [0u8; PAIRED_SIGNATURE_LEN]; + input.read(&mut bytes)?; + Ok(PairedSignature(bytes)) + } + } + + let VersionedFinalityProof::V1(paired) = + VersionedFinalityProof::::decode(&mut &*bytes)?; + let signatures = paired + .signatures + .into_iter() + .map(|maybe_signature| { + maybe_signature + .map(|signature| { + // The ECDSA half is the first 65 bytes: r || s || v. + sp_consensus_beefy::ecdsa_crypto::Signature::decode( + &mut &signature.0[..ECDSA_SIGNATURE_LEN], + ) + }) + .transpose() + }) + .collect::, _>>()?; + + Ok(SignedCommitment { commitment: paired.commitment, signatures }) } /// Parathreads whitelisted to be added to the beefy mmr leaf parachains header root @@ -184,11 +237,7 @@ pub async fn fetch_next_beefy_justification( if (current_set_id..=(current_set_id + 1)).contains(&set_id) && beefy_justification.is_some() { - let VersionedFinalityProof::V1(signed_commitment) = - VersionedFinalityProof::::decode( - &mut &*beefy_justification.unwrap(), - ) - .expect("Beefy justification should decode correctly"); + let signed_commitment = decode_beefy_justification(&beefy_justification.unwrap())?; break (signed_commitment, block_hash); } block_hash = SubstrateHeader::::decode(&mut &*block.block.header.encode()) @@ -258,14 +307,23 @@ pub async fn query_mmr_leaf( let leaf_extra = { let heads = paras_parachains(rpc, Some(parent_hash)).await?; - // Calculate leaf hashes from the parachain headers - let leaf_hashes = heads.iter().map(|leaf| keccak_256(&leaf.encode())).collect::>(); - - let tree = MerkleTree::::from_leaves(&leaf_hashes); - let root = tree - .root() - .ok_or_else(|| anyhow!("Failed to parachain heads calculate root!"))?; - H256(root) + if heads.is_empty() { + // A relay chain with no registered parachains commits an empty parachain-heads root. + // The runtime uses `binary_merkle_tree::merkle_root`, which returns `H::Out::default()` + // for an empty leaf set. `rs_merkle`'s empty tree has no root (`.root()` is `None`), so + // mirror the runtime here instead of erroring. + H256::default() + } else { + // Calculate leaf hashes from the parachain headers + let leaf_hashes = + heads.iter().map(|leaf| keccak_256(&leaf.encode())).collect::>(); + + let tree = MerkleTree::::from_leaves(&leaf_hashes); + let root = tree + .root() + .ok_or_else(|| anyhow!("Failed to parachain heads calculate root!"))?; + H256(root) + } }; let leaf = MmrLeaf { version: MmrLeafVersion::new(0, 0), diff --git a/modules/consensus/beefy/verifier/Cargo.toml b/modules/consensus/beefy/verifier/Cargo.toml index 0737eb95a..b2fc521cf 100644 --- a/modules/consensus/beefy/verifier/Cargo.toml +++ b/modules/consensus/beefy/verifier/Cargo.toml @@ -22,11 +22,25 @@ thiserror = { workspace = true } sp1-verifier = { git = "https://github.com/polytope-labs/sp1.git", branch = "polytope-labs/v6.1.0-wasm-compatible", default-features = false } alloy-sol-types = { workspace = true, default-features = false } +sha2 = { version = "0.10", default-features = false, optional = true } +hex-literal = { workspace = true, optional = true } +ark-bls12-381 = { version = "0.5", default-features = false, features = ["curve"], optional = true } +ark-ec = { version = "0.5", default-features = false, optional = true } +ark-ff = { version = "0.5", default-features = false, optional = true } +ark-serialize = { version = "0.5", default-features = false, optional = true } + +[dependencies.gnark-plonk-verifier] +git = "https://github.com/polytope-labs/gnark-apk-proofs" +rev = "1c15d06d8180906b16201c5206711808b80742ef" +default-features = false +optional = true + [dependencies.polkadot-sdk] workspace = true features = [ "sp-consensus-beefy", "sp-core", + "sp-mmr-primitives", "sp-runtime" ] @@ -35,11 +49,24 @@ hex-literal = { workspace = true } hex = { workspace = true, default-features = true } beefy-prover = { workspace = true } ismp-abi = { workspace = true, default-features = true } +# The APK fixture is built in two halves: this crate collects the live BEEFY data, the SNARK is +# generated by `gnark-apk-proofs` out of tree, and the two meet over a json file. +json = { workspace = true, default-features = true } +alloy-primitives = { workspace = true, default-features = true } subxt = { workspace = true, default-features = true } subxt-core = { workspace = true, default-features = true } subxt-utils = { workspace = true, default-features = true } futures = { workspace = true } tokio = { workspace = true } +# Pinned to the versions w3f-bls builds against, so the hash-to-curve test vector is generated by +# exactly the code path that signs on the relay chain. +ark-bls12-381 = { version = "0.4.0", features = ["curve"], default-features = false } +ark-ec = { version = "0.4.0", default-features = false } +ark-ff = { version = "0.4.0", default-features = false } +ark-serialize = { version = "0.4.0", default-features = false } +sha2 = { version = "0.10", default-features = false } +# Aggregate BLS verification prototype (Option B): the same double-scheme BLS as substrate BEEFY. +w3f-bls = { version = "0.1.9", default-features = true } [dev-dependencies.polkadot-sdk] workspace = true @@ -49,6 +76,25 @@ features = ["sp-io"] [features] default = ["std"] +# Tests that need `w3f-bls` itself: the hash-to-curve vector and the aggregate checks the APK path +# is built on. Nothing in the library depends on it. +bls-crypto = [] +apk = [ + "dep:gnark-plonk-verifier", + "dep:sha2", + "dep:hex-literal", + "dep:ark-bls12-381", + "dep:ark-ec", + "dep:ark-ff", + "dep:ark-serialize", +] +# Runs the BLS-BEEFY tests against a relay whose BEEFY authorities are paired ecdsa_bls_crypto +# keys. Pulls the prover's `bls` decode path, which reads 177-byte paired signatures and keys. +bls = ["beefy-prover/bls-aggregate", "bls-crypto"] +# Storage keys for a relay that names its beefy-mmr pallet `MmrLeaf` rather than `BeefyMmrLeaf`. +# Westend (and Polkadot/Kusama) use `BeefyMmrLeaf`, so leave this off for them; our rococo fork +# used `MmrLeaf`, so pair it with `bls` as `--features bls,local` when testing against that chain. +local = ["beefy-prover/local"] std = [ "log/std", "anyhow/std", @@ -61,4 +107,10 @@ std = [ "rs_merkle/std", "sp1-verifier/std", "alloy-sol-types/std", + "sha2?/std", + "ark-bls12-381?/std", + "ark-ec?/std", + "ark-ff?/std", + "ark-serialize?/std", + "gnark-plonk-verifier?/std", ] \ No newline at end of file diff --git a/modules/consensus/beefy/verifier/src/apk.rs b/modules/consensus/beefy/verifier/src/apk.rs new file mode 100644 index 000000000..962247a02 --- /dev/null +++ b/modules/consensus/beefy/verifier/src/apk.rs @@ -0,0 +1,425 @@ +// Copyright (C) Polytope Labs Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +//! Verifying BEEFY finality through an aggregate public key proof. +//! +//! This is the runtime counterpart of `BlsApkBeefy.sol` and the two have to agree exactly, since +//! they check the same proofs. The SNARK establishes that an aggregate key is the sum of precisely +//! the validators named in a bitlist, drawn from the set a commitment describes, and a single +//! pairing then checks the aggregate signature against that key while binding its G2 counterpart. +//! +//! Nothing here grows with the number of signers, which is the whole point of the scheme. + +use alloc::vec::Vec; + +use ark_bls12_381::{Bls12_381, Fq, Fq2, Fr, G1Affine, G1Projective, G2Affine}; +use ark_ec::{AffineRepr, CurveGroup, PrimeGroup, pairing::Pairing}; +use ark_ff::{BigInteger, One, PrimeField, Zero}; +use ark_serialize::CanonicalDeserialize; +use beefy_verifier_primitives::{ + APK_BITLIST_WORDS, APK_G1_LEN, APK_G2_LEN, ApkCommitmentDigest, ApkConsensusMessage, + ApkMmrProof, ConsensusState, ParachainHeader, +}; +use codec::{Decode, Encode}; +use polkadot_sdk::*; +use primitive_types::H256; +use sp_runtime::traits::BlakeTwo256; + +use crate::Keccak256; +use sha2::{Digest, Sha256}; + +use crate::error::Error; + +/// Half of a G1 point, and the width of every coordinate the circuit's verifier deals in. +const COORDINATE: usize = 48; + +/// The aggregate key reaches the circuit as six 64 bit limbs per coordinate. +const APK_LIMBS: usize = 12; + +/// The seed point the circuit aggregates onto, hashed to the curve from `dst="gnark-apk-proofs"` +/// and `msg="apk-seed-coset"`. Hardcoded in the circuit and in `ApkProof.sol`, packed here the +/// same way a key is. +/// +/// It sits on the curve but deliberately outside G1, which is what lets the circuit aggregate +/// with incomplete addition. Every key is in G1, so `seed + sum(pk)` stays in the coset `seed + G1` +/// and can never meet a key or its negation, the two cases the chord formula cannot handle. +const SEED: [u8; APK_G1_LEN] = hex_literal::hex!( + "19742ffba069554d8cacceb8ed5514b2ecf72cd7372d3414203338f4fd3b3cc7" + "42fb160f8eb5818422246de186e0814a0e0f5d1199876e646952fb74d39e0b34" + "042a8d48786adae7e0fccf4b0236c72e82343de94c9d12bf17d22bec9edbbe2b" +); + +/// Verify a whole update and return the new trusted state with the verified parachain headers. +/// +/// The order matters and mirrors the Solidity client. The commitment is only believed once the +/// aggregate proof and the pairing both pass, the mmr leaf is only believed once the commitment +/// is, and the headers only once the leaf is. Any commitment picked up from those headers is +/// therefore learned from something already proven. +pub fn verify_apk_consensus( + trusted_state: ConsensusState, + proof: ApkConsensusMessage, + verifying_key: &[u8], + digest_para_id: u32, +) -> Result<(ConsensusState, Vec), Error> { + // The leaf names the incoming set's ecdsa root, which this client has no use for but records + // anyway so a state it advances stays usable by the per signer and sp1 clients. + let ecdsa_merkle_root = proof.mmr.latest_mmr_leaf.beefy_next_authority_set.keyset_commitment; + let (mut state, heads_root) = + verify_apk_mmr_update_proof::(trusted_state, proof.mmr, verifying_key)?; + let headers = crate::verify_parachain_headers::(heads_root, proof.parachain)?; + + // Forward chaining: the set's id, its size and its commitment all come from here together, + // rather than taking the size from the mmr leaf and the commitment from a digest, which would + // leave the two free to describe different sets. + // + // Only `digest_para_id`'s headers are read. A proof carries whichever parachains the relay + // finalized, and every one of them is proven against the heads root, so a digest from another + // parachain is authentic yet says nothing about this relay's authorities. Left unfiltered any + // parachain could name the keys this client trusts next. + if let Some((current, next)) = headers + .iter() + .filter(|header| header.para_id == digest_para_id) + .find_map(|header| read_apk_digest(&header.header)) + .and_then(|digest| { + state.with_bls_commitment( + digest.set_id, + digest.len, + H256(digest.commitment), + ecdsa_merkle_root, + ) + }) { + state.current_authorities = current; + state.next_authorities = next; + } + + Ok((state, headers)) +} + +/// Verify the signed mmr root and roll the authority sets forward. +pub fn verify_apk_mmr_update_proof( + mut trusted_state: ConsensusState, + mmr: ApkMmrProof, + verifying_key: &[u8], +) -> Result<(ConsensusState, H256), Error> { + if trusted_state.latest_beefy_height >= mmr.commitment.block_number { + return Err(Error::StaleHeight { + trusted_height: trusted_state.latest_beefy_height, + current_height: mmr.commitment.block_number, + }); + } + + let set_id = mmr.commitment.validator_set_id; + let authority_set = if set_id == trusted_state.current_authorities.id { + &trusted_state.current_authorities + } else if set_id == trusted_state.next_authorities.id { + &trusted_state.next_authorities + } else { + return Err(Error::UnknownAuthoritySet { id: set_id }); + }; + + // A set whose commitment has not been learned from a digest yet cannot be verified against. + // Refusing is deliberate: proceeding would check the proof against a zero commitment, which + // establishes nothing at all. + if authority_set.bls_poseidon_hash.is_zero() { + return Err(Error::ApkCommitmentMissing { id: set_id }); + } + let apk_commitment = authority_set.bls_poseidon_hash; + let authority_count = authority_set.len; + + verify_signed_by_apk( + &mmr.commitment.encode(), + &mmr, + apk_commitment, + authority_count, + verifying_key, + )?; + + let mmr_root = mmr + .commitment + .payload + .get_raw(&sp_consensus_beefy::known_payloads::MMR_ROOT_ID) + .and_then(|raw| H256::decode(&mut &raw[..]).ok()) + .ok_or(Error::MmrRootHashMissing)?; + + crate::verify_mmr_leaf::(&mmr.latest_mmr_leaf, &mmr.mmr_proof, mmr_root)?; + + trusted_state.latest_beefy_height = mmr.commitment.block_number; + trusted_state.mmr_root_hash = mmr_root; + + Ok((trusted_state, mmr.latest_mmr_leaf.leaf_extra)) +} + +/// Read an APK commitment out of a scale encoded parachain header, if it carries one. +fn read_apk_digest(header: &[u8]) -> Option { + let header = sp_runtime::generic::Header::::decode(&mut &header[..]).ok()?; + ApkCommitmentDigest::find_in(&header.digest) +} + +/// Verify that a supermajority of `authority_set` signed `commitment`. +/// +/// Two separate facts are established and both are needed. The proof says the aggregate key +/// belongs to the set, and the pairing says that key produced the signature. Counting the +/// signers is left here, because the circuit proves who signed but has no opinion on whether it +/// is enough. +pub fn verify_signed_by_apk( + encoded_commitment: &[u8], + mmr: &ApkMmrProof, + apk_commitment: H256, + authority_count: u32, + verifying_key: &[u8], +) -> Result<(), Error> { + if !supermajority(count_signers(&mmr.bitlist), authority_count) { + return Err(Error::SuperMajorityRequired); + } + + verify_apk_proof(mmr, apk_commitment, verifying_key)?; + + let message = hash_commitment_to_g1(encoded_commitment)?; + let apk = read_g1(&mmr.apk)?; + let apk2 = read_g2(&mmr.apk2)?; + let signature = read_g1(&mmr.signature)?; + + if !verify_aggregate(message, apk, apk2, signature) { + return Err(Error::ApkPairingFailed); + } + + Ok(()) +} + +/// Check the PLONK proof against the set's commitment. +/// +/// The public inputs are the bitlist, then the commitment, then the aggregate key as twelve +/// limbs, which is the order the circuit exposes them in and the order the Solidity verifier +/// reads them. +fn verify_apk_proof( + mmr: &ApkMmrProof, + apk_commitment: H256, + verifying_key: &[u8], +) -> Result<(), Error> { + let vk = gnark_plonk_verifier::VerifyingKey::try_from(verifying_key) + .map_err(|_| Error::ApkVerifyingKeyInvalid)?; + let proof = gnark_plonk_verifier::PlonkProof::try_from((&mmr.apk_proof[..], vk.qcp.len())) + .map_err(|_| Error::ApkProofMalformed)?; + + let mut public_inputs = Vec::with_capacity(APK_BITLIST_WORDS + 1 + APK_LIMBS); + for word in mmr.bitlist.iter() { + public_inputs.push(Fr::from_be_bytes_mod_order(word)); + } + public_inputs.push(Fr::from_be_bytes_mod_order(apk_commitment.as_bytes())); + + // The circuit aggregates onto a fixed seed point, so what it proves about is `seed + apk` + // rather than the aggregate on its own. `ApkProof._encodePublicInputs` adds it the same way + // before handing the inputs to the PLONK verifier. + let seeded = (read_g1(&mmr.apk)?.into_group() + read_seed()?).into_affine(); + let (x, y) = seeded.xy().ok_or(Error::ApkPointInvalid)?; + + // Each coordinate is too wide for one scalar, so it travels as six 64 bit limbs, least + // significant first. + for coordinate in [x, y] { + let bytes = coordinate.into_bigint().to_bytes_be(); + for limb in bytes.chunks(8).rev() { + public_inputs.push(Fr::from_be_bytes_mod_order(limb)); + } + } + + gnark_plonk_verifier::verify(&proof, &vk, &public_inputs) + .map_err(|_| Error::ApkProofVerificationFailed) +} + +/// The batched signature and binding check, matching `ApkProof._verifyBls`: +/// +/// ```text +/// e(sig + t·apk, -g2) · e(msg + t·g1, apk2) == 1 +/// ``` +/// +/// which is the random linear combination of the signature equation and the equation binding +/// `apk2` to `apk`. Doing them together costs one pairing rather than two, and the challenge `t` +/// is derived from all four points so a caller cannot pick points to suit a known `t`. +fn verify_aggregate(message: G1Affine, apk: G1Affine, apk2: G2Affine, signature: G1Affine) -> bool { + let t = challenge(&message, &signature, &apk, &apk2); + + let lhs: G1Affine = (signature.into_group() + apk * t).into_affine(); + let rhs: G1Affine = (message.into_group() + G1Projective::generator() * t).into_affine(); + + let result = Bls12_381::multi_pairing([lhs, rhs], [-G2Affine::generator(), apk2]); + result.0 == ark_bls12_381::Fq12::one() +} + +/// `expand_message_xmd` with SHA-256 over the four points, taking 48 bytes and reducing them into +/// the scalar field. An empty domain separation tag, matching the contract. +fn challenge(message: &G1Affine, signature: &G1Affine, apk: &G1Affine, apk2: &G2Affine) -> Fr { + let mut data = Vec::with_capacity(APK_G1_LEN * 3 + APK_G2_LEN); + data.extend_from_slice(&write_g1(message)); + data.extend_from_slice(&write_g1(signature)); + data.extend_from_slice(&write_g1(apk)); + data.extend_from_slice(&write_g2(apk2)); + + // b0 = SHA256(Z_pad || data || I2OSP(48, 2) || I2OSP(0, 1) || DST_prime), with a 64 byte zero + // pad for SHA-256's block size and an empty DST, so DST_prime is a single zero length byte. + let mut hasher = Sha256::new(); + hasher.update([0u8; 64]); + hasher.update(&data); + hasher.update([0x00, 0x30]); + hasher.update([0x00]); + hasher.update([0x00]); + let b0: [u8; 32] = hasher.finalize().into(); + + let mut hasher = Sha256::new(); + hasher.update(b0); + hasher.update([0x01, 0x00]); + let b1: [u8; 32] = hasher.finalize().into(); + + let mut xored = [0u8; 32]; + for (i, byte) in xored.iter_mut().enumerate() { + *byte = b0[i] ^ b1[i]; + } + let mut hasher = Sha256::new(); + hasher.update(xored); + hasher.update([0x02, 0x00]); + let b2: [u8; 32] = hasher.finalize().into(); + + // t = b1 ‖ the top 16 bytes of b2, reduced. 48 bytes of uniform output, as the spec asks for. + let mut wide = [0u8; 48]; + wide[..32].copy_from_slice(&b1); + wide[32..].copy_from_slice(&b2[..16]); + Fr::from_be_bytes_mod_order(&wide) +} + +/// Population count over the bitlist. Fixed cost regardless of how many signed. +/// The 1024 slots are packed 250 to a word across the first four and 24 into the last, matching +/// the `ToBinary` decomposition in the circuit. Bits above those ranges are constrained to zero +/// there, so a proof carrying any would not verify; masking them off keeps the count honest on its +/// own terms rather than relying on that. +pub fn count_signers(bitlist: &[[u8; 32]; APK_BITLIST_WORDS]) -> u32 { + bitlist + .iter() + .enumerate() + .map(|(word, bytes)| { + let width = if word == APK_BITLIST_WORDS - 1 { 24 } else { 250 }; + // The word is big endian, so the packed bits are the low ones at the end. + bytes + .iter() + .rev() + .enumerate() + .map(|(i, byte)| { + let low = i * 8; + if low >= width { + return 0; + } + let keep = (width - low).min(8); + (byte & (((1u16 << keep) - 1) as u8)).count_ones() + }) + .sum::() + }) + .sum() +} + +/// Two thirds plus one, matching substrate's own rule and the Solidity client. +fn supermajority(signed: u32, total: u32) -> bool { + total > 0 && (signed as u64) * 3 > (total as u64) * 2 +} + +/// Hash the encoded commitment onto G1 the way `w3f-bls` does. +/// +/// Note this is not the textbook construction. The ciphersuite is prepended to the message rather +/// than used as the domain separation tag, and the tag itself is the single byte `0x01`. Getting +/// this wrong yields a well formed point and a pairing that fails with nothing to explain why. +fn hash_commitment_to_g1(encoded_commitment: &[u8]) -> Result { + use ark_ec::hashing::{ + HashToCurve, curve_maps::wb::WBMap, map_to_curve_hasher::MapToCurveBasedHasher, + }; + use ark_ff::field_hashers::DefaultFieldHasher; + + let mut preimage = Vec::with_capacity(CIPHER_SUITE.len() + encoded_commitment.len()); + preimage.extend_from_slice(CIPHER_SUITE); + preimage.extend_from_slice(encoded_commitment); + + let hasher = MapToCurveBasedHasher::< + G1Projective, + DefaultFieldHasher, + WBMap, + >::new(&[0x01]) + .map_err(|_| Error::ApkHashToCurveFailed)?; + + hasher.hash(&preimage).map_err(|_| Error::ApkHashToCurveFailed) +} + +/// The ciphersuite `w3f-bls` prepends to the message. Substrate signs with the basic scheme, so +/// the tail is `NUL` rather than `POP`. +const CIPHER_SUITE: &[u8] = b"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_"; + +/// Read a G1 point from raw big-endian coordinates. +/// Read the aggregation seed, which is on the curve but not in G1, so the subgroup check +/// [`read_g1`] applies to keys would reject it. +fn read_seed() -> Result { + let x = read_fq(&SEED[..COORDINATE])?; + let y = read_fq(&SEED[COORDINATE..])?; + let point = G1Affine::new_unchecked(x, y); + if !point.is_on_curve() { + return Err(Error::ApkPointInvalid); + } + Ok(point) +} + +fn read_g1(bytes: &[u8; APK_G1_LEN]) -> Result { + let x = read_fq(&bytes[..COORDINATE])?; + let y = read_fq(&bytes[COORDINATE..])?; + let point = G1Affine::new_unchecked(x, y); + if !point.is_on_curve() || !point.is_in_correct_subgroup_assuming_on_curve() { + return Err(Error::ApkPointInvalid); + } + Ok(point) +} + +/// Read a G2 point, whose coordinates are pairs in the quadratic extension. +fn read_g2(bytes: &[u8; APK_G2_LEN]) -> Result { + let x = Fq2::new(read_fq(&bytes[..COORDINATE])?, read_fq(&bytes[COORDINATE..2 * COORDINATE])?); + let y = Fq2::new( + read_fq(&bytes[2 * COORDINATE..3 * COORDINATE])?, + read_fq(&bytes[3 * COORDINATE..])?, + ); + let point = G2Affine::new_unchecked(x, y); + if !point.is_on_curve() || !point.is_in_correct_subgroup_assuming_on_curve() { + return Err(Error::ApkPointInvalid); + } + Ok(point) +} + +fn read_fq(bytes: &[u8]) -> Result { + Fq::deserialize_uncompressed(&reverse(bytes)[..]).map_err(|_| Error::ApkPointInvalid) +} + +/// Arkworks serialises field elements little-endian while the circuit and the contract both work +/// big-endian, so every coordinate is reversed on the way in and out. +fn reverse(bytes: &[u8]) -> Vec { + bytes.iter().rev().copied().collect() +} + +fn write_g1(point: &G1Affine) -> Vec { + let (x, y) = point.xy().unwrap_or((Fq::zero(), Fq::zero())); + let mut out = Vec::with_capacity(APK_G1_LEN); + out.extend_from_slice(&x.into_bigint().to_bytes_be()); + out.extend_from_slice(&y.into_bigint().to_bytes_be()); + out +} + +fn write_g2(point: &G2Affine) -> Vec { + let (x, y) = point.xy().unwrap_or((Fq2::zero(), Fq2::zero())); + let mut out = Vec::with_capacity(APK_G2_LEN); + for coordinate in [&x.c0, &x.c1, &y.c0, &y.c1] { + out.extend_from_slice(&coordinate.into_bigint().to_bytes_be()); + } + out +} diff --git a/modules/consensus/beefy/verifier/src/ecdsa.rs b/modules/consensus/beefy/verifier/src/ecdsa.rs new file mode 100644 index 000000000..10a62981a --- /dev/null +++ b/modules/consensus/beefy/verifier/src/ecdsa.rs @@ -0,0 +1,204 @@ +// Copyright (C) Polytope Labs Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +//! Verifying BEEFY finality from per signer ecdsa signatures. +//! +//! Each signer travels with its own signature and a merkle path proving membership of the +//! authority set, so both the proof and the work grow with the number of signers. The aggregate +//! route in [`crate::apk`] is the same protocol with that cost removed. + +use alloc::{string::ToString, vec, vec::Vec}; + +use crate::{ + EcdsaRecover, MMR_ROOT_PAYLOAD_ID, MerkleHasher, error::Error, verify_mmr_leaf, + verify_parachain_headers, +}; +use beefy_verifier_primitives::{ + AuthoritySet, ConsensusMessage, ConsensusState, MmrProof, ParachainHeader, +}; +use codec::Encode; +use ismp::messaging::Keccak256; +use merkle_mountain_range::{ + Error as MmrError, Merge as MmrMerge, MerkleProof as MmrMerkleProof, leaf_index_to_mmr_size, + leaf_index_to_pos, +}; +use polkadot_sdk::{ + sp_consensus_beefy::{Commitment, mmr::MmrLeaf}, + sp_mmr_primitives::LeafProof, +}; +use primitive_types::H256; +use rs_merkle::MerkleProof; + +/// Verify the consensus proof and return the new trusted consensus state and verified parachain +/// headers +pub fn verify_consensus( + trusted_state: ConsensusState, + proof: ConsensusMessage, +) -> Result<(Vec, Vec), Error> { + let (state, heads_root) = verify_mmr_update_proof::(trusted_state, proof.mmr)?; + let verified_headers = verify_parachain_headers::(heads_root, proof.parachain)?; + Ok((state.encode(), verified_headers)) +} + +/// Verifies a new Mmr root update, the relay chain accumulates it's blocks into a merkle mountain +/// range tree which light clients can use as a source for log_2(n) ancestry proofs. This new mmr +/// root hash is signed by the relay chain authority set and we can verify the membership of the +/// authorities that signed this new root using a merkle multi proof and a merkle commitment to the +/// total authorities +pub fn verify_mmr_update_proof( + trusted_state: ConsensusState, + mmr: MmrProof, +) -> Result<(ConsensusState, H256), Error> { + let commitment = &mmr.signed_commitment.commitment; + let preamble = + prepare_update(&trusted_state, commitment, mmr.signed_commitment.signatures.len() as u32)?; + + // The ECDSA half signs the keccak hash of the commitment, and the authority set is committed + // to as the keccak of each signer's Ethereum address, so the signers are identified by + // recovering them rather than by being named in the proof. + let commitment_hash = H::keccak256(&commitment.encode()); + let mut authority_leaves: Vec<[u8; 32]> = Vec::new(); + let mut authority_indices = Vec::new(); + + for sig in mmr.signed_commitment.signatures.iter() { + let uncompressed = H::secp256k1_recover(&commitment_hash.0, &sig.signature) + .map_err(|_| Error::FailedToRecoverPublicKey)?; + + let hashed_uncompressed = H::keccak256(&uncompressed); + + let mut eth_address = [0u8; 20]; + eth_address.copy_from_slice(&hashed_uncompressed.as_ref()[12..]); + + let authority_address_hash = H::keccak256(ð_address); + + authority_leaves.push(authority_address_hash.into()); + authority_indices.push(sig.index as usize); + } + + verify_authority_membership::( + preamble.keyset_commitment, + &mmr.authority_proof, + &authority_indices, + &authority_leaves, + preamble.authority_count, + )?; + + verify_mmr_leaf::(&mmr.latest_mmr_leaf, &mmr.mmr_proof, preamble.mmr_root)?; + + let latest_height = commitment.block_number; + let state = apply_update(trusted_state, &mmr.latest_mmr_leaf, latest_height); + + Ok((state, mmr.latest_mmr_leaf.leaf_extra)) +} + +/// The parts of an update that hold regardless of how the commitment was signed. +struct UpdatePreamble { + /// Commitment to the authority set the signers must belong to. + keyset_commitment: H256, + /// Size of that authority set. + authority_count: u32, + /// MMR root carried in the commitment payload. + mmr_root: H256, +} + +/// Checks staleness, resolves which authority set the commitment claims to be signed under, +/// judges participation against that set alone, and extracts the MMR root from the payload. +fn prepare_update( + trusted_state: &ConsensusState, + commitment: &Commitment, + signer_count: u32, +) -> Result { + if trusted_state.latest_beefy_height >= commitment.block_number { + return Err(Error::StaleHeight { + trusted_height: trusted_state.latest_beefy_height, + current_height: commitment.block_number, + }); + } + + let authority_set = if commitment.validator_set_id == trusted_state.current_authorities.id { + &trusted_state.current_authorities + } else if commitment.validator_set_id == trusted_state.next_authorities.id { + &trusted_state.next_authorities + } else { + return Err(Error::UnknownAuthoritySet { id: commitment.validator_set_id }); + }; + + if !check_participation_threshold(signer_count, authority_set.len) { + return Err(Error::SuperMajorityRequired); + } + + let mmr_root_data = commitment + .payload + .get_raw(&MMR_ROOT_PAYLOAD_ID) + .ok_or(Error::MmrRootHashMissing)?; + + if mmr_root_data.len() != 32 { + return Err(Error::InvalidMmrRootHashLength { len: mmr_root_data.len() }); + } + + Ok(UpdatePreamble { + keyset_commitment: authority_set.ecdsa_merkle_root, + authority_count: authority_set.len, + mmr_root: H256::from_slice(mmr_root_data), + }) +} + +/// Proves the signing authorities are members of the committed authority set. +fn verify_authority_membership( + keyset_commitment: H256, + proof: &[[u8; 32]], + indices: &[usize], + leaves: &[[u8; 32]], + authority_count: u32, +) -> Result<(), Error> { + let merkle_proof = MerkleProof::>::new(proof.to_vec()); + + let valid = + merkle_proof.verify(keyset_commitment.into(), indices, leaves, authority_count as usize); + + if !valid { + Err(Error::InvalidAuthoritiesProof)?; + } + + Ok(()) +} + +/// Rotates the tracked authority sets if the leaf announces a newer one, and records the height. +fn apply_update( + mut trusted_state: ConsensusState, + leaf: &MmrLeaf, + latest_height: u32, +) -> ConsensusState { + if leaf.beefy_next_authority_set.id > trusted_state.next_authorities.id { + trusted_state.current_authorities = trusted_state.next_authorities.clone(); + trusted_state.next_authorities = AuthoritySet { + id: leaf.beefy_next_authority_set.id, + len: leaf.beefy_next_authority_set.len, + // This client cannot compute the poseidon hash, and it only reaches a chain through a + // hyperbridge header digest, so it stays empty until one supplies it. + bls_poseidon_hash: H256::zero(), + ecdsa_merkle_root: leaf.beefy_next_authority_set.keyset_commitment, + }; + } + + trusted_state.latest_beefy_height = latest_height; + + trusted_state +} + +/// Checks for supermajority participation +fn check_participation_threshold(len: u32, total: u32) -> bool { + len >= ((2 * total) / 3) + 1 +} diff --git a/modules/consensus/beefy/verifier/src/error.rs b/modules/consensus/beefy/verifier/src/error.rs index dbd29608a..43bfd0cca 100644 --- a/modules/consensus/beefy/verifier/src/error.rs +++ b/modules/consensus/beefy/verifier/src/error.rs @@ -73,6 +73,34 @@ pub enum Error { /// The SP1 Groth16 verifier rejected the proof bytes. #[error("SP1 proof verification failed")] Sp1VerificationFailed, + /// The apk proof payload failed to SCALE-decode. + #[error("Cannot decode apk proof: {0}")] + DecodeApkProof(String), + /// No commitment has been learned for this authority set yet, so nothing can be checked + /// against it. + #[error("No apk commitment known for authority set {id}")] + ApkCommitmentMissing { + /// Id of the set + id: u64, + }, + /// The apk verifying key stored for the client did not parse. + #[error("Invalid apk verifying key")] + ApkVerifyingKeyInvalid, + /// The proof bytes are not a well formed PLONK proof. + #[error("Malformed apk proof")] + ApkProofMalformed, + /// The PLONK verifier rejected the proof, so the aggregate key is not this set's. + #[error("Apk proof verification failed")] + ApkProofVerificationFailed, + /// A point in the proof is not on the curve or not in the right subgroup. + #[error("Invalid apk curve point")] + ApkPointInvalid, + /// Hashing the commitment onto the curve failed. + #[error("Apk hash to curve failed")] + ApkHashToCurveFailed, + /// The aggregate signature did not verify against the aggregate key. + #[error("Apk pairing check failed")] + ApkPairingFailed, // -- ismp-beefy client wrapper -- /// The trusted state failed to SCALE-decode into a `ConsensusState`. diff --git a/modules/consensus/beefy/verifier/src/lib.rs b/modules/consensus/beefy/verifier/src/lib.rs index 37ef08a06..524e151d0 100644 --- a/modules/consensus/beefy/verifier/src/lib.rs +++ b/modules/consensus/beefy/verifier/src/lib.rs @@ -23,6 +23,9 @@ extern crate alloc; +#[cfg(feature = "apk")] +pub mod apk; +pub mod ecdsa; pub mod error; pub mod sp1; #[cfg(test)] @@ -41,11 +44,15 @@ use merkle_mountain_range::{ Error as MmrError, Merge as MmrMerge, MerkleProof as MmrMerkleProof, leaf_index_to_mmr_size, leaf_index_to_pos, }; +use polkadot_sdk::{ + sp_consensus_beefy::{Commitment, mmr::MmrLeaf}, + sp_mmr_primitives::LeafProof, +}; use primitive_types::H256; use rs_merkle::{Hasher, MerkleProof}; /// The payload ID for the MMR root hash in a BEEFY commitment -const MMR_ROOT_PAYLOAD_ID: [u8; 2] = *b"mh"; +pub(crate) const MMR_ROOT_PAYLOAD_ID: [u8; 2] = *b"mh"; /// A trait for recovering secp256k1 public keys from ECDSA signatures. /// This allows the verifier to be generic. @@ -86,106 +93,6 @@ impl MmrMerge for KeccakMerge { } } -/// Verify the consensus proof and return the new trusted consensus state and verified parachain -/// headers -pub fn verify_consensus( - trusted_state: ConsensusState, - proof: ConsensusMessage, -) -> Result<(Vec, Vec), Error> { - let (state, heads_root) = verify_mmr_update_proof::(trusted_state, proof.mmr)?; - let verified_headers = verify_parachain_headers::(heads_root, proof.parachain)?; - Ok((state.encode(), verified_headers)) -} - -/// Verifies a new Mmr root update, the relay chain accumulates it's blocks into a merkle mountain -/// range tree which light clients can use as a source for log_2(n) ancestry proofs. This new mmr -/// root hash is signed by the relay chain authority set and we can verify the membership of the -/// authorities that signed this new root using a merkle multi proof and a merkle commitment to the -/// total authorities -pub fn verify_mmr_update_proof( - mut trusted_state: ConsensusState, - mmr: MmrProof, -) -> Result<(ConsensusState, H256), Error> { - let signatures_length = mmr.signed_commitment.signatures.len(); - let latest_height = mmr.signed_commitment.commitment.block_number; - - if trusted_state.latest_beefy_height >= latest_height { - return Err(Error::StaleHeight { - trusted_height: trusted_state.latest_beefy_height, - current_height: latest_height, - }); - } - - let commitment = mmr.signed_commitment.commitment.clone(); - - // Pick the authority set the commitment claims to be signed under, then judge - // participation against that set alone. - let authority_set = if commitment.validator_set_id == trusted_state.current_authorities.id { - &trusted_state.current_authorities - } else if commitment.validator_set_id == trusted_state.next_authorities.id { - &trusted_state.next_authorities - } else { - return Err(Error::UnknownAuthoritySet { id: commitment.validator_set_id }); - }; - - if !check_participation_threshold(signatures_length as u32, authority_set.len) { - return Err(Error::SuperMajorityRequired); - } - - let mmr_root_data = commitment - .payload - .get_raw(&MMR_ROOT_PAYLOAD_ID) - .ok_or(Error::MmrRootHashMissing)?; - - if mmr_root_data.len() != 32 { - return Err(Error::InvalidMmrRootHashLength { len: mmr_root_data.len() }); - } - let mmr_root = H256::from_slice(mmr_root_data); - - let commitment_hash = H::keccak256(&commitment.encode()); - let mut authority_leaves: Vec<[u8; 32]> = Vec::new(); - let mut authority_indices = Vec::new(); - - for sig in mmr.signed_commitment.signatures.iter() { - let uncompressed = H::secp256k1_recover(&commitment_hash.0, &sig.signature) - .map_err(|_| Error::FailedToRecoverPublicKey)?; - - let hashed_uncompressed = H::keccak256(&uncompressed); - - let mut eth_address = [0u8; 20]; - eth_address.copy_from_slice(&hashed_uncompressed.as_ref()[12..]); - - let authority_address_hash = H::keccak256(ð_address); - - authority_leaves.push(authority_address_hash.into()); - authority_indices.push(sig.index as usize); - } - - let merkle_proof = MerkleProof::>::new(mmr.authority_proof.clone()); - - let valid = merkle_proof.verify( - authority_set.keyset_commitment.into(), - &authority_indices, - &authority_leaves, - authority_set.len as usize, - ); - - if !valid { - Err(Error::InvalidAuthoritiesProof)?; - } - - verify_mmr_leaf::(&mmr, mmr_root)?; - - if mmr.latest_mmr_leaf.beefy_next_authority_set.id > trusted_state.next_authorities.id { - trusted_state.current_authorities = trusted_state.next_authorities.clone(); - trusted_state.next_authorities = mmr.latest_mmr_leaf.beefy_next_authority_set.clone(); - } - - trusted_state.latest_beefy_height = latest_height; - - Ok((trusted_state, mmr.latest_mmr_leaf.leaf_extra)) -} - /// Verifies the inclusion of parachain headers in the parachain heads root via a merkle multi proof pub fn verify_parachain_headers( heads_root: H256, @@ -222,8 +129,9 @@ pub fn verify_parachain_headers( Ok(parachain_proof.parachains) } -fn verify_mmr_leaf( - mmr: &MmrProof, +pub(crate) fn verify_mmr_leaf( + leaf: &MmrLeaf, + proof: &LeafProof, mmr_root: H256, ) -> Result<(), Error> { // `leaf_indices` is supplied by the relayer in the unsigned consensus message; @@ -231,16 +139,16 @@ fn verify_mmr_leaf( // after the BEEFY signature and authority membership checks had already succeeded. // This verifier checks a single MMR leaf, so reject any proof that does not carry // exactly one leaf index. - if mmr.mmr_proof.leaf_indices.len() != 1 { + if proof.leaf_indices.len() != 1 { Err(Error::InvalidMmrProof)? } - let leaf_index = mmr.mmr_proof.leaf_indices[0]; - let leaf_hash = H::keccak256(&mmr.latest_mmr_leaf.encode()); + let leaf_index = proof.leaf_indices[0]; + let leaf_hash = H::keccak256(&leaf.encode()); let mmr_size = leaf_index_to_mmr_size(leaf_index); let mmr_proof = MmrMerkleProof::<[u8; 32], KeccakMerge>::new( mmr_size, - mmr.mmr_proof.items.iter().map(|h| (*h).into()).collect(), + proof.items.iter().map(|h| (*h).into()).collect(), ); let leaf_pos = leaf_index_to_pos(leaf_index); let leaf = (leaf_pos, leaf_hash.into()); @@ -254,8 +162,3 @@ fn verify_mmr_leaf( Ok(()) } - -/// Checks for supermajority participation -fn check_participation_threshold(len: u32, total: u32) -> bool { - len >= ((2 * total) / 3) + 1 -} diff --git a/modules/consensus/beefy/verifier/src/sp1.rs b/modules/consensus/beefy/verifier/src/sp1.rs index a4b8afb13..5de945da7 100644 --- a/modules/consensus/beefy/verifier/src/sp1.rs +++ b/modules/consensus/beefy/verifier/src/sp1.rs @@ -22,9 +22,10 @@ use alloy_sol_types::{ private::{FixedBytes, U256}, sol, }; -use beefy_verifier_primitives::{ConsensusState, ParachainHeader, Sp1BeefyProof}; +use beefy_verifier_primitives::{AuthoritySet, ConsensusState, ParachainHeader, Sp1BeefyProof}; use codec::Encode; use ismp::messaging::Keccak256; +use primitive_types::H256; // Matches `PublicInputs` and `ParachainHeaderHash` in evm/src/consensus/Types.sol sol! { @@ -91,7 +92,7 @@ pub fn verify_sp1_consensus( .collect(); let public_inputs = PublicInputs { - authorities_root: FixedBytes::from(Into::<[u8; 32]>::into(authority.keyset_commitment)), + authorities_root: FixedBytes::from(Into::<[u8; 32]>::into(authority.ecdsa_merkle_root)), authorities_len: U256::from(authority.len), leaf_hash: FixedBytes::from(Into::<[u8; 32]>::into(H::keccak256(&proof.mmr_leaf.encode()))), block_number: U256::from(proof.block_number), @@ -111,7 +112,14 @@ pub fn verify_sp1_consensus( let mut new_state = trusted_state; if proof.mmr_leaf.beefy_next_authority_set.id > new_state.next_authorities.id { new_state.current_authorities = new_state.next_authorities.clone(); - new_state.next_authorities = proof.mmr_leaf.beefy_next_authority_set.clone(); + new_state.next_authorities = AuthoritySet { + id: proof.mmr_leaf.beefy_next_authority_set.id, + len: proof.mmr_leaf.beefy_next_authority_set.len, + // This client cannot compute the poseidon hash, and it only reaches a chain through a + // hyperbridge header digest, so it stays empty until one supplies it. + bls_poseidon_hash: H256::zero(), + ecdsa_merkle_root: proof.mmr_leaf.beefy_next_authority_set.keyset_commitment, + }; } new_state.latest_beefy_height = proof.block_number; diff --git a/modules/consensus/beefy/verifier/src/test.rs b/modules/consensus/beefy/verifier/src/test.rs index 82bb88953..3af84fe92 100644 --- a/modules/consensus/beefy/verifier/src/test.rs +++ b/modules/consensus/beefy/verifier/src/test.rs @@ -38,7 +38,11 @@ use polkadot_sdk::sp_consensus_beefy::{ }; use sp_mmr_primitives::LeafProof; -use crate::{EcdsaRecover, error::Error, verify_consensus, verify_mmr_update_proof}; +use crate::{ + EcdsaRecover, + ecdsa::{verify_consensus, verify_mmr_update_proof}, + error::Error, +}; struct TestHost; @@ -319,7 +323,8 @@ fn test_sp1_verify_consensus_accepts_solidity_fixture() { // Proof payload matches SP1Beefy.sol:verifyConsensus's `abi.decode(...)` call: // a sequence of four top-level types, not a struct wrapper. - type ProofTuple = sol! { (MiniCommitment, PartialBeefyMmrLeaf, ParachainHeader[], bytes, bytes32) }; + type ProofTuple = + sol! { (MiniCommitment, PartialBeefyMmrLeaf, ParachainHeader[], bytes, bytes32) }; let (commitment, leaf, headers, plonk_proof, nonce) = ::abi_decode_sequence(&proof_bytes).expect("decode proof tuple"); let sp1_proof = Sp1BeefyProof { @@ -453,8 +458,911 @@ fn rejects_sp1_proof_carrying_a_stale_mmr_leaf() { // Swap in a leaf from an earlier block, as an attacker replaying a historical leaf would. proof.mmr_leaf.parent_number_and_hash.0 = BLOCK_NUMBER - 500; - let stale = sp_io::TestExternalities::default().execute_with(|| { - crate::sp1::verify_sp1_consensus::(trusted_state, proof, VKEY) - }); + let stale = sp_io::TestExternalities::default() + .execute_with(|| crate::sp1::verify_sp1_consensus::(trusted_state, proof, VKEY)); assert!(matches!(stale, Err(Error::StaleMmrLeaf { .. })), "got {stale:?}"); } + +/// End-to-end verification against a relay chain whose BEEFY authorities use the paired +/// (ECDSA, BLS12-381) `ecdsa_bls_crypto` key type. Requires the `beefy-prover/bls` feature, which +/// makes the prover keep the ECDSA half of each 177-byte paired signature and authority key. The +/// verifier itself is unchanged: this is "Option A", where a BLS-BEEFY relay is verified through +/// the existing ECDSA path. +/// +/// RELAY_WS_URL=ws://127.0.0.1:9977 \ +/// cargo test -p beefy-verifier --features bls test_verify_consensus_bls -- --ignored +/// --nocapture +#[cfg(feature = "bls")] +#[tokio::test] +#[ignore] +async fn test_verify_consensus_bls() { + let max_rpc_payload_size = 15 * 1024 * 1024; + let relay_ws_url = std::env::var("RELAY_WS_URL").expect("RELAY_WS_URL must be set"); + + let (relay_client, relay_rpc_client) = + subxt_utils::client::ws_client::(&relay_ws_url, max_rpc_payload_size) + .await + .unwrap(); + let relay_rpc = LegacyRpcMethods::::new(relay_rpc_client.clone()); + // Relay-only: point the "para" client at the same relay; `para_ids` is empty so no parachain + // headers are proven (our local relay has no registered parachains). + let (para_client, para_rpc_client) = + subxt_utils::client::ws_client::(&relay_ws_url, max_rpc_payload_size) + .await + .unwrap(); + let para_rpc = LegacyRpcMethods::::new(para_rpc_client.clone()); + + let prover = Prover { + beefy_activation_block: 0, + relay: relay_client, + relay_rpc: relay_rpc.clone(), + relay_rpc_client: relay_rpc_client.clone(), + para: para_client, + para_rpc, + para_rpc_client, + para_ids: vec![], + query_batch_size: Some(100), + }; + + let engine_id = polkadot_sdk::sp_consensus_beefy::BEEFY_ENGINE_ID; + let latest: H256 = + relay_rpc_client.request("beefy_getFinalizedHead", rpc_params!()).await.unwrap(); + + // Walk back to the previous BEEFY-justified block to seed the trusted state. + let mut previous = H256::default(); + let mut cursor = latest; + for _ in 0..2000 { + let header = relay_rpc.chain_get_header(Some(cursor.into())).await.unwrap().unwrap(); + let parent: H256 = header.parent_hash.into(); + if parent.is_zero() { + panic!("reached genesis without a previous beefy block"); + } + let block = relay_rpc.chain_get_block(Some(parent.into())).await.unwrap().unwrap(); + if block + .justifications + .map(|js| js.iter().any(|j| j.0 == engine_id)) + .unwrap_or(false) + { + previous = parent; + break; + } + cursor = parent; + } + assert!(!previous.is_zero(), "no previous beefy block found"); + + // Initial trusted state via the prover, which exercises the folded BLS justification decode. + let trusted_state = prover.get_initial_consensus_state(Some(previous)).await.unwrap(); + + // Latest justification -> signed commitment with ECDSA-half signatures (folded BLS decode). + let latest_block = relay_rpc.chain_get_block(Some(latest.into())).await.unwrap().unwrap(); + let latest_just = latest_block + .justifications + .expect("latest beefy block must have justifications") + .into_iter() + .find_map(|j| (j.0 == engine_id).then_some(j.1)) + .expect("latest beefy block must have a beefy justification"); + let signed = beefy_prover::relay::decode_beefy_justification(&latest_just).unwrap(); + let block_number = signed.commitment.block_number; + let signed_count = signed.signatures.iter().filter(|s| s.is_some()).count(); + + let signatures = signed + .signatures + .iter() + .enumerate() + .filter_map(|(index, s)| { + s.as_ref().map(|sig| { + let slice: &[u8] = sig.as_ref(); + let signature: [u8; 65] = slice.try_into().expect("ecdsa half is 65 bytes"); + SignatureWithAuthorityIndex { index: index as u32, signature } + }) + }) + .collect::>(); + + let (mmr_leaf_proof, latest_leaf) = + fetch_mmr_proof(&prover.relay_rpc, block_number, None).await.unwrap(); + + // Folded BLS-aware authorities: the ECDSA halves of the paired keys. + let current_authorities = prover.beefy_authorities(Some(latest)).await.unwrap(); + let authority_address_hashes = + hash_authority_addresses(current_authorities.into_iter().map(|x| x.encode()).collect()) + .unwrap(); + + let authority_indices = signatures.iter().map(|x| x.index as usize).collect::>(); + let authority_tree = MerkleTree::::from_leaves(&authority_address_hashes); + let authority_proof = authority_tree.proof(&authority_indices).proof_hashes().to_vec(); + + let signed_commitment = SignedCommitment { commitment: signed.commitment.clone(), signatures }; + let mmr = MmrProof { + signed_commitment, + latest_mmr_leaf: latest_leaf.clone(), + mmr_proof: mmr_leaf_proof, + authority_proof, + }; + // Relay-only: `verify_parachain_headers` short-circuits to `Ok(vec![])` on empty parachains. + let parachain_proof = ParachainProof { parachains: vec![], proof: vec![], total_leaves: 0 }; + let consensus_proof = ConsensusMessage { mmr, parachain: parachain_proof }; + + let result = sp_io::TestExternalities::default() + .execute_with(|| verify_consensus::(trusted_state, consensus_proof)); + + assert!(result.is_ok(), "BLS BEEFY verification failed: {:?}", result.err()); + println!( + "BLS BEEFY verify OK: verified {signed_count} paired signatures for beefy block #{block_number}" + ); +} + +/// Option B prototype: actually verify the BLS signatures, aggregated into a single pairing check. +/// +/// This reads the validators' paired keys straight from `Beefy.Authorities` and the paired +/// signatures from the latest justification, extracts the BLS halves (G2 public key, G1 signature), +/// and does two things: +/// 1. Per-signature Chaum-Pedersen verification via w3f-bls (proves our byte extraction and the +/// message hash-to-curve are correct). +/// 2. The aggregate pairing check `e(gen, sum(sig)) == e(sum(pubkey), H(commitment))` by summing +/// the signer G1 signatures and G2 public keys and verifying the sums as one signature. +/// +/// If step 2 passes, plain aggregation over the on-chain-committed key set is sound and we do not +/// need delinearization, which makes the eventual Solidity/EIP-2537 path much simpler. +/// +/// RELAY_WS_URL=ws://127.0.0.1:9977 \ +/// cargo test -p beefy-verifier --features bls test_bls_aggregate_verify -- --ignored +/// --nocapture +#[cfg(feature = "bls")] +#[tokio::test] +#[ignore] +async fn test_bls_aggregate_verify() { + use w3f_bls::{ + DoublePublicKey, DoubleSignature, Message, PublicKey, SerializableToBytes, Signature, + TinyBLS381, + }; + + /// A 177-byte paired signature exactly as SCALE-encoded on the wire. + #[derive(Clone)] + struct Sig177([u8; 177]); + impl codec::Decode for Sig177 { + fn decode(input: &mut I) -> Result { + let mut bytes = [0u8; 177]; + input.read(&mut bytes)?; + Ok(Sig177(bytes)) + } + } + + let max_rpc_payload_size = 15 * 1024 * 1024; + let relay_ws_url = std::env::var("RELAY_WS_URL").expect("RELAY_WS_URL must be set"); + let (_relay_client, relay_rpc_client) = + subxt_utils::client::ws_client::(&relay_ws_url, max_rpc_payload_size) + .await + .unwrap(); + let relay_rpc = LegacyRpcMethods::::new(relay_rpc_client.clone()); + let engine_id = polkadot_sdk::sp_consensus_beefy::BEEFY_ENGINE_ID; + + let latest: H256 = + relay_rpc_client.request("beefy_getFinalizedHead", rpc_params!()).await.unwrap(); + let latest_block = relay_rpc.chain_get_block(Some(latest.into())).await.unwrap().unwrap(); + let latest_just = latest_block + .justifications + .expect("justifications") + .into_iter() + .find_map(|j| (j.0 == engine_id).then_some(j.1)) + .expect("beefy justification"); + + let VersionedFinalityProof::V1(sc) = + VersionedFinalityProof::::decode(&mut &*latest_just).unwrap(); + let commitment_encoded = sc.commitment.encode(); + // The BLS half signs the SCALE-encoded commitment with an empty context (see sp-core + // `bls381::Pair::sign` -> `Message::new(b"", message)`), hashed to G1 by w3f-bls. + let message = Message::new(b"", &commitment_encoded); + + // Validator keys are the 177-byte paired keys; the BLS DoublePublicKey is bytes [33..177]. + let raw_auth = relay_rpc + .state_get_storage(beefy_prover::BEEFY_AUTHORITIES.as_slice(), Some(latest.into())) + .await + .unwrap() + .expect("beefy authorities storage"); + let authorities = Vec::<[u8; 177]>::decode(&mut raw_auth.as_ref()).unwrap(); + + let mut agg_sig: Option<::SignatureGroup> = None; + let mut agg_pub: Option<::PublicKeyGroup> = None; + let mut count = 0u32; + + for (i, maybe_sig) in sc.signatures.iter().enumerate() { + let Some(sig) = maybe_sig else { continue }; + // DoublePublicKey = G1(48) || G2(96); it lives at bytes [33..177] of the paired key. + let dpk = DoublePublicKey::::from_bytes(&authorities[i][33..177]) + .expect("double public key"); + // DoubleSignature = G1 sig(48) || SchnorrProof(64); at bytes [65..177] of the paired sig. + let dsig = + DoubleSignature::::from_bytes(&sig.0[65..177]).expect("double signature"); + + // 1. Per-signature Chaum-Pedersen verification. + assert!(dpk.verify(&message, &dsig), "per-signature BLS verify failed for validator {i}"); + + // 2. Accumulate for the aggregate pairing check. + agg_sig = Some(agg_sig.map_or(dsig.0, |acc| acc + dsig.0)); + agg_pub = Some(agg_pub.map_or(dpk.1, |acc| acc + dpk.1)); + count += 1; + } + + let agg_sig = agg_sig.expect("at least one signer"); + let agg_pub = agg_pub.expect("at least one signer"); + + let aggregate_ok = + Signature::(agg_sig).verify(&message, &PublicKey::(agg_pub)); + + assert!(aggregate_ok, "aggregate BLS pairing check failed"); + println!( + "Aggregate BLS verify OK: {count} BLS signatures aggregated into ONE pairing check; \ + per-signature Chaum-Pedersen also verified. Plain aggregation over the committed key set \ + is sound (no delinearization needed)." + ); +} + +/// Pins down exactly how BEEFY's BLS half hashes a commitment onto the signature curve, and emits +/// a test vector for the Solidity/EIP-2537 implementation to be checked against. +/// +/// This is the make-or-break detail of the EVM path. `w3f-bls` does *not* use the IETF ciphersuite +/// string as the domain separation tag the way a textbook implementation would. It uses a one-byte +/// DST of `0x01`, and prepends the ciphersuite string to the message instead: +/// +/// ```text +/// suite = "BLS_SIG_" || "BLS12381" || "G1" || "_XMD:SHA-256_SSWU_RO_" || "NUL_" +/// preimage = suite || context || message // context is empty for BEEFY +/// point = hash_to_curve(preimage, DST = 0x01) // expand_message_xmd, WB/SSWU map +/// ``` +/// +/// Everything after that composition is standard RFC 9380, which is what the EIP-2537 +/// `MAP_FP_TO_G1` precompile implements, so the contract has to reproduce the composition and the +/// `expand_message_xmd` step and can lean on precompiles for the rest. +/// +/// cargo test -p beefy-verifier --features bls bls_hash_to_curve_vector -- --nocapture +#[cfg(feature = "bls")] +#[test] +fn bls_hash_to_curve_vector() { + use ark_bls12_381::{Fq, G1Affine, g1::Config as G1Config}; + use ark_ec::{ + AffineRepr, CurveGroup, + hashing::{HashToCurve, curve_maps::wb::WBMap, map_to_curve_hasher::MapToCurveBasedHasher}, + }; + use ark_ff::{ + BigInteger, PrimeField, + field_hashers::{DefaultFieldHasher, HashToField}, + }; + use w3f_bls::{Message, TinyBLS381}; + + // Any byte string stands in for a SCALE-encoded commitment here; the composition is what is + // being pinned down, and it does not depend on the contents. + let message = b"beefy-bls-hash-to-curve-vector"; + + let suite = [ + b"BLS_SIG_".as_ref(), + b"BLS12381".as_ref(), + b"G1".as_ref(), + b"_XMD:SHA-256_SSWU_RO_".as_ref(), + b"NUL_".as_ref(), + ] + .concat(); + assert_eq!( + suite.as_slice(), + b"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_".as_ref(), + "ciphersuite string drifted from what w3f-bls composes" + ); + + // context is empty for BEEFY, matching sp-core's `bls381::Pair::sign`. + let preimage = [suite.as_slice(), b"".as_ref(), message.as_ref()].concat(); + + // Rebuild the hasher from the documented parameters rather than going through w3f-bls, then + // check it lands on the same point. If this assert holds, the recipe above is the whole story + // and a Solidity implementation has everything it needs. + let hasher = MapToCurveBasedHasher::< + ark_ec::short_weierstrass::Projective, + DefaultFieldHasher, + WBMap, + >::new(&[1u8]) + .expect("hasher construction"); + let reconstructed: G1Affine = hasher.hash(&preimage).expect("hash to curve"); + + let expected = Message::new(b"", message).hash_to_signature_curve::().into_affine(); + + assert_eq!( + reconstructed, expected, + "reconstructing the hash-to-curve from DST=0x01 and suite-prefixed message did not match \ + w3f-bls; the Solidity recipe would be wrong" + ); + + let (x, y) = expected.xy().expect("point is not the identity"); + let fq_hex = |value: &Fq| hex::encode(value.into_bigint().to_bytes_be()); + + // The two field elements the contract has to derive before it can call MAP_FP_TO_G1. This is + // the only part of the pipeline Solidity implements by hand, so it is the part worth pinning. + let field_hasher = as HashToField>::new(&[1u8]); + let u: Vec = field_hasher.hash_to_field(&preimage, 2); + + println!("=== BEEFY BLS hash-to-curve vector (BLS12-381 G1) ==="); + println!("dst 0x01"); + println!("suite {}", core::str::from_utf8(&suite).unwrap()); + println!("message {}", hex::encode(message)); + println!("preimage {}", hex::encode(&preimage)); + println!("u[0] {}", fq_hex(&u[0])); + println!("u[1] {}", fq_hex(&u[1])); + println!("point.x {}", fq_hex(x)); + println!("point.y {}", fq_hex(y)); + println!(); + println!( + "Solidity must: expand_message_xmd(preimage, 0x01, 128) -> 2 field elements," + ); + println!("MAP_FP_TO_G1 each, G1_ADD them. Cofactor clearing is linear, so it may be applied"); + println!("per point by the precompile or once at the end without changing the result."); +} + +/// Emits an EIP-2537 shaped fixture for the Solidity aggregate verifier. +/// +/// The Solidity side cannot consume the compressed points the proof carries, because EIP-2537 has +/// no decompression precompile: it wants uncompressed, big-endian, zero-padded coordinates. This +/// prints exactly that, for a deterministic validator set, and asserts `w3f-bls` accepts the same +/// aggregate first, so the fixture cannot drift from what the chain would produce. +/// +/// cargo test -p beefy-verifier --features bls-crypto bls_eip2537_fixture -- --nocapture +#[cfg(feature = "bls-crypto")] +#[test] +fn bls_eip2537_fixture() { + use ark_bls12_381::{Fq, G1Affine, G2Affine}; + use ark_ec::{AffineRepr, CurveGroup}; + use ark_ff::{BigInteger, PrimeField}; + use w3f_bls::{ + EngineBLS, Message, PublicKey, SecretKeyVT, SerializableToBytes, Signature as BlsSignature, + TinyBLS381, + }; + + let message = b"beefy-bls-aggregate-fixture"; + let msg = Message::new(b"", message); + + // Same deterministic construction the offline tests use. + let validators: Vec<_> = + (0..3).map(|i| SecretKeyVT::::from_seed(&[b'v', i as u8])).collect(); + + let mut agg_sig: Option<::SignatureGroup> = None; + let mut agg_pub: Option<::PublicKeyGroup> = None; + for secret in &validators { + let sig = secret.sign(&msg); + let public = secret.into_public(); + agg_sig = Some(agg_sig.map_or(sig.0, |acc| acc + sig.0)); + agg_pub = Some(agg_pub.map_or(public.0, |acc| acc + public.0)); + } + let agg_sig = agg_sig.expect("signers"); + let agg_pub = agg_pub.expect("signers"); + + // The aggregate must verify before the fixture is worth anything. + assert!( + BlsSignature::(agg_sig).verify(&msg, &PublicKey::(agg_pub)), + "aggregate does not verify, fixture would be meaningless" + ); + + let fq = |v: &Fq| hex::encode(v.into_bigint().to_bytes_be()); + + let sig_affine: G1Affine = agg_sig.into_affine(); + let (sx, sy) = sig_affine.xy().expect("signature is not the identity"); + + let pub_affine: G2Affine = agg_pub.into_affine(); + let (px, py) = pub_affine.xy().expect("public key is not the identity"); + + println!("=== EIP-2537 fixture: {} signers ===", validators.len()); + println!("message {}", hex::encode(message)); + println!( + "compressed signature {}", + hex::encode(BlsSignature::(agg_sig).to_bytes()) + ); + println!("compressed pubkey {}", hex::encode(PublicKey::(agg_pub).to_bytes())); + println!("-- aggregate signature, G1 uncompressed --"); + println!("sig.x {}", fq(sx)); + println!("sig.y {}", fq(sy)); + println!("-- aggregate public key, G2 uncompressed --"); + println!("pk.x.c0 {}", fq(&px.c0)); + println!("pk.x.c1 {}", fq(&px.c1)); + println!("pk.y.c0 {}", fq(&py.c0)); + println!("pk.y.c1 {}", fq(&py.c1)); + + // Each signer's key on its own, for the G2_ADD path. + for (i, secret) in validators.iter().enumerate() { + let affine: G2Affine = secret.into_public().0.into_affine(); + let (x, y) = affine.xy().expect("public key is not the identity"); + println!("-- signer {i} --"); + println!(" x.c0 {}", fq(&x.c0)); + println!(" x.c1 {}", fq(&x.c1)); + println!(" y.c0 {}", fq(&y.c0)); + println!(" y.c1 {}", fq(&y.c1)); + } +} + +/// Checks the group-bridging step an APK proof would need, against a live BLS relay. +/// +/// `gnark-apk-proofs` aggregates public keys in G1 and pairs them against a G2 signature, while +/// BEEFY signs in G1 with G2 public keys. `DoublePublicKey` publishes the same secret in both +/// groups, so both aggregates describe one aggregate secret and the two can be tied together +/// without changing how the relay signs: +/// +/// ```text +/// e(apk_g1, g2) == e(g1, apk_g2) binds an untrusted apk_g2 to apk_g1 +/// e(sig_g1, g2) == e(hash_to_g1(msg), apk_g2) BEEFY's existing G1 signature +/// ``` +/// +/// A verifier gets `apk_g1` from the SNARK and takes `apk_g2` as an untrusted input, so proving +/// both equations hold for real validator keys is what makes the design viable. Both checks are +/// constant cost, unlike the per-signer merkle paths they would replace. +/// +/// RELAY_WS_URL=ws://127.0.0.1:9979 \ +/// cargo test -p beefy-verifier --features bls,bls-crypto bls_apk_group_binding -- --ignored +/// --nocapture +#[cfg(all(feature = "bls", feature = "bls-crypto"))] +#[tokio::test] +#[ignore] +async fn bls_apk_group_binding() { + use ark_bls12_381::{Bls12_381, G1Affine, G1Projective, G2Affine, G2Projective}; + use ark_ec::{AffineRepr, CurveGroup, Group, pairing::Pairing}; + use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; + use beefy_prover::bls::{ + aggregate_signatures, beefy_g1_authorities, beefy_g2_authorities, + decode_paired_justification, + }; + use w3f_bls::{Message, TinyBLS381}; + + let relay_ws_url = std::env::var("RELAY_WS_URL").expect("RELAY_WS_URL must be set"); + let (_relay_client, relay_rpc_client) = + subxt_utils::client::ws_client::(&relay_ws_url, 15 * 1024 * 1024) + .await + .unwrap(); + let relay_rpc = LegacyRpcMethods::::new(relay_rpc_client.clone()); + + // A real finalized commitment, signed by the relay's paired ecdsa_bls381 validators. + let latest: H256 = + relay_rpc_client.request("beefy_getFinalizedHead", rpc_params!()).await.unwrap(); + let block = relay_rpc.chain_get_block(Some(latest.into())).await.unwrap().unwrap(); + let justification = block + .justifications + .expect("justifications") + .into_iter() + .find_map(|j| (j.0 == polkadot_sdk::sp_consensus_beefy::BEEFY_ENGINE_ID).then_some(j.1)) + .expect("beefy justification"); + let signed = decode_paired_justification(&justification).unwrap(); + + let at = Some(latest); + let g1_keys = beefy_g1_authorities(&relay_rpc, at).await.unwrap(); + let g2_keys = beefy_g2_authorities(&relay_rpc, at).await.unwrap(); + assert_eq!(g1_keys.len(), g2_keys.len(), "both halves come from the same paired keys"); + + // Aggregate only the validators that actually signed, which is what a bitmask selects. + let mut apk_g1 = G1Projective::default(); + let mut apk_g2 = G2Projective::default(); + let mut signatures = Vec::new(); + let mut signer_count = 0usize; + for (index, maybe_signature) in signed.signatures.iter().enumerate() { + let Some(signature) = maybe_signature else { continue }; + apk_g1 += G1Affine::deserialize_compressed(&g1_keys[index][..]) + .expect("validator G1 public key decodes"); + apk_g2 += G2Affine::deserialize_compressed(&g2_keys[index][..]) + .expect("validator G2 public key decodes"); + signatures.push(signature.g1_signature()); + signer_count += 1; + } + assert!(signer_count > 0, "commitment carries no signatures"); + + let apk_g1 = apk_g1.into_affine(); + let apk_g2 = apk_g2.into_affine(); + + // 1. The binding check. Holds only when both aggregates share a discrete log, so a verifier can + // accept apk_g2 from an untrusted relayer once the SNARK has fixed apk_g1. + let bound = Bls12_381::pairing(apk_g1, G2Affine::generator()) == + Bls12_381::pairing(G1Affine::generator(), apk_g2); + assert!(bound, "e(apk_g1, g2) != e(g1, apk_g2): the two aggregates disagree"); + + // 2. BEEFY's unmodified G1 signature, verified against the G2 aggregate just bound above. + let message = signed.commitment.encode(); + let aggregate_signature = aggregate_signatures(&signatures).unwrap(); + let sig_g1 = G1Affine::deserialize_compressed(&aggregate_signature[..]) + .expect("aggregate signature decodes"); + let message_point = Message::new(b"", &message) + .hash_to_signature_curve::() + .into_affine(); + let signed_ok = Bls12_381::pairing(sig_g1, G2Affine::generator()) == + Bls12_381::pairing(message_point, apk_g2); + assert!(signed_ok, "e(sig_g1, g2) != e(H(m), apk_g2): signature does not verify"); + + // 3. A negative control, so the equations are not vacuously true. + let tampered = (apk_g2.into_group() + G2Projective::generator()).into_affine(); + assert!( + Bls12_381::pairing(apk_g1, G2Affine::generator()) != + Bls12_381::pairing(G1Affine::generator(), tampered), + "binding check accepted a tampered apk_g2", + ); + + println!( + "[ok] group binding holds for {signer_count} live signers: apk_g1 <-> apk_g2 bound, \ + and BEEFY's G1 signature verifies against the bound apk_g2", + ); + + // Dump everything an APK proof fixture needs, so a test elsewhere can be built against real + // BEEFY data rather than synthetic keypairs. + let hex_affine_g1 = |p: &G1Affine| { + let mut b = Vec::new(); + p.serialize_compressed(&mut b).unwrap(); + hex::encode(b) + }; + let hex_affine_g2 = |p: &G2Affine| { + let mut b = Vec::new(); + p.serialize_compressed(&mut b).unwrap(); + hex::encode(b) + }; + println!("=== apk fixture inputs ==="); + println!("message {}", hex::encode(&message)); + println!("apk_g1 {}", hex_affine_g1(&apk_g1)); + println!("apk_g2 {}", hex_affine_g2(&apk_g2)); + println!("agg_sig {}", hex::encode(aggregate_signature)); + for (i, key) in g1_keys.iter().enumerate() { + println!("g1[{i}] {}", hex::encode(key)); + } +} + +/// Collects everything an APK consensus proof needs from a live BLS relay, except the SNARK. +/// +/// The SNARK is generated by `gnark-apk-proofs`, which pulls in a Go toolchain through cgo and an +/// 800MB structured reference string, so it stays out of this workspace. The two halves meet over +/// the json this writes: +/// +/// ```text +/// this test -> apk-inputs.json (live BEEFY data, validator G1 keys) +/// gnark-apk-proofs -> apk-snark.json (PLONK proof, public inputs) +/// bls_apk_live_fixture -> the two .hex files BlsApkBeefy.t.sol reads +/// ``` +/// +/// The parachain must be registered as 4009, which is what gargantua tracks. +/// +/// RELAY_WS_URL=ws://127.0.0.1:9979 PARA_WS_URL=ws://127.0.0.1:9981 \ +/// APK_FIXTURE_DIR=/tmp/apk \ +/// cargo test -p beefy-verifier --features bls,bls-crypto bls_apk_live_inputs -- --ignored +/// --nocapture +#[cfg(all(feature = "bls", feature = "bls-crypto"))] +#[tokio::test] +#[ignore] +async fn bls_apk_live_inputs() { + use ark_bls12_381::{G1Affine, G1Projective, G2Affine, G2Projective}; + use ark_ec::{AffineRepr, CurveGroup}; + use ark_ff::{BigInteger, PrimeField}; + use ark_serialize::CanonicalDeserialize; + use beefy_prover::bls::{ + aggregate_signatures, beefy_g1_authorities, beefy_g2_authorities, + decode_paired_justification, + }; + + // EIP-2537 wants padded coordinates, but `ApkProof` takes bytes32[3] and bytes32[6], which are + // the coordinates packed with no padding at all. Getting this wrong produces a well formed + // point and a silent pairing failure, so both encodings exist side by side deliberately. + fn fq_be(fq: &ark_bls12_381::Fq, out: &mut Vec) { + out.extend_from_slice(&fq.into_bigint().to_bytes_be()); + } + fn g1_packed(point: &G1Affine) -> String { + let (x, y) = point.xy().expect("not the identity"); + let mut bytes = Vec::with_capacity(96); + fq_be(x, &mut bytes); + fq_be(y, &mut bytes); + hex::encode(bytes) + } + fn g2_packed(point: &G2Affine) -> String { + let (x, y) = point.xy().expect("not the identity"); + let mut bytes = Vec::with_capacity(192); + fq_be(&x.c0, &mut bytes); + fq_be(&x.c1, &mut bytes); + fq_be(&y.c0, &mut bytes); + fq_be(&y.c1, &mut bytes); + hex::encode(bytes) + } + + let max_rpc_payload_size = 15 * 1024 * 1024; + let relay_ws_url = std::env::var("RELAY_WS_URL").expect("RELAY_WS_URL must be set"); + let para_ws_url = std::env::var("PARA_WS_URL").expect("PARA_WS_URL must be set"); + let out_dir = std::env::var("APK_FIXTURE_DIR").expect("APK_FIXTURE_DIR must be set"); + + let (relay_client, relay_rpc_client) = + subxt_utils::client::ws_client::(&relay_ws_url, max_rpc_payload_size) + .await + .unwrap(); + let relay_rpc = LegacyRpcMethods::::new(relay_rpc_client.clone()); + let (para_client, para_rpc_client) = + subxt_utils::client::ws_client::(¶_ws_url, max_rpc_payload_size) + .await + .unwrap(); + let para_rpc = LegacyRpcMethods::::new(para_rpc_client.clone()); + + let prover = Prover { + beefy_activation_block: 0, + relay: relay_client, + relay_rpc: relay_rpc.clone(), + relay_rpc_client: relay_rpc_client.clone(), + para: para_client, + para_rpc, + para_rpc_client, + para_ids: vec![4009], + query_batch_size: Some(100), + }; + + let latest: H256 = + relay_rpc_client.request("beefy_getFinalizedHead", rpc_params!()).await.unwrap(); + let block = relay_rpc.chain_get_block(Some(latest.into())).await.unwrap().unwrap(); + let justification = block + .justifications + .expect("justifications") + .into_iter() + .find_map(|j| (j.0 == polkadot_sdk::sp_consensus_beefy::BEEFY_ENGINE_ID).then_some(j.1)) + .expect("beefy justification"); + + let signed = decode_paired_justification(&justification).unwrap(); + let set_id = signed.commitment.validator_set_id; + + // Anchor one authority set back, so the proof also exercises a rotation rather than only the + // current set. Exactly one back: the verifier accepts the trusted state's current or next set. + let mut anchor = H256::default(); + let mut cursor = latest; + for _ in 0..4000 { + let header = relay_rpc.chain_get_header(Some(cursor.into())).await.unwrap().unwrap(); + let parent: H256 = header.parent_hash.into(); + if parent.is_zero() { + break; + } + if let Ok(Some(b)) = relay_rpc.chain_get_block(Some(parent.into())).await { + if let Some(js) = b.justifications { + if let Some(raw) = js.into_iter().find_map(|j| { + (j.0 == polkadot_sdk::sp_consensus_beefy::BEEFY_ENGINE_ID).then_some(j.1) + }) { + let prev = decode_paired_justification(&raw).unwrap(); + if prev.commitment.validator_set_id + 1 == set_id { + anchor = parent; + break; + } + } + } + } + cursor = parent; + } + assert!(!anchor.is_zero(), "no anchor one set back"); + + let state = prover.get_initial_consensus_state(Some(anchor)).await.unwrap(); + let message = prover.bls_consensus_proof(signed.clone()).await.unwrap(); + + // The validator set that signed, both halves of the same paired keys. + let g1_keys = beefy_g1_authorities(&relay_rpc, Some(latest)).await.unwrap(); + let g2_keys = beefy_g2_authorities(&relay_rpc, Some(latest)).await.unwrap(); + assert_eq!(g1_keys.len(), g2_keys.len()); + + let mut apk_g1 = G1Projective::default(); + let mut apk_g2 = G2Projective::default(); + let mut signatures = Vec::new(); + let mut participation = Vec::new(); + for (index, maybe_signature) in signed.signatures.iter().enumerate() { + let Some(signature) = maybe_signature else { continue }; + apk_g1 += G1Affine::deserialize_compressed(&g1_keys[index][..]).expect("G1 key decodes"); + apk_g2 += G2Affine::deserialize_compressed(&g2_keys[index][..]).expect("G2 key decodes"); + signatures.push(signature.g1_signature()); + participation.push(index as u64); + } + assert!(!participation.is_empty(), "commitment carries no signatures"); + + let apk_g1 = apk_g1.into_affine(); + let apk_g2 = apk_g2.into_affine(); + let aggregate = aggregate_signatures(&signatures).unwrap(); + let sig_g1 = G1Affine::deserialize_compressed(&aggregate[..]).expect("aggregate decodes"); + + let mmr = &message.mmr; + let leaf_index = mmr.mmr_proof.leaf_indices.first().copied().unwrap_or_default(); + let bundle = json::json!({ + "validatorSetId": set_id, + "blockNumber": mmr.commitment.block_number, + "payloadMh": hex::encode(mmr.commitment.payload.get_raw(b"mh").expect("mmr payload")), + "keys": g1_keys.iter().map(|k| { + g1_packed(&G1Affine::deserialize_compressed(&k[..]).expect("G1 key decodes")) + }).collect::>(), + "participation": participation, + "apk": g1_packed(&apk_g1), + "apk2": g2_packed(&apk_g2), + "signature": g1_packed(&sig_g1), + "mmrLeaf": { + "parentNumber": mmr.latest_mmr_leaf.parent_number_and_hash.0, + "parentHash": hex::encode(mmr.latest_mmr_leaf.parent_number_and_hash.1.0), + "nextAuthoritySetId": mmr.latest_mmr_leaf.beefy_next_authority_set.id, + "nextAuthoritySetLen": mmr.latest_mmr_leaf.beefy_next_authority_set.len, + "nextAuthoritySetRoot": + hex::encode(mmr.latest_mmr_leaf.beefy_next_authority_set.keyset_commitment.0), + "extra": hex::encode(mmr.latest_mmr_leaf.leaf_extra.0), + "leafIndex": leaf_index, + }, + "mmrProof": mmr.mmr_proof.items.iter().map(|h| hex::encode(h.0)).collect::>(), + "parachains": message.parachain.parachains.iter().map(|p| json::json!({ + "index": p.index, + "id": p.para_id, + "header": hex::encode(&p.header), + })).collect::>(), + "parachainProof": + message.parachain.proof.iter().map(|h| hex::encode(h)).collect::>(), + "parachainLeafCount": message.parachain.total_leaves, + "trusted": { + "latestHeight": state.latest_beefy_height, + "beefyActivationBlock": state.beefy_activation_block, + "currentId": state.current_authorities.id, + "currentLen": state.current_authorities.len, + "nextId": state.next_authorities.id, + "nextLen": state.next_authorities.len, + }, + }); + + std::fs::create_dir_all(&out_dir).expect("create fixture dir"); + let path = format!("{out_dir}/apk-inputs.json"); + std::fs::write(&path, json::to_string_pretty(&bundle).unwrap()).expect("write bundle"); + + println!("=== apk live inputs ==="); + println!( + "set {set_id} | {} validators | {} signed | block {} | mmr nodes {} | parachains {}", + g1_keys.len(), + participation.len(), + mmr.commitment.block_number, + mmr.mmr_proof.items.len(), + message.parachain.parachains.len(), + ); + println!("wrote {path}"); + assert!( + !message.parachain.parachains.is_empty(), + "expected a parachain header from the registered para", + ); +} + +/// Assembles the two hex fixtures `BlsApkBeefy.t.sol` reads, from the live data collected by +/// `bls_apk_live_inputs` and the proof generated by `gnark-apk-proofs`. Needs no chain access, so +/// the fixture can be rebuilt without the relay still running. +/// +/// APK_FIXTURE_DIR=/tmp/apk \ +/// cargo test -p beefy-verifier --features bls bls_apk_live_fixture -- --ignored --nocapture +#[cfg(feature = "bls")] +#[test] +#[ignore] +fn bls_apk_live_fixture() { + use alloy_primitives::{Bytes, FixedBytes, U256}; + use alloy_sol_types::{SolType, SolValue}; + use ismp_abi::bls_beefy::BlsBeefy; + + let dir = std::env::var("APK_FIXTURE_DIR").expect("APK_FIXTURE_DIR must be set"); + let read = |name: &str| -> json::Value { + let path = format!("{dir}/{name}"); + json::from_str(&std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{path}: {e}"))) + .expect("valid json") + }; + let inputs = read("apk-inputs.json"); + let snark = read("apk-snark.json"); + + let hex_bytes = |value: &json::Value| hex::decode(value.as_str().expect("hex string")).unwrap(); + let fixed32 = |value: &json::Value| FixedBytes::<32>::from_slice(&hex_bytes(value)); + let u64_of = |value: &json::Value| value.as_u64().expect("number"); + // Splits a packed curve point into the bytes32 words the SNARK verifier takes. Not the padded + // EIP-2537 layout the rest of the BLS code uses. + let words = |value: &json::Value, count: usize| -> Vec> { + let raw = hex_bytes(value); + assert_eq!(raw.len(), count * 32, "point is the wrong width for bytes32[{count}]"); + raw.chunks(32).map(FixedBytes::<32>::from_slice).collect() + }; + + // The commitment the proof was generated against is the one the client has to check it with, + // so it comes from the SNARK's own public inputs. That it matches what this workspace would + // compute is not asserted here any more: both would now be `gnark-plonk-verifier`, and the + // implementation is pinned against the go one by that crate's own vectors. + let apk_commitment = fixed32(&snark["apkCommitment"]); + + let trusted = &inputs["trusted"]; + let signing_set_id = u64_of(&inputs["validatorSetId"]); + // Only the set that signed needs its commitment seeded; the other is learned from a digest. + let authority_set = |id: u64, len: u64| BlsBeefy::AuthoritySetCommitment { + id, + len: len as u32, + root: if id == signing_set_id { apk_commitment } else { FixedBytes::ZERO }, + }; + + let state = BlsBeefy::BeefyConsensusState { + latestHeight: U256::from(u64_of(&trusted["latestHeight"])), + beefyActivationBlock: U256::from(u64_of(&trusted["beefyActivationBlock"])), + currentAuthoritySet: authority_set( + u64_of(&trusted["currentId"]), + u64_of(&trusted["currentLen"]), + ), + nextAuthoritySet: authority_set(u64_of(&trusted["nextId"]), u64_of(&trusted["nextLen"])), + }; + assert!( + state.currentAuthoritySet.root != FixedBytes::ZERO || + state.nextAuthoritySet.root != FixedBytes::ZERO, + "neither trusted set matches the signing set, so the client would have no commitment", + ); + + let bitlist: [U256; 5] = snark["bitlist"] + .as_array() + .expect("bitlist") + .iter() + .map(|w| U256::from_be_slice(&hex_bytes(w))) + .collect::>() + .try_into() + .expect("five words"); + + let leaf = &inputs["mmrLeaf"]; + let relay = BlsBeefy::BlsApkRelayChainProof { + commitment: BlsBeefy::Commitment { + payload: vec![BlsBeefy::Payload { + id: FixedBytes(*b"mh"), + data: Bytes::from(hex_bytes(&inputs["payloadMh"])), + }], + blockNumber: u64_of(&inputs["blockNumber"]) as u32, + validatorSetId: signing_set_id, + }, + bitlist, + apk: words(&inputs["apk"], 3).try_into().expect("bytes32[3]"), + apk2: words(&inputs["apk2"], 6).try_into().expect("bytes32[6]"), + apkProof: Bytes::from(hex_bytes(&snark["apkProof"])), + signature: words(&inputs["signature"], 3).try_into().expect("bytes32[3]"), + latestMmrLeaf: BlsBeefy::BeefyMmrLeaf { + version: 0, + parentNumber: u64_of(&leaf["parentNumber"]) as u32, + parentHash: fixed32(&leaf["parentHash"]), + nextAuthoritySet: BlsBeefy::AuthoritySetCommitment { + id: u64_of(&leaf["nextAuthoritySetId"]), + len: u64_of(&leaf["nextAuthoritySetLen"]) as u32, + root: fixed32(&leaf["nextAuthoritySetRoot"]), + }, + extra: fixed32(&leaf["extra"]), + leafIndex: U256::from(u64_of(&leaf["leafIndex"])), + }, + mmrProof: inputs["mmrProof"].as_array().expect("mmrProof").iter().map(fixed32).collect(), + }; + + let parachain = BlsBeefy::ParachainProof { + parachains: inputs["parachains"] + .as_array() + .expect("parachains") + .iter() + .map(|para| BlsBeefy::Parachain { + index: U256::from(u64_of(¶["index"])), + id: U256::from(u64_of(¶["id"])), + header: Bytes::from(hex_bytes(¶["header"])), + }) + .collect(), + proof: inputs["parachainProof"] + .as_array() + .expect("proof") + .iter() + .map(fixed32) + .collect(), + leafCount: U256::from(u64_of(&inputs["parachainLeafCount"])), + }; + + // SolValue, not SolType: this has to match `abi.encode(struct)` on the Solidity side. + let encoded_state = SolValue::abi_encode(&state); + let encoded_proof = + <(BlsBeefy::BlsApkRelayChainProof, BlsBeefy::ParachainProof) as SolType>::abi_encode_params( + &(relay.clone(), parachain.clone()), + ); + + let fixtures = std::env::var("APK_FIXTURE_OUT") + .unwrap_or_else(|_| "../../../../evm/tests/foundry/fixtures".to_string()); + std::fs::write( + format!("{fixtures}/bls-apk-beefy-state.hex"), + format!("0x{}", hex::encode(&encoded_state)), + ) + .expect("write state"); + std::fs::write( + format!("{fixtures}/bls-apk-beefy-proof.hex"), + format!("0x{}", hex::encode(&encoded_proof)), + ) + .expect("write proof"); + + println!("=== apk live fixture ==="); + println!( + "signers {} | parachains {} | mmr nodes {} | block {} | apk proof {} bytes", + bitlist.iter().map(|w| w.count_ones()).sum::(), + parachain.parachains.len(), + relay.mmrProof.len(), + relay.commitment.blockNumber, + relay.apkProof.len(), + ); + println!("state {} bytes, proof {} bytes", encoded_state.len(), encoded_proof.len()); + println!("wrote {fixtures}/bls-apk-beefy-{{state,proof}}.hex"); +} diff --git a/modules/consensus/beefy/verifier/tests/apk_fixture.rs b/modules/consensus/beefy/verifier/tests/apk_fixture.rs new file mode 100644 index 000000000..6c0a1efb7 --- /dev/null +++ b/modules/consensus/beefy/verifier/tests/apk_fixture.rs @@ -0,0 +1,131 @@ +// Copyright (C) Polytope Labs Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +//! The runtime verifier against the fixture `BlsApkBeefy.sol` verifies. +//! +//! Lives out here rather than in the unit tests because those pull `w3f-bls` and its arkworks 0.4, +//! while `crate::apk` is on 0.5, and the two cannot both be in scope in one test binary. + +#![cfg(feature = "apk")] + +use alloy_sol_types::SolType; +use beefy_verifier::apk::{count_signers, verify_apk_consensus}; +use beefy_verifier_primitives::{ApkConsensusMessage, ConsensusState}; +use ismp_abi::bls_beefy::BlsBeefy; +use polkadot_sdk::*; +use primitive_types::H256; + +/// Generated by the circuit's trusted setup, and the same key that goes into pallet storage. +/// The parachain the fixture was captured from, and the only one whose digests it should read. +const PARA_ID: u32 = 4009; + +const VERIFYING_KEY: &[u8] = + include_bytes!("../../../../../evm/tests/foundry/fixtures/apk-verifying-key.bin"); + +struct TestHost; + +impl ismp::messaging::Keccak256 for TestHost { + fn keccak256(bytes: &[u8]) -> H256 { + sp_io::hashing::keccak_256(bytes).into() + } +} + +/// The two verifiers have to agree, since they check the same proofs against the same commitment. +/// This runs the Solidity test's fixture, byte for byte, through the Rust path: same ABI encoding +/// off the wire, same verifying key, same expectations. +/// +/// A disagreement here is the expensive kind to find later, because in production it looks like a +/// proof that verifies on one chain and not the other with nothing to explain why. +/// +/// cargo test -p beefy-verifier --features apk --test apk_fixture -- --nocapture +#[test] +fn apk_verifier_agrees_with_solidity() { + let decode_hex = |raw: &str| hex::decode(raw.trim().trim_start_matches("0x")).expect("hex"); + let state_bytes = decode_hex(include_str!( + "../../../../../evm/tests/foundry/fixtures/bls-apk-beefy-state.hex" + )); + let proof_bytes = decode_hex(include_str!( + "../../../../../evm/tests/foundry/fixtures/bls-apk-beefy-proof.hex" + )); + + // The state is one abi-encoded struct, the proof is the two the client's `verify` takes as + // separate arguments, which is why they decode differently. + let trusted: ConsensusState = + ::abi_decode(&state_bytes) + .expect("state decodes") + .try_into() + .expect("state converts"); + let proof: ApkConsensusMessage = + ::abi_decode_params(&proof_bytes) + .expect("proof decodes") + .try_into() + .expect("proof converts"); + + let signing_set = proof.mmr.commitment.validator_set_id; + let signers = count_signers(&proof.mmr.bitlist); + + let (state, headers) = + verify_apk_consensus::(trusted.clone(), proof, VERIFYING_KEY, PARA_ID) + .expect("the proof solidity verifies must verify here too"); + + assert!( + state.latest_beefy_height > trusted.latest_beefy_height, + "height should advance, was {} now {}", + trusted.latest_beefy_height, + state.latest_beefy_height + ); + assert_eq!(headers.len(), 1, "should finalize the registered parachain"); + assert_eq!(headers[0].para_id, PARA_ID, "should be para {PARA_ID}"); + + println!( + "apk proof verified in the runtime path: set {signing_set}, {signers} signers, beefy height {} -> {}", + trusted.latest_beefy_height, state.latest_beefy_height + ); +} + +/// The signature and the binding of `apk` to `apk2` are folded into one pairing with a random +/// challenge, which is cheaper than checking them separately but only sound if it really enforces +/// both. A proof that verifies proves nothing about that: an equation that quietly ignored the +/// signature would still accept every honest proof. +/// +/// The substitute is the aggregate key, which is a real point in G1 and therefore passes the +/// curve and subgroup checks that come first. Only the pairing itself can reject it, so if the +/// update is refused, the signature is genuinely part of the equation. +#[test] +fn the_batched_pairing_enforces_the_signature() { + let decode_hex = |raw: &str| hex::decode(raw.trim().trim_start_matches("0x")).expect("hex"); + let state_bytes = decode_hex(include_str!( + "../../../../../evm/tests/foundry/fixtures/bls-apk-beefy-state.hex" + )); + let proof_bytes = decode_hex(include_str!( + "../../../../../evm/tests/foundry/fixtures/bls-apk-beefy-proof.hex" + )); + let trusted: ConsensusState = + ::abi_decode(&state_bytes) + .expect("state decodes") + .try_into() + .expect("state converts"); + let mut proof: ApkConsensusMessage = + ::abi_decode_params(&proof_bytes) + .expect("proof decodes") + .try_into() + .expect("proof converts"); + + proof.mmr.signature = proof.mmr.apk; + assert!( + verify_apk_consensus::(trusted, proof, VERIFYING_KEY, PARA_ID).is_err(), + "a well formed point that is not the signature was accepted", + ); +} diff --git a/modules/ismp/clients/beefy/Cargo.toml b/modules/ismp/clients/beefy/Cargo.toml index fb447975b..80e796491 100644 --- a/modules/ismp/clients/beefy/Cargo.toml +++ b/modules/ismp/clients/beefy/Cargo.toml @@ -9,7 +9,7 @@ codec = { workspace = true, features = ["derive"], default-features = false } primitive-types = { workspace = true, default-features = false } ismp = { workspace = true, default-features = false } -beefy-verifier = { workspace = true, default-features = false } +beefy-verifier = { workspace = true, default-features = false, features = ["apk"] } pallet-ismp = { workspace = true, default-features = false } substrate-state-machine = { workspace = true, default-features = false } beefy-verifier-primitives = { workspace = true, default-features = false } diff --git a/modules/ismp/clients/beefy/src/consensus.rs b/modules/ismp/clients/beefy/src/consensus.rs index a0af6c4b1..5891214dd 100644 --- a/modules/ismp/clients/beefy/src/consensus.rs +++ b/modules/ismp/clients/beefy/src/consensus.rs @@ -14,10 +14,10 @@ // limitations under the License. use alloc::{boxed::Box, collections::BTreeMap, format, vec, vec::Vec}; -use beefy_verifier::{error::Error as BeefyError, verify_consensus}; +use beefy_verifier::{ecdsa::verify_consensus, error::Error as BeefyError}; use beefy_verifier_primitives::{ - ConsensusMessage, ConsensusState, MmrProof, PROOF_TYPE_NAIVE, PROOF_TYPE_SP1, ParachainProof, - Sp1BeefyProof, + ConsensusMessage, ConsensusState, MmrProof, PROOF_TYPE_APK, PROOF_TYPE_NAIVE, PROOF_TYPE_SP1, + ParachainProof, Sp1BeefyProof, }; use codec::{Decode, Encode}; use core::marker::PhantomData; @@ -76,9 +76,12 @@ where trusted_consensus_state: Vec, proof: Vec, ) -> Result<(Vec, VerifiedCommitments), Error> { - let consensus_state: ConsensusState = + // Decoded per arm rather than up front, since the apk path carries a different state + // shape: authority sets there are identified by a commitment to their keys. + let decode_state = || -> Result { codec::Decode::decode(&mut &trusted_consensus_state[..]) - .map_err(|e| BeefyError::DecodeConsensusState(format!("{e:?}")))?; + .map_err(|e| BeefyError::DecodeConsensusState(format!("{e:?}"))) + }; let proof_type = proof.first().ok_or(BeefyError::EmptyProof)?; if !C::allowed_proof_types().contains(proof_type) { @@ -90,7 +93,7 @@ where PROOF_TYPE_NAIVE => { let consensus_proof: ConsensusMessage = codec::Decode::decode(&mut &payload[..]) .map_err(|e| BeefyError::DecodeNaiveProof(format!("{e:?}")))?; - verify_consensus::(consensus_state, consensus_proof)? + verify_consensus::(decode_state()?, consensus_proof)? }, PROOF_TYPE_SP1 => { let sp1_proof: Sp1BeefyProof = codec::Decode::decode(&mut &payload[..]) @@ -98,11 +101,23 @@ where let vkey_hash = C::sp1_vkey_hash(); let vkey = alloc::format!("0x{:x}", vkey_hash); beefy_verifier::sp1::verify_sp1_consensus::( - consensus_state, + decode_state()?, sp1_proof, &vkey, )? }, + PROOF_TYPE_APK => { + let apk_proof: beefy_verifier_primitives::ApkConsensusMessage = + codec::Decode::decode(&mut &payload[..]) + .map_err(|e| BeefyError::DecodeApkProof(format!("{e:?}")))?; + let (state, headers) = beefy_verifier::apk::verify_apk_consensus::( + decode_state()?, + apk_proof, + &C::apk_verifying_key(), + C::apk_digest_para_id(), + )?; + (state.encode(), headers) + }, _ => return Err(BeefyError::UnknownProofType(*proof_type).into()), }; diff --git a/modules/ismp/clients/beefy/src/lib.rs b/modules/ismp/clients/beefy/src/lib.rs index 11a28f44b..9f7d50fb7 100644 --- a/modules/ismp/clients/beefy/src/lib.rs +++ b/modules/ismp/clients/beefy/src/lib.rs @@ -19,7 +19,7 @@ extern crate alloc; extern crate core; pub mod consensus; -pub use beefy_verifier_primitives::{PROOF_TYPE_NAIVE, PROOF_TYPE_SP1}; +pub use beefy_verifier_primitives::{PROOF_TYPE_APK, PROOF_TYPE_NAIVE, PROOF_TYPE_SP1}; pub use consensus::{BEEFY_CONSENSUS_ID, BeefyConsensusClient}; use polkadot_sdk::*; @@ -48,6 +48,24 @@ pub trait BeefyClientConfig { /// Returns the SP1 verification key hash. fn sp1_vkey_hash() -> primitive_types::H256; + /// Verifying key for the aggregate public key circuit, read on every apk proof. + /// + /// This is the key itself rather than a hash, since the verification happens here. A chain + /// that never sees an apk proof can leave it empty. + fn apk_verifying_key() -> alloc::vec::Vec { + Default::default() + } + + /// The parachain whose header digests carry apk commitments, which is hyperbridge. + /// + /// Every parachain in a proof is proven against the heads root, so a digest from any of them + /// is authentic; only this one's says anything about the relay chain's authorities. The + /// default of zero matches no parachain, so a chain that never sees an apk proof learns + /// nothing rather than learning from whoever turns up. + fn apk_digest_para_id() -> u32 { + 0 + } + /// Allowed proof types. Controls which consensus proof formats this client will /// accept. On mainnet set to `&[PROOF_TYPE_SP1]`, on testnets set to /// `&[PROOF_TYPE_NAIVE, PROOF_TYPE_SP1]`. A proof whose type byte is not listed is diff --git a/modules/pallets/beefy-apk-digest/Cargo.toml b/modules/pallets/beefy-apk-digest/Cargo.toml new file mode 100644 index 000000000..29abb0b56 --- /dev/null +++ b/modules/pallets/beefy-apk-digest/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "pallet-beefy-apk-digest" +version = "0.1.0" +edition = "2021" +authors = ["Polytope Labs "] +license = "Apache-2.0" +description = "Commits the relay chain's BEEFY BLS public keys to a header digest for APK proofs" +publish = false + +[dependencies] +codec = { workspace = true } +scale-info = { workspace = true } +log = { workspace = true } + +beefy-verifier-primitives = { workspace = true, default-features = false } +cumulus-pallet-parachain-system = { workspace = true, default-features = false } + +ark-bls12-381 = { version = "0.5", features = ["curve"], default-features = false } + +ark-serialize = { version = "0.5", default-features = false } + +hex-literal = { workspace = true, optional = true } + +[dependencies.gnark-plonk-verifier] +git = "https://github.com/polytope-labs/gnark-apk-proofs" +rev = "1c15d06d8180906b16201c5206711808b80742ef" +default-features = false + +[dependencies.polkadot-sdk] +workspace = true +features = ["frame-support", "frame-system", "sp-io", "sp-runtime"] +default-features = false + +[dev-dependencies] +hex = { workspace = true, default-features = true } + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "log/std", + "polkadot-sdk/std", + "gnark-plonk-verifier/std", + "beefy-verifier-primitives/std", + "cumulus-pallet-parachain-system/std", + "ark-bls12-381/std", + "ark-serialize/std", +] +try-runtime = ["polkadot-sdk/try-runtime"] +runtime-benchmarks = [ + "polkadot-sdk/frame-benchmarking", + "polkadot-sdk/runtime-benchmarks", + "dep:hex-literal", +] diff --git a/modules/pallets/beefy-apk-digest/src/benchmarking.rs b/modules/pallets/beefy-apk-digest/src/benchmarking.rs new file mode 100644 index 000000000..15be18fd6 --- /dev/null +++ b/modules/pallets/beefy-apk-digest/src/benchmarking.rs @@ -0,0 +1,69 @@ +// Copyright (C) Polytope Labs Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +//! Weights for absorbing validator keys into the commitment. +//! +//! The cost is the hashing, and it is linear in the number of slots, so the benchmark sweeps the +//! slot count up to the full circuit width. Reading the relay state proof is not measured: it +//! happens once per block regardless and is the same work the parachain already does to build a +//! block at all. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use frame_benchmarking::v2::*; +use gnark_plonk_verifier::NUM_VALIDATORS; +use polkadot_sdk::*; + +/// The G1 half of a real BEEFY key from a live BLS relay, so the decompression inside +/// `absorb_slots` does the work a live key costs rather than failing early on a made up point. +const A_KEY: [u8; BLS_G1_SIGNATURE_LEN] = hex_literal::hex!( + "b7235087b611457915f812c4c9af17fe9c590f0a9c9d2f3b62f5d31673a5d5ae02309ea191fe6ebeb66cb3ad23db3b04" +); + +#[benchmarks] +mod benches { + use super::*; + + /// Commit to a full validator set, which is what a block costs when the membership changes. + #[benchmark] + fn commit() { + let keys = alloc::vec![A_KEY; NUM_VALIDATORS]; + + #[block] + { + super::commit(&keys).expect("the keys are well formed"); + } + } + + /// Decompressing a full set and nothing else. + /// + /// A key arrives as 48 compressed bytes and recovering `y` needs a square root in the base + /// field, which is not obviously cheaper or dearer than the hashing it feeds. Measuring it + /// apart from `absorb` says which of the two a saving would have to come from. + #[benchmark] + fn decompress() { + let keys = alloc::vec![A_KEY; NUM_VALIDATORS]; + + #[block] + { + for key in keys.iter() { + let point = G1Affine::deserialize_compressed(&key[..]) + .expect("the key is a well formed point"); + core::hint::black_box(point); + } + } + } +} diff --git a/modules/pallets/beefy-apk-digest/src/lib.rs b/modules/pallets/beefy-apk-digest/src/lib.rs new file mode 100644 index 000000000..71c62abc8 --- /dev/null +++ b/modules/pallets/beefy-apk-digest/src/lib.rs @@ -0,0 +1,439 @@ +// Copyright (C) Polytope Labs Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +//! Publishes a commitment to the relay chain's BEEFY BLS public keys in this chain's headers. +//! +//! An APK proof binds a prover to a validator set through a single Poseidon2 commitment over the +//! validators' BLS12-381 G1 keys. A client verifying BEEFY finality needs that commitment, and the +//! cheapest trustworthy place to get it is a header this chain already publishes: the relay state +//! proof carried in every parachain block is checked against the relay parent's state root by the +//! validators, so the keys can be read out of it without any new trust assumption. +//! +//! The commitment is expensive, around 800ms of wasm for a full 1024 slot set, most of it spent +//! decompressing the keys rather than hashing them. It is committed in one block all the same, +//! since it happens once per authority set, which is once every four hours on polkadot, and a set +//! that has not changed is republished from the stored commitment rather than hashed again. + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +use alloc::vec::Vec; + +use ark_bls12_381::G1Affine; +use ark_serialize::CanonicalDeserialize; +pub use beefy_verifier_primitives::{ + ApkCommitmentDigest, APK_ENGINE_ID, RELAY_BEEFY_NEXT_AUTHORITIES, +}; +use beefy_verifier_primitives::{PairedAuthority, BLS_G1_SIGNATURE_LEN}; +use codec::{Decode, Encode, MaxEncodedLen}; +use cumulus_pallet_parachain_system::RelayChainStateProof; +use frame_support::weights::Weight; +use gnark_plonk_verifier::{padded_to_circuit_width, public_keys_commitment_bytes}; +use polkadot_sdk::*; +use scale_info::TypeInfo; + +pub use pallet::*; + +/// `Beefy::ValidatorSetId` on the relay chain, the id of the *current* set. The digest reports +/// `set_id + 1`, since it describes [`RELAY_BEEFY_NEXT_AUTHORITIES`]. +pub const RELAY_BEEFY_VALIDATOR_SET_ID: [u8; 32] = [ + 0x08, 0xc4, 0x19, 0x74, 0xa9, 0x7d, 0xbf, 0x15, 0xcf, 0xbe, 0xc2, 0x83, 0x65, 0xbe, 0xa2, 0xda, + 0x8f, 0x05, 0xbc, 0xcc, 0x2f, 0x70, 0xec, 0x66, 0xa3, 0x29, 0x99, 0xc5, 0x76, 0x11, 0x56, 0xbe, +]; + +pub mod benchmarking; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: + polkadot_sdk::frame_system::Config + cumulus_pallet_parachain_system::Config + { + /// Cost of committing to a set. `()` carries a rough default, see [`WeightInfo`]. + /// + /// Disambiguated at use as `::WeightInfo`, since + /// `cumulus_pallet_parachain_system::Config` also has one. + type WeightInfo: WeightInfo; + } + + /// Bumped whenever the commitment or the record around it changes shape, so [`migration`] + /// knows to throw away what was published under the old one. + pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(2); + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(_); + + /// Whether the next block should carry the relay's authority keys in its proof. + /// + /// Set by the block that sees the set id move, read by [`crate::wants_keys`] when the next + /// block is built. Keeping the keys out of the proof the rest of the time is the point. + #[pallet::storage] + pub type KeysWanted = StorageValue<_, bool, ValueQuery>; + + /// The last commitment published to a header digest, and the set it describes. + #[pallet::storage] + pub type Published = StorageValue<_, (u64, u32, [u8; 32]), OptionQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Committed to a new authority set, and wrote the commitment to this block's header. + CommitmentPublished { set_id: u64, len: u32, commitment: [u8; 32] }, + } + + #[pallet::hooks] + impl Hooks> for Pallet { + /// Runs in `on_finalize` for two reasons: the relay state proof is only fresh after the + /// validation data inherent, and a digest has to be deposited before the header is sealed. + /// + /// `on_finalize` cannot return weight, so the cost is registered explicitly. Absorbing a + /// chunk is real work, tens of milliseconds, and a block that does not account for it can + /// overrun its budget. + fn on_finalize(_now: BlockNumberFor) { + let hashed = match Self::advance() { + Ok(hashed) => hashed, + Err(e) => { + log::debug!(target: "apk-digest", "commitment did not advance: {e:?}"); + false + }, + }; + if hashed { + frame_system::Pallet::::register_extra_weight_unchecked( + ::WeightInfo::commit(), + DispatchClass::Mandatory, + ); + } + } + } + + impl Pallet { + /// Publish this block's digest, committing to a new set when the relay has rotated. + /// + /// The set id is in every proof and costs nothing to read. The keys are only asked for + /// once the id has moved, so the block that notices a rotation records that it needs them + /// and the next one gets them. That keeps 1024 keys out of the relay proof on the ordinary + /// block, which is every block but one or two a session. + fn advance() -> Result> { + let set_id = Self::relay_beefy_set_id()?; + + // Nothing has moved, so the commitment already computed still describes this set. + if let Some((published, len, commitment)) = Published::::get() { + if published == set_id { + Self::deposit_digest(set_id, len, commitment); + return Ok(false); + } + } + + // The id moved. The keys are only in the proof if a previous block asked for them, + // so the first block after a rotation asks and the next one does the work. + let Ok(keys) = Self::relay_beefy_g1_keys() else { + KeysWanted::::put(true); + // The old commitment is still the truth about the old set, and a client files + // commitments by set id, so republishing it under the id it was computed for + // keeps this block's header useful rather than empty. + if let Some((published, len, commitment)) = Published::::get() { + Self::deposit_digest(published, len, commitment); + } + return Ok(false); + }; + KeysWanted::::kill(); + + let commitment = commit(&keys).map_err(|_| Error::::MalformedAuthorityKey)?; + let len = keys.len() as u32; + Self::deposit_digest(set_id, len, commitment); + Published::::put((set_id, len, commitment)); + Self::deposit_event(Event::CommitmentPublished { set_id, len, commitment }); + Ok(true) + } + + /// Put the commitment in this block's header. + /// + /// The header is the delivery mechanism: a client that has already authenticated it + /// through the BEEFY MMR's parachain heads root reads the commitment straight out of it, + /// with no further proof. It goes in every header the set covers rather than only the one + /// where the hashing finished, since a verifier only ever sees the single header a proof + /// finalizes and cannot choose which. + fn deposit_digest(set_id: u64, len: u32, commitment: [u8; 32]) { + let payload = ApkCommitmentDigest { set_id, len, commitment }; + frame_system::Pallet::::deposit_log(sp_runtime::DigestItem::Consensus( + APK_ENGINE_ID, + payload.encode(), + )); + } + + /// The id of the set the commitment describes: the relay's current set id plus one, since + /// the keys come from `NextAuthorities`. + fn relay_beefy_set_id() -> Result> { + let current: u64 = Self::relay_state()? + .read_entry(&RELAY_BEEFY_VALIDATOR_SET_ID, None) + .map_err(|_| Error::::KeyNotProven)?; + Ok(current.saturating_add(1)) + } + + /// The relay state proof for this block, checked against the relay parent's state root. + /// + /// Only carries the keys the runtime asks for through + /// `KeyToIncludeInRelayProof::keys_to_prove`; anything else reads back as `KeyNotProven`. + fn relay_state() -> Result> { + let proof = cumulus_pallet_parachain_system::RelayStateProof::::get() + .ok_or(Error::::NoRelayProof)?; + let validation_data = cumulus_pallet_parachain_system::ValidationData::::get() + .ok_or(Error::::NoRelayProof)?; + + RelayChainStateProof::new( + T::SelfParaId::get(), + validation_data.relay_parent_storage_root, + proof, + ) + .map_err(|_| Error::::BadRelayProof) + } + + /// The G1 halves of the relay chain's *next* BEEFY authority set. + fn relay_beefy_g1_keys() -> Result, Error> { + let authorities: Vec = Self::relay_state()? + .read_entry(&RELAY_BEEFY_NEXT_AUTHORITIES, None) + .map_err(|_| Error::::KeyNotProven)?; + + Ok(authorities.iter().map(|a| a.g1()).collect()) + } + } + + #[pallet::error] + pub enum Error { + /// No relay state proof in storage yet, which is normal before the first inherent. + NoRelayProof, + /// The relay state proof did not verify against the relay parent's state root. + BadRelayProof, + /// The BEEFY authorities key was absent from the proof, so `keys_to_prove` is not asking + /// for it. + KeyNotProven, + /// An authority's G1 half did not decode as a curve point. + MalformedAuthorityKey, + } +} + +/// Cost of committing to a validator set. +pub trait WeightInfo { + fn commit() -> Weight; +} + +/// A rough default for tests and for a chain that has not generated its own. +/// +/// A full set is around 580ms in wasm on the benchmarking machine, of which roughly four fifths is +/// decompressing the keys rather than hashing them: a key arrives as 48 compressed bytes and +/// recovering `y` needs a square root in the base field. It is only paid when the relay rotates. +impl WeightInfo for () { + fn commit() -> Weight { + Weight::from_parts(588_000_000_000, 0).saturating_add(Weight::from_parts(0, 4096)) + } +} + +/// Whether the next block's relay proof should carry the authority keys. +/// +/// A runtime answers `KeyToIncludeInRelayProof` with this: asking for 1024 keys on every block +/// costs proof size for data that only changes once a session. The block that sees the set id +/// move records that it wants them, and this reports it while the next block is built. Also true +/// before anything has been published, since the first commitment has to come from somewhere. +pub fn wants_keys() -> bool { + KeysWanted::::get() || Published::::get().is_none() +} + +/// Throw away a commitment computed under an older scheme. +/// +/// The stored record is republished unchanged until the relay's set id moves, so a runtime upgrade +/// that changes how keys are hashed would otherwise keep publishing the old value for the rest of +/// the session. Clearing it means the next block recomputes instead of waiting for the rotation. +pub mod migration { + use super::*; + use frame_support::traits::{Get, GetStorageVersion, OnRuntimeUpgrade}; + + pub struct ClearStaleCommitment(core::marker::PhantomData); + + impl OnRuntimeUpgrade for ClearStaleCommitment { + fn on_runtime_upgrade() -> Weight { + if as GetStorageVersion>::on_chain_storage_version() >= + pallet::STORAGE_VERSION + { + return T::DbWeight::get().reads(1); + } + Published::::kill(); + pallet::STORAGE_VERSION.put::>(); + T::DbWeight::get().reads_writes(1, 2) + } + } +} + +/// A key that did not decode as a G1 curve point. +#[derive(Debug, PartialEq, Eq)] +pub struct MalformedKey; + +/// The commitment over a validator set, padded to the circuit's width with the identity point. +/// +/// Kept free of the pallet so the part that can actually be wrong, the decompression and the +/// padding, is testable without a mock chain. +pub fn commit(keys: &[[u8; BLS_G1_SIGNATURE_LEN]]) -> Result<[u8; 32], MalformedKey> { + let points: Vec = keys + .iter() + .map(|k| G1Affine::deserialize_compressed(&k[..]).map_err(|_| MalformedKey)) + .collect::>()?; + + Ok(public_keys_commitment_bytes(&padded_to_circuit_width(&points))) +} + +#[cfg(test)] +mod tests { + use super::*; + use ark_serialize::CanonicalSerialize; + use gnark_plonk_verifier::{padded_to_circuit_width, public_keys_commitment_bytes}; + + /// Real G1 halves from a live BLS relay's `Beefy` authorities. + const RELAY_KEYS: [&str; 2] = [ + "b7235087b611457915f812c4c9af17fe9c590f0a9c9d2f3b62f5d31673a5d5ae02309ea191fe6ebeb66cb3ad23db3b04", + "a3948b7bd16acfa3b7a113826a1a8b192c2c462f31ddd9dc90131f5b014d85ff9669ad1a686f33f89e6b0d821761b2cc", + ]; + + fn relay_keys() -> Vec<[u8; BLS_G1_SIGNATURE_LEN]> { + RELAY_KEYS + .iter() + .map(|h| { + let mut out = [0u8; BLS_G1_SIGNATURE_LEN]; + out.copy_from_slice(&hex::decode(h).unwrap()); + out + }) + .collect() + } + + fn expected_commitment(keys: &[[u8; BLS_G1_SIGNATURE_LEN]]) -> [u8; 32] { + let points: Vec = + keys.iter().map(|k| G1Affine::deserialize_compressed(&k[..]).unwrap()).collect(); + public_keys_commitment_bytes(&padded_to_circuit_width(&points)) + } + + /// A set shorter than the circuit's width is padded, which is most of what `commit` does + /// beyond hashing. + #[test] + fn a_short_set_is_padded_to_the_circuit_width() { + let keys = relay_keys(); + assert_eq!(commit(&keys).unwrap(), expected_commitment(&keys)); + } + + /// An empty set is all padding, and must still be well defined. + #[test] + fn an_empty_set_is_all_padding() { + let all_identity: Vec = (0..gnark_plonk_verifier::NUM_VALIDATORS) + .map(|_| G1Affine::identity()) + .collect(); + assert_eq!(commit(&[]).unwrap(), public_keys_commitment_bytes(&all_identity)); + } + + /// Order matters, since the bitlist selects signers positionally. + #[test] + fn key_order_changes_the_commitment() { + let keys = relay_keys(); + let mut swapped = keys.clone(); + swapped.swap(0, 1); + assert_ne!(commit(&keys).unwrap(), commit(&swapped).unwrap()); + } + + /// A key whose x coordinate is the field modulus, which is not a canonical field element. + /// + /// Note an all-`0xff` key is *not* a good negative case: the top bits are the compression and + /// infinity flags, so it decodes happily as the identity point. + #[test] + fn a_malformed_key_is_rejected_rather_than_hashed() { + let mut key = hex::decode( + "1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f624\ + 1eabfffeb153ffffb9feffffffffaaab", + ) + .unwrap(); + key[0] |= 0x80; // compressed form, so the x bytes are actually parsed + let mut fixed = [0u8; BLS_G1_SIGNATURE_LEN]; + fixed.copy_from_slice(&key); + + assert_eq!(commit(&[fixed]), Err(MalformedKey)); + } + + /// The identity point encodes as a valid compressed key, so a set genuinely containing one is + /// absorbed rather than rejected. Worth pinning so the malformed-key check is not mistaken for + /// an identity check. + #[test] + fn an_identity_key_is_valid_input() { + let mut encoded = Vec::new(); + G1Affine::identity().serialize_compressed(&mut encoded).unwrap(); + let mut key = [0u8; BLS_G1_SIGNATURE_LEN]; + key.copy_from_slice(&encoded); + assert!(commit(&[key]).is_ok()); + } + + /// Two different sets must not reach the same commitment. + #[test] + fn a_different_set_commits_differently() { + let first = relay_keys(); + let mut second = relay_keys(); + second.swap(0, 1); + assert_ne!(commit(&first).unwrap(), commit(&second).unwrap()); + } + + /// normal case: aura and the parachain system both write their own. + #[test] + fn commitment_is_found_among_other_digest_items() { + let payload = ApkCommitmentDigest { set_id: 577, len: 2, commitment: [3u8; 32] }; + let digest = sp_runtime::generic::Digest { + logs: alloc::vec![ + sp_runtime::DigestItem::PreRuntime(*b"aura", alloc::vec![1, 2, 3]), + sp_runtime::DigestItem::Consensus(APK_ENGINE_ID, payload.encode()), + sp_runtime::DigestItem::Seal(*b"aura", alloc::vec![4, 5, 6]), + ], + }; + assert_eq!(ApkCommitmentDigest::find_in(&digest), Some(payload)); + } + + /// A header from a block that did not complete a set carries nothing, and a client must treat + /// that as "no update" rather than an error. + #[test] + fn a_header_without_our_digest_yields_nothing() { + let digest = sp_runtime::generic::Digest { + logs: alloc::vec![sp_runtime::DigestItem::PreRuntime(*b"aura", alloc::vec![1])], + }; + assert_eq!(ApkCommitmentDigest::find_in(&digest), None); + } + + /// Another engine's consensus item must not be mistaken for ours, even though the variant is + /// the same. This is what the engine id is for. + #[test] + fn another_engines_consensus_item_is_ignored() { + let payload = ApkCommitmentDigest { set_id: 1, len: 2, commitment: [9u8; 32] }; + let digest = sp_runtime::generic::Digest { + logs: alloc::vec![sp_runtime::DigestItem::Consensus(*b"BEEF", payload.encode())], + }; + assert_eq!(ApkCommitmentDigest::find_in(&digest), None); + } + + /// The digest is a wire format, so its encoding is pinned here: a client decodes these bytes + /// out of a header. + #[test] + fn digest_payload_round_trips() { + let payload = ApkCommitmentDigest { set_id: 42, len: 3, commitment: [7u8; 32] }; + let encoded = payload.encode(); + assert_eq!(encoded.len(), 8 + 4 + 32, "set id, size, then commitment, no padding"); + assert_eq!(ApkCommitmentDigest::decode(&mut &encoded[..]).unwrap(), payload); + } +} diff --git a/modules/pallets/beefy-consensus-proofs/Cargo.toml b/modules/pallets/beefy-consensus-proofs/Cargo.toml index 446481c7d..80a04e3d0 100644 --- a/modules/pallets/beefy-consensus-proofs/Cargo.toml +++ b/modules/pallets/beefy-consensus-proofs/Cargo.toml @@ -65,5 +65,6 @@ runtime-benchmarks = [ "polkadot-sdk/frame-benchmarking", "polkadot-sdk/runtime-benchmarks", "dep:hex-literal", + "beefy-verifier/apk", ] try-runtime = ["polkadot-sdk/try-runtime"] diff --git a/modules/pallets/beefy-consensus-proofs/src/benchmarking.rs b/modules/pallets/beefy-consensus-proofs/src/benchmarking.rs index a729edf0e..db03cb228 100644 --- a/modules/pallets/beefy-consensus-proofs/src/benchmarking.rs +++ b/modules/pallets/beefy-consensus-proofs/src/benchmarking.rs @@ -16,6 +16,7 @@ #![cfg(feature = "runtime-benchmarks")] use super::*; +use alloy_sol_types::SolType; use frame_benchmarking::v2::*; use frame_system::RawOrigin; use polkadot_sdk::*; @@ -46,6 +47,15 @@ const WIRE_PROOF: [u8; 1281] = hex_literal::hex!("010000000000000000000000000000 const FIXTURE_VKEY: H256 = H256(hex_literal::hex!("007d1720c695842ed647a1a72e981751f9b5e26fc5ca038523b23430a1292f08")); +/// The fixtures are hex text, shared with the solidity tests, so they are decoded here rather +/// than duplicated as byte arrays. +fn decode_hex(raw: &str) -> alloc::vec::Vec { + let trimmed = raw.trim().trim_start_matches("0x"); + (0..trimmed.len() / 2) + .map(|i| u8::from_str_radix(&trimmed[i * 2..i * 2 + 2], 16).expect("fixture is hex")) + .collect() +} + #[benchmarks( where T::AccountId: From<[u8; 32]>, @@ -106,6 +116,59 @@ mod benchmarks { assert_eq!(pallet::AcceptedProvers::::get(0u64).len(), 1); } + /// The cost an apk proof adds on top of what `submit_proof` already measures: decoding the + /// abi payload, the PLONK verification, the pairing that checks the aggregate signature and + /// binds `apk2`, the mmr leaf and the parachain header proof. + /// + /// Measured on the verifier rather than through the extrinsic, because the fixture's + /// parachain header carries no ismp overlay root and the pallet requires one to settle a + /// proof. The cryptography is identical either way, and the storage the pallet writes around + /// it is what the `submit_proof` benchmark already covers. + /// + /// Everything here is the fixture `BlsApkBeefy.sol` verifies and `apk_fixture.rs` runs + /// through the runtime verifier, so this is a real proof from a live relay. + #[benchmark] + fn verify_apk() { + let state_bytes = decode_hex(include_str!( + "../../../../evm/tests/foundry/fixtures/bls-apk-beefy-state.hex" + )); + let proof_bytes = decode_hex(include_str!( + "../../../../evm/tests/foundry/fixtures/bls-apk-beefy-proof.hex" + )); + let verifying_key = + include_bytes!("../../../../evm/tests/foundry/fixtures/apk-verifying-key.bin").to_vec(); + + let state: beefy_verifier_primitives::ConsensusState = + ::abi_decode( + &state_bytes, + ) + .expect("apk state fixture decodes") + .try_into() + .expect("apk state fixture converts"); + + #[block] + { + let proof = ::abi_decode_params( + &proof_bytes, + ) + .expect("apk proof fixture decodes"); + let message: beefy_verifier_primitives::ApkConsensusMessage = + proof.try_into().expect("apk proof fixture converts"); + + // The parachain the fixture was captured from, so the digest is read and the + // measurement covers the path a real proof takes. + const FIXTURE_PARA_ID: u32 = 4009; + + beefy_verifier::apk::verify_apk_consensus::( + state.clone(), + message, + &verifying_key, + FIXTURE_PARA_ID, + ) + .expect("the fixture proof verifies"); + } + } + #[benchmark] fn set_proof_reward() { let reward: <::Currency as frame_support::traits::fungible::Inspect< diff --git a/modules/pallets/beefy-consensus-proofs/src/lib.rs b/modules/pallets/beefy-consensus-proofs/src/lib.rs index cacdc0407..1fcf12353 100644 --- a/modules/pallets/beefy-consensus-proofs/src/lib.rs +++ b/modules/pallets/beefy-consensus-proofs/src/lib.rs @@ -149,6 +149,11 @@ pub mod pallet { #[pallet::constant] type MaxUncleProvers: Get; + /// Upper bound on the apk circuit's verifying key. The key generated for the 1024 slot + /// circuit is around 49KB, so this wants headroom rather than a tight fit. + #[pallet::constant] + type MaxApkVerifyingKeyLen: Get; + /// The pallet-assets instance used for managing the reputation token. /// Mints reputation tokens 1:1 with native token rewards to proof submitters. type ReputationAsset: fungible::Mutate>; @@ -172,6 +177,16 @@ pub mod pallet { #[pallet::storage] pub type Sp1VkeyHash = StorageValue<_, H256, ValueQuery>; + /// Verifying key for the aggregate public key circuit, consumed by + /// `beefy_verifier::apk::verify_apk_consensus`. + /// + /// Unlike the SP1 key this is the key itself rather than a hash of it, since the verification + /// runs here rather than inside a proof system that already knows it. It is around 49KB, set + /// once by governance and read on every apk proof. + #[pallet::storage] + pub type ApkVerifyingKey = + StorageValue<_, BoundedVec, ValueQuery>; + /// Heights of recent messaging proofs (no authority-set rotation). Values are /// strictly increasing because every accepted proof advances the proven height, /// so `vec[0]` is always the oldest — FIFO eviction via `remove(0)` when full. @@ -287,6 +302,8 @@ pub mod pallet { ProofRewardUpdated { new_reward: BalanceOf }, /// SP1 verification key hash updated. Sp1VkeyHashUpdated, + /// Apk circuit verifying key replaced. + ApkVerifyingKeyUpdated, /// Reward curve updated. RewardCurveUpdated, } @@ -309,52 +326,45 @@ pub mod pallet { Error::::AbiDecodeFailed })? .into(); - let current_set_id = state.current_authorities.id; - let next_set_id = state.next_authorities.id; - let latest_beefy_height = state.latest_beefy_height; - let host = pallet_ismp::Pallet::::default(); - // Seed an initial commitment for the host state machine at the current block height. - pallet_ismp::Pallet::::create_consensus_client( - frame_system::RawOrigin::Root.into(), - ismp::messaging::CreateConsensusState { - consensus_state: state.encode(), - consensus_client_id: ismp_beefy::BEEFY_CONSENSUS_ID, - consensus_state_id: ismp_beefy::BEEFY_CONSENSUS_ID, - unbonding_period: T::UnbondingPeriod::get(), - challenge_periods: Default::default(), - state_machine_commitments: vec![( - StateMachineId { - consensus_state_id: T::ConsensusStateId::get(), - state_id: host.host_state_machine(), - }, - StateCommitmentHeight { - height: 1, - commitment: StateCommitment { - timestamp: host.timestamp().as_secs(), - overlay_root: None, - state_root: H256::zero(), - }, - }, - )], - }, + Self::create_state( + state.encode(), + state.current_authorities.id, + state.next_authorities.id, + state.latest_beefy_height, ) - .map_err(|e| { - log::warn!( - target: "ismp", - "[beefy-consensus-proofs]: pallet_ismp::create_consensus_client failed: {e:?}", - ); - Error::::IsmpUpdateFailed - })?; + } - LastRewardedDispatchRoot::::kill(); + /// Initialize or reset the consensus state for a chain verified through aggregate public + /// key proofs, from its solidity-ABI encoding. + /// + /// The starting set's commitment has to be supplied here, since it is normally learned + /// from a header digest and there is no earlier verified header at this point. + #[pallet::call_index(6)] + #[pallet::weight(T::WeightInfo::initialize_state())] + pub fn initialize_apk_state(origin: OriginFor, abi_state: Vec) -> DispatchResult { + ::AdminOrigin::ensure_origin(origin)?; - Self::deposit_event(Event::StateInitialized { - current_set_id, - next_set_id, - latest_beefy_height, - }); - Ok(()) + let state: beefy_verifier_primitives::ConsensusState = + ::abi_decode( + &abi_state, + ) + .map_err(|e| { + log::warn!( + target: "ismp", + "[beefy-consensus-proofs]: abi_decode(BlsApkConsensusState) failed: {e}", + ); + Error::::AbiDecodeFailed + })? + .try_into() + .map_err(|_| Error::::AbiDecodeFailed)?; + + Self::create_state( + state.encode(), + state.current_authorities.id, + state.next_authorities.id, + state.latest_beefy_height, + ) } /// Submit a BEEFY consensus proof. Signed: the signer is the reward payee. @@ -364,7 +374,7 @@ pub mod pallet { /// (first or uncle) refund their transaction fee via `Pays::No`; failed proofs /// pay the fee, which is the spam deterrent. #[pallet::call_index(1)] - #[pallet::weight(T::WeightInfo::submit_proof())] + #[pallet::weight(Pallet::::submit_proof_weight(proof))] pub fn submit_proof( origin: OriginFor, proof: BoundedVec, @@ -383,6 +393,19 @@ pub mod pallet { Ok(()) } + /// Replace the apk circuit's verifying key. + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::set_apk_verifying_key())] + pub fn set_apk_verifying_key( + origin: OriginFor, + key: BoundedVec, + ) -> DispatchResult { + ::AdminOrigin::ensure_origin(origin)?; + ApkVerifyingKey::::put(key); + Self::deposit_event(Event::ApkVerifyingKeyUpdated); + Ok(()) + } + /// Update the SP1 verification key hash. #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::set_sp1_vkey_hash())] @@ -433,6 +456,100 @@ pub mod pallet { impl Pallet { /// Returns the latest proven parachain height from `pallet-ismp` for the /// coprocessor state machine. + /// Register the consensus client with whichever state shape was decoded, and seed a + /// commitment for the host state machine at the current height. + fn create_state( + consensus_state: Vec, + current_set_id: u64, + next_set_id: u64, + latest_beefy_height: u32, + ) -> DispatchResult { + let host = pallet_ismp::Pallet::::default(); + + // Seed an initial commitment for the host state machine at the current block height. + pallet_ismp::Pallet::::create_consensus_client( + frame_system::RawOrigin::Root.into(), + ismp::messaging::CreateConsensusState { + consensus_state, + consensus_client_id: ismp_beefy::BEEFY_CONSENSUS_ID, + consensus_state_id: ismp_beefy::BEEFY_CONSENSUS_ID, + unbonding_period: T::UnbondingPeriod::get(), + challenge_periods: Default::default(), + state_machine_commitments: vec![( + StateMachineId { + consensus_state_id: T::ConsensusStateId::get(), + state_id: host.host_state_machine(), + }, + StateCommitmentHeight { + // The chain this runs on is rarely at genesis, and a commitment at + // height 1 tells a prover to start from a block whose state has long + // been pruned. + height: { + use sp_runtime::SaturatedConversion; + frame_system::Pallet::::block_number().saturated_into::() + }, + commitment: StateCommitment { + timestamp: host.timestamp().as_secs(), + // A seeded commitment outranks anything a proof later creates for + // an older height, so it has to carry a real overlay root or every + // proof settles against an empty one. + overlay_root: Some(H256::from_slice( + pallet_ismp::ChildTrieRoot::::get().as_ref(), + )), + state_root: H256::zero(), + }, + }, + )], + }, + ) + .map_err(|e| { + log::warn!( + target: "ismp", + "[beefy-consensus-proofs]: pallet_ismp::create_consensus_client failed: {e:?}", + ); + Error::::IsmpUpdateFailed + })?; + + LastRewardedDispatchRoot::::kill(); + + Self::deposit_event(Event::StateInitialized { + current_set_id, + next_set_id, + latest_beefy_height, + }); + Ok(()) + } + + /// Weight of a `submit_proof` call, which depends on what is being verified. + /// + /// The benchmark covers the storage and the sp1 verification. An apk proof runs a PLONK + /// verification and a pairing instead, which is measured separately and added here. + /// Charging the same for both would let a block of apk proofs overrun its budget. + pub fn submit_proof_weight(proof: &BoundedVec) -> Weight { + let base = T::WeightInfo::submit_proof(); + match proof.first() { + Some(&types::PROOF_TYPE_APK) => base.saturating_add(T::WeightInfo::verify_apk()), + _ => base, + } + } + + /// Authority set ids out of a stored consensus state. + /// + /// The shape follows the proof type: an apk state identifies a set by a commitment to its + /// keys where the others carry a merkle root, so the two do not decode into each other. + /// Only the ids are wanted here, and both shapes have them. + fn authority_set_ids(state: &[u8], proof_type: u8) -> Result<(u64, u64), Error> { + if proof_type == types::PROOF_TYPE_APK { + let state: beefy_verifier_primitives::ConsensusState = + Decode::decode(&mut &state[..]).map_err(|_| Error::::NotInitialized)?; + Ok((state.current_authorities.id, state.next_authorities.id)) + } else { + let state: beefy_verifier_primitives::ConsensusState = + Decode::decode(&mut &state[..]).map_err(|_| Error::::NotInitialized)?; + Ok((state.current_authorities.id, state.next_authorities.id)) + } + } + fn latest_height() -> Result> { let host = pallet_ismp::Pallet::::default(); let id = ismp::consensus::StateMachineId { @@ -480,7 +597,10 @@ pub mod pallet { } Some(nonce) }, - types::PROOF_TYPE_NAIVE => None, + // Only SP1 proofs are bound to a prover account. The naive and apk paths verify + // signatures the relay chain's validators produced, so there is nothing + // prover-specific in them to bind and no anti-theft gate to apply. + types::PROOF_TYPE_NAIVE | types::PROOF_TYPE_APK => None, _ => Err(Error::::UnknownProofType)?, }; @@ -810,9 +930,9 @@ pub mod pallet { let prev_state_bytes = host .consensus_state(ismp_beefy::BEEFY_CONSENSUS_ID) .map_err(|_| Error::::NotInitialized)?; - let prev_state: beefy_verifier_primitives::ConsensusState = - Decode::decode(&mut &prev_state_bytes[..]) - .map_err(|_| Error::::NotInitialized)?; + let (prev_current_set, _) = Self::authority_set_ids(&prev_state_bytes, proof_type)?; + let prev_commitment_unknown = + types::next_commitment_unknown(&prev_state_bytes, proof_type); let prev_height = Self::latest_height()?; let consensus_proof = match proof_type { @@ -834,6 +954,15 @@ pub mod pallet { let scale_proof: beefy_verifier_primitives::ConsensusMessage = abi_proof.into(); [&[types::PROOF_TYPE_NAIVE], scale_proof.encode().as_slice()].concat() }, + types::PROOF_TYPE_APK => { + let abi_proof = ::abi_decode_params( + abi_payload, + ) + .map_err(|_| Error::::AbiDecodeFailed)?; + let scale_proof: beefy_verifier_primitives::ApkConsensusMessage = + abi_proof.try_into().map_err(|_| Error::::AbiDecodeFailed)?; + [&[types::PROOF_TYPE_APK], scale_proof.encode().as_slice()].concat() + }, _ => Err(Error::::UnknownProofType)?, }; @@ -883,16 +1012,22 @@ pub mod pallet { let new_state_bytes = host .consensus_state(ismp_beefy::BEEFY_CONSENSUS_ID) .map_err(|_| Error::::VerificationFailed)?; - let new_state: beefy_verifier_primitives::ConsensusState = - Decode::decode(&mut &new_state_bytes[..]) - .map_err(|_| Error::::VerificationFailed)?; + let (new_current_set, new_next_set) = + Self::authority_set_ids(&new_state_bytes, proof_type)?; // BEEFY invariant: `next` is always `current + 1`. - if new_state.next_authorities.id != new_state.current_authorities.id.saturating_add(1) { + if new_next_set != new_current_set.saturating_add(1) { Err(Error::::UnexpectedAuthoritySet)?; } - let rotated = new_state.current_authorities.id > prev_state.current_authorities.id; + let rotated = new_current_set > prev_current_set; + + // An apk client learns each set's commitment from a digest in a parachain header, and + // the proof that carries it neither rotates nor has to finalize anything new. Without + // this the rules below would turn it away as pointless, and the client would sit on a + // set it can never rotate out of. + let learned_commitment = prev_commitment_unknown && + !types::next_commitment_unknown(&new_state_bytes, proof_type); // Messaging proofs must finalize a parachain head we haven't seen; one that doesn't // carries no new work and is rejected. Rotation proofs are exempt: the session @@ -903,7 +1038,7 @@ pub mod pallet { // pinning the consensus state on the old set forever — the mandatory-block // justification is the only one a prover can obtain for that session, so every // retry fails identically. - if !rotated && latest_height <= prev_height { + if !rotated && !learned_commitment && latest_height <= prev_height { Err(Error::::StaleProof)? } @@ -928,13 +1063,13 @@ pub mod pallet { // Reject proofs that would be no-ops: no rotation and no new messages. let last_rewarded = LastRewardedDispatchRoot::::get().unwrap_or_default(); let has_new_messages = child_trie_root != last_rewarded && latest_height > prev_height; - if !rotated && !has_new_messages { + if !rotated && !learned_commitment && !has_new_messages { Err(Error::::NoNewWork)? } Ok(VerifyOutcome { latest_height, - current_set_id: new_state.current_authorities.id, + current_set_id: new_current_set, rotated, has_new_messages, child_trie_root, diff --git a/modules/pallets/beefy-consensus-proofs/src/types.rs b/modules/pallets/beefy-consensus-proofs/src/types.rs index 5c4e1e4d0..86fdf2644 100644 --- a/modules/pallets/beefy-consensus-proofs/src/types.rs +++ b/modules/pallets/beefy-consensus-proofs/src/types.rs @@ -16,6 +16,7 @@ //! Types for `pallet-beefy-consensus-proofs`. use alloc::vec::Vec; +use codec::Decode; /// Offchain-storage prefix for messaging proof bytes, combined with the proven parachain /// height. @@ -34,6 +35,8 @@ pub const ROTATION_OFFCHAIN_PREFIX: &[u8] = b"beefy_consensus_proofs::rotation:: pub const PROOF_TYPE_NAIVE: u8 = 0x00; /// Proof type byte: SP1 ZK BEEFY proof. pub const PROOF_TYPE_SP1: u8 = 0x01; +/// Proof type byte: aggregate public key BEEFY proof. +pub const PROOF_TYPE_APK: u8 = 0x02; fn offchain_key(prefix: &[u8], id: u64) -> Vec { let mut key = Vec::with_capacity(prefix.len() + 8); @@ -92,6 +95,63 @@ mod tests { } } +/// Whether an apk state is still missing the commitment for the set it will rotate into. +/// +/// A proof that fills this in is doing work even if it finalizes nothing new, since the client +/// cannot accept the rotation until it knows the incoming set's keys. +pub fn next_commitment_unknown(state: &[u8], proof_type: u8) -> bool { + proof_type == PROOF_TYPE_APK && + beefy_verifier_primitives::ConsensusState::decode(&mut &state[..]) + .map(|state| state.next_authorities.bls_poseidon_hash.is_zero()) + .unwrap_or(false) +} + +#[cfg(test)] +mod commitment_tests { + use super::*; + use beefy_verifier_primitives::{AuthoritySet, ConsensusState}; + use codec::Encode; + use primitive_types::H256; + + fn state(next: H256) -> Vec { + ConsensusState { + latest_beefy_height: 100, + beefy_activation_block: 0, + mmr_root_hash: H256::zero(), + current_authorities: AuthoritySet { + id: 7, + len: 2, + bls_poseidon_hash: H256::repeat_byte(1), + ecdsa_merkle_root: H256::repeat_byte(2), + }, + next_authorities: AuthoritySet { + id: 8, + len: 2, + bls_poseidon_hash: next, + ecdsa_merkle_root: H256::repeat_byte(3), + }, + } + .encode() + } + + #[test] + fn an_empty_next_commitment_is_the_only_thing_worth_learning() { + assert!(next_commitment_unknown(&state(H256::zero()), PROOF_TYPE_APK)); + assert!(!next_commitment_unknown(&state(H256::repeat_byte(2)), PROOF_TYPE_APK)); + } + + #[test] + fn other_proof_types_never_learn_a_commitment() { + assert!(!next_commitment_unknown(&state(H256::zero()), PROOF_TYPE_NAIVE)); + assert!(!next_commitment_unknown(&state(H256::zero()), PROOF_TYPE_SP1)); + } + + #[test] + fn a_state_that_is_not_the_apk_shape_is_not_missing_anything() { + assert!(!next_commitment_unknown(&[0u8; 3], PROOF_TYPE_APK)); + } +} + /// BEEFY host-function backed crypto used by `beefy-verifier`. pub struct SubstrateCrypto; diff --git a/modules/pallets/beefy-consensus-proofs/src/weights.rs b/modules/pallets/beefy-consensus-proofs/src/weights.rs index d51e10902..1a1b492f1 100644 --- a/modules/pallets/beefy-consensus-proofs/src/weights.rs +++ b/modules/pallets/beefy-consensus-proofs/src/weights.rs @@ -34,6 +34,10 @@ pub trait WeightInfo { fn set_sp1_vkey_hash() -> Weight; /// Weight of `set_reward_curve`. fn set_reward_curve() -> Weight; + /// Weight of `set_apk_verifying_key`. + fn set_apk_verifying_key() -> Weight; + /// Cost of verifying an apk proof, charged on top of `submit_proof`. + fn verify_apk() -> Weight; } /// No-op [`WeightInfo`] for tests and genesis bootstrap. @@ -53,4 +57,10 @@ impl WeightInfo for () { fn set_reward_curve() -> Weight { Weight::zero() } + fn set_apk_verifying_key() -> Weight { + Weight::zero() + } + fn verify_apk() -> Weight { + Weight::zero() + } } diff --git a/modules/pallets/testsuite/src/runtime.rs b/modules/pallets/testsuite/src/runtime.rs index 247883637..cf0b2e116 100644 --- a/modules/pallets/testsuite/src/runtime.rs +++ b/modules/pallets/testsuite/src/runtime.rs @@ -517,6 +517,7 @@ impl pallet_beefy_consensus_proofs::Config for Test { type ConsensusStateId = BeefyConsensusStateId; type UnbondingPeriod = ConstU64<10>; type MaxUncleProvers = ConstU32<5>; + type MaxApkVerifyingKeyLen = ConstU32<131072>; type ReputationAsset = ReputationAsset; type WeightInfo = (); } diff --git a/parachain/runtimes/gargantua/Cargo.toml b/parachain/runtimes/gargantua/Cargo.toml index d4dc51a2b..52593c9a4 100644 --- a/parachain/runtimes/gargantua/Cargo.toml +++ b/parachain/runtimes/gargantua/Cargo.toml @@ -27,6 +27,7 @@ ismp = { workspace = true } pallet-ismp = { workspace = true } pallet-fishermen = { workspace = true } pallet-ismp-demo = { workspace = true } +pallet-beefy-apk-digest = { workspace = true } pallet-beefy-consensus-proofs = { workspace = true } pallet-ismp-runtime-api = { workspace = true } ismp-sync-committee = { workspace = true } @@ -143,6 +144,7 @@ std = [ "pallet-ismp/std", "pallet-ismp-runtime-api/std", "pallet-ismp-demo/std", + "pallet-beefy-apk-digest/std", "pallet-beefy-consensus-proofs/std", "ismp-sync-committee/std", "ismp-bsc/std", @@ -183,6 +185,7 @@ runtime-benchmarks = [ "ismp-parachain/runtime-benchmarks", "pallet-intents-coprocessor/runtime-benchmarks", "pallet-beefy-consensus-proofs/runtime-benchmarks", + "pallet-beefy-apk-digest/runtime-benchmarks", "pallet-hyper-fungible-token/runtime-benchmarks", ] try-runtime = [ diff --git a/parachain/runtimes/gargantua/src/ismp.rs b/parachain/runtimes/gargantua/src/ismp.rs index 4c45642d4..b416c4cad 100644 --- a/parachain/runtimes/gargantua/src/ismp.rs +++ b/parachain/runtimes/gargantua/src/ismp.rs @@ -241,9 +241,17 @@ impl ismp_beefy::BeefyClientConfig for Runtime { pallet_beefy_consensus_proofs::Sp1VkeyHash::::get() } + fn apk_verifying_key() -> alloc::vec::Vec { + pallet_beefy_consensus_proofs::ApkVerifyingKey::::get().into_inner() + } + + fn apk_digest_para_id() -> u32 { + 4009 + } + fn allowed_proof_types() -> &'static [u8] { - // Testnet: accept both the naive ECDSA and SP1 ZK proof formats. - &[ismp_beefy::PROOF_TYPE_NAIVE, ismp_beefy::PROOF_TYPE_SP1] + // Testnet: accept the naive ECDSA and SP1 ZK proof formats, plus aggregate public key. + &[ismp_beefy::PROOF_TYPE_NAIVE, ismp_beefy::PROOF_TYPE_SP1, ismp_beefy::PROOF_TYPE_APK] } } @@ -304,7 +312,9 @@ pub struct HftBenchmarkHelper; #[cfg(feature = "runtime-benchmarks")] impl pallet_hyper_fungible_token::types::BenchmarkHelper for HftBenchmarkHelper { fn create_asset(decimals: u8, who: &AccountId, amount: u128) -> H256 { - use frame_support::traits::fungibles::{metadata::Mutate as MutateMetadata, Create, Mutate}; + use frame_support::traits::fungibles::{ + metadata::Mutate as MutateMetadata, Create, Mutate, + }; let asset_id: H256 = sp_io::hashing::keccak_256(b"HFT_BENCHMARK_ASSET").into(); >::create(asset_id, who.clone(), true, 1) diff --git a/parachain/runtimes/gargantua/src/lib.rs b/parachain/runtimes/gargantua/src/lib.rs index 22df762b2..59cd2de82 100644 --- a/parachain/runtimes/gargantua/src/lib.rs +++ b/parachain/runtimes/gargantua/src/lib.rs @@ -189,6 +189,7 @@ pub type Migrations = ( pallet_beefy_consensus_proofs::migrations::ClearSp1VkeyHash, pallet_beefy_consensus_proofs::migrations::ClearAcceptedProofHashes, pallet_collator_manager::migrations::MigrateBondsToReserves, + pallet_beefy_apk_digest::migration::ClearStaleCommitment, ); /// Handles converting a weight scalar to a fee value, based on the scale and granularity of the @@ -251,7 +252,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: Cow::Borrowed("gargantua"), impl_name: Cow::Borrowed("gargantua"), authoring_version: 1, - spec_version: 8_100, + spec_version: 8_101, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -873,6 +874,7 @@ impl pallet_beefy_consensus_proofs::Config for Runtime { type ConsensusStateId = BeefyConsensusStateId; type UnbondingPeriod = BeefyUnbondingPeriod; type MaxUncleProvers = MaxBeefyUncleProvers; + type MaxApkVerifyingKeyLen = ConstU32<131072>; type ReputationAsset = ReputationAsset; type WeightInfo = weights::pallet_beefy_consensus_proofs::WeightInfo; } @@ -882,6 +884,10 @@ impl pallet_messaging_incentives::Config for Runtime { type AdminOrigin = EnsureRoot; } +impl pallet_beefy_apk_digest::Config for Runtime { + type WeightInfo = weights::pallet_beefy_apk_digest::WeightInfo; +} + // Create the runtime by composing the FRAME pallets that were previously configured. #[frame_support::runtime] mod runtime { @@ -1006,6 +1012,8 @@ mod runtime { pub type HyperFungibleToken = pallet_hyper_fungible_token; #[runtime::pallet_index(92)] pub type MessagingIncentives = pallet_messaging_incentives; + #[runtime::pallet_index(93)] + pub type BeefyApkDigest = pallet_beefy_apk_digest; #[runtime::pallet_index(255)] pub type IsmpGrandpa = ismp_grandpa; } @@ -1041,6 +1049,7 @@ mod benches { [pallet_vesting, Vesting] [pallet_tx_pause, TxPause] [pallet_beefy_consensus_proofs, BeefyConsensusProofs] + [pallet_beefy_apk_digest, BeefyApkDigest] [pallet_hyper_fungible_token, HyperFungibleToken] ); } @@ -1285,8 +1294,30 @@ impl_runtime_apis! { impl cumulus_primitives_core::KeyToIncludeInRelayProof for Runtime { fn keys_to_prove() -> cumulus_primitives_core::RelayProofRequest { - // This runtime reads no extra relay chain storage, so no keys need proving. - Default::default() + // The collator only puts keys in the relay state proof that were asked for here, and + // the proof is checked against the relay parent's state root by the validators, so + // reading them out needs no trust beyond what a parachain already places in its relay + // parent. + // + // The keys wanted are `Beefy::NextAuthorities`, the next set rather than the current + // one so a client verifying a header signed by set N learns the commitment for N+1, + // and `Beefy::ValidatorSetId`, without which the commitment says nothing about which + // set it describes. Note the former is not `well_known_keys::AUTHORITIES`, which is + // `Babe::Authorities`; both end in twox128("Authorities") but differ in the prefix. + // + // The set id goes in every block, since it is one entry and it is what tells the + // pallet a rotation happened. The keys are asked for only when the pallet says it + // needs them, which is the block after a rotation, so the ordinary block does not + // carry a thousand keys it already has a commitment for. + let mut keys = alloc::vec![cumulus_primitives_core::RelayStorageKey::Top( + pallet_beefy_apk_digest::RELAY_BEEFY_VALIDATOR_SET_ID.to_vec() + )]; + if pallet_beefy_apk_digest::wants_keys::() { + keys.push(cumulus_primitives_core::RelayStorageKey::Top( + pallet_beefy_apk_digest::RELAY_BEEFY_NEXT_AUTHORITIES.to_vec(), + )); + } + cumulus_primitives_core::RelayProofRequest { keys } } } diff --git a/parachain/runtimes/gargantua/src/weights/mod.rs b/parachain/runtimes/gargantua/src/weights/mod.rs index 7b666b48d..b7a81f13a 100644 --- a/parachain/runtimes/gargantua/src/weights/mod.rs +++ b/parachain/runtimes/gargantua/src/weights/mod.rs @@ -31,6 +31,7 @@ pub mod ismp_parachain; pub mod pallet_asset_rate; pub mod pallet_assets; pub mod pallet_balances; +pub mod pallet_beefy_apk_digest; pub mod pallet_beefy_consensus_proofs; pub mod pallet_collective; pub mod pallet_hyper_fungible_token; diff --git a/parachain/runtimes/gargantua/src/weights/pallet_beefy_apk_digest.rs b/parachain/runtimes/gargantua/src/weights/pallet_beefy_apk_digest.rs new file mode 100644 index 000000000..9985f9093 --- /dev/null +++ b/parachain/runtimes/gargantua/src/weights/pallet_beefy_apk_digest.rs @@ -0,0 +1,48 @@ + +//! Autogenerated weights for `pallet_beefy_apk_digest` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 58.0.0 +//! DATE: 2026-08-18, STEPS: `2`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `polytope-labs`, CPU: `AMD Ryzen Threadripper PRO 5995WX 64-Cores` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 + +// Executed Command: +// frame-omni-bencher +// v1 +// benchmark +// pallet +// --runtime +// target/release/wbuild/gargantua-runtime/gargantua_runtime.compact.compressed.wasm +// --pallet +// pallet_beefy_apk_digest +// --extrinsic +// commit +// --steps +// 2 +// --repeat +// 20 +// --output +// /tmp/apk_digest_weights.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use polkadot_sdk::*; +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_beefy_apk_digest`. +pub struct WeightInfo(PhantomData); +impl pallet_beefy_apk_digest::WeightInfo for WeightInfo { + fn commit() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 584_718_741_000 picoseconds. + Weight::from_parts(587_530_204_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } +} diff --git a/parachain/runtimes/gargantua/src/weights/pallet_beefy_consensus_proofs.rs b/parachain/runtimes/gargantua/src/weights/pallet_beefy_consensus_proofs.rs index 4598e0da3..6e150adce 100644 --- a/parachain/runtimes/gargantua/src/weights/pallet_beefy_consensus_proofs.rs +++ b/parachain/runtimes/gargantua/src/weights/pallet_beefy_consensus_proofs.rs @@ -108,6 +108,29 @@ impl pallet_beefy_consensus_proofs::WeightInfo for Weig .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `BeefyConsensusProofs::ApkVerifyingKey` (r:1 w:0) + /// Proof: `BeefyConsensusProofs::ApkVerifyingKey` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn verify_apk() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 41_210_000_000 picoseconds. + Weight::from_parts(41_210_000_000, 0) + .saturating_add(Weight::from_parts(0, 51_200)) + .saturating_add(T::DbWeight::get().reads(1)) + } + /// Storage: `BeefyConsensusProofs::ApkVerifyingKey` (r:0 w:1) + /// Proof: `BeefyConsensusProofs::ApkVerifyingKey` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn set_apk_verifying_key() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Approximated: one write like `set_sp1_vkey_hash`, of a value three orders of magnitude + // larger, so the byte cost dominates rather than the write itself. + Weight::from_parts(2_000_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } /// Storage: `BeefyConsensusProofs::Sp1VkeyHash` (r:0 w:1) /// Proof: `BeefyConsensusProofs::Sp1VkeyHash` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn set_sp1_vkey_hash() -> Weight { diff --git a/parachain/runtimes/nexus/src/ismp.rs b/parachain/runtimes/nexus/src/ismp.rs index e3759b1a6..073c9af0a 100644 --- a/parachain/runtimes/nexus/src/ismp.rs +++ b/parachain/runtimes/nexus/src/ismp.rs @@ -210,6 +210,10 @@ impl ismp_beefy::BeefyClientConfig for Runtime { pallet_beefy_consensus_proofs::Sp1VkeyHash::::get() } + fn apk_verifying_key() -> alloc::vec::Vec { + pallet_beefy_consensus_proofs::ApkVerifyingKey::::get().into_inner() + } + fn allowed_proof_types() -> &'static [u8] { // Mainnet: only accept SP1 ZK proofs. &[ismp_beefy::PROOF_TYPE_SP1] @@ -325,7 +329,9 @@ pub struct HftBenchmarkHelper; #[cfg(feature = "runtime-benchmarks")] impl pallet_hyper_fungible_token::types::BenchmarkHelper for HftBenchmarkHelper { fn create_asset(decimals: u8, who: &AccountId, amount: u128) -> H256 { - use frame_support::traits::fungibles::{metadata::Mutate as MutateMetadata, Create, Mutate}; + use frame_support::traits::fungibles::{ + metadata::Mutate as MutateMetadata, Create, Mutate, + }; let asset_id: H256 = sp_io::hashing::keccak_256(b"HFT_BENCHMARK_ASSET").into(); >::create(asset_id, who.clone(), true, 1) diff --git a/parachain/runtimes/nexus/src/lib.rs b/parachain/runtimes/nexus/src/lib.rs index 98e61fef6..ee63d8247 100644 --- a/parachain/runtimes/nexus/src/lib.rs +++ b/parachain/runtimes/nexus/src/lib.rs @@ -1010,6 +1010,7 @@ impl pallet_beefy_consensus_proofs::Config for Runtime { type ConsensusStateId = BeefyConsensusStateId; type UnbondingPeriod = BeefyUnbondingPeriod; type MaxUncleProvers = MaxBeefyUncleProvers; + type MaxApkVerifyingKeyLen = ConstU32<131072>; type ReputationAsset = ReputationAsset; type WeightInfo = weights::pallet_beefy_consensus_proofs::WeightInfo; } diff --git a/parachain/runtimes/nexus/src/weights/pallet_beefy_consensus_proofs.rs b/parachain/runtimes/nexus/src/weights/pallet_beefy_consensus_proofs.rs index 4598e0da3..6e150adce 100644 --- a/parachain/runtimes/nexus/src/weights/pallet_beefy_consensus_proofs.rs +++ b/parachain/runtimes/nexus/src/weights/pallet_beefy_consensus_proofs.rs @@ -108,6 +108,29 @@ impl pallet_beefy_consensus_proofs::WeightInfo for Weig .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `BeefyConsensusProofs::ApkVerifyingKey` (r:1 w:0) + /// Proof: `BeefyConsensusProofs::ApkVerifyingKey` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn verify_apk() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 41_210_000_000 picoseconds. + Weight::from_parts(41_210_000_000, 0) + .saturating_add(Weight::from_parts(0, 51_200)) + .saturating_add(T::DbWeight::get().reads(1)) + } + /// Storage: `BeefyConsensusProofs::ApkVerifyingKey` (r:0 w:1) + /// Proof: `BeefyConsensusProofs::ApkVerifyingKey` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn set_apk_verifying_key() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Approximated: one write like `set_sp1_vkey_hash`, of a value three orders of magnitude + // larger, so the byte cost dominates rather than the write itself. + Weight::from_parts(2_000_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } /// Storage: `BeefyConsensusProofs::Sp1VkeyHash` (r:0 w:1) /// Proof: `BeefyConsensusProofs::Sp1VkeyHash` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn set_sp1_vkey_hash() -> Weight { diff --git a/parachain/simtests/Cargo.toml b/parachain/simtests/Cargo.toml index ef0039f20..c00129827 100644 --- a/parachain/simtests/Cargo.toml +++ b/parachain/simtests/Cargo.toml @@ -35,7 +35,9 @@ pallet-intents-rpc = { workspace = true } pallet-intents-coprocessor = { workspace = true, default-features = true } ismp-parachain = { workspace = true, default-features = true } pallet-beefy-consensus-proofs = { workspace = true, default-features = true } -beefy-prover = { workspace = true } +# `bls-aggregate` only adds `beefy_prover::bls`; it does not change how the ECDSA paths read the +# wire, so the naive BEEFY simtest against Paseo is unaffected. +beefy-prover = { workspace = true, features = ["bls-aggregate"] } beefy-verifier-primitives = { workspace = true, default-features = true } ismp-abi = { workspace = true, default-features = true } alloy-sol-types = { workspace = true, default-features = true } @@ -47,7 +49,7 @@ hex-literal = { workspace = true } hex = { workspace = true } serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" -subxt-signer = "0.41.0" +subxt-signer = "0.42" nexus-runtime = { workspace = true, default-features = true} gargantua-runtime = { workspace = true, default-features = true} crypto-utils = { workspace = true, default-features = true } diff --git a/parachain/simtests/src/bls_relay_setup.rs b/parachain/simtests/src/bls_relay_setup.rs new file mode 100644 index 000000000..782a7987c --- /dev/null +++ b/parachain/simtests/src/bls_relay_setup.rs @@ -0,0 +1,116 @@ +//! One-off setup for the BLS BEEFY end-to-end test: register a parachain on the local relay. +//! +//! `pallet-beefy-consensus-proofs` only accepts a proof that finalizes a parachain head it has +//! not seen, and it reads the child trie root out of that head. A relay with no registered +//! parachains therefore cannot satisfy it, no matter how good the BLS proof is. Registering one +//! gives the relay's MMR leaves a non-empty parachain heads root and lets the whole path run. +//! +//! The para id must be 4009, which is what gargantua's `is_parachain_tracked` allows and what its +//! coprocessor state machine resolves to; a head under any other id is filtered out and the +//! commitment lookup misses. +//! +//! Generate the artifacts first: +//! +//! ```text +//! hyperbridge build-spec --chain gargantua-4009 --disable-default-bootnode > plain.json +//! # patch "relay_chain" to the relay's id, then +//! hyperbridge build-spec --chain plain.json --raw --disable-default-bootnode > raw.json +//! hyperbridge export-genesis-head --chain raw.json > head.hex +//! hyperbridge export-genesis-wasm --chain raw.json > wasm.hex +//! ``` +//! +//! then: +//! +//! ```text +//! RELAY_WS_URL=ws://127.0.0.1:9979 PARA_HEAD_PATH=head.hex PARA_WASM_PATH=wasm.hex \ +//! cargo test -p simtests register_parachain -- --ignored --nocapture +//! ``` + +#![cfg(test)] + +use std::{env, fs}; + +use anyhow::anyhow; +use polkadot_sdk::sp_core::Bytes; +use subxt::{dynamic::Value, ext::subxt_rpcs::rpc_params, PolkadotConfig}; + +/// Read a `0x`-prefixed hex blob written by `export-genesis-*`. +fn read_hex(path: &str) -> Result, anyhow::Error> { + let raw = fs::read_to_string(path).map_err(|e| anyhow!("reading {path}: {e}"))?; + let trimmed = raw.trim().trim_start_matches("0x"); + hex::decode(trimmed).map_err(|e| anyhow!("decoding {path}: {e}")) +} + +#[tokio::test] +#[ignore] +async fn register_parachain_on_bls_relay() -> Result<(), anyhow::Error> { + let relay_ws_url = env::var("RELAY_WS_URL") + .map_err(|_| anyhow!("RELAY_WS_URL must point at the BLS BEEFY relay"))?; + let head_path = env::var("PARA_HEAD_PATH").unwrap_or_else(|_| "/tmp/g4009-head.hex".into()); + let wasm_path = env::var("PARA_WASM_PATH").unwrap_or_else(|_| "/tmp/g4009-wasm.hex".into()); + let para_id: u32 = env::var("PARA_ID").unwrap_or_else(|_| "4009".into()).parse()?; + + let genesis_head = read_hex(&head_path)?; + let validation_code = read_hex(&wasm_path)?; + eprintln!( + "[stage] para {para_id}: head {} bytes, validation code {} bytes", + genesis_head.len(), + validation_code.len(), + ); + + // The validation code alone is ~2MB, so the default payload ceiling is not enough. + let (client, rpc_client) = + subxt_utils::client::ws_client::(&relay_ws_url, u32::MAX).await?; + + let already: Vec = parachains(&rpc_client).await.unwrap_or_default(); + if already.contains(¶_id) { + eprintln!("[ok] para {para_id} is already registered, nothing to do"); + return Ok(()); + } + + // `para_kind: true` registers a parachain rather than a parathread, so it gets a core and + // its heads land in the relay's parachain heads root. + let genesis = Value::named_composite(vec![ + ("genesis_head", Value::from_bytes(&genesis_head)), + ("validation_code", Value::from_bytes(&validation_code)), + ("para_kind", Value::bool(true)), + ]); + + let inner = subxt::dynamic::tx( + "ParasSudoWrapper", + "sudo_schedule_para_initialize", + vec![Value::u128(para_id as u128), genesis], + ); + let sudo_call = subxt::dynamic::tx("Sudo", "sudo", vec![inner.into_value()]); + + // The relay's sudo key is Alice on a dev genesis; verified against `Sudo::Key` on chain. + let signer = subxt_signer::sr25519::dev::alice(); + + eprintln!("[stage] submitting sudoScheduleParaInitialize"); + let progress = client.tx().sign_and_submit_then_watch_default(&sudo_call, &signer).await?; + progress.wait_for_finalized_success().await?; + eprintln!("[stage] registration extrinsic finalized"); + + // Onboarding takes effect at a session boundary, so the para list does not update immediately. + for attempt in 1..=60 { + let current = parachains(&rpc_client).await.unwrap_or_default(); + if current.contains(¶_id) { + eprintln!("[ok] para {para_id} onboarded after {attempt} checks: {current:?}"); + return Ok(()); + } + tokio::time::sleep(std::time::Duration::from_secs(3)).await; + } + + Err(anyhow!("para {para_id} did not appear in Paras::Parachains within the timeout")) +} + +/// `Paras::Parachains`, the ids currently onboarded as parachains. +async fn parachains( + rpc_client: &subxt::ext::subxt_rpcs::RpcClient, +) -> Result, anyhow::Error> { + let key = format!("0x{}", hex::encode(beefy_prover::PARAS_PARACHAINS)); + let raw: Option = rpc_client.request("state_getStorage", rpc_params![key]).await?; + + let Some(bytes) = raw else { return Ok(vec![]) }; + Ok(codec::Decode::decode(&mut &bytes.0[..])?) +} diff --git a/parachain/simtests/src/lib.rs b/parachain/simtests/src/lib.rs index f48c86b56..76e87e8ce 100644 --- a/parachain/simtests/src/lib.rs +++ b/parachain/simtests/src/lib.rs @@ -1,4 +1,5 @@ mod base_call_filter; +mod bls_relay_setup; mod intents_rpc; mod migration_test; mod pallet_beefy_consensus_proofs; diff --git a/parachain/simtests/src/pallet_beefy_consensus_proofs.rs b/parachain/simtests/src/pallet_beefy_consensus_proofs.rs index 7df44c00e..55ccff55d 100644 --- a/parachain/simtests/src/pallet_beefy_consensus_proofs.rs +++ b/parachain/simtests/src/pallet_beefy_consensus_proofs.rs @@ -79,7 +79,7 @@ const MAX_UNCLE_PROVERS: usize = 5; /// `ConsensusClientId` for BEEFY (`b"BEEF"`); duplicated here because pulling /// `ismp-beefy` into simtests just for this constant is excessive. -const BEEFY_CONSENSUS_ID: [u8; 4] = *b"BEEF"; +pub(crate) const BEEFY_CONSENSUS_ID: [u8; 4] = *b"BEEF"; /// Path-embedded SP1 fixture produced by the prover (`zk-beefy::tests::test_sp1_beefy`) /// and consumed by the on-chain SP1Beefy fork test under `evm/tests/foundry/`. Sourcing @@ -178,7 +178,7 @@ fn curve_value(fractions: &[(u32, u32)]) -> Value { /// Submit a sudo-wrapped call signed by Alice (the simnode sudo key) and wait /// for finalization. Returns the dispatch result so callers can assert on /// success / failure of the inner call. -async fn submit_sudo( +pub(crate) async fn submit_sudo( client: &OnlineClient, rpc_client: &RpcClient, inner: subxt::tx::DynamicPayload, @@ -187,7 +187,7 @@ async fn submit_sudo( submit_signed(client, rpc_client, sudo_call, Keyring::Alice).await } -async fn submit_signed( +pub(crate) async fn submit_signed( client: &OnlineClient, rpc_client: &RpcClient, call: subxt::tx::DynamicPayload, @@ -407,7 +407,7 @@ async fn test_admin_extrinsics_and_submit_proof_validation() -> Result<(), anyho /// a BEEFY justification. We use that parent as the trusted-state anchor so the /// proof at `latest_beefy_hash` is guaranteed to advance state. Mirrors the lookup /// in `modules/pallets/testsuite/src/tests/pallet_ismp_beefy.rs::setup`. -async fn previous_beefy_anchor( +pub(crate) async fn previous_beefy_anchor( relay_rpc: &LegacyRpcMethods, latest_beefy_hash: H256, ) -> Result { diff --git a/tesseract/consensus/admin-relayer/Cargo.toml b/tesseract/consensus/admin-relayer/Cargo.toml index fc3eb2f13..1eec6f685 100644 --- a/tesseract/consensus/admin-relayer/Cargo.toml +++ b/tesseract/consensus/admin-relayer/Cargo.toml @@ -39,7 +39,7 @@ ismp-abi = { workspace = true, default-features = true } beefy-verifier-primitives = { workspace = true } # tesseract -tesseract-beefy = { workspace = true } +tesseract-beefy = { workspace = true, features = ["sp1-local"] } tesseract-evm = { workspace = true } tesseract-primitives = { workspace = true } diff --git a/tesseract/consensus/beefy/Cargo.toml b/tesseract/consensus/beefy/Cargo.toml index 94f60c8ab..fe6c13d05 100644 --- a/tesseract/consensus/beefy/Cargo.toml +++ b/tesseract/consensus/beefy/Cargo.toml @@ -46,7 +46,8 @@ sp-consensus-beefy = { workspace = true } # tesseract tesseract-substrate = { workspace = true } tesseract-primitives = { workspace = true } -zk-beefy = { path = "zk" } +zk-beefy = { path = "zk", default-features = false } +apk-beefy = { path = "apk", default-features = false } rsmq_async = { workspace = true } redis-async = { version = "0.17.1", features = ["with-rustls"] } @@ -55,6 +56,10 @@ workspace = true features = ["sp-runtime"] [features] +default = ["sp1-local"] +sp1-local = ["zk-beefy/local"] +sp1-cluster = ["zk-beefy/cluster"] +local = ["apk-beefy/local"] # a feature that tells the tests to write a new consensus state new-consensus-state = [] diff --git a/tesseract/consensus/beefy/apk/Cargo.toml b/tesseract/consensus/beefy/apk/Cargo.toml new file mode 100644 index 000000000..9ffcdde55 --- /dev/null +++ b/tesseract/consensus/beefy/apk/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "apk-beefy" +version = "0.1.0" +edition = "2021" +authors = ["Polytope Labs "] +description = "Builds BEEFY consensus proofs carrying an aggregate public key proof" + +[dependencies] +anyhow = "1.0.79" +async-trait = { workspace = true } +hex = { workspace = true } +codec = { package = "parity-scale-codec", version = "3.2.2" } +alloy-primitives = { workspace = true, default-features = true } +subxt = { workspace = true, default-features = true } +sp-consensus-beefy = { workspace = true } + +beefy-prover = { workspace = true, features = ["bls-aggregate"] } +beefy-verifier-primitives = { workspace = true, default-features = true } +ismp-abi = { workspace = true, default-features = true } + +ark-bls12-381 = { version = "0.4.0", features = ["curve"], default-features = false } +ark-ec = { version = "0.4.0", default-features = false } +ark-ff = { version = "0.4.0", default-features = false } +ark-serialize = { version = "0.4.0", default-features = false } +json = { workspace = true, default-features = true } + +[dependencies.gnark-plonk-verifier] +git = "https://github.com/polytope-labs/gnark-apk-proofs" +rev = "1c15d06d8180906b16201c5206711808b80742ef" + +[dependencies.gnark-apk-prover] +git = "https://github.com/polytope-labs/gnark-apk-proofs" +rev = "1c15d06d8180906b16201c5206711808b80742ef" +optional = true + +[dependencies.ark-serialize-05] +package = "ark-serialize" +version = "0.5" + +[dependencies.tokio] +workspace = true +features = ["fs", "process", "rt"] + +[features] +default = [] +local = ["dep:gnark-apk-prover"] + +[dev-dependencies] +alloy-sol-types = { workspace = true, default-features = true } +tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } +subxt-utils = { workspace = true, default-features = true } +beefy-verifier = { workspace = true, default-features = true, features = ["apk"] } +polkadot-sdk = { workspace = true, default-features = true, features = ["sp-io"] } +ismp = { workspace = true, default-features = true } +primitive-types = { workspace = true, default-features = true } diff --git a/tesseract/consensus/beefy/apk/src/lib.rs b/tesseract/consensus/beefy/apk/src/lib.rs new file mode 100644 index 000000000..4e3eec878 --- /dev/null +++ b/tesseract/consensus/beefy/apk/src/lib.rs @@ -0,0 +1,357 @@ +// Copyright (C) Polytope Labs Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +//! Assembles the BEEFY consensus proof that `BlsApkBeefy` verifies. +//! +//! The relay chain half comes from `beefy_prover`, and everything specific to this path is built +//! here: the aggregate of the signers' keys in both groups, their aggregate signature, and the +//! SNARK that ties the aggregate to the authority set. +//! +//! Generating that SNARK needs a Go toolchain through cgo and a large structured reference string, +//! so it sits behind [`ApkProver`] and the `local` feature. A build without the feature assembles +//! everything here except the SNARK, and has no way to produce one. + +use alloy_primitives::{Bytes, FixedBytes, U256}; +use anyhow::anyhow; +use ark_bls12_381::{Fq, G1Affine, G1Projective, G2Affine, G2Projective}; +use ark_ec::{AffineRepr, CurveGroup}; +use ark_ff::{BigInteger, PrimeField}; +use ark_serialize::CanonicalDeserialize; +use codec::Decode; +use std::sync::Arc; +use subxt::config::HashFor; + +use beefy_prover::bls::{ + aggregate_signatures, beefy_g1_authorities, beefy_g2_authorities, fetch_paired_justification, + PairedSignature, +}; +use beefy_verifier_primitives::{ConsensusState, BLS_G1_SIGNATURE_LEN}; +use ismp_abi::bls_beefy::BlsBeefy; + +#[cfg(feature = "local")] +mod local; +#[cfg(feature = "local")] +pub use local::LocalProver; + +/// Payload id of the mmr root in a BEEFY commitment. +const MMR_ROOT_ID: &[u8; 2] = b"mh"; + +/// Number of words in the circuit's participation bitlist. +const BITLIST_WORDS: usize = 5; + +/// What the circuit is asked to prove. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ApkProofRequest { + /// Compressed G1 keys of the whole authority set, in authority order. + pub keys: Vec<[u8; BLS_G1_SIGNATURE_LEN]>, + /// Indices of the authorities that signed. + pub participation: Vec, +} + +/// What it produces. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ApkProof { + /// The PLONK proof bytes the contract passes to its verifier. + pub proof: Vec, + /// The participation set as the contract reads it, one bit per authority slot. + pub bitlist: [U256; BITLIST_WORDS], + /// Poseidon2 over the padded key set, a public input of the proof. + pub apk_commitment: [u8; 32], +} + +/// Generates APK proofs. +/// +/// An implementation may prove in process or hand the work to something else. Either way it is +/// slow, minutes rather than seconds, which is why this is only ever called by the prover service +/// and never on the delivery path. +#[async_trait::async_trait] +pub trait ApkProver: Send + Sync { + /// Prove that the aggregate of the participating keys belongs to this key set. + async fn prove(&self, request: ApkProofRequest) -> Result; +} + +/// Consensus prover for BEEFY verified through an aggregate public key proof. +/// +/// The SNARK prover is a trait object rather than a type parameter, since nothing here needs to +/// know which one it is and a second generic would spread through every caller. +pub struct Prover { + /// The relay chain half, shared with every other BEEFY proof variant. + pub inner: beefy_prover::Prover, + /// Produces the SNARK. + pub apk: Arc, +} + +impl Clone for Prover +where + R: subxt::Config, + P: subxt::Config, + beefy_prover::Prover: Clone, +{ + fn clone(&self) -> Self { + Self { inner: self.inner.clone(), apk: self.apk.clone() } + } +} + +impl Prover +where + R: subxt::Config, + P: subxt::Config, +{ + /// Build a prover over an existing relay chain prover. + pub fn new(prover: beefy_prover::Prover, apk: Arc) -> Self { + Self { inner: prover, apk } + } + + /// Build the whole update: the signed commitment, the aggregate proof, the mmr leaf and the + /// parachain headers, in the shape `BlsApkBeefy.verify` decodes. + /// + /// Takes the ECDSA shaped commitment every variant is handed, then reads the justification + /// again itself, since that decode throws away the BLS half this path signs with. + pub async fn consensus_proof( + &self, + signed_commitment: sp_consensus_beefy::SignedCommitment< + u32, + sp_consensus_beefy::ecdsa_crypto::Signature, + >, + consensus_state: ConsensusState, + ) -> Result { + let set_id = signed_commitment.commitment.validator_set_id; + if set_id != consensus_state.current_authorities.id && + set_id != consensus_state.next_authorities.id + { + Err(anyhow!("Unknown validator set {set_id}"))? + } + + let height = signed_commitment.commitment.block_number; + let at = self + .inner + .relay_rpc + .chain_get_block_hash(Some(height.into())) + .await? + .ok_or_else(|| anyhow!("No block hash for beefy block {height}"))?; + + let paired = fetch_paired_justification(&self.inner.relay_rpc, at).await?; + let message = self.inner.bls_consensus_proof(paired.clone()).await?; + let aggregate = self.aggregate(&paired, at).await?; + let proof = self.apk.prove(aggregate.request).await?; + + // The circuit and the runtime must describe the same set, since the client checks the + // proof against whichever of the two it was given. Disagreement here means the relay was + // read at a height where the set had already rotated. + let expected = apk_commitment_of(&aggregate.keys)?; + if proof.apk_commitment != expected { + Err(anyhow!( + "apk commitment mismatch, circuit says {} and the key set hashes to {}", + hex::encode(proof.apk_commitment), + hex::encode(expected) + ))? + } + + let mmr_root = message + .mmr + .commitment + .payload + .get_raw(MMR_ROOT_ID) + .ok_or_else(|| anyhow!("Commitment carries no mmr root payload"))? + .clone(); + + let leaf = &message.mmr.latest_mmr_leaf; + let relay = BlsBeefy::BlsApkRelayChainProof { + commitment: BlsBeefy::Commitment { + payload: vec![BlsBeefy::Payload { + id: FixedBytes(*MMR_ROOT_ID), + data: Bytes::from(mmr_root), + }], + blockNumber: message.mmr.commitment.block_number, + validatorSetId: set_id, + }, + bitlist: proof.bitlist, + apk: aggregate.apk, + apk2: aggregate.apk2, + apkProof: Bytes::from(proof.proof), + signature: aggregate.signature, + latestMmrLeaf: BlsBeefy::BeefyMmrLeaf { + // One byte carrying the major version in the top three bits and the minor in the + // rest, which is how the reverse conversion in `ismp-abi` reads it back. + version: { + let (major, minor) = leaf.version.split(); + (major << 5) | minor + }, + parentNumber: leaf.parent_number_and_hash.0, + parentHash: FixedBytes(leaf.parent_number_and_hash.1 .0), + nextAuthoritySet: BlsBeefy::AuthoritySetCommitment { + id: leaf.beefy_next_authority_set.id, + len: leaf.beefy_next_authority_set.len, + root: FixedBytes(leaf.beefy_next_authority_set.keyset_commitment.0), + }, + extra: FixedBytes(leaf.leaf_extra.0), + leafIndex: U256::from( + message.mmr.mmr_proof.leaf_indices.first().copied().unwrap_or_default(), + ), + }, + mmrProof: message.mmr.mmr_proof.items.iter().map(|item| FixedBytes(item.0)).collect(), + }; + + let parachain = BlsBeefy::ParachainProof { + parachains: message + .parachain + .parachains + .iter() + .map(|para| BlsBeefy::Parachain { + index: U256::from(para.index), + id: U256::from(para.para_id), + header: Bytes::from(para.header.clone()), + }) + .collect(), + proof: message.parachain.proof.iter().map(|node| FixedBytes(*node)).collect(), + leafCount: U256::from(message.parachain.total_leaves), + }; + + Ok(BlsBeefy::BlsApkBeefyConsensusProof { relay, parachain }) + } + + /// Poseidon2 over the relay's current BEEFY keys at `at`. + /// + /// Needed when bootstrapping a client, since the commitment for the set that signs the first + /// update has to be seeded by hand. Every later one arrives in a header digest. + pub async fn current_apk_commitment(&self, at: HashFor) -> Result<[u8; 32], anyhow::Error> { + let keys = beefy_g1_authorities(&self.inner.relay_rpc, Some(at)).await?; + apk_commitment_of(&keys) + } + + /// Poseidon2 over the relay's *next* BEEFY keys at `at`. + /// + /// Bootstrapping a client cold needs both, since the first proof it sees may already be the + /// rotation into the next set, and a set without a commitment is refused rather than checked + /// against zero. + pub async fn next_apk_commitment(&self, at: HashFor) -> Result<[u8; 32], anyhow::Error> { + let data = self + .inner + .relay_rpc + .state_get_storage(&beefy_verifier_primitives::RELAY_BEEFY_NEXT_AUTHORITIES, Some(at)) + .await? + .ok_or_else(|| anyhow!("No next beefy authorities found"))?; + + let keys = Vec::::decode(&mut data.as_ref())? + .iter() + .map(beefy_verifier_primitives::PairedAuthority::g1) + .collect::>(); + apk_commitment_of(&keys) + } + + /// Sum the signers' keys in both groups and their signatures, and note who they were. + /// + /// The G1 halves are what the circuit binds to and the G2 halves are what BEEFY's signature + /// verifies against, so both are needed even though they describe one aggregate secret. + async fn aggregate( + &self, + signed_commitment: &sp_consensus_beefy::SignedCommitment, + at: HashFor, + ) -> Result { + let keys = beefy_g1_authorities(&self.inner.relay_rpc, Some(at)).await?; + let g2_keys = beefy_g2_authorities(&self.inner.relay_rpc, Some(at)).await?; + + let mut apk_g1 = G1Projective::default(); + let mut apk_g2 = G2Projective::default(); + let mut signatures = Vec::new(); + let mut participation = Vec::new(); + for (index, signature) in signed_commitment.signatures.iter().enumerate() { + let Some(signature) = signature else { continue }; + let g1 = + keys.get(index).ok_or_else(|| anyhow!("Signer {index} is not an authority"))?; + let g2 = g2_keys + .get(index) + .ok_or_else(|| anyhow!("Signer {index} is not an authority"))?; + + apk_g1 += decompress_g1(g1)?; + apk_g2 += G2Affine::deserialize_compressed(&g2[..]) + .map_err(|_| anyhow!("Authority {index} has a malformed G2 key"))?; + signatures.push(signature.g1_signature()); + participation.push(index as u64); + } + + if participation.is_empty() { + Err(anyhow!("Commitment carries no signatures"))? + } + + let signature = decompress_g1(&aggregate_signatures(&signatures)?)?; + + Ok(Aggregate { + apk: pack_g1(&apk_g1.into_affine())?, + apk2: pack_g2(&apk_g2.into_affine())?, + signature: pack_g1(&signature)?, + request: ApkProofRequest { keys: keys.clone(), participation }, + keys, + }) + } +} + +/// The aggregated halves of one signed commitment. +struct Aggregate { + apk: [FixedBytes<32>; 3], + apk2: [FixedBytes<32>; 6], + signature: [FixedBytes<32>; 3], + keys: Vec<[u8; BLS_G1_SIGNATURE_LEN]>, + request: ApkProofRequest, +} + +/// Poseidon2 over the key set, padded to the circuit's width. The same value the runtime pallet +/// publishes in a header digest. +pub(crate) fn apk_commitment_of( + keys: &[[u8; BLS_G1_SIGNATURE_LEN]], +) -> Result<[u8; 32], anyhow::Error> { + // The commitment is defined by the circuit, so it is computed in the arkworks version the + // circuit's crate speaks rather than the one the rest of this crate uses for the prover. + let points = keys + .iter() + .map(|key| { + ::deserialize_compressed( + &key[..], + ) + .map_err(|_| anyhow!("Malformed G1 point")) + }) + .collect::, anyhow::Error>>()?; + let padded = gnark_plonk_verifier::padded_to_circuit_width(&points); + Ok(gnark_plonk_verifier::public_keys_commitment_bytes(&padded)) +} + +pub(crate) fn decompress_g1(key: &[u8; BLS_G1_SIGNATURE_LEN]) -> Result { + G1Affine::deserialize_compressed(&key[..]).map_err(|_| anyhow!("Malformed G1 point")) +} + +/// Curve points reach the SNARK verifier as raw 48 byte coordinates packed into 32 byte words, +/// which is not the zero padded layout the EIP-2537 precompiles take. +fn pack_g1(point: &G1Affine) -> Result<[FixedBytes<32>; 3], anyhow::Error> { + let (x, y) = point.xy().ok_or_else(|| anyhow!("Aggregate is the identity"))?; + words(&[x, y])?.try_into().map_err(|_| anyhow!("G1 point is not three words")) +} + +fn pack_g2(point: &G2Affine) -> Result<[FixedBytes<32>; 6], anyhow::Error> { + let (x, y) = point.xy().ok_or_else(|| anyhow!("Aggregate is the identity"))?; + words(&[&x.c0, &x.c1, &y.c0, &y.c1])? + .try_into() + .map_err(|_| anyhow!("G2 point is not six words")) +} + +fn words(coordinates: &[&Fq]) -> Result>, anyhow::Error> { + let mut bytes = Vec::with_capacity(coordinates.len() * 48); + for coordinate in coordinates { + bytes.extend_from_slice(&coordinate.into_bigint().to_bytes_be()); + } + if bytes.len() % 32 != 0 { + Err(anyhow!("Packed point is not a whole number of words"))? + } + Ok(bytes.chunks(32).map(FixedBytes::<32>::from_slice).collect()) +} diff --git a/tesseract/consensus/beefy/apk/src/local.rs b/tesseract/consensus/beefy/apk/src/local.rs new file mode 100644 index 000000000..df7de10f9 --- /dev/null +++ b/tesseract/consensus/beefy/apk/src/local.rs @@ -0,0 +1,103 @@ +// Copyright (C) Polytope Labs Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +//! Proving in process with `gnark-apk-proofs`. + +use std::{path::PathBuf, sync::Arc}; + +use alloy_primitives::U256; +use anyhow::anyhow; +use ark_serialize_05::CanonicalDeserialize; +use gnark_apk_prover::{G1Affine, ProofBuilder, ProverContext}; + +use crate::{ApkProof, ApkProofRequest, ApkProver, BITLIST_WORDS}; + +/// Width of one public input, and of a bitlist word. +const WORD: usize = 32; + +/// The public inputs the circuit exposes: the bitlist, then the commitment, then the aggregate key +/// as twelve limbs. +const PUBLIC_INPUTS: usize = 18; + +/// Proves with the circuit compiled into this process. +/// +/// Construction runs the setup, which compiles the circuit and generates the keys. That takes a +/// few minutes and a large structured reference string, so build one at startup and keep it. +pub struct LocalProver { + context: Arc, +} + +impl LocalProver { + /// Compile the circuit and generate the proving key. + /// + /// `srs_dir` holds the structured reference string. Leave it unset for + /// `$HOME/.config/gnark-apk-proofs/srs`, which the Go side populates from the Filecoin + /// ceremony if it is empty. + pub fn new(srs_dir: Option) -> Result { + let context = ProverContext::setup(srs_dir.as_deref()) + .map_err(|e| anyhow!("APK circuit setup failed: {e}"))?; + Ok(Self { context: Arc::new(context) }) + } +} + +#[async_trait::async_trait] +impl ApkProver for LocalProver { + async fn prove(&self, request: ApkProofRequest) -> Result { + let keys = request + .keys + .iter() + .map(|key| { + G1Affine::deserialize_compressed(&key[..]) + .map_err(|_| anyhow!("Malformed G1 authority key")) + }) + .collect::, _>>()?; + let participation = request + .participation + .iter() + .map(|index| { + u16::try_from(*index) + .map_err(|_| anyhow!("Authority index {index} is out of range")) + }) + .collect::, _>>()?; + + // Proving is minutes of cpu, so keep it off the runtime's worker threads. + let context = self.context.clone(); + let proof = tokio::task::spawn_blocking(move || { + ProofBuilder::new(&context) + .public_keys(keys) + .participation(participation) + .prove() + .map_err(|e| anyhow!("APK proving failed: {e}")) + }) + .await??; + + let inputs = proof.public_inputs_calldata(); + if inputs.len() != PUBLIC_INPUTS * WORD { + Err(anyhow!("Expected {PUBLIC_INPUTS} public inputs, got {}", inputs.len() / WORD))? + } + let word = |i: usize| &inputs[i * WORD..(i + 1) * WORD]; + + let bitlist: [U256; BITLIST_WORDS] = (0..BITLIST_WORDS) + .map(|i| U256::from_be_slice(word(i))) + .collect::>() + .try_into() + .map_err(|_| anyhow!("Bitlist is not {BITLIST_WORDS} words"))?; + + let mut apk_commitment = [0u8; WORD]; + apk_commitment.copy_from_slice(word(BITLIST_WORDS)); + + Ok(ApkProof { proof: proof.proof_calldata().to_vec(), bitlist, apk_commitment }) + } +} diff --git a/tesseract/consensus/beefy/apk/tests/live_prover.rs b/tesseract/consensus/beefy/apk/tests/live_prover.rs new file mode 100644 index 000000000..de7835416 --- /dev/null +++ b/tesseract/consensus/beefy/apk/tests/live_prover.rs @@ -0,0 +1,276 @@ +// Copyright (C) Polytope Labs Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +//! Assembles a proof from a live chain and checks the verifier accepts it. +//! +//! The fixture tests prove the verifier agrees with Solidity on a proof somebody else built. This +//! closes the other half, that what this crate assembles is something the verifier accepts, which +//! is the path a relayer actually takes. +//! +//! Needs a relay whose BEEFY authorities hold paired `ecdsa_bls_crypto` keys and the parachain it +//! finalizes. Takes minutes, since it compiles the circuit and generates a real SNARK. +//! +//! RELAY_WS_URL=ws://127.0.0.1:9979 PARA_WS_URL=ws://127.0.0.1:9981 PARA_ID=4009 \ +//! cargo test -p apk-beefy --features local --test live_prover -- --ignored --nocapture + +// Both tests build a prover, and the only one there is compiles the circuit in. +#![cfg(feature = "local")] + +use std::sync::Arc; + +use apk_beefy::Prover; +use beefy_prover::relay::fetch_latest_beefy_justification; +use beefy_verifier_primitives::{ApkConsensusMessage, AuthoritySet, ConsensusState}; +use polkadot_sdk::*; +use primitive_types::H256; +use subxt::{backend::legacy::LegacyRpcMethods, PolkadotConfig}; + +const VERIFYING_KEY: &[u8] = + include_bytes!("../../../../../evm/tests/foundry/fixtures/apk-verifying-key.bin"); + +struct TestHost; + +/// Seeding reads the chain and never proves, so the slot is filled rather than occupied. +struct NoProver; + +#[async_trait::async_trait] +impl apk_beefy::ApkProver for NoProver { + async fn prove( + &self, + _request: apk_beefy::ApkProofRequest, + ) -> Result { + Err(anyhow::anyhow!("seeding does not prove")) + } +} + +impl ismp::messaging::Keccak256 for TestHost { + fn keccak256(bytes: &[u8]) -> H256 { + sp_io::hashing::keccak_256(bytes).into() + } +} + +#[tokio::test(flavor = "multi_thread")] +#[ignore = "needs a live BLS relay, its parachain, and the prover binary"] +async fn assembles_a_proof_the_verifier_accepts() { + let max_rpc_payload_size = 15 * 1024 * 1024; + let relay_ws_url = std::env::var("RELAY_WS_URL").expect("RELAY_WS_URL must be set"); + let para_ws_url = std::env::var("PARA_WS_URL").expect("PARA_WS_URL must be set"); + let para_id: u32 = std::env::var("PARA_ID") + .expect("PARA_ID must be set") + .parse() + .expect("para id is a number"); + let (relay_client, relay_rpc_client) = + subxt_utils::client::ws_client::(&relay_ws_url, max_rpc_payload_size) + .await + .unwrap(); + let relay_rpc = LegacyRpcMethods::::new(relay_rpc_client.clone()); + let (para_client, para_rpc_client) = + subxt_utils::client::ws_client::(¶_ws_url, max_rpc_payload_size) + .await + .unwrap(); + let para_rpc = LegacyRpcMethods::::new(para_rpc_client.clone()); + + let inner = beefy_prover::Prover { + beefy_activation_block: 0, + relay: relay_client, + relay_rpc: relay_rpc.clone(), + relay_rpc_client: relay_rpc_client.clone(), + para: para_client, + para_rpc, + para_rpc_client, + para_ids: vec![para_id], + query_batch_size: Some(100), + }; + + // Setup saturates every core for a couple of minutes, so it stays off the runtime. + let srs_dir = std::env::var("APK_SRS_DIR").ok().map(Into::into); + let apk: Arc = Arc::new( + std::thread::spawn(move || apk_beefy::LocalProver::new(srs_dir)) + .join() + .expect("apk circuit setup panicked") + .expect("apk circuit setup"), + ); + let prover = Prover::new(inner, apk); + + let latest: H256 = relay_rpc_client + .request("beefy_getFinalizedHead", subxt::ext::subxt_rpcs::rpc_params!()) + .await + .unwrap(); + + // Anchor the trusted state at the previous beefy justified block, so the update is not stale. + let mut previous = H256::default(); + let mut cursor = latest; + for _ in 0..2000 { + let header = relay_rpc.chain_get_header(Some(cursor.into())).await.unwrap().unwrap(); + let parent: H256 = header.parent_hash.into(); + if parent.is_zero() { + panic!("reached genesis without a previous beefy block"); + } + let block = relay_rpc.chain_get_block(Some(parent.into())).await.unwrap().unwrap(); + if block + .justifications + .map(|justifications| { + justifications.iter().any(|j| j.0 == sp_consensus_beefy::BEEFY_ENGINE_ID) + }) + .unwrap_or(false) + { + previous = parent; + break; + } + cursor = parent; + } + assert!(!previous.is_zero(), "no previous beefy block found"); + + let trusted = prover.inner.get_initial_consensus_state(Some(previous)).await.unwrap(); + let (signed_commitment, _) = + fetch_latest_beefy_justification(&prover.inner.relay_rpc, latest).await.unwrap(); + + let signed_set_id = signed_commitment.commitment.validator_set_id; + println!( + "proving beefy block {} for set {signed_set_id} against trusted height {}, this takes minutes", + signed_commitment.commitment.block_number, trusted.latest_beefy_height + ); + let proof = prover + .consensus_proof(signed_commitment, trusted.clone()) + .await + .expect("the prover assembles a proof"); + + // The signing set's commitment has to be seeded, exactly as `initialize_apk_state` does when + // bootstrapping a client. Every later one arrives in a header digest, and a set that has none + // is refused rather than checked against zero, so it has to go on whichever set signed. + let commitment = H256(prover.current_apk_commitment(latest).await.unwrap()); + let signing_set = signed_set_id; + let authority_set = |set: AuthoritySet| AuthoritySet { + bls_poseidon_hash: if set.id == signing_set { commitment } else { H256::zero() }, + ..set + }; + let state = ConsensusState { + latest_beefy_height: trusted.latest_beefy_height, + beefy_activation_block: trusted.beefy_activation_block, + mmr_root_hash: trusted.mmr_root_hash, + current_authorities: authority_set(trusted.current_authorities), + next_authorities: authority_set(trusted.next_authorities), + }; + assert!( + state.current_authorities.bls_poseidon_hash != H256::zero() || + state.next_authorities.bls_poseidon_hash != H256::zero(), + "neither trusted set is the one that signed, so the client would have no commitment", + ); + + let message: ApkConsensusMessage = proof.try_into().expect("proof converts to scale"); + let (new_state, headers) = beefy_verifier::apk::verify_apk_consensus::( + state.clone(), + message, + VERIFYING_KEY, + para_id, + ) + .expect("the verifier accepts what the prover built"); + + assert!(new_state.latest_beefy_height > state.latest_beefy_height); + assert_eq!(headers.len(), 1, "should finalize the parachain"); + assert_eq!(headers[0].para_id, para_id); + println!( + "prover to verifier round trip: beefy height {} -> {}, para {} finalized", + state.latest_beefy_height, new_state.latest_beefy_height, para_id + ); +} + +/// Writes the abi encoded state `initialize_apk_state` takes, for the set signing right now. +/// +/// This is what the relayer's `query_initial_consensus_state` produces for the apk variant, run on +/// its own so a chain can be bootstrapped by hand. +/// +/// RELAY_WS_URL=ws://127.0.0.1:9979 PARA_WS_URL=ws://127.0.0.1:9981 PARA_ID=4009 \ +/// APK_STATE_OUT=/tmp/apk-state.hex \ +/// cargo test -p apk-beefy --test live_prover -- --ignored --nocapture writes_initial_state +#[tokio::test(flavor = "multi_thread")] +#[ignore = "needs a live BLS relay"] +async fn writes_initial_state_for_bootstrapping() { + use alloy_sol_types::SolValue; + + let max_rpc_payload_size = 15 * 1024 * 1024; + let relay_ws_url = std::env::var("RELAY_WS_URL").expect("RELAY_WS_URL must be set"); + let para_ws_url = std::env::var("PARA_WS_URL").expect("PARA_WS_URL must be set"); + let para_id: u32 = std::env::var("PARA_ID") + .expect("PARA_ID must be set") + .parse() + .expect("para id is a number"); + let out = std::env::var("APK_STATE_OUT").expect("APK_STATE_OUT must be set"); + + let (relay_client, relay_rpc_client) = + subxt_utils::client::ws_client::(&relay_ws_url, max_rpc_payload_size) + .await + .unwrap(); + let relay_rpc = LegacyRpcMethods::::new(relay_rpc_client.clone()); + let (para_client, para_rpc_client) = + subxt_utils::client::ws_client::(¶_ws_url, max_rpc_payload_size) + .await + .unwrap(); + let para_rpc = LegacyRpcMethods::::new(para_rpc_client.clone()); + + let inner = beefy_prover::Prover { + beefy_activation_block: 0, + relay: relay_client, + relay_rpc: relay_rpc.clone(), + relay_rpc_client: relay_rpc_client.clone(), + para: para_client, + para_rpc, + para_rpc_client, + para_ids: vec![para_id], + query_batch_size: Some(100), + }; + let prover = Prover::new(inner, Arc::new(NoProver)); + + let latest: H256 = relay_rpc_client + .request("beefy_getFinalizedHead", subxt::ext::subxt_rpcs::rpc_params!()) + .await + .unwrap(); + let trusted = prover.inner.get_initial_consensus_state(Some(latest)).await.unwrap(); + + // Both sets are seeded, not just the current one. A cold start can meet the rotation proof + // into the next set before it has ever seen a header digest, and a set with no commitment is + // refused rather than checked against zero. After this the digests take over. + let current = H256(prover.current_apk_commitment(latest).await.unwrap()); + // Leaving the next set empty is how forward chaining gets demonstrated: the client then has + // to learn that commitment from a header digest rather than being handed it here. + let next = match std::env::var("APK_SEED_NEXT").as_deref() { + Ok("0") => H256::zero(), + _ => H256(prover.next_apk_commitment(latest).await.unwrap()), + }; + let state = ConsensusState { + latest_beefy_height: trusted.latest_beefy_height, + beefy_activation_block: trusted.beefy_activation_block, + mmr_root_hash: trusted.mmr_root_hash, + current_authorities: AuthoritySet { + id: trusted.current_authorities.id, + len: trusted.current_authorities.len, + bls_poseidon_hash: current, + ecdsa_merkle_root: trusted.current_authorities.ecdsa_merkle_root, + }, + next_authorities: AuthoritySet { + id: trusted.next_authorities.id, + len: trusted.next_authorities.len, + bls_poseidon_hash: next, + ecdsa_merkle_root: trusted.next_authorities.ecdsa_merkle_root, + }, + }; + + let abi = ismp_abi::bls_beefy::BlsBeefy::BeefyConsensusState::from(state.clone()); + std::fs::write(&out, format!("0x{}", hex::encode(abi.abi_encode()))).expect("write"); + println!( + "wrote state for sets {} and {} at beefy height {} to {out}", + state.current_authorities.id, state.next_authorities.id, state.latest_beefy_height + ); +} diff --git a/tesseract/consensus/beefy/src/host.rs b/tesseract/consensus/beefy/src/host.rs index 4a7486ca2..7ef4481c4 100644 --- a/tesseract/consensus/beefy/src/host.rs +++ b/tesseract/consensus/beefy/src/host.rs @@ -28,6 +28,7 @@ use subxt::{ use beefy_verifier_primitives::ConsensusState; use ismp::{ consensus::ConsensusStateId, + host::StateMachine, messaging::{CreateConsensusState, Message}, }; use ismp_abi::ecdsa_beefy::BeefyConsensusState; @@ -46,6 +47,13 @@ pub struct BeefyHostConfig { pub consensus_state_id: ConsensusStateId, } +/// What the host needs out of a destination's consensus state, independent of its shape. +struct StateSummary { + latest_beefy_height: u32, + current_set_id: u64, + next_set_id: u64, +} + /// The beefy host is responsible for receiving BEEFY proofs from the queue and submitting /// them to the counterparty. pub struct BeefyHost @@ -86,6 +94,60 @@ where Ok(BeefyHost { backend, prover, client, config }) } + /// The parts of the destination's consensus state this host reasons about. + /// + /// Two things decide how the bytes are read. A solidity client stores its state abi-encoded + /// where a substrate one stores it SCALE-encoded, and an apk client identifies an authority + /// set by a commitment to its keys where the others carry a merkle root. Only these three + /// values are wanted here, and every shape has them. + fn state_summary( + &self, + encoded: &[u8], + destination: StateMachine, + ) -> Result { + use alloy_sol_types::SolType; + + let summarise = |state: ConsensusState| StateSummary { + latest_beefy_height: state.latest_beefy_height, + current_set_id: state.current_authorities.id, + next_set_id: state.next_authorities.id, + }; + let summarise_apk = |state: beefy_verifier_primitives::ConsensusState| StateSummary { + latest_beefy_height: state.latest_beefy_height, + current_set_id: state.current_authorities.id, + next_set_id: state.next_authorities.id, + }; + + let apk = matches!(self.prover, Prover::Apk(_)); + match (matches!(destination, StateMachine::Evm(_)), apk) { + (true, true) => { + let state = + ::abi_decode( + encoded, + ) + .context("Could not abi-decode apk consensus state")?; + let state: beefy_verifier_primitives::ConsensusState = + state.try_into().map_err(|e| anyhow!("{e}"))?; + Ok(summarise_apk(state)) + }, + (true, false) => { + let state = ::abi_decode(encoded) + .context("Could not abi-decode consensus state")?; + Ok(summarise(state.into())) + }, + (false, true) => { + let state = beefy_verifier_primitives::ConsensusState::decode(&mut &encoded[..]) + .context("Could not decode apk consensus state")?; + Ok(summarise_apk(state)) + }, + (false, false) => { + let state = ConsensusState::decode(&mut &encoded[..]) + .context("Could not decode consensus state")?; + Ok(summarise(state)) + }, + } + } + /// Initialize the consensus state for the prover (used by the state storage backend), then /// returns it. pub async fn hydrate_initial_consensus_state( @@ -202,14 +264,13 @@ where .query_consensus_state(None, self.config.consensus_state_id) .await .context("Could not fetch consenus state")?; // somewhat fatal - let consensus_state = ConsensusState::decode(&mut &encoded[..]) - .expect("Infallible, consensus state was encoded correctly"); + let StateSummary { next_set_id, .. } = + self.state_summary(&encoded, counterparty_state_machine)?; // just some sanity checks - if set_id < consensus_state.next_authorities.id { + if set_id < next_set_id { tracing::error!( - target: crate::LOG_TARGET, "{counterparty_state_machine} got proof with set_id: {set_id} < next_set_id:{}", - consensus_state.next_authorities.id + target: crate::LOG_TARGET, "{counterparty_state_machine} got proof with set_id: {set_id} < next_set_id:{next_set_id}", ); self.backend .delete_message( @@ -222,10 +283,9 @@ where } // just some sanity checks - if set_id != consensus_state.next_authorities.id { + if set_id != next_set_id { tracing::error!( - target: crate::LOG_TARGET, "{counterparty_state_machine} consensus proof with set_id: {set_id} does not match next_set_id: {}", - consensus_state.next_authorities.id + target: crate::LOG_TARGET, "{counterparty_state_machine} consensus proof with set_id: {set_id} does not match next_set_id: {next_set_id}", ); // try to pull something else continue; @@ -283,14 +343,13 @@ where let encoded = counterparty .query_consensus_state(None, self.config.consensus_state_id) .await?; // somewhat fatal - let consensus_state = ConsensusState::decode(&mut &encoded[..]) - .expect("Infallible, consensus state was encoded correctly"); + let StateSummary { latest_beefy_height, current_set_id, next_set_id } = + self.state_summary(&encoded, counterparty_state_machine)?; // check if the update is relevant to us. - if consensus_state.latest_beefy_height >= finalized_height { + if latest_beefy_height >= finalized_height { tracing::info!( - target: crate::LOG_TARGET, "{counterparty_state_machine} saw proof for stale height {finalized_height}, current: {}", - consensus_state.latest_beefy_height + target: crate::LOG_TARGET, "{counterparty_state_machine} saw proof for stale height {finalized_height}, current: {latest_beefy_height}", ); // delete the message and pull another one self.backend @@ -303,26 +362,20 @@ where continue; } - if set_id != consensus_state.current_authorities.id && - set_id != consensus_state.next_authorities.id - { + if set_id != current_set_id && set_id != next_set_id { tracing::info!( - target: crate::LOG_TARGET, "{counterparty_state_machine} saw proof for unknown set_id {set_id}, current: {}, next: {}", - consensus_state.current_authorities.id, - consensus_state.next_authorities.id, + target: crate::LOG_TARGET, "{counterparty_state_machine} saw proof for unknown set_id {set_id}, current: {current_set_id}, next: {next_set_id}", ); - if set_id > consensus_state.next_authorities.id { + if set_id > next_set_id { tracing::info!( - target: crate::LOG_TARGET, "{counterparty_state_machine} proof was for future set: {set_id}, next: {}", - consensus_state.next_authorities.id, + target: crate::LOG_TARGET, "{counterparty_state_machine} proof was for future set: {set_id}, next: {next_set_id}", ); // break so that we can process a mandatory update break; - } else if set_id < consensus_state.current_authorities.id { + } else if set_id < current_set_id { tracing::info!( - target: crate::LOG_TARGET, "{counterparty_state_machine} proof was for older set: {set_id}, current: {}", - consensus_state.current_authorities.id, + target: crate::LOG_TARGET, "{counterparty_state_machine} proof was for older set: {set_id}, current: {current_set_id}", ); self.backend .delete_message( @@ -364,11 +417,44 @@ where &self, ) -> Result, anyhow::Error> { use alloy_sol_types::SolValue; - let consensus_state: BeefyConsensusState = - self.prover.query_initial_consensus_state(None).await?.inner.into(); + let prover_state = self.prover.query_initial_consensus_state(None).await?; + + // An apk client is seeded with a commitment to the signing set's keys instead of a merkle + // root, and the set that signs the first update has to be given one by hand, since every + // later commitment arrives in a header digest. The next set's is left empty for that + // reason. + let consensus_state = match self.prover { + Prover::Apk(ref apk) => { + let at = apk + .inner + .relay_rpc + .chain_get_block_hash(Some(prover_state.inner.latest_beefy_height.into())) + .await? + .ok_or_else(|| anyhow!("No block hash for the initial beefy height"))?; + let commitment = apk.current_apk_commitment(at).await?; + + let inner = prover_state.inner.clone(); + let authority_set = |set: beefy_verifier_primitives::AuthoritySet, + bls_poseidon_hash: H256| { + beefy_verifier_primitives::AuthoritySet { bls_poseidon_hash, ..set } + }; + let state = beefy_verifier_primitives::ConsensusState { + latest_beefy_height: inner.latest_beefy_height, + beefy_activation_block: inner.beefy_activation_block, + mmr_root_hash: inner.mmr_root_hash, + current_authorities: authority_set(inner.current_authorities, H256(commitment)), + next_authorities: authority_set(inner.next_authorities, H256::zero()), + }; + ismp_abi::bls_beefy::BlsBeefy::BeefyConsensusState::from(state).abi_encode() + }, + _ => { + let state: BeefyConsensusState = prover_state.inner.into(); + state.abi_encode() + }, + }; Ok(Some(CreateConsensusState { - consensus_state: consensus_state.abi_encode(), + consensus_state, consensus_client_id: *b"BEEF", consensus_state_id: self.config.consensus_state_id, unbonding_period: 60 * 60 * 60 * 27, diff --git a/tesseract/consensus/beefy/src/lib.rs b/tesseract/consensus/beefy/src/lib.rs index 7f6a71465..6f8d9aeb2 100644 --- a/tesseract/consensus/beefy/src/lib.rs +++ b/tesseract/consensus/beefy/src/lib.rs @@ -12,6 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Both in process provers carry a go runtime, sp1's through gnark's ffi and ours through the apk +// circuit. Two of those in one process corrupt each other's allocator and the binary dies on the +// first call into either, so the combination is refused here rather than at runtime. Prove sp1 on +// a cluster to compile the apk circuit in, or leave the apk circuit in its own process. +#[cfg(all(feature = "local", feature = "sp1-local"))] +compile_error!( + "`local` compiles the apk circuit into this binary, which cannot be done alongside `sp1-local`. \ + Build with `--no-default-features --features sp1-cluster,local`, or drop `local` and let the \ + apk prover run as its own process." +); + /// Log/tracing target for this crate. pub const LOG_TARGET: &str = "consensus-beefy"; @@ -57,7 +68,7 @@ impl BeefyConfig { /// [`BeefyProverConfig::backend`](prover::BeefyProverConfig::backend). pub async fn into_client( self, - ) -> Result, anyhow::Error> + ) -> Result, anyhow::Error> where R: subxt::Config + Send + Sync + Clone, P: subxt::Config> + Send + Sync + Clone, @@ -74,7 +85,7 @@ impl BeefyConfig { .map_err(|_| anyhow!("beefy submission signer account must be 32 bytes"))? .into(); let prover = - Prover::::new(self.prover.clone(), account).await?; + Prover::::new(self.prover.clone(), account).await?; let backend: Arc = match self.prover_config.backend.clone() { backend::ProofBackendConfig::Redis { config } => { @@ -98,7 +109,7 @@ impl BeefyConfig { }, }; - BeefyHost::::new( + BeefyHost::::new( self.host, prover, client, backend, ) .await diff --git a/tesseract/consensus/beefy/src/prover.rs b/tesseract/consensus/beefy/src/prover.rs index 3a7446651..2afab4840 100644 --- a/tesseract/consensus/beefy/src/prover.rs +++ b/tesseract/consensus/beefy/src/prover.rs @@ -44,7 +44,7 @@ use beefy_prover::{ relay::{fetch_latest_beefy_justification, parachain_header_storage_key}, BEEFY_VALIDATOR_SET_ID, }; -use beefy_verifier_primitives::ConsensusState; +use beefy_verifier_primitives::{ApkCommitmentDigest, ConsensusState}; use ismp::{ consensus::ConsensusStateId, events::Event, host::StateMachine, messaging::ConsensusMessage, }; @@ -105,6 +105,10 @@ pub enum ProofVariant { /// Delegate signature verification to an SP1 zero-knowledge proof (SP1Beefy). #[serde(alias = "zk")] Sp1, + /// Prove the signers with an aggregate public key proof (BlsApkBeefy). Only for a relay whose + /// BEEFY authorities hold paired `ecdsa_bls_crypto` keys. + #[serde(alias = "bls")] + Apk, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -122,6 +126,14 @@ pub struct ProverConfig { pub max_rpc_payload_size: Option, /// Query batch size for mmr leaves pub query_batch_size: Option, + /// Where the circuit's structured reference string lives. Left unset it falls back to the + /// prover's own default. + #[serde(default)] + pub apk_srs_dir: Option, + /// Where sp1 proving happens when this build proves on a cluster. Ignored by a build that + /// proves sp1 locally, and required by one that does not. + #[serde(default)] + pub sp1_cluster: Option, } /// The BEEFY prover produces BEEFY consensus proofs using either the naive or zk variety. Consensus @@ -154,6 +166,9 @@ pub const PROOF_TYPE_ECDSA: u8 = 0x00; /// Proof type identifier for ZK proofs (SP1Beefy) pub const PROOF_TYPE_SP1: u8 = 0x01; +/// Proof type identifier for aggregate public key proofs (BlsApkBeefy) +pub const PROOF_TYPE_APK: u8 = 0x02; + impl BeefyProver where R: subxt::Config + Send + Sync + Clone, @@ -200,6 +215,10 @@ where let message = zk.consensus_proof(signed_commitment, consensus_state).await?; [&[PROOF_TYPE_SP1], message.abi_encode_params().as_slice()].concat() }, + Prover::Apk(ref apk) => { + let message = apk.consensus_proof(signed_commitment, consensus_state).await?; + [&[PROOF_TYPE_APK], message.abi_encode_params().as_slice()].concat() + }, }; Ok(encoded) @@ -323,6 +342,60 @@ where Ok((block_hash_and_set_id, header)) } + /// Performs a linear search for the BEEFY justification which finalizes the given epoch + /// boundary + /// The earliest justification the client can verify whose parachain header names `set_id`. + /// + /// Position in the session is not enough to tell. The digest naming a set only appears once + /// the relay has queued it and the parachain has published it, so blocks early in a session + /// still name the set before. Taking the earliest one that does name it, rather than the last + /// block of the session, leaves the rest of the session provable, which is what a proof for + /// new messages needs. + /// + /// `trusted` is the client's own state, and it bounds the walk: only the two sets it holds can + /// sign anything it will accept. + pub async fn teaching_justification( + &self, + from: u64, + until: u64, + set_id: u64, + para_id: u32, + trusted: &ConsensusState, + ) -> anyhow::Result>> { + let relay_rpc = self.prover.inner().relay_rpc.clone(); + let mut cursor = from; + while cursor < until { + let Some(commitment) = self.epoch_justification_for(cursor).await? else { + return Ok(None); + }; + let number: u64 = commitment.commitment.block_number.into(); + if number >= until { + return Ok(None); + } + cursor = number + 1; + + // Past the sets the client holds is past what it can verify, and every later + // justification is signed by a set further ahead still. Stopping here reports a client + // that has fallen behind its own window as such, rather than handing the proof builder + // a set it will refuse. + let signer = commitment.commitment.validator_set_id; + if signer != trusted.current_authorities.id && signer != trusted.next_authorities.id { + return Ok(None); + } + + let Some(hash) = relay_rpc.chain_get_block_hash(Some(number.into())).await? else { + continue; + }; + let header = query_parachain_header(&relay_rpc, hash, para_id).await?; + if ApkCommitmentDigest::find_in(&header.digest).map(|digest| digest.set_id) == + Some(set_id) + { + return Ok(Some(commitment)); + } + } + Ok(None) + } + /// Performs a linear search for the BEEFY justification which finalizes the given epoch /// boundary pub async fn epoch_justification_for( @@ -414,6 +487,184 @@ where commitment.commitment ); + // An apk client checks a proof against a commitment to the signing set's + // keys, and the justification that rotates into a set is signed by that + // same set, so a set whose commitment is still unknown can never be + // rotated into directly. The commitment reaches the client through a + // digest in a parachain header, and the header this rotation carries sits + // on the session boundary, where the relay's next set is only just being + // queued. Proving finality one block earlier carries a header from the end + // of the session instead, which does name the incoming set, so the client + // holds its commitment by the time the rotation below is proven. + if matches!(self.prover, Prover::Apk(_)) && + consensus_state.inner.next_authorities.bls_poseidon_hash.is_zero() + { + let epoch_change_number: u64 = epoch_change_header.number().into(); + let from = u64::from(consensus_state.inner.latest_beefy_height) + 1; + let Some(commitment) = self + .teaching_justification( + from, + epoch_change_number, + next_set_id, + para_id, + &consensus_state.inner, + ) + .await? + else { + // Either the parachain has not published the commitment yet, in + // which case a later tick picks it up, or the client has consumed + // the session and never will. Proving anyway would spend minutes + // on something the client rejects as stale, and would do it again + // every tick, so nothing is built here. + tracing::warn!( + target: crate::LOG_TARGET, + "No block in {from}..{epoch_change_number} names the commitment for {next_set_id}, the client is on {}", + consensus_state.inner.current_authorities.id, + ); + return Ok(()); + }; + + let consensus_proof = self + .consensus_proof( + commitment.clone(), + consensus_state.inner.clone(), + ) + .await?; + let message = ConsensusProof { + finalized_height: commitment.commitment.block_number, + set_id: consensus_state.inner.current_authorities.id, + message: ConsensusMessage { + consensus_proof, + consensus_state_id: self.config.consensus_state_id, + signer: H256::random().encode(), + }, + }; + + tracing::info!( + target: crate::LOG_TARGET, + "Proving finality at {} so the client learns the commitment for {next_set_id}", + commitment.commitment.block_number, + ); + let destinations: Vec = + self.config.state_machines.clone(); + self.backend.send_messages_proof(&destinations, message).await?; + + consensus_state.inner.latest_beefy_height = + commitment.commitment.block_number; + self.backend.save_state(&consensus_state).await?; + return Ok(()); + } + + // The rotation arrives through a digest too. A set is only entered when a + // header names the set after it, so the boundary justification cannot + // carry one: its header still names the set being entered, which the + // client already holds. The first justification inside the new session is + // the one that names the set after, and it is signed by the incoming set, + // whose commitment the client learned above. Proving that block rotates + // the client and teaches the following commitment at once, which is why + // steady state costs one proof a session rather than two. + if matches!(self.prover, Prover::Apk(_)) { + let from = u64::from(consensus_state.inner.latest_beefy_height) + 1; + let latest = u64::from(*latest_beefy_header.number()); + let following = next_set_id.saturating_add(1); + let Some(commitment) = self + .teaching_justification( + from, + latest + 1, + following, + para_id, + &consensus_state.inner, + ) + .await? + else { + // Either the parachain has not published the commitment yet, since + // it takes a block or two into the session that queues it, or the + // relay has left the only session this client could still verify, + // in which case it stays here until it is re-seeded. + tracing::info!( + target: crate::LOG_TARGET, + "No justification signed by {} or {} in {from}..{latest} names {following}", + consensus_state.inner.current_authorities.id, + next_set_id, + ); + return Ok(()); + }; + + let finalized_hash = relay_rpc + .chain_get_block_hash(Some( + commitment.commitment.block_number.into(), + )) + .await? + .ok_or_else(|| { + anyhow!( + "no block at {} on the relay", + commitment.commitment.block_number + ) + })?; + let para_header = + query_parachain_header(&relay_rpc, finalized_hash, para_id).await?; + let digest = ApkCommitmentDigest::find_in(¶_header.digest) + .ok_or_else(|| { + anyhow!( + "parachain header at {} lost its apk digest", + commitment.commitment.block_number + ) + })?; + + let consensus_proof = self + .consensus_proof( + commitment.clone(), + consensus_state.inner.clone(), + ) + .await?; + let message = ConsensusProof { + finalized_height: commitment.commitment.block_number, + set_id: next_set_id, + message: ConsensusMessage { + consensus_proof, + consensus_state_id: self.config.consensus_state_id, + signer: H256::random().encode(), + }, + }; + + tracing::info!( + target: crate::LOG_TARGET, + "Proving finality at {} to rotate into {next_set_id}", + commitment.commitment.block_number, + ); + let destinations: Vec = + self.config.state_machines.clone(); + self.backend.send_mandatory_proof(&destinations, message).await?; + + // Mirror what the digest does to the client: the set being entered + // becomes current, and the set the digest names becomes next, already + // carrying the commitment the same header supplied. + let incoming = beefy_prover::relay::beefy_mmr_leaf_next_authorities( + &self.prover.inner().relay_rpc, + Some(finalized_hash), + ) + .await?; + consensus_state.finalized_parachain_height = para_header.number.into(); + consensus_state.inner.latest_beefy_height = + commitment.commitment.block_number; + consensus_state.inner.current_authorities = + consensus_state.inner.next_authorities.clone(); + consensus_state.inner.next_authorities = + beefy_verifier_primitives::AuthoritySet { + id: digest.set_id, + len: digest.len, + bls_poseidon_hash: H256(digest.commitment), + ecdsa_merkle_root: incoming.keyset_commitment, + }; + tracing::info!( + target: crate::LOG_TARGET, "Rotated authority set. Current {}, Next: {}", + consensus_state.inner.current_authorities.id, + consensus_state.inner.next_authorities.id, + ); + self.backend.save_state(&consensus_state).await?; + return Ok(()); + } + let consensus_proof = self .consensus_proof(commitment.clone(), consensus_state.inner.clone()) .await?; @@ -452,12 +703,20 @@ where commitment.commitment.block_number; consensus_state.inner.current_authorities = consensus_state.inner.next_authorities.clone(); + let incoming = beefy_prover::relay::beefy_mmr_leaf_next_authorities( + &self.prover.inner().relay_rpc, + Some(epoch_change_block_hash), + ) + .await?; + // The relay names the ecdsa root; the poseidon hash reaches a client + // through a header digest, so it starts empty here. consensus_state.inner.next_authorities = - beefy_prover::relay::beefy_mmr_leaf_next_authorities( - &self.prover.inner().relay_rpc, - Some(epoch_change_block_hash), - ) - .await?; + beefy_verifier_primitives::AuthoritySet { + id: incoming.id, + len: incoming.len, + bls_poseidon_hash: H256::zero(), + ecdsa_merkle_root: incoming.keyset_commitment, + }; tracing::info!( target: crate::LOG_TARGET, "Rotated authority set. Current {}, Next: {}", consensus_state.inner.current_authorities.id, @@ -578,12 +837,14 @@ where } } -/// Beefy prover, can produce ECDSA or SP1 proofs +/// Beefy prover, can produce ECDSA, SP1 or aggregate public key proofs pub enum Prover { /// ECDSA prover — verifies all 2/3+1 signatures on-chain Ecdsa(beefy_prover::Prover, PhantomData), /// SP1 prover — delegates signature verification to an SP1 ZK program Sp1(zk_beefy::Prover), + /// APK prover — proves the signers against a commitment to the authority set + Apk(apk_beefy::Prover), } impl Clone for Prover @@ -598,12 +859,13 @@ where match self { Prover::Ecdsa(p, _) => Prover::Ecdsa(p.clone(), PhantomData), Prover::Sp1(p) => Prover::Sp1(p.clone()), + Prover::Apk(p) => Prover::Apk(p.clone()), } } } // Implementation for LocalProver -impl Prover +impl Prover where R: subxt::Config, P: subxt::Config, @@ -652,10 +914,32 @@ where let prover = match config.proof_variant { ProofVariant::Sp1 => { - let sp1_prover = zk_beefy::LocalProver::new().await?; + let sp1_prover = zk_beefy::default_prover(config.sp1_cluster.clone()).await?; Prover::Sp1(zk_beefy::Prover::new(prover, sp1_prover, account)) }, ProofVariant::Ecdsa => Prover::Ecdsa(prover, PhantomData), + ProofVariant::Apk => { + // The circuit is only ever proven in process, and it brings a go runtime that + // cannot share one with the runtime sp1's gnark ffi links, so a build proving sp1 + // locally has no way to produce an apk proof: see the guard in this crate's + // lib.rs. + #[cfg(not(feature = "local"))] + return Err(anyhow!( + "the apk variant proves through the circuit compiled into this binary, build \ + with `--no-default-features --features sp1-cluster,local`" + )); + + #[cfg(feature = "local")] + { + // Setup takes a couple of minutes and saturates every core, so it stays off + // the runtime's workers. + let srs_dir = config.apk_srs_dir.clone(); + let local = std::thread::spawn(move || apk_beefy::LocalProver::new(srs_dir)) + .join() + .map_err(|_| anyhow!("apk circuit setup panicked"))??; + Prover::Apk(apk_beefy::Prover::new(prover, Arc::new(local))) + } + }, }; Ok(prover) @@ -674,6 +958,7 @@ where match self { Prover::Sp1(ref p) => &p.inner, Prover::Ecdsa(ref p, _) => p, + Prover::Apk(ref p) => &p.inner, } } @@ -713,8 +998,20 @@ where mmr_root_hash, beefy_activation_block: inner.beefy_activation_block, latest_beefy_height: signed_commitment.commitment.block_number, - current_authorities: current_authority_set.clone(), - next_authorities: next_authority_set.clone(), + // The relay names only the ecdsa root. A client picks the poseidon hash up from a + // hyperbridge header digest, so it starts empty here. + current_authorities: beefy_verifier_primitives::AuthoritySet { + id: current_authority_set.id, + len: current_authority_set.len, + bls_poseidon_hash: H256::zero(), + ecdsa_merkle_root: current_authority_set.keyset_commitment, + }, + next_authorities: beefy_verifier_primitives::AuthoritySet { + id: next_authority_set.id, + len: next_authority_set.len, + bls_poseidon_hash: H256::zero(), + ecdsa_merkle_root: next_authority_set.keyset_commitment, + }, }; Ok(ProverConsensusState { diff --git a/tesseract/consensus/beefy/tests/apk_messaging.rs b/tesseract/consensus/beefy/tests/apk_messaging.rs new file mode 100644 index 000000000..8286859ad --- /dev/null +++ b/tesseract/consensus/beefy/tests/apk_messaging.rs @@ -0,0 +1,266 @@ +// Copyright (C) Polytope Labs Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +//! Checks the pallet accepts an apk proof whose only contribution is new messages. +//! +//! Rotating and teaching are both proven by watching the prover run, but neither of them is what +//! the bridge is for. Messaging is, and it is the one thing the prover's own loop will not reach on +//! a test relay: a session there lasts about as long as a proof takes, so there is always a +//! rotation waiting and the loop takes that path first. The test does what the loop cannot and +//! picks the block itself. +//! +//! The pallet turns away a proof that rotates nothing, finalizes nothing new and teaches nothing, +//! so acceptance has to come through one of the three. Picking a justification from the set the +//! client already trusts rules out rotation, and the state afterwards shows whether a commitment +//! was learned. If it was not, and the proof was accepted anyway, messages are the only thing left +//! it can have been accepted for, which pins the branch without reaching inside the pallet. +//! +//! Needs the relay, its parachain, and ismp traffic on the parachain so there is something to +//! prove. Takes minutes, since it compiles the circuit and generates a real SNARK. +//! +//! RELAY_WS_URL=ws://127.0.0.1:9979 PARA_WS_URL=ws://127.0.0.1:9981 PARA_ID=4009 \ +//! PARA_SIGNER=0xe5be9a50... \ +//! cargo test -p tesseract-beefy --no-default-features --features sp1-cluster,local \ +//! --test apk_messaging -- --ignored --nocapture + +use std::sync::Arc; + +use anyhow::anyhow; +use codec::Decode; +use ismp::{consensus::StateMachineId, host::StateMachine, messaging::ConsensusMessage}; +use polkadot_sdk::sp_consensus_beefy::{ + ecdsa_crypto::Signature, SignedCommitment, VersionedFinalityProof, +}; +use primitive_types::H256; +use subxt::{backend::legacy::LegacyRpcMethods, config::Header as _}; +use tesseract_beefy::{ + backend::{ConsensusProof, OnchainBackend, ProofBackend}, + prover::{ + query_parachain_header, BeefyProver, BeefyProverConfig, ProofVariant, Prover, ProverConfig, + }, +}; +use tesseract_substrate::{ + config::{Blake2SubstrateChain, KeccakSubstrateChain}, + SubstrateClient, SubstrateConfig, +}; + +/// How far back to look for a justification the client can still verify. +const SEARCH_WINDOW: u64 = 2400; + +fn env(name: &str) -> String { + std::env::var(name).unwrap_or_else(|_| panic!("{name} must be set")) +} + +/// The first BEEFY justification at or above `from`, searched over `window` blocks. +/// +/// The prover has its own version of this, but it belongs to a type whose construction compiles +/// the circuit, and the point here is to find out whether there is anything worth proving before +/// paying for that. +async fn justification_at_or_above( + rpc: &LegacyRpcMethods, + from: u64, + window: u64, +) -> Result>, anyhow::Error> { + for number in from..from + window { + let Some(hash) = rpc.chain_get_block_hash(Some(number.into())).await? else { + continue; + }; + let Some(justifications) = rpc + .chain_get_block(Some(hash)) + .await? + .ok_or_else(|| anyhow!("failed to find block for {hash:?}"))? + .justifications + else { + continue; + }; + if let Some(found) = justifications + .into_iter() + .find(|(id, _)| id == b"BEEF") + .map(|(_, encoded)| VersionedFinalityProof::::decode(&mut &*encoded)) + .transpose()? + .map(|VersionedFinalityProof::V1(commitment)| commitment) + { + return Ok(Some(found)); + } + } + Ok(None) +} + +#[tokio::test(flavor = "multi_thread")] +#[ignore = "needs a live BLS relay, its parachain, the prover binary and ismp traffic"] +async fn a_messaging_proof_is_accepted_as_new_work() -> Result<(), anyhow::Error> { + let relay_ws = env("RELAY_WS_URL"); + let para_ws = env("PARA_WS_URL"); + let para_id: u32 = env("PARA_ID").parse().expect("para id is a number"); + let signer = env("PARA_SIGNER"); + + let substrate = SubstrateClient::::new( + SubstrateConfig { + state_machine: Some(StateMachine::Kusama(para_id)), + hashing: None, + consensus_state_id: None, + rpc_ws: para_ws.clone(), + max_rpc_payload_size: None, + signer: Some(signer), + initial_height: None, + max_concurent_queries: None, + poll_interval: None, + fee_token_decimals: None, + } + .resolve() + .await?, + ) + .await?; + + let state_machine_id = + StateMachineId { state_id: StateMachine::Kusama(para_id), consensus_state_id: *b"PAS0" }; + let backend: Arc = Arc::new(OnchainBackend::::new( + substrate.client.clone(), + substrate.rpc_client.clone(), + substrate.signer.clone(), + state_machine_id, + )); + + let before = backend.load_state().await?; + let trusted_set = before.inner.current_authorities.id; + + // The client can only check a signature from the set it currently trusts, and it rejects a + // justification it has already seen, so the block has to sit above its height and below the + // end of its session. It also has to finalize a parachain head the client has not reached, + // which is checked here rather than after spending minutes on a proof the pallet will refuse. + let (_, relay_rpc_client) = + subxt_utils::client::ws_client::(&relay_ws, 15 * 1024 * 1024).await?; + let relay_rpc = LegacyRpcMethods::::new(relay_rpc_client); + + // Search upward rather than down. The earliest usable block is the one whose parachain header + // still names the set the client just rotated into, so it teaches nothing and the acceptance + // can only be about its messages. Blocks later in the session start naming the set after, + // which the client does not know yet, and would be accepted for teaching that instead. + let mut cursor = u64::from(before.inner.latest_beefy_height) + 1; + let commitment = loop { + let Some(candidate) = justification_at_or_above(&relay_rpc, cursor, SEARCH_WINDOW).await? + else { + return Err(anyhow!("no justification above {cursor} on the relay")); + }; + let number = candidate.commitment.block_number; + cursor = u64::from(number) + 1; + + if candidate.commitment.validator_set_id != trusted_set { + return Err(anyhow!( + "the client at {} has consumed everything set {trusted_set} signed, so there is \ + nothing left it can verify. Let the prover run it forward and try again.", + before.inner.latest_beefy_height, + )); + } + let hash = relay_rpc + .chain_get_block_hash(Some(number.into())) + .await? + .ok_or_else(|| anyhow!("relay block {number} vanished"))?; + let para_head: u64 = query_parachain_header(&relay_rpc, hash, para_id).await?.number.into(); + if para_head > before.finalized_parachain_height { + break candidate; + } + // Seeding a client commits it to the parachain head of the block the seeding ran in, and + // the relay's view of the parachain trails that, so right after a seed there is nothing to + // prove until the relay catches up. + println!( + "beefy block {number} only finalizes parachain {para_head}, at or below the client's {}", + before.finalized_parachain_height, + ); + }; + + println!( + "proving beefy block {} for set {trusted_set} against trusted height {}, this takes minutes", + commitment.commitment.block_number, before.inner.latest_beefy_height, + ); + + // The prover is built last. Starting it compiles the circuit, minutes of work, and there is no + // sense paying that before knowing there is a block worth proving. + let prover_config = ProverConfig { + relay_rpc_ws: relay_ws.clone(), + para_rpc_ws: para_ws.clone(), + para_ids: vec![para_id], + proof_variant: ProofVariant::Apk, + max_rpc_payload_size: None, + query_batch_size: None, + apk_srs_dir: None, + sp1_cluster: None, + }; + let prover: Prover = + Prover::new(prover_config, Default::default()).await?; + let beefy = BeefyProver::< + Blake2SubstrateChain, + KeccakSubstrateChain, + zk_beefy::DefaultProver, + dyn ProofBackend, + >::new( + BeefyProverConfig { + consensus_state_id: *b"PAS0", + minimum_finalization_height: 0, + state_machines: vec![StateMachine::Evm(97)], + backend: Default::default(), + }, + substrate, + prover, + backend.clone(), + ) + .await?; + let consensus_proof = beefy.consensus_proof(commitment.clone(), before.inner.clone()).await?; + + // The same call the prover's loop makes, so the test exercises submission rather than + // imitating it. + backend + .send_messages_proof( + &[StateMachine::Evm(97)], + ConsensusProof { + finalized_height: commitment.commitment.block_number, + set_id: trusted_set, + message: ConsensusMessage { + consensus_proof, + consensus_state_id: *b"PAS0", + signer: H256::random().as_bytes().to_vec(), + }, + }, + ) + .await?; + + let after = backend.load_state().await?; + assert_eq!( + after.inner.current_authorities.id, trusted_set, + "the proof rotated the authority set, so it was not accepted for its messages", + ); + assert_eq!( + after.inner.next_authorities.bls_poseidon_hash, + before.inner.next_authorities.bls_poseidon_hash, + "the proof taught a commitment, so that is what it could have been accepted for", + ); + assert!( + after.inner.latest_beefy_height > before.inner.latest_beefy_height, + "the client did not move, so the proof was not applied", + ); + assert!( + after.finalized_parachain_height > before.finalized_parachain_height, + "no new parachain height was finalized, which is the whole point of a messaging proof", + ); + + println!( + "messaging proof accepted on set {trusted_set}: beefy {} -> {}, parachain {} -> {}", + before.inner.latest_beefy_height, + after.inner.latest_beefy_height, + before.finalized_parachain_height, + after.finalized_parachain_height, + ); + Ok(()) +} diff --git a/tesseract/consensus/beefy/tests/mainnet_rotation.rs b/tesseract/consensus/beefy/tests/mainnet_rotation.rs index 902fdef39..b58ebdde2 100644 --- a/tesseract/consensus/beefy/tests/mainnet_rotation.rs +++ b/tesseract/consensus/beefy/tests/mainnet_rotation.rs @@ -31,12 +31,16 @@ use alloy::{ use alloy_sol_types::SolValue; use anyhow::{anyhow, Context}; use beefy_prover::relay::fetch_latest_beefy_justification; -use ismp_abi::{ecdsa_beefy::BeefyConsensusState, evm_host::EvmHost, handler::handler_v2::HandlerV2}; +use ismp_abi::{ + ecdsa_beefy::BeefyConsensusState, evm_host::EvmHost, handler::handler_v2::HandlerV2, +}; use sp_consensus_beefy::{ecdsa_crypto::Signature, SignedCommitment}; use subxt::{backend::legacy::LegacyRpcMethods, config::Header as _}; use tesseract_beefy::{ backend::{InMemoryProofBackend, ProofBackend}, - prover::{BeefyProver, BeefyProverConfig, Prover, ProverConfig, ProverConsensusState, ProofVariant}, + prover::{ + BeefyProver, BeefyProverConfig, ProofVariant, Prover, ProverConfig, ProverConsensusState, + }, ConsensusState, }; use tesseract_substrate::{ @@ -91,10 +95,14 @@ async fn submit_via_sequencer( ) -> anyhow::Result { // `DynProvider` erases the filler layer, so populate the tx fields explicitly against the // read RPC, sign locally, and push the raw signed tx to the sequencer. - let calldata = - HandlerV2::new(HANDLER, read.clone()).handleConsensus(HOST, proof).calldata().clone(); - let base = - TransactionRequest::default().with_from(from).with_to(HANDLER).with_input(calldata); + let calldata = HandlerV2::new(HANDLER, read.clone()) + .handleConsensus(HOST, proof) + .calldata() + .clone(); + let base = TransactionRequest::default() + .with_from(from) + .with_to(HANDLER) + .with_input(calldata); let chain_id = read.get_chain_id().await.context("get_chain_id")?; let nonce = read.get_transaction_count(from).await.context("get_transaction_count")?; let gas = read.estimate_gas(base.clone()).await.context("estimate_gas")?; @@ -140,7 +148,8 @@ async fn rotate_authorities_across_all_chains() -> anyhow::Result<()> { let from = signer.address(); let wallet = EthereumWallet::from(signer); - let mut chains: Vec<(String, DynProvider, Option)> = Vec::with_capacity(CHAINS.len()); + let mut chains: Vec<(String, DynProvider, Option)> = + Vec::with_capacity(CHAINS.len()); println!("EVM RPC endpoints:"); for (id, name, rpc_env, submit_url) in CHAINS { let url = std::env::var(rpc_env) @@ -181,7 +190,10 @@ async fn rotate_authorities_across_all_chains() -> anyhow::Result<()> { " {name}: height={} current_set={} next_set={}", s.latest_beefy_height, s.current_authorities.id, s.next_authorities.id, ); - if best.as_ref().map_or(true, |(_, b)| s.latest_beefy_height < b.latest_beefy_height) { + if best + .as_ref() + .map_or(true, |(_, b)| s.latest_beefy_height < b.latest_beefy_height) + { best = Some((name.clone(), s)); } } @@ -204,10 +216,12 @@ async fn rotate_authorities_across_all_chains() -> anyhow::Result<()> { proof_variant: ProofVariant::Ecdsa, max_rpc_payload_size: None, query_batch_size: None, + apk_srs_dir: None, + sp1_cluster: None, }; // ECDSA proof submitted to the EVM handler, which does not enforce the SP1 committed- // nonce binding, so a zero account is fine here. - let prover: Prover = + let prover: Prover = Prover::new(prover_config, Default::default()).await?; let substrate = SubstrateClient::::new( @@ -229,10 +243,11 @@ async fn rotate_authorities_across_all_chains() -> anyhow::Result<()> { .await?; // Seed the in-memory backend with the genesis state read from the chains. - let backend: Arc = Arc::new(InMemoryProofBackend::new(ProverConsensusState { - inner: genesis.clone(), - finalized_parachain_height: 0, - })); + let backend: Arc = + Arc::new(InMemoryProofBackend::new(ProverConsensusState { + inner: genesis.clone(), + finalized_parachain_height: 0, + })); let beefy_config = BeefyProverConfig { consensus_state_id: *b"DOT0", @@ -241,12 +256,12 @@ async fn rotate_authorities_across_all_chains() -> anyhow::Result<()> { backend: Default::default(), }; - let beefy = BeefyProver::::new( - beefy_config, - substrate, - prover, - backend, - ) + let beefy = BeefyProver::< + Blake2SubstrateChain, + KeccakSubstrateChain, + zk_beefy::DefaultProver, + dyn ProofBackend, + >::new(beefy_config, substrate, prover, backend) .await?; // A second relay connection for the auxiliary queries the helpers don't expose (resolving @@ -305,15 +320,17 @@ async fn rotate_authorities_across_all_chains() -> anyhow::Result<()> { .chain_get_header(Some(epoch_hash)) .await? .ok_or_else(|| anyhow!("epoch-change header missing"))?; - beefy - .epoch_justification_for(epoch_header.number().into()) - .await? - .ok_or_else(|| anyhow!("no BEEFY justification found for epoch {next_set_id}"))? + beefy.epoch_justification_for(epoch_header.number().into()).await?.ok_or_else( + || anyhow!("no BEEFY justification found for epoch {next_set_id}"), + )? }, None => { // Sets are caught up. Do a final height advance to the live head, then stop. if live_header.number <= anchor.latest_beefy_height { - println!("\nActive chains caught up at height {} set {}", anchor.latest_beefy_height, anchor.current_authorities.id); + println!( + "\nActive chains caught up at height {} set {}", + anchor.latest_beefy_height, anchor.current_authorities.id + ); break; } let head = live_header.hash(); @@ -344,11 +361,17 @@ async fn rotate_authorities_across_all_chains() -> anyhow::Result<()> { // mark the chain skipped, and continue with the rest. Chains with a dedicated submit // endpoint (Arbitrum sequencer) take the raw-tx path; the rest use a normal send. let result: anyhow::Result = match submit { - Some(sequencer) => submit_via_sequencer(read, sequencer, &wallet, from, proof.clone()).await, + Some(sequencer) => + submit_via_sequencer(read, sequencer, &wallet, from, proof.clone()).await, None => { let handler = HandlerV2::new(HANDLER, read.clone()); async { - Ok(handler.handleConsensus(HOST, proof.clone()).send().await?.get_receipt().await?) + Ok(handler + .handleConsensus(HOST, proof.clone()) + .send() + .await? + .get_receipt() + .await?) } .await }, @@ -367,7 +390,10 @@ async fn rotate_authorities_across_all_chains() -> anyhow::Result<()> { advanced_any = true; }, Ok(receipt) => { - println!(" ✗ {name}: reverted (tx {:?}) — skipping this chain.", receipt.transaction_hash); + println!( + " ✗ {name}: reverted (tx {:?}) — skipping this chain.", + receipt.transaction_hash + ); failed.insert(name.clone()); }, Err(e) => { @@ -388,8 +414,11 @@ async fn rotate_authorities_across_all_chains() -> anyhow::Result<()> { } } - let advanced: Vec<&str> = - chains.iter().map(|(n, _, _)| n.as_str()).filter(|n| !failed.contains(*n)).collect(); + let advanced: Vec<&str> = chains + .iter() + .map(|(n, _, _)| n.as_str()) + .filter(|n| !failed.contains(*n)) + .collect(); println!("\nDone — {rotations} rotation(s)."); println!(" advanced: {advanced:?}"); if !failed.is_empty() { diff --git a/tesseract/consensus/beefy/zk/Cargo.toml b/tesseract/consensus/beefy/zk/Cargo.toml index 2d9df3f03..5a1ba9666 100644 --- a/tesseract/consensus/beefy/zk/Cargo.toml +++ b/tesseract/consensus/beefy/zk/Cargo.toml @@ -6,6 +6,7 @@ authors = ["Polytope Labs "] description = "SNARK Circuits for BEEFY consensus proofs written in noir" [dependencies] +serde = { workspace = true, features = ["derive"] } tracing = { workspace = true } tokio = { workspace = true, features = ["fs", "macros", "rt-multi-thread"] } rs_merkle = { workspace = true, default-features = true } @@ -27,6 +28,7 @@ hex-literal = "0.4.1" [dependencies.sp1-beefy] git = "https://github.com/polytope-labs/sp1-beefy" tag = "v1.1.0" +default-features = false [dependencies.sp1-beefy-primitives] git = "https://github.com/polytope-labs/sp1-beefy" diff --git a/tesseract/consensus/beefy/zk/src/lib.rs b/tesseract/consensus/beefy/zk/src/lib.rs index e74a0c6e1..028a14af7 100644 --- a/tesseract/consensus/beefy/zk/src/lib.rs +++ b/tesseract/consensus/beefy/zk/src/lib.rs @@ -27,6 +27,45 @@ pub use sp1_beefy::local::LocalProver; #[cfg(test)] mod tests; +/// Which sp1 backend this build proves with. +/// +/// Proving locally links gnark's go runtime into the binary. A process can only hold one of +/// those, and the apk circuit brings its own, so the two in process provers cannot be built +/// together: see the guard in `tesseract-beefy`. Proving on a cluster leaves the go runtime out +/// altogether, which is what makes room for the apk one. +#[cfg(feature = "local")] +pub type DefaultProver = LocalProver; + +/// See [`DefaultProver`]. +#[cfg(all(feature = "cluster", not(feature = "local")))] +pub type DefaultProver = ClusterProver; + +/// Where a cluster build sends its proofs. Unused when proving locally. +#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] +pub struct ClusterConfig { + /// The cluster's grpc api service. + pub rpc: String, + /// Redis, which the cluster reads its artifacts from. + pub redis: String, +} + +/// Builds whichever backend this build was compiled for. +/// +/// Keeping the two constructors behind one call means the callers stay free of feature flags, +/// since only the cluster backend needs to be told where to reach anything. +#[cfg(feature = "local")] +pub async fn default_prover(_cluster: Option) -> Result { + LocalProver::new().await +} + +/// See [`default_prover`]. +#[cfg(all(feature = "cluster", not(feature = "local")))] +pub async fn default_prover(cluster: Option) -> Result { + let cluster = cluster + .ok_or_else(|| anyhow!("`sp1_cluster` is required when sp1 proves on a cluster"))?; + ClusterProver::new(cluster.rpc, cluster.redis).await +} + /// Consensus prover for zk BEEFY. pub struct Prover { pub inner: beefy_prover::Prover, @@ -108,18 +147,18 @@ where let tree = MerkleTree::::from_leaves(&leaf_hashes); // Sanity check: the merkle root of the actual on-chain authorities must equal the - // `keyset_commitment` of the set selected by the commitment's `validator_set_id`. The - // guest verifies authority membership against `authority.keyset_commitment`, so if this + // ecdsa merkle root of the set selected by the commitment's `validator_set_id`. The + // guest verifies authority membership against `authority.ecdsa_merkle_root`, so if this // invariant is broken the proof would fail on-chain. A mismatch here means either the // validator-set selection is wrong or `hash_authority_addresses` has diverged from // `pallet-beefy-mmr`'s eth-address commitment (see `FAILED_BEEFY_TO_ETH_ADDRESS`). let computed_root = tree.root().ok_or_else(|| anyhow!("empty authority set"))?; - if computed_root != authority.keyset_commitment.0 { + if computed_root != authority.ecdsa_merkle_root.0 { Err(anyhow!( - "authority root mismatch for validator set {}: computed 0x{} != keyset_commitment 0x{}", + "authority root mismatch for validator set {}: computed 0x{} != ecdsa merkle root 0x{}", authority.id, hex::encode(computed_root), - hex::encode(authority.keyset_commitment.0), + hex::encode(authority.ecdsa_merkle_root.0), ))? } @@ -153,7 +192,7 @@ where authorities: AuthoritiesProof { len: authority.len, proof: authorities_witness, - root: authority.keyset_commitment.0.into(), + root: authority.ecdsa_merkle_root.0.into(), votes: message .mmr .signed_commitment diff --git a/tesseract/consensus/config/Cargo.toml b/tesseract/consensus/config/Cargo.toml index 802993aae..6fab26277 100644 --- a/tesseract/consensus/config/Cargo.toml +++ b/tesseract/consensus/config/Cargo.toml @@ -22,7 +22,7 @@ arb-host = { workspace = true } evm-host = { path = "../evm-host" } op-host = { workspace = true } pharos-primitives = { workspace = true, default-features = true } -tesseract-beefy = { path = "../beefy" } +tesseract-beefy = { path = "../beefy", default-features = false } tesseract-bsc = { workspace = true } tesseract-evm = { workspace = true } tesseract-grandpa = { workspace = true } @@ -34,8 +34,14 @@ tesseract-substrate = { workspace = true } tesseract-substrate-evm = { workspace = true } tesseract-sync-committee = { workspace = true } tesseract-tendermint = { workspace = true } -zk-beefy = { path = "../beefy/zk" } +zk-beefy = { path = "../beefy/zk", default-features = false } [dependencies.polkadot-sdk] workspace = true features = ["sp-runtime"] + +[features] +default = ["sp1-local"] +sp1-local = ["tesseract-beefy/sp1-local", "zk-beefy/local"] +sp1-cluster = ["tesseract-beefy/sp1-cluster", "zk-beefy/cluster"] +local = ["tesseract-beefy/local"] diff --git a/tesseract/consensus/config/src/lib.rs b/tesseract/consensus/config/src/lib.rs index 9643cfcd3..58c2a6bcf 100644 --- a/tesseract/consensus/config/src/lib.rs +++ b/tesseract/consensus/config/src/lib.rs @@ -112,7 +112,7 @@ pub enum AnyConfig { } pub enum AnyHost { - Beefy(BeefyHost), + Beefy(BeefyHost), Grandpa(GrandpaHost), } @@ -245,7 +245,7 @@ impl HyperbridgeHostConfig { .map_err(|_| anyhow!("beefy submission signer account must be 32 bytes"))? .into(); let prover_instance = - Prover::::new(prover.clone(), account).await?; + Prover::::new(prover.clone(), account).await?; let backend = Arc::new(tesseract_beefy::backend::RedisProofBackend::new(redis).await?); diff --git a/tesseract/consensus/relayer/Cargo.toml b/tesseract/consensus/relayer/Cargo.toml index 8800a40b1..7ff515ace 100644 --- a/tesseract/consensus/relayer/Cargo.toml +++ b/tesseract/consensus/relayer/Cargo.toml @@ -33,9 +33,9 @@ subxt = { workspace = true, default-features = false } sp-core = { workspace = true, features = ["full_crypto"] } subxt-utils = { workspace = true } -tesseract-beefy = { path = "../beefy" } +tesseract-beefy = { path = "../beefy", default-features = false } tesseract-consensus-config = { workspace = true } -zk-beefy = { path = "../beefy/zk" } +zk-beefy = { path = "../beefy/zk", default-features = false } tesseract-bsc = { workspace = true } tesseract-evm = { workspace = true } tesseract-sync-committee = { workspace = true } @@ -56,3 +56,9 @@ pharos-primitives = { workspace = true } [dependencies.polkadot-sdk] workspace = true features = ["sp-runtime", "sc-service"] + +[features] +default = ["sp1-local"] +sp1-local = ["tesseract-beefy/sp1-local", "zk-beefy/local"] +sp1-cluster = ["tesseract-beefy/sp1-cluster", "zk-beefy/cluster"] +local = ["tesseract-beefy/local"] diff --git a/tesseract/prover/Cargo.toml b/tesseract/prover/Cargo.toml index 1f0cb1e72..b1ac1b808 100644 --- a/tesseract/prover/Cargo.toml +++ b/tesseract/prover/Cargo.toml @@ -19,8 +19,15 @@ rustls = { version = "0.23.23", features = ["ring"] } primitive-types = { workspace = true } subxt-utils = { workspace = true } -tesseract-beefy = { workspace = true } +tesseract-beefy = { workspace = true, default-features = false } tesseract-substrate = { workspace = true } tesseract-primitives = { workspace = true } -zk-beefy = { path = "../consensus/beefy/zk" } +zk-beefy = { path = "../consensus/beefy/zk", default-features = false } + +[features] +default = ["sp1-local"] +sp1-local = ["tesseract-beefy/sp1-local", "zk-beefy/local"] +sp1-cluster = ["tesseract-beefy/sp1-cluster", "zk-beefy/cluster"] +# Compile the apk circuit into this binary rather than talking to a prover process. +local = ["tesseract-beefy/local"] diff --git a/tesseract/prover/src/main.rs b/tesseract/prover/src/main.rs index 537ff64bf..9b0a860a6 100644 --- a/tesseract/prover/src/main.rs +++ b/tesseract/prover/src/main.rs @@ -99,7 +99,7 @@ async fn main() -> Result<(), anyhow::Error> { BeefyProver::< Blake2SubstrateChain, KeccakSubstrateChain, - zk_beefy::LocalProver, + zk_beefy::DefaultProver, dyn tesseract_beefy::backend::ProofBackend, >::new(beefy_config, substrate, prover, backend) .await? diff --git a/tesseract/relayer/Cargo.toml b/tesseract/relayer/Cargo.toml index 98af803e4..476a1b60b 100644 --- a/tesseract/relayer/Cargo.toml +++ b/tesseract/relayer/Cargo.toml @@ -50,3 +50,9 @@ features = ["sc-service"] [dev-dependencies] tesseract-primitives = { workspace = true, features = ["testing"] } tempfile = "3.10" + +[features] +default = ["sp1-local"] +sp1-local = ["tesseract-beefy/sp1-local", "tesseract-consensus-config/sp1-local"] +sp1-cluster = ["tesseract-beefy/sp1-cluster", "tesseract-consensus-config/sp1-cluster"] +local = ["tesseract-beefy/local", "tesseract-consensus-config/local"]