diff --git a/Cargo.lock b/Cargo.lock index d514eeb45..3dc975bfa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -451,7 +451,6 @@ dependencies = [ "protobuf 2.10.2 (registry+https://github.com/rust-lang/crates.io-index)", "quickcheck 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", "ra-client 0.5.0", - "rustls 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", "secp256k1zkp 0.13.0 (git+https://github.com/crypto-com/rust-secp256k1-zkp.git?rev=f8759809f6e3fed793b37166f7cd91c57cdb2eab)", "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", @@ -478,14 +477,17 @@ dependencies = [ "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "fixed 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "mls 0.5.0", "parity-scale-codec 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "quickcheck 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ra-client 0.5.0", "secp256k1zkp 0.13.0 (git+https://github.com/crypto-com/rust-secp256k1-zkp.git?rev=f8759809f6e3fed793b37166f7cd91c57cdb2eab)", "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", "sgx_tstd 1.1.2 (git+https://github.com/apache/teaclave-sgx-sdk.git?rev=v1.1.2)", "sha2 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "test-common 0.5.0", "thiserror 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", "tiny-keccak 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -616,6 +618,7 @@ dependencies = [ "secstr 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "test-common 0.5.0", "tiny-bip39 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "vergen 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -719,6 +722,7 @@ dependencies = [ "secp256k1zkp 0.13.0 (git+https://github.com/crypto-com/rust-secp256k1-zkp.git?rev=f8759809f6e3fed793b37166f7cd91c57cdb2eab)", "secstr 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "tendermint 0.12.0-rc0 (git+https://github.com/crypto-com/tendermint-rs.git?rev=e8d350960726b242fdaa67d293d71ba8cfdb8024)", + "test-common 0.5.0", ] [[package]] @@ -756,6 +760,7 @@ dependencies = [ "secstr 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", + "test-common 0.5.0", "zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1069,6 +1074,7 @@ dependencies = [ "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "test-common 0.5.0", ] [[package]] @@ -2868,7 +2874,7 @@ version = "0.5.0" dependencies = [ "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "der-parser 3.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "ra-common 0.5.0", "rustls 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/chain-abci/Cargo.toml b/chain-abci/Cargo.toml index 24a72e0d1..d9ca29f57 100644 --- a/chain-abci/Cargo.toml +++ b/chain-abci/Cargo.toml @@ -37,7 +37,6 @@ structopt = "0.3" secp256k1zkp = { git = "https://github.com/crypto-com/rust-secp256k1-zkp.git", rev = "f8759809f6e3fed793b37166f7cd91c57cdb2eab", features = ["recovery", "endomorphism"] } parity-scale-codec = { features = ["derive"], version = "1.3" } thiserror = "1.0" -rustls = { version = "0.17", features = ["dangerous_configuration"] } [target.'cfg(target_os = "linux")'.dependencies] enclave-u-common = { path = "../chain-tx-enclave/enclave-u-common" } diff --git a/chain-abci/fuzz/Cargo.toml b/chain-abci/fuzz/Cargo.toml index 9842367c9..4b7bc42a8 100644 --- a/chain-abci/fuzz/Cargo.toml +++ b/chain-abci/fuzz/Cargo.toml @@ -16,10 +16,12 @@ kvdb = "0.6" kvdb-memorydb = "0.6" chain-storage = { path = "../../chain-storage" } chain-core = { path = "../../chain-core" } +test-common = { path = "../../test-common" } abci = "0.7" protobuf = "2.10.0" serde_json = "1.0" hex = "0.4" +base64 = "0.11" [dependencies.chain-abci] path = ".." diff --git a/chain-abci/fuzz/fuzz_targets/abci_cycle.rs b/chain-abci/fuzz/fuzz_targets/abci_cycle.rs index 6b76a4225..6567c23e0 100644 --- a/chain-abci/fuzz/fuzz_targets/abci_cycle.rs +++ b/chain-abci/fuzz/fuzz_targets/abci_cycle.rs @@ -21,6 +21,7 @@ use parity_scale_codec::Decode; use protobuf; use std::convert::TryInto; use std::sync::Arc; +use test_common::chain_env::KEYPACKAGE_VECTOR; pub fn get_enclave_bridge_mock() -> MockClient { MockClient::new(0) @@ -75,7 +76,7 @@ const TEST_GENESIS: &str = "{ \"value\": \"MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA=\" }, { - \"keypackage\": \"RklYTUU=\" + \"keypackage\": \"$KEYPACKAGE\" } ] } @@ -99,7 +100,12 @@ fn init_request() -> RequestInitChain { let t = ::protobuf::well_known_types::Timestamp::new(); let mut req = RequestInitChain::default(); req.set_time(t); - req.set_app_state_bytes(TEST_GENESIS.as_bytes().to_vec()); + req.set_app_state_bytes( + TEST_GENESIS + .replace("$KEYPACKAGE", &base64::encode(KEYPACKAGE_VECTOR)) + .as_bytes() + .to_vec(), + ); req.set_chain_id(String::from(TEST_CHAIN_ID)); req.set_validators(vec![validator].into()); req.set_consensus_params(ConsensusParams { @@ -156,10 +162,10 @@ fuzz_target!(|data: &[u8]| { (Ok(c), Some(t)) => { let result = c.validate_config_get_genesis(t.get_seconds().try_into().unwrap()); - if let Ok((accounts, rp, nodes)) = result { + if let Ok(state) = result { let network_params = NetworkParameters::Genesis(c.network_params); let r = check_validators( - &nodes, + &state.validators, req.validators.clone().into_vec(), &c.distribution, ); @@ -168,12 +174,12 @@ fuzz_target!(|data: &[u8]| { } else { let tx_tree = MerkleTree::empty(); let mut storage = Storage::new_db(create_db()); - let new_account_root = storage.put_stakings(0, &accounts); + let new_account_root = storage.put_stakings(0, &state.accounts); let genesis_app_hash = compute_app_hash( &tx_tree, &new_account_root, - &rp, + &state.rewards_pool, &network_params, ); if req.chain_id.len() > 3 { diff --git a/chain-abci/src/app/app_init.rs b/chain-abci/src/app/app_init.rs index fb5502f5c..c01418dbb 100644 --- a/chain-abci/src/app/app_init.rs +++ b/chain-abci/src/app/app_init.rs @@ -31,7 +31,6 @@ use chain_storage::buffer::{ }; use chain_storage::jellyfish::{compute_staking_root, sum_staking_coins, StakingGetter, Version}; use chain_storage::{Storage, StoredChainState}; -use ra_client::EnclaveCertVerifier; /// ABCI app state snapshot #[derive(Serialize, Deserialize, Clone, Encode, Decode)] @@ -55,6 +54,9 @@ pub struct ChainNodeState { pub staking_version: Version, /// Record the sum of all the coins in UTxO set pub utxo_coins: Coin, + /// Record the biggest enclave ISVSVN (Security Version Number of the Enclave) we've seen in + /// keypackage so far + pub enclave_isv_svn: u16, /// The parts of states which involved in computing app_hash pub top_level: ChainState, @@ -87,6 +89,7 @@ impl ChainNodeState { rewards_pool: RewardsPoolState, network_params: NetworkParameters, staking_table: StakingTable, + enclave_isv_svn: u16, ) -> Self { ChainNodeState { last_block_height: BlockHeight::genesis(), @@ -98,6 +101,7 @@ impl ChainNodeState { max_evidence_age, staking_version: 0, utxo_coins: Coin::zero(), + enclave_isv_svn, top_level: ChainState { account_root, rewards_pool, @@ -137,8 +141,6 @@ pub struct ChainNodeApp { pub rewards_pool_updated: bool, /// address of tx query enclave to supply to clients (if any) pub tx_query_address: Option, - /// Enclave certificate verifier - pub enclave_cert_verifier: EnclaveCertVerifier, /// consensus buffer of staking merkle trie storage pub staking_buffer: StakingBuffer, @@ -225,14 +227,14 @@ fn get_voting_power( } pub fn init_app_hash(conf: &InitConfig, genesis_time: Timespec) -> H256 { - let (accounts, rp, _nodes) = conf + let state = conf .validate_config_get_genesis(genesis_time) .expect("distribution validation error"); compute_app_hash( &MerkleTree::empty(), - &compute_staking_root(&accounts), - &rp, + &compute_staking_root(&state.accounts), + &state.rewards_pool, &NetworkParameters::Genesis(conf.network_params.clone()), ) } @@ -245,7 +247,6 @@ impl ChainNodeApp { chain_id: &str, storage: Storage, tx_query_address: Option, - enclave_cert_verifier: EnclaveCertVerifier, ) -> Self { let stored_genesis = storage.get_genesis_app_hash(); @@ -276,7 +277,6 @@ impl ChainNodeApp { tx_validator, rewards_pool_updated: false, tx_query_address, - enclave_cert_verifier, staking_buffer: HashMap::new(), mempool_staking_buffer: HashMap::new(), @@ -315,9 +315,6 @@ impl ChainNodeApp { let _ = start_zmq(_conn_str, chain_hex_id, storage.get_read_only()); } - let enclave_cert_verifier = EnclaveCertVerifier::new(Default::default()) - .expect("enclave cert verifier init failed"); - if let Some(data) = storage.get_last_app_state() { info!("last app state stored"); let mut last_state = @@ -365,7 +362,6 @@ impl ChainNodeApp { chain_id, storage, tx_query_address, - enclave_cert_verifier, ) } else { info!("no last app state stored"); @@ -390,7 +386,6 @@ impl ChainNodeApp { tx_validator, rewards_pool_updated: false, tx_query_address, - enclave_cert_verifier, staking_buffer: HashMap::new(), mempool_staking_buffer: HashMap::new(), @@ -426,7 +421,7 @@ impl ChainNodeApp { .get_seconds() .try_into() .expect("invalid genesis time"); - let (accounts, rp, nodes) = conf + let state = conf .validate_config_get_genesis(genesis_time) .expect("distribution validation error"); @@ -440,11 +435,11 @@ impl ChainNodeApp { } let network_params = NetworkParameters::Genesis(conf.network_params); - let new_account_root = self.storage.put_stakings(0, &accounts); + let new_account_root = self.storage.put_stakings(0, &state.accounts); let genesis_app_hash = compute_app_hash( &MerkleTree::empty(), &new_account_root, - &rp, + &state.rewards_pool, &network_params, ); @@ -454,19 +449,23 @@ impl ChainNodeApp { check_and_store_consensus_params( req.consensus_params.as_ref(), - &nodes, + &state.validators, &network_params, &mut self.storage, ); check_validators( - &nodes, + &state.validators, req.validators.clone().into_vec(), &conf.distribution, ) .expect("validators in genesis configuration are not consistent with app_state"); - let val_addresses = nodes.iter().map(|(addr, _)| *addr).collect::>(); + let val_addresses = state + .validators + .iter() + .map(|(addr, _)| *addr) + .collect::>(); let staking_table = StakingTable::from_genesis( &staking_getter!(self, 0), network_params.get_required_council_node_stake(), @@ -479,9 +478,10 @@ impl ChainNodeApp { genesis_time, max_evidence_age, new_account_root, - rp, + state.rewards_pool, network_params, staking_table, + state.isv_svn, ); chain_storage::store_genesis_state( &mut kv_store!(self), diff --git a/chain-abci/src/app/mod.rs b/chain-abci/src/app/mod.rs index f12281ca9..846b00526 100644 --- a/chain-abci/src/app/mod.rs +++ b/chain-abci/src/app/mod.rs @@ -404,9 +404,11 @@ fn generate_tx_staking_change_event(tx_action: TxAction) -> Option fee, .. } => Some(StakingEvent::Unbond(&unbond.0, unbond.1, unbonded_from, fee).into()), - TxPublicAction::NodeJoin(staking_address, council_node) => { - Some(StakingEvent::NodeJoin(&staking_address, council_node).into()) - } + TxPublicAction::NodeJoin { + address, + council_node, + .. + } => Some(StakingEvent::NodeJoin(&address, council_node).into()), TxPublicAction::Unjail(staking_address) => { Some(StakingEvent::Unjail(&staking_address).into()) } diff --git a/chain-abci/src/app/validate_tx.rs b/chain-abci/src/app/validate_tx.rs index 843bc400b..d4beae1f4 100644 --- a/chain-abci/src/app/validate_tx.rs +++ b/chain-abci/src/app/validate_tx.rs @@ -1,6 +1,8 @@ use super::{BufferType, ChainNodeApp, ChainNodeState}; use crate::enclave_bridge::EnclaveProxy; -use crate::storage::{process_public_tx, verify_enclave_tx, TxAction, TxEnclaveAction}; +use crate::storage::{ + process_public_tx, verify_enclave_tx, TxAction, TxEnclaveAction, TxPublicAction, +}; use crate::tx_error::TxError; use abci::*; use chain_core::tx::data::TxId; @@ -118,11 +120,18 @@ impl ChainNodeApp { let action = process_public_tx( &mut staking_store!(self, state.staking_version, buffer_type), &mut state.staking_table, - &self.enclave_cert_verifier, + state.enclave_isv_svn, &extra_info, &tx, )?; + match action { + TxPublicAction::NodeJoin { isv_svn, .. } => { + state.enclave_isv_svn = isv_svn; + } + _ => {} + }; + TxAction::Public(action) } }; diff --git a/chain-abci/src/staking/mod.rs b/chain-abci/src/staking/mod.rs index a8d11bcc0..f81cecbcc 100644 --- a/chain-abci/src/staking/mod.rs +++ b/chain-abci/src/staking/mod.rs @@ -16,14 +16,13 @@ mod tests { use chain_core::init::config::SlashRatio; use chain_core::init::params::NetworkParameters; use chain_core::state::account::{ - ConfidentialInit, CouncilNode, PunishmentKind, StakedState, StakedStateAddress, UnbondTx, - UnjailTx, Validator, + PunishmentKind, StakedState, StakedStateAddress, UnbondTx, UnjailTx, Validator, }; use chain_core::state::tendermint::{BlockHeight, TendermintValidatorPubKey}; use chain_core::state::validator::NodeJoinRequestTx; use chain_core::tx::fee::Fee; use chain_storage::buffer::{Get, GetStaking, MemStore, StoreStaking}; - use test_common::chain_env::get_init_network_params; + use test_common::chain_env::{get_init_network_params, mock_council_node}; use super::*; use crate::app::BeginBlockInfo; @@ -58,11 +57,8 @@ mod tests { fn new_validator(seed: &[u8; 32], bonded: Coin) -> StakedState { let mut staking = StakedState::default(staking_address(seed)); staking.bonded = bonded; - staking.validator = Some(Validator::new(CouncilNode::new( + staking.validator = Some(Validator::new(mock_council_node( TendermintValidatorPubKey::Ed25519(seed.clone()), - ConfidentialInit { - keypackage: b"FIXME".to_vec(), - }, ))); staking } @@ -120,16 +116,9 @@ mod tests { nonce, address: addr4, attributes: Default::default(), - node_meta: CouncilNode::new( - val_pk4.clone(), - ConfidentialInit { - keypackage: b"FIXME".to_vec(), - }, - ), + node_meta: mock_council_node(val_pk4.clone()), }; - table - .node_join(&mut store, 10, 0, &Default::default(), &node_join) - .unwrap(); + table.node_join(&mut store, 10, 0, 0, &node_join).unwrap(); assert_eq!(table.end_block(&store, 3), vec![]); // node-join increase nonce by one assert_eq!(store.get(&addr4).unwrap().nonce, nonce + 1); @@ -273,15 +262,10 @@ mod tests { nonce, address: addr1, attributes: Default::default(), - node_meta: CouncilNode::new( - val_pk_new, - ConfidentialInit { - keypackage: b"FIXME".to_vec(), - }, - ), + node_meta: mock_council_node(val_pk_new), }; assert!(matches!( - table.node_join(&mut store, 3, 0, &Default::default(), &node_join), + table.node_join(&mut store, 3, 0, 0, &node_join), Err(PublicTxError::NodeJoin(NodeJoinError::IsJailed)) )); // failed execution don't increase nonce @@ -337,15 +321,10 @@ mod tests { nonce: staking.nonce + 1, address: addr, attributes: Default::default(), - node_meta: CouncilNode::new( - val_pk_new.clone(), - ConfidentialInit { - keypackage: b"FIXME".to_vec(), - }, - ), + node_meta: mock_council_node(val_pk_new.clone()), }; // change to new validator key - let result = table.node_join(store, 1, 1, &Default::default(), &node_join); + let result = table.node_join(store, 1, 1, 0, &node_join); if result.is_ok() { let staking = store.get(&addr).unwrap(); assert_eq!( @@ -353,7 +332,7 @@ mod tests { vec![(val_pk_new, staking.bonded.into())] ); } - result + result.map(|_| ()) } #[test] @@ -397,16 +376,11 @@ mod tests { nonce: 0, address: addr_new, attributes: Default::default(), - node_meta: CouncilNode::new( - val_pk1, - ConfidentialInit { - keypackage: b"FIXME".to_vec(), - }, - ), + node_meta: mock_council_node(val_pk1), }; // can't join with used key assert!(matches!( - table.node_join(&mut store, 1, 0, &Default::default(), &node_join), + table.node_join(&mut store, 1, 0, 0, &node_join), Err(PublicTxError::NodeJoin( NodeJoinError::DuplicateValidatorAddress )) @@ -479,12 +453,7 @@ mod tests { nonce, address: addr1, attributes: Default::default(), - node_meta: CouncilNode::new( - val_pk1.clone(), - ConfidentialInit { - keypackage: b"FIXME".to_vec(), - }, - ), + node_meta: mock_council_node(val_pk1.clone()), }; let mut init_params = get_init_network_params(Coin::zero()); @@ -586,7 +555,7 @@ mod tests { let slashed = (bonded_slashed + unbonded_slashed).unwrap(); table.deposit(&mut store, &addr1, slashed).unwrap(); table - .node_join(&mut store, 8, 0, &Default::default(), &node_join_tx(0)) + .node_join(&mut store, 8, 0, 0, &node_join_tx(0)) .unwrap(); assert_eq!( table.end_block(&mut store, 3), @@ -638,7 +607,7 @@ mod tests { .deposit(&mut store, &addr1, Coin::new(11_0000_0000).unwrap()) .unwrap(); table - .node_join(&mut store, 11, 0, &Default::default(), &node_join_tx(2)) + .node_join(&mut store, 11, 0, 0, &node_join_tx(2)) .unwrap(); assert_eq!( table.end_block(&mut store, 3), @@ -783,16 +752,9 @@ mod tests { nonce: 1, address: addr2, attributes: Default::default(), - node_meta: CouncilNode::new( - val_pk_new.clone(), - ConfidentialInit { - keypackage: b"FIXME".to_vec(), - }, - ), + node_meta: mock_council_node(val_pk_new.clone()), }; - table - .node_join(&mut store, 2, 0, &Default::default(), &tx) - .unwrap(); + table.node_join(&mut store, 2, 0, 0, &tx).unwrap(); assert_eq!( table.end_block(&mut store, 3), vec![(val_pk_new.clone(), Coin::new(12_0000_0000).unwrap().into())] diff --git a/chain-abci/src/staking/tx.rs b/chain-abci/src/staking/tx.rs index a3064718f..34f207327 100644 --- a/chain-abci/src/staking/tx.rs +++ b/chain-abci/src/staking/tx.rs @@ -1,4 +1,4 @@ -use rustls::internal::msgs::codec::Codec; +use log::warn; use chain_core::common::Timespec; use chain_core::init::coin::Coin; @@ -7,8 +7,8 @@ use chain_core::state::tendermint::{BlockHeight, TendermintValidatorAddress}; use chain_core::state::validator::NodeJoinRequestTx; use chain_core::tx::fee::Fee; use chain_storage::buffer::StoreStaking; -use mls::KeyPackage; -use ra_client::EnclaveCertVerifier; +use mls::{Codec, KeyPackage}; +use ra_client::ENCLAVE_CERT_VERIFIER; use super::table::{set_staking, StakingTable}; use crate::tx_error::{ @@ -24,9 +24,9 @@ impl StakingTable { heap: &mut impl StoreStaking, block_time: Timespec, max_evidence_age: Timespec, - ra_verifier: &EnclaveCertVerifier, + recent_isv_svn: u16, tx: &NodeJoinRequestTx, - ) -> Result<(), PublicTxError> { + ) -> Result { let mut staking = self.get_or_default(heap, &tx.address); if tx.nonce != staking.nonce { return Err(PublicTxError::IncorrectNonce); @@ -35,14 +35,17 @@ impl StakingTable { return Err(NodeJoinError::BondedNotEnough.into()); } - // FIXME verify keypackage - if let Ok(keypackage) = KeyPackage::read_bytes(&tx.node_meta.confidential_init.keypackage) - .ok_or(NodeJoinError::KeyPackageDecodeError) - { - let _ = keypackage - .verify(ra_verifier, block_time) - .map_err(NodeJoinError::KeyPackageVerifyError); - } + let keypackage = KeyPackage::read_bytes(&tx.node_meta.confidential_init.keypackage) + .ok_or(NodeJoinError::KeyPackageDecodeError)?; + let info = keypackage + .verify(&ENCLAVE_CERT_VERIFIER, block_time) + .map_err(NodeJoinError::KeyPackageVerifyError)?; + let new_isv_svn = if info.quote.report_body.isv_svn > recent_isv_svn { + warn!("more recent version of enclave"); + info.quote.report_body.isv_svn + } else { + recent_isv_svn + }; let val_addr = TendermintValidatorAddress::from(&tx.node_meta.consensus_pubkey); if let Some(val) = &mut staking.validator { @@ -94,9 +97,11 @@ impl StakingTable { } staking.inc_nonce(); set_staking(heap, staking, self.minimal_required_staking); + #[cfg(debug_assertions)] self.check_invariants(heap); - Ok(()) + + Ok(new_isv_svn) } /// Handle `UnjailTx` diff --git a/chain-abci/src/storage/mod.rs b/chain-abci/src/storage/mod.rs index e62522efc..70d972990 100644 --- a/chain-abci/src/storage/mod.rs +++ b/chain-abci/src/storage/mod.rs @@ -10,7 +10,6 @@ use chain_core::tx::{TransactionId, TxEnclaveAux, TxObfuscated, TxPublicAux}; use chain_storage::buffer::{GetKV, GetStaking, StoreStaking}; use chain_tx_validation::{verify_unjailed, witness::verify_tx_recover_address, ChainInfo, Error}; use enclave_protocol::{IntraEnclaveRequest, IntraEnclaveResponseOk, SealedLog}; -use ra_client::EnclaveCertVerifier; pub enum TxAction { Enclave(TxEnclaveAction), @@ -117,7 +116,12 @@ pub enum TxPublicAction { unbond: (StakedStateAddress, Coin), unbonded_from: Timespec, }, - NodeJoin(StakedStateAddress, CouncilNode), + NodeJoin { + address: StakedStateAddress, + council_node: CouncilNode, + // most recent isv_svn + isv_svn: u16, + }, Unjail(StakedStateAddress), } @@ -129,8 +133,12 @@ impl TxPublicAction { unbonded_from, } } - fn node_join(staking_address: StakedStateAddress, council_node: CouncilNode) -> Self { - Self::NodeJoin(staking_address, council_node) + fn node_join(address: StakedStateAddress, council_node: CouncilNode, isv_svn: u16) -> Self { + Self::NodeJoin { + address, + council_node, + isv_svn, + } } fn unjail(staking_address: StakedStateAddress) -> Self { Self::Unjail(staking_address) @@ -139,7 +147,7 @@ impl TxPublicAction { pub fn fee(&self) -> Fee { match self { Self::Unbond { fee, .. } => *fee, - Self::NodeJoin(_, _) => Fee::new(Coin::zero()), + Self::NodeJoin { .. } => Fee::new(Coin::zero()), Self::Unjail(_) => Fee::new(Coin::zero()), } } @@ -147,7 +155,7 @@ impl TxPublicAction { pub fn staking_address(&self) -> Option { match self { Self::Unbond { unbond, .. } => Some(unbond.0), - Self::NodeJoin(staking_address, _) => Some(*staking_address), + Self::NodeJoin { address, .. } => Some(*address), Self::Unjail(staking_address) => Some(*staking_address), } } @@ -287,7 +295,7 @@ fn check_staking_attributes( pub fn process_public_tx( staking_store: &mut impl StoreStaking, staking_table: &mut StakingTable, - enclave_cert_verifier: &EnclaveCertVerifier, + enclave_isv_svn: u16, chain_info: &ChainInfo, txaux: &TxPublicAux, ) -> Result { @@ -331,15 +339,19 @@ pub fn process_public_tx( if address != maintx.address { return Err(PublicTxError::StakingWitnessNotMatch); } - staking_table.node_join( + let isv_svn = staking_table.node_join( staking_store, chain_info.block_time, chain_info.max_evidence_age, - enclave_cert_verifier, + enclave_isv_svn, maintx, )?; - Ok(TxPublicAction::node_join(address, maintx.node_meta.clone())) + Ok(TxPublicAction::node_join( + address, + maintx.node_meta.clone(), + isv_svn, + )) } } } diff --git a/chain-abci/tests/abci_app.rs b/chain-abci/tests/abci_app.rs index 9a6a40b2c..f519a404a 100644 --- a/chain-abci/tests/abci_app.rs +++ b/chain-abci/tests/abci_app.rs @@ -14,9 +14,8 @@ use chain_core::init::config::{ JailingParameters, RewardsParameters, SlashRatio, SlashingParameters, }; use chain_core::state::account::{ - ConfidentialInit, CouncilNode, DepositBondTx, StakedState, StakedStateAddress, - StakedStateDestination, StakedStateOpAttributes, StakedStateOpWitness, UnbondTx, - WithdrawUnbondedTx, + DepositBondTx, StakedState, StakedStateAddress, StakedStateDestination, + StakedStateOpAttributes, StakedStateOpWitness, UnbondTx, WithdrawUnbondedTx, }; use chain_core::state::tendermint::{ BlockHeight, TendermintValidatorAddress, TendermintValidatorPubKey, TendermintVotePower, @@ -57,7 +56,7 @@ use std::convert::TryFrom; use std::convert::TryInto; use std::str::FromStr; use std::sync::Arc; -use test_common::chain_env::ChainEnv; +use test_common::chain_env::{mock_confidential_init, mock_council_node, ChainEnv}; const TEST_CHAIN_ID: &str = "test-00"; const EXAMPLE_HASH: &str = "F5E8DFBF717082D6E9508E1A5A5C9B8EAC04A39F69C40262CB733C920DA10962"; @@ -219,6 +218,7 @@ fn get_dummy_app_state(app_hash: H256) -> ChainNodeState { staking_table: StakingTable::default(), staking_version: 0, utxo_coins: Coin::zero(), + enclave_isv_svn: 0, top_level: ChainState { account_root: [0u8; 32], rewards_pool: RewardsPoolState::new(0, params.get_rewards_monetary_expansion_tau()), @@ -271,9 +271,7 @@ fn init_chain_for(address: RedeemAddress) -> ChainNodeApp { "test".to_owned(), None, pub_key.clone(), - ConfidentialInit { - keypackage: b"FIXME".to_vec(), - }, + mock_confidential_init(), ); let validator = ValidatorUpdate { pub_key: Some(PubKey { @@ -289,14 +287,14 @@ fn init_chain_for(address: RedeemAddress) -> ChainNodeApp { let c = InitConfig::new(distribution, params, nodes); let t = ::protobuf::well_known_types::Timestamp::new(); let result = c.validate_config_get_genesis(t.get_seconds().try_into().unwrap()); - if let Ok((accounts, rp, _nodes)) = result { + if let Ok(genesis_state) = result { let tx_tree = MerkleTree::empty(); let mut storage = Storage::new_db(db.clone()); - let new_account_root = storage.put_stakings(0, &accounts); + let new_account_root = storage.put_stakings(0, &genesis_state.accounts); let genesis_app_hash = compute_app_hash( &tx_tree, &new_account_root, - &rp, + &genesis_state.rewards_pool, &get_dummy_network_params(), ); @@ -993,12 +991,7 @@ fn all_valid_tx_types_should_commit() { 1, addr.into(), StakedStateOpAttributes::new(0), - CouncilNode::new( - TendermintValidatorPubKey::Ed25519([2u8; 32]), - ConfidentialInit { - keypackage: b"FIXME".to_vec(), - }, - ), + mock_council_node(TendermintValidatorPubKey::Ed25519([2u8; 32])), ); let secp = Secp256k1::new(); let witness = StakedStateOpWitness::new(get_ecdsa_witness(&secp, &tx.id(), &secret_key)); diff --git a/chain-abci/tests/tx_validation.rs b/chain-abci/tests/tx_validation.rs index 93900144e..74984c56c 100644 --- a/chain-abci/tests/tx_validation.rs +++ b/chain-abci/tests/tx_validation.rs @@ -17,8 +17,7 @@ use chain_core::state::account::StakedState; use chain_core::state::account::StakedStateAddress; use chain_core::state::account::StakedStateOpAttributes; use chain_core::state::account::{ - ConfidentialInit, DepositBondTx, StakedStateOpWitness, UnbondTx, UnjailTx, Validator, - WithdrawUnbondedTx, + DepositBondTx, StakedStateOpWitness, UnbondTx, UnjailTx, Validator, WithdrawUnbondedTx, }; use chain_core::state::tendermint::BlockHeight; use chain_core::state::tendermint::TendermintValidatorPubKey; @@ -53,6 +52,7 @@ use secp256k1::{key::PublicKey, key::SecretKey, key::XOnlyPublicKey, Message, Se use std::fmt::Debug; use std::mem; use std::sync::Arc; +use test_common::chain_env::{mock_confidential_init, mock_council_node}; fn verify_enclave_tx( tx_validator: &mut T, @@ -82,8 +82,7 @@ fn verify_public_tx( let mut buffer = HashMap::new(); let mut store = StakingBufferStore::new(StakingGetter::new(storage, version), &mut buffer); - let tx_action = - process_public_tx(&mut store, &mut tbl, &Default::default(), extra_info, txaux)?; + let tx_action = process_public_tx(&mut store, &mut tbl, 0, extra_info, txaux)?; let fee = tx_action.fee(); let maddress = tx_action.staking_address(); @@ -1152,12 +1151,7 @@ fn prepare_jailed_accounts() -> ( 0, addr.into(), Some(Validator { - council_node: CouncilNode::new( - TendermintValidatorPubKey::Ed25519([0xcd; 32]), - ConfidentialInit { - keypackage: b"FIXME".to_vec(), - }, - ), + council_node: mock_council_node(TendermintValidatorPubKey::Ed25519([0xcd; 32])), jailed_until: Some(100), inactive_time: Some(0), inactive_block: Some(BlockHeight::genesis()), @@ -1346,9 +1340,7 @@ fn prepare_nodejoin_transaction( name: "test".to_string(), security_contact: None, consensus_pubkey: TendermintValidatorPubKey::Ed25519([1u8; 32]), - confidential_init: ConfidentialInit { - keypackage: b"FIXME".to_vec(), - }, + confidential_init: mock_confidential_init(), }, }; let witness = get_account_op_witness(secp, &tx.id(), &secret_key); @@ -1378,11 +1370,8 @@ fn prepare_valid_nodejoin_tx( 0, addr.into(), if validator { - Some(Validator::new(CouncilNode::new( + Some(Validator::new(mock_council_node( TendermintValidatorPubKey::Ed25519([1u8; 32]), - ConfidentialInit { - keypackage: b"FIXME".to_vec(), - }, ))) } else { None diff --git a/chain-core/Cargo.toml b/chain-core/Cargo.toml index 43632fca6..53b97bed9 100644 --- a/chain-core/Cargo.toml +++ b/chain-core/Cargo.toml @@ -7,12 +7,13 @@ readme = "../README.md" edition = "2018" [features] -default = ["serde", "bech32", "hex", "base64", "secp256k1zkp/serde", "secp256k1zkp/std"] +default = ["serde", "bech32", "hex", "base64", "secp256k1zkp/serde", "secp256k1zkp/std", "mls", "ra-client"] edp = ["secp256k1zkp/edp"] mesalock_sgx = ["sgx_tstd", "secp256k1zkp/sgx"] - [dependencies] +mls = { path = "../chain-tx-enclave-next/mls", optional = true } +ra-client = { path = "../chain-tx-enclave-next/enclave-ra/ra-client", optional = true} digest = { version = "0.8", default-features = false} tiny-keccak = { version = "2.0", features = ["keccak"] } sha2 = { version = "0.8", default-features = false } @@ -32,3 +33,4 @@ thiserror = { version = "1.0", default-features = false } quickcheck = "0.9" serde_json = "1.0" fixed = "0.5.7" +test-common = { path = "../test-common" } diff --git a/chain-core/src/init/config.rs b/chain-core/src/init/config.rs index afb2b196c..7d137c54b 100644 --- a/chain-core/src/init/config.rs +++ b/chain-core/src/init/config.rs @@ -2,85 +2,61 @@ use crate::common::Timespec; use crate::init::address::RedeemAddress; use crate::init::coin::{sum_coins, Coin, CoinError}; pub use crate::init::params::*; -use crate::init::MAX_COIN; use crate::state::account::{ ConfidentialInit, CouncilNode, StakedState, StakedStateAddress, StakedStateDestination, ValidatorName, ValidatorSecurityContact, }; -use crate::state::tendermint::{TendermintValidatorPubKey, TendermintVotePower}; +use crate::state::tendermint::TendermintValidatorPubKey; use crate::state::RewardsPoolState; +use mls::{keypackage, Codec, KeyPackage}; +use ra_client::ENCLAVE_CERT_VERIFIER; #[cfg(not(feature = "mesalock_sgx"))] use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, HashSet}; -use std::fmt; /// problems with initial config -#[derive(Debug)] +#[derive(thiserror::Error, Debug)] pub enum DistributionError { /// coin arithmetic problems - DistributionCoinError(CoinError), + #[error("coin error: {0}")] + DistributionCoinError(#[from] CoinError), /// lower than the expected supply + #[error("The total sum of allocated amounts ({0}) does not match the expected total supply")] DoesNotMatchMaxSupply(Coin), /// council node uses non-specified address + #[error("Address not found in the distribution ({0})")] AddressNotInDistribution(RedeemAddress), /// address should have that amount assigned + #[error("Address ({0}) does not have the expected amount ({1}) or is not an externally owned account")] DoesNotMatchRequiredAmount(RedeemAddress, Coin), /// problems with encoded consensus pubkeys + #[error("Invalid validator key")] InvalidValidatorKey, /// duplicate consensus pubkeys + #[error("Duplicate validator key")] DuplicateValidatorKey, /// associated state not in distribution + #[error("Invalid validator account")] InvalidValidatorAccount, /// at least one validator needs to be specified + #[error("No validators / council nodes specified")] NoValidators, /// voting power too large or otherwise invalid - InvalidVotingPower, + #[error("Invalid minimal required staking: {0}")] + InvalidMinimalStake(CoinError), /// problems with reward configuration /// TODO: embed the error type? + #[error("Invalid rewards parameters: {0}")] InvalidRewardsParamter(&'static str), /// Invalid punishment configuration parameter + #[error("Invalid punishment parameters")] InvalidPunishmentParamter, -} - -impl fmt::Display for DistributionError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - DistributionError::DistributionCoinError(c) => c.fmt(f), - DistributionError::DoesNotMatchMaxSupply(c) => write!( - f, - "The total sum of allocated amounts ({}) does not match the expected total supply ({})", - c, - MAX_COIN - ), - DistributionError::AddressNotInDistribution(a) => { - write!(f, "Address not found in the distribution ({})", a) - }, - DistributionError::DoesNotMatchRequiredAmount(a, c) => { - write!(f, "Address ({}) does not have the expected amount ({}) or is not an externally owned account", a, c) - }, - DistributionError::InvalidValidatorKey => { - write!(f, "Invalid validator key") - }, - DistributionError::InvalidValidatorAccount => { - write!(f, "Invalid validator account") - }, - DistributionError::DuplicateValidatorKey => { - write!(f, "Duplicate validator key") - }, - DistributionError::NoValidators => { - write!(f, "No validators / council nodes specified") - }, - DistributionError::InvalidVotingPower => { - write!(f, "Invalid voting power") - }, - DistributionError::InvalidRewardsParamter(err) => { - write!(f, "Invalid rewards parameters: {}", err) - } - DistributionError::InvalidPunishmentParamter => { - write!(f, "Invalid punishment parameters") - } - } - } + /// keypackage decode error + #[error("key package decode failed")] + KeyPackageDecodeError, + /// keypackage verify error + #[error("invalid key package: {0}")] + KeyPackageVerifyError(#[from] keypackage::Error), } /// Initial configuration ("app_state" in genesis.json of Tendermint config) @@ -105,16 +81,17 @@ pub struct InitConfig { >, } -/// the initial state at genesis: -/// - initial states -/// - initial rewards pool -/// - initial tendermint validators -/// TODO: some tdbe state? -pub type GenesisState = ( - Vec, - RewardsPoolState, - Vec<(StakedStateAddress, CouncilNode)>, -); +/// the initial state at genesis +pub struct GenesisState { + /// initial states + pub accounts: Vec, + /// initial rewards pool + pub rewards_pool: RewardsPoolState, + /// initial tendermint validators + pub validators: Vec<(StakedStateAddress, CouncilNode)>, + /// enclave ISVSVN in genesis keypackage + pub isv_svn: u16, +} impl InitConfig { /// creates a new config (mainly for testing / tools) @@ -209,39 +186,34 @@ impl InitConfig { return Err(DistributionError::DuplicateValidatorKey); } - let validators: Result, DistributionError> = self + let validators = self .council_nodes .keys() .map(|address| { let council_node = self.get_council_node(address)?; Ok((StakedStateAddress::BasicRedeem(*address), council_node)) }) - .collect(); + .collect::, DistributionError>>()?; + + let isv_svn = validators + .iter() + .map(|v| verify_keypackage(genesis_time, &v.1.confidential_init.keypackage)) + .collect::, _>>()? + .into_iter() + .max() + .unwrap_or(0); + Coin::new( u64::from(self.network_params.required_council_node_stake) * self.council_nodes.len() as u64, ) - .map(TendermintVotePower::from) // sanity check - .map_err(|_| DistributionError::InvalidVotingPower)?; + .map_err(DistributionError::InvalidMinimalStake)?; // check the total amount - match sum_coins(self.distribution.iter().map(|(_, (_, amount))| *amount)) { - Ok(s) => { - let sum_result = s + self.network_params.rewards_config.monetary_expansion_cap; - match sum_result { - Ok(sum) => { - if sum != Coin::max() { - return Err(DistributionError::DoesNotMatchMaxSupply(sum)); - } - } - Err(e) => { - return Err(DistributionError::DistributionCoinError(e)); - } - } - } - Err(e) => { - return Err(DistributionError::DistributionCoinError(e)); - } + let sum = sum_coins(self.distribution.iter().map(|(_, (_, amount))| *amount))?; + let sum = (sum + self.network_params.rewards_config.monetary_expansion_cap)?; + if sum != Coin::max() { + return Err(DistributionError::DoesNotMatchMaxSupply(sum)); } let accounts = self.get_account(genesis_time); @@ -253,6 +225,20 @@ impl InitConfig { genesis_time, self.network_params.rewards_config.monetary_expansion_tau, ); - Ok((accounts, rewards_pool, validators?)) + Ok(GenesisState { + accounts, + rewards_pool, + validators, + isv_svn, + }) } } + +fn verify_keypackage(genesis_time: Timespec, keypackage: &[u8]) -> Result { + let keypackage = + KeyPackage::read_bytes(keypackage).ok_or(DistributionError::KeyPackageDecodeError)?; + let info = keypackage + .verify(&ENCLAVE_CERT_VERIFIER, genesis_time) + .map_err(DistributionError::KeyPackageVerifyError)?; + Ok(info.quote.report_body.isv_svn) +} diff --git a/chain-core/tests/verify_config.rs b/chain-core/tests/verify_config.rs index 3fefa8103..9a3c69814 100644 --- a/chain-core/tests/verify_config.rs +++ b/chain-core/tests/verify_config.rs @@ -4,12 +4,13 @@ use chain_core::init::config::{ InitConfig, InitNetworkParameters, JailingParameters, RewardsParameters, SlashRatio, SlashingParameters, }; -use chain_core::state::account::{ConfidentialInit, StakedStateDestination}; +use chain_core::state::account::StakedStateDestination; use chain_core::state::tendermint::TendermintValidatorPubKey; use chain_core::tx::fee::{LinearFee, Milli}; use serde::Deserialize; use std::collections::BTreeMap; use std::str::FromStr; +use test_common::chain_env::mock_confidential_init; #[derive(Deserialize)] pub struct Distribution { @@ -40,7 +41,7 @@ fn test_verify_test_example_snapshot() { "no-name".to_owned(), None, node_pubkey, - ConfidentialInit { keypackage: vec![] }, + mock_confidential_init(), ), ); @@ -81,12 +82,10 @@ fn test_verify_test_example_snapshot() { }; let config = InitConfig::new(dist.clone(), params.clone(), nodes.clone()); - let result = config.validate_config_get_genesis(0); - assert!(result.is_ok()); + config.validate_config_get_genesis(0).unwrap(); // add 1 into rewards_pool params.rewards_config.monetary_expansion_cap = Coin::new(951_6484_5705_9733_7035).unwrap(); let config = InitConfig::new(dist, params, nodes); - let result = config.validate_config_get_genesis(0); - assert!(result.is_err()); + assert!(config.validate_config_get_genesis(0).is_err()); } diff --git a/chain-tx-enclave-next/enclave-ra/ra-client/Cargo.toml b/chain-tx-enclave-next/enclave-ra/ra-client/Cargo.toml index db9e5ca9a..113b3d967 100644 --- a/chain-tx-enclave-next/enclave-ra/ra-client/Cargo.toml +++ b/chain-tx-enclave-next/enclave-ra/ra-client/Cargo.toml @@ -10,11 +10,11 @@ edition = "2018" [dependencies] chrono = "0.4" der-parser = "3.0" -log = "0.4" rustls = { version = "0.17", features = ["dangerous_configuration"] } serde_json = "1.0" thiserror = "1.0" webpki = "0.21" x509-parser = "0.7" +lazy_static = "1.4" ra-common = { path = "../ra-common" } diff --git a/chain-tx-enclave-next/enclave-ra/ra-client/src/config.rs b/chain-tx-enclave-next/enclave-ra/ra-client/src/config.rs index 8a483acce..8498cc690 100644 --- a/chain-tx-enclave-next/enclave-ra/ra-client/src/config.rs +++ b/chain-tx-enclave-next/enclave-ra/ra-client/src/config.rs @@ -19,7 +19,8 @@ impl<'a> EnclaveCertVerifierConfig<'a> { pub fn new() -> Self { Self { signing_ca_cert_pem: IAS_CERT.into(), - valid_enclave_quote_statuses: vec!["OK".into()].into(), + // https://software.intel.com/security-software-guidance/insights/deep-dive-load-value-injection#mitigationguidelines + valid_enclave_quote_statuses: vec!["OK".into(), "SW_HARDENING_NEEDED".into()].into(), report_validity_secs: 86400, // FIXME construct enclave_info from env var or config file enclave_info: None, diff --git a/chain-tx-enclave-next/enclave-ra/ra-client/src/lib.rs b/chain-tx-enclave-next/enclave-ra/ra-client/src/lib.rs index d09ef5247..a997d8e1a 100644 --- a/chain-tx-enclave-next/enclave-ra/ra-client/src/lib.rs +++ b/chain-tx-enclave-next/enclave-ra/ra-client/src/lib.rs @@ -23,5 +23,7 @@ mod verifier; pub use self::{ config::{EnclaveCertVerifierConfig, EnclaveInfo}, - verifier::{EnclaveCertVerifier, EnclaveCertVerifierError}, + verifier::{ + CertVerifyResult, EnclaveCertVerifier, EnclaveCertVerifierError, ENCLAVE_CERT_VERIFIER, + }, }; diff --git a/chain-tx-enclave-next/enclave-ra/ra-client/src/verifier.rs b/chain-tx-enclave-next/enclave-ra/ra-client/src/verifier.rs index df18deeaa..5d4511b1a 100644 --- a/chain-tx-enclave-next/enclave-ra/ra-client/src/verifier.rs +++ b/chain-tx-enclave-next/enclave-ra/ra-client/src/verifier.rs @@ -2,8 +2,10 @@ use std::{collections::HashSet, sync::Arc, time::SystemTime}; use chrono::{DateTime, Duration, Utc}; use der_parser::oid::Oid; +use lazy_static::lazy_static; use ra_common::{ - AttestationReport, AttestationReportBody, EnclaveQuoteStatus, OID_EXTENSION_ATTESTATION_REPORT, + AttestationReport, AttestationReportBody, EnclaveQuoteStatus, Quote, + OID_EXTENSION_ATTESTATION_REPORT, }; use rustls::{ internal::pemfile::certs, Certificate, ClientCertVerified, ClientCertVerifier, ClientConfig, @@ -21,6 +23,10 @@ use crate::{EnclaveCertVerifierConfig, EnclaveInfo}; static SUPPORTED_SIG_ALGS: &[&SignatureAlgorithm] = &[&ECDSA_P256_SHA256]; +lazy_static! { + pub static ref ENCLAVE_CERT_VERIFIER: EnclaveCertVerifier = EnclaveCertVerifier::default(); +} + pub struct EnclaveCertVerifier { root_cert_store: RootCertStore, valid_enclave_quote_statuses: HashSet, @@ -65,7 +71,7 @@ impl EnclaveCertVerifier { &self, certificate: &[u8], now: DateTime, - ) -> Result, EnclaveCertVerifierError> { + ) -> Result { let (_, certificate) = parse_x509_der(certificate) .map_err(|_| EnclaveCertVerifierError::CertificateParsingError)?; @@ -75,33 +81,30 @@ impl EnclaveCertVerifier { } = certificate.tbs_certificate.validity; let now_sec = now.timestamp(); if now_sec < not_before.to_timespec().sec { - return Err(EnclaveCertVerifierError::CertificateExpired); + return Err(EnclaveCertVerifierError::CertificateNotBegin); } if now_sec >= not_after.to_timespec().sec { return Err(EnclaveCertVerifierError::CertificateExpired); } let attestation_report_oid = Oid::from(OID_EXTENSION_ATTESTATION_REPORT); - let mut attestation_report_received = false; - let public_key = certificate .tbs_certificate .subject_pki .subject_public_key .data; - for extension in certificate.tbs_certificate.extensions { - if extension.oid == attestation_report_oid { - attestation_report_received = true; - self.verify_attestation_report(extension.value, public_key, now)?; - } - } - - if attestation_report_received { - Ok(public_key.to_vec()) - } else { - Err(EnclaveCertVerifierError::MissingAttestationReport) - } + let extension = certificate + .tbs_certificate + .extensions + .iter() + .find(|ext| ext.oid == attestation_report_oid) + .ok_or(EnclaveCertVerifierError::MissingAttestationReport)?; + let quote = self.verify_attestation_report(extension.value, public_key, now)?; + Ok(CertVerifyResult { + public_key: public_key.to_vec(), + quote, + }) } /// Verifies attestation report @@ -110,9 +113,7 @@ impl EnclaveCertVerifier { attestation_report: &[u8], public_key: &[u8], now: DateTime, - ) -> Result<(), EnclaveCertVerifierError> { - log::info!("Verifying attestation report"); - + ) -> Result { let trust_anchors: Vec = self .root_cert_store .roots @@ -144,10 +145,7 @@ impl EnclaveCertVerifier { )?; } - self.verify_attestation_report_body(&attestation_report.body, public_key, now)?; - - log::info!("Attestation report is valid!"); - Ok(()) + self.verify_attestation_report_body(&attestation_report.body, public_key, now) } fn verify_attestation_report_body( @@ -155,7 +153,7 @@ impl EnclaveCertVerifier { attestation_report_body: &[u8], public_key: &[u8], now: DateTime, - ) -> Result<(), EnclaveCertVerifierError> { + ) -> Result { let attestation_report_body: AttestationReportBody = serde_json::from_slice(attestation_report_body)?; @@ -206,7 +204,7 @@ impl EnclaveCertVerifier { } } - Ok(()) + Ok(quote) } /// Converts enclave certificate verifier into client config expected by `rustls` @@ -268,6 +266,8 @@ impl ClientCertVerifier for EnclaveCertVerifier { pub enum EnclaveCertVerifierError { #[error("Enclave certificate expired")] CertificateExpired, + #[error("Enclave certificate not begin yet")] + CertificateNotBegin, #[error("Failed to parse server certificate")] CertificateParsingError, #[error("Unable to parse date time: {0}")] @@ -301,3 +301,11 @@ impl From for TLSError { TLSError::General(e.to_string()) } } + +/// Extracted information after success verify attestation certificate +pub struct CertVerifyResult { + /// the returned public key is in uncompressed raw format (65 bytes) + pub public_key: Vec, + /// the quote + pub quote: Quote, +} diff --git a/chain-tx-enclave-next/enclave-ra/ra-enclave/src/lib.rs b/chain-tx-enclave-next/enclave-ra/ra-enclave/src/lib.rs index a9e6492eb..c579369d2 100644 --- a/chain-tx-enclave-next/enclave-ra/ra-enclave/src/lib.rs +++ b/chain-tx-enclave-next/enclave-ra/ra-enclave/src/lib.rs @@ -30,4 +30,4 @@ mod context; pub use self::{certificate::Certificate, config::EnclaveRaConfig}; #[cfg(target_env = "sgx")] -pub use {EnclaveRaContext, EnclaveRaContextError}; +pub use context::{EnclaveRaContext, EnclaveRaContextError}; diff --git a/chain-tx-enclave-next/mls/src/keypackage.rs b/chain-tx-enclave-next/mls/src/keypackage.rs index 4a3d26897..81d623b81 100644 --- a/chain-tx-enclave-next/mls/src/keypackage.rs +++ b/chain-tx-enclave-next/mls/src/keypackage.rs @@ -1,7 +1,9 @@ //! Key package of mls protocol (draft-ietf-mls-protocol.md#key-packages) +#[cfg(target_env = "sgx")] +use std::convert::TryInto; use std::time::{Duration, UNIX_EPOCH}; -use ra_client::{EnclaveCertVerifier, EnclaveCertVerifierError}; +use ra_client::{CertVerifyResult, EnclaveCertVerifier, EnclaveCertVerifierError}; #[cfg(target_env = "sgx")] use ra_enclave::{Certificate, EnclaveRaContext, EnclaveRaContextError}; use rustls::internal::msgs::codec::{self, Codec, Reader}; @@ -18,7 +20,7 @@ pub type Timespec = u64; pub type CipherSuite = u16; pub const PROTOCOL_VERSION_MLS10: ProtocolVersion = 0; -pub const DEFAULT_LIFE_TIME: Timespec = 30 * 24 * 3600; +pub const DEFAULT_LIFE_TIME: Timespec = 90 * 24 * 3600; // certificate has 90 days valid duration pub const MLS10_128_DHKEMP256_AES128GCM_SHA256_P256: CipherSuite = 2; pub const CREDENTIAL_TYPE_X509: u8 = 1; @@ -79,7 +81,11 @@ impl KeyPackagePayload { } /// Verify key package payload - pub fn verify(&self, ra_verifier: &EnclaveCertVerifier, now: Timespec) -> Result<(), Error> { + pub fn verify( + &self, + ra_verifier: &EnclaveCertVerifier, + now: Timespec, + ) -> Result { if self.cipher_suite != MLS10_128_DHKEMP256_AES128GCM_SHA256_P256 { return Err(Error::UnsupportedCipherSuite(self.cipher_suite)); } @@ -108,13 +114,13 @@ impl KeyPackagePayload { let x509 = self.credential.x509().ok_or(Error::InvalidCredential)?; - let cert_pubkey = ra_verifier + let info = ra_verifier .verify_cert(x509, (UNIX_EPOCH + Duration::from_secs(now)).into()) .map_err(Error::CertificateVerifyError)?; - if cert_pubkey.as_slice() != self.init_key.as_ref() { + if info.public_key.as_slice() != self.init_key.as_ref() { return Err(Error::InitKeyDontMatch); } - Ok(()) + Ok(info) } } @@ -140,12 +146,17 @@ impl Codec for KeyPackage { impl KeyPackage { /// Verify key package and signature - pub fn verify(&self, ra_verifier: &EnclaveCertVerifier, now: Timespec) -> Result<(), Error> { - self.payload.verify(ra_verifier, now)?; + pub fn verify( + &self, + ra_verifier: &EnclaveCertVerifier, + now: Timespec, + ) -> Result { + let info = self.payload.verify(ra_verifier, now)?; self.payload .init_key .verify_signature(&self.payload.get_encoding(), &self.signature) - .map_err(Error::SignatureVerifyError) + .map_err(Error::SignatureVerifyError)?; + Ok(info) } } @@ -173,8 +184,11 @@ impl OwnedKeyPackage { let extensions = vec![ ext::SupportedVersionsExt(vec![PROTOCOL_VERSION_MLS10]).entry(), ext::SupportedCipherSuitesExt(vec![MLS10_128_DHKEMP256_AES128GCM_SHA256_P256]).entry(), - ext::LifeTimeExt::new(not_before.to_timespec().sec, not_after.to_timespec().sec) - .entry(), + ext::LifeTimeExt::new( + not_before.to_timespec().sec.try_into().unwrap(), + not_after.to_timespec().sec.try_into().unwrap(), + ) + .entry(), ]; let private_key = PrivateKey::from_pkcs8(&private_key.0).expect("invalid private key"); diff --git a/chain-tx-enclave-next/mls/src/lib.rs b/chain-tx-enclave-next/mls/src/lib.rs index 6d0c279e8..30270fe12 100644 --- a/chain-tx-enclave-next/mls/src/lib.rs +++ b/chain-tx-enclave-next/mls/src/lib.rs @@ -9,3 +9,4 @@ pub mod tree; pub mod utils; pub use keypackage::{KeyPackage, OwnedKeyPackage}; +pub use rustls::internal::msgs::codec::{self, Codec, Reader}; diff --git a/chain-tx-enclave-next/mls/tests/test_vectors/keypackage.bin b/chain-tx-enclave-next/mls/tests/test_vectors/keypackage.bin index ca1572ad4..02a00891b 100644 Binary files a/chain-tx-enclave-next/mls/tests/test_vectors/keypackage.bin and b/chain-tx-enclave-next/mls/tests/test_vectors/keypackage.bin differ diff --git a/chain-tx-enclave-next/mls/tests/verify_test_vectors.rs b/chain-tx-enclave-next/mls/tests/verify_test_vectors.rs index 9ef76beca..f8f39c3e4 100644 --- a/chain-tx-enclave-next/mls/tests/verify_test_vectors.rs +++ b/chain-tx-enclave-next/mls/tests/verify_test_vectors.rs @@ -4,16 +4,15 @@ use mls::{ keypackage::{Error, DEFAULT_LIFE_TIME}, KeyPackage, }; -use ra_client::{EnclaveCertVerifier, EnclaveCertVerifierConfig}; +use ra_client::ENCLAVE_CERT_VERIFIER; #[test] fn verify_keypackage_test_vector_mock() { // keypackage_mock.bin is generated by golang implementation static VECTOR: &[u8] = include_bytes!("test_vectors/keypackage_mock.bin"); let kp = ::read_bytes(VECTOR).expect("decode"); - let verifier = EnclaveCertVerifier::new(Default::default()).unwrap(); assert!(matches!( - kp.verify(&verifier, 0), + kp.verify(&ENCLAVE_CERT_VERIFIER, 0), Err(Error::InvalidCredential) )); } @@ -23,17 +22,11 @@ fn verify_keypackage_test_vector() { static VECTOR: &[u8] = include_bytes!("test_vectors/keypackage.bin"); let kp = ::read_bytes(VECTOR).expect("decode"); - let verifier = EnclaveCertVerifier::new(EnclaveCertVerifierConfig { - // FIXME GROUP_OUT_OF_DATE is not valid status - valid_enclave_quote_statuses: vec!["OK".into(), "GROUP_OUT_OF_DATE".into()].into(), - ..Default::default() - }) - .unwrap(); - let now = 1590042554; + let now = 1590490084; let expire = now + DEFAULT_LIFE_TIME; - kp.verify(&verifier, now).unwrap(); + kp.verify(&ENCLAVE_CERT_VERIFIER, now).unwrap(); assert!(matches!( - kp.verify(&verifier, expire + 1), + kp.verify(&ENCLAVE_CERT_VERIFIER, expire + 1), Err(Error::NotAfter(_)) )); } diff --git a/client-cli/Cargo.toml b/client-cli/Cargo.toml index 96e914b92..66b233d2f 100644 --- a/client-cli/Cargo.toml +++ b/client-cli/Cargo.toml @@ -15,6 +15,7 @@ chain-core = { path = "../chain-core"} client-common = { path = "../client-common" } client-core = { path = "../client-core" } client-network = { path = "../client-network" } +test-common = { path = "../test-common" } rand = "0.7" once_cell = "1.4" structopt = "0.3" diff --git a/client-cli/src/command/transaction_command.rs b/client-cli/src/command/transaction_command.rs index e740a6c5c..46b7b7191 100644 --- a/client-cli/src/command/transaction_command.rs +++ b/client-cli/src/command/transaction_command.rs @@ -8,9 +8,7 @@ use std::str::FromStr; use chain_core::common::{Timespec, HASH_SIZE_256}; use chain_core::init::coin::Coin; use chain_core::init::network::get_network_id; -use chain_core::state::account::{ - ConfidentialInit, CouncilNode, StakedStateAddress, StakedStateOpAttributes, -}; +use chain_core::state::account::{CouncilNode, StakedStateAddress, StakedStateOpAttributes}; use chain_core::state::tendermint::TendermintValidatorPubKey; use chain_core::tx::data::access::{TxAccess, TxAccessPolicy}; use chain_core::tx::data::address::ExtendedAddr; @@ -23,6 +21,7 @@ use client_core::transaction_builder::SignedTransferTransaction; use client_core::types::{BalanceChange, TransactionPending}; use client_core::WalletClient; use client_network::NetworkOpsClient; +use test_common::chain_env::mock_confidential_init; use chrono::{DateTime, Local, NaiveDateTime, Utc}; use cli_table::format::{CellFormat, Color, Justify}; @@ -1038,8 +1037,7 @@ fn ask_node_metadata() -> Result { name, security_contact: None, consensus_pubkey: TendermintValidatorPubKey::Ed25519(pubkey_bytes), - confidential_init: ConfidentialInit { - keypackage: b"FIXME".to_vec(), - }, + // FIXME real keypackage + confidential_init: mock_confidential_init(), }) } diff --git a/client-network/Cargo.toml b/client-network/Cargo.toml index 292633f34..83d7becf6 100644 --- a/client-network/Cargo.toml +++ b/client-network/Cargo.toml @@ -22,3 +22,4 @@ tendermint = { git = "https://github.com/crypto-com/tendermint-rs.git", default- [dev-dependencies] secp256k1zkp = { git = "https://github.com/crypto-com/rust-secp256k1-zkp.git", rev = "f8759809f6e3fed793b37166f7cd91c57cdb2eab", features = ["serde", "zeroize", "rand", "recovery", "endomorphism"] } +test-common = { path = "../test-common" } diff --git a/client-network/src/network_ops/default_network_ops_client.rs b/client-network/src/network_ops/default_network_ops_client.rs index 558f7dce8..f7f24c3fc 100644 --- a/client-network/src/network_ops/default_network_ops_client.rs +++ b/client-network/src/network_ops/default_network_ops_client.rs @@ -542,9 +542,7 @@ mod tests { use chain_core::init::address::RedeemAddress; use chain_core::init::coin::CoinError; - use chain_core::state::account::{ - ConfidentialInit, StakedState, StakedStateOpAttributes, Validator, - }; + use chain_core::state::account::{StakedState, StakedStateOpAttributes, Validator}; use chain_core::state::tendermint::BlockHeight; use chain_core::state::tendermint::TendermintValidatorPubKey; use chain_core::state::ChainState; @@ -563,6 +561,7 @@ mod tests { use client_core::signer::WalletSignerManager; use client_core::types::WalletKind; use client_core::wallet::DefaultWalletClient; + use test_common::chain_env::mock_confidential_init; #[derive(Debug, Clone)] struct MockTransactionCipher; @@ -680,9 +679,7 @@ mod tests { Some(Validator { council_node: CouncilNode::new( TendermintValidatorPubKey::Ed25519([0xcd; 32]), - ConfidentialInit { - keypackage: b"FIXME".to_vec(), - }, + mock_confidential_init(), ), jailed_until: Some(100), inactive_time: Some(0), @@ -1180,9 +1177,7 @@ mod tests { name: "test".to_owned(), security_contact: None, consensus_pubkey: TendermintValidatorPubKey::Ed25519(validator_pubkey), - confidential_init: ConfidentialInit { - keypackage: b"FIXME".to_vec(), - }, + confidential_init: mock_confidential_init(), }; let transaction = network_ops_client diff --git a/client-rpc/Cargo.toml b/client-rpc/Cargo.toml index 28cf121c1..8733dac52 100644 --- a/client-rpc/Cargo.toml +++ b/client-rpc/Cargo.toml @@ -9,6 +9,7 @@ chain-core = { path = "../chain-core" } client-common = { path = "../client-common" } client-core = { path = "../client-core" } client-network = { path= "../client-network"} +test-common = { path = "../test-common" } base64 = "0.11" jsonrpc-core = "14.1" diff --git a/client-rpc/src/rpc/staking_rpc.rs b/client-rpc/src/rpc/staking_rpc.rs index 875752aaa..266a3c8b5 100644 --- a/client-rpc/src/rpc/staking_rpc.rs +++ b/client-rpc/src/rpc/staking_rpc.rs @@ -1,3 +1,4 @@ +use std::collections::BTreeSet; use std::str::FromStr; use jsonrpc_core::Result; @@ -6,7 +7,7 @@ use jsonrpc_derive::rpc; use crate::{rpc_error_from_string, to_rpc_error}; use chain_core::init::coin::Coin; use chain_core::state::account::{ - ConfidentialInit, CouncilNode, StakedState, StakedStateAddress, StakedStateOpAttributes, + CouncilNode, StakedState, StakedStateAddress, StakedStateOpAttributes, }; use chain_core::state::tendermint::TendermintValidatorPubKey; use chain_core::tx::data::access::{TxAccess, TxAccessPolicy}; @@ -18,7 +19,7 @@ use client_common::{Error, ErrorKind, PublicKey, Result as CommonResult, ResultE use client_core::wallet::WalletRequest; use client_core::{MultiSigWalletClient, WalletClient}; use client_network::NetworkOpsClient; -use std::collections::BTreeSet; +use test_common::chain_env::mock_confidential_init; #[rpc(server)] pub trait StakingRpc: Send + Sync { @@ -461,8 +462,7 @@ fn get_node_metadata(validator_name: &str, validator_pubkey: &str) -> Result, ) -> Result> { let to_address = StakedStateAddress::from_str(&to_address_user).chain(|| { ( @@ -111,9 +113,7 @@ fn create_encoded_signed_join( security_contact: Some(validator_contact.to_string()), // 32 bytes consensus_pubkey: pubkey, - confidential_init: ConfidentialInit { - keypackage: b"FIXME".to_vec(), - }, + confidential_init: ConfidentialInit { keypackage }, }; let transaction: NodeJoinRequestTx = NodeJoinRequestTx { nonce, @@ -149,6 +149,8 @@ pub unsafe extern "C" fn cro_join( validator_name_user: *const c_char, validator_contact_user: *const c_char, validator_pubkey_user: *const c_char, + keypackage: *const u8, + keypackage_len: usize, output: *mut u8, output_length: *mut u32, ) -> CroResult { @@ -166,6 +168,7 @@ pub unsafe extern "C" fn cro_join( &validator_name, &validator_contact, &validator_pubkey, + slice::from_raw_parts(keypackage, keypackage_len).to_vec(), ) { Ok(encoded) => { ptr::copy_nonoverlapping(encoded.as_ptr(), output, encoded.len()); diff --git a/dev-utils/Cargo.toml b/dev-utils/Cargo.toml index c7b2f25ef..1855eb2dd 100644 --- a/dev-utils/Cargo.toml +++ b/dev-utils/Cargo.toml @@ -11,6 +11,7 @@ client-core = { path = "../client-core" } client-network = { path = "../client-network" } chain-core = { path = "../chain-core/" } chain-abci = { path = "../chain-abci/" } +test-common = { path = "../test-common" } structopt = "0.3" hex = "0.4" serde_json = "1.0" diff --git a/dev-utils/src/commands/init_command.rs b/dev-utils/src/commands/init_command.rs index 00df1e74b..c6c505f6d 100644 --- a/dev-utils/src/commands/init_command.rs +++ b/dev-utils/src/commands/init_command.rs @@ -9,15 +9,13 @@ use secstr::SecUtf8; use serde_json::json; use chain_core::init::{address::RedeemAddress, coin::Coin, config::InitConfig}; -use chain_core::state::{ - account::ConfidentialInit, - tendermint::{TendermintValidator, TendermintValidatorPubKey}, -}; +use chain_core::state::tendermint::{TendermintValidator, TendermintValidatorPubKey}; use client_common::storage::SledStorage; use client_common::tendermint::types::Time; use client_common::{Error, ErrorKind, Result, ResultExt}; use client_core::types::WalletKind; use client_core::wallet::{DefaultWalletClient, WalletClient}; +use test_common::chain_env::mock_confidential_init; use super::genesis_command::generate_genesis; use super::genesis_dev_config::GenesisDevConfig; @@ -190,9 +188,8 @@ impl InitCommand { "dev test".to_owned(), None, pubkey, - ConfidentialInit { - keypackage: b"FIXME".to_vec(), - }, + // FIXME real keypackage + mock_confidential_init(), ), ); Ok(()) diff --git a/dev-utils/src/commands/test_vector_command.rs b/dev-utils/src/commands/test_vector_command.rs index ac15be12e..6bd1a173f 100644 --- a/dev-utils/src/commands/test_vector_command.rs +++ b/dev-utils/src/commands/test_vector_command.rs @@ -6,8 +6,8 @@ use chain_core::init::address::{CroAddress, RedeemAddress}; use chain_core::init::coin::Coin; use chain_core::init::network::Network; use chain_core::state::account::{ - ConfidentialInit, CouncilNode, DepositBondTx, StakedStateAddress, StakedStateOpAttributes, - StakedStateOpWitness, UnbondTx, WithdrawUnbondedTx, + CouncilNode, DepositBondTx, StakedStateAddress, StakedStateOpAttributes, StakedStateOpWitness, + UnbondTx, WithdrawUnbondedTx, }; use chain_core::state::tendermint::TendermintValidatorPubKey; use chain_core::state::validator::NodeJoinRequestTx; @@ -27,6 +27,7 @@ use client_core::service::{HDAccountType, HdKey}; use client_core::HDSeed; use secp256k1::Secp256k1; use secp256k1::{key::XOnlyPublicKey, SecretKey}; +use test_common::chain_env::mock_confidential_init; #[derive(Debug)] pub struct TestVectorCommand { @@ -321,9 +322,8 @@ impl VectorFactory { "example".to_string(), Some("security@example.com".to_string()), tendermint_validator_pubkey.clone(), - ConfidentialInit { - keypackage: b"FIXME".to_vec(), - }, + // real keypackage + mock_confidential_init(), ), ); let txid = tx.id(); diff --git a/docker/config/devnet/dev-conf.json b/docker/config/devnet/dev-conf.json index c1ca05307..83b75ce27 100644 --- a/docker/config/devnet/dev-conf.json +++ b/docker/config/devnet/dev-conf.json @@ -33,7 +33,7 @@ "type": "tendermint/PubKeyEd25519", "value": "FF5JxhRrCUNLj6UZmYdjv/AWgSWUeiomeOMeJG71owE=" }, - {"keypackage": "RklYTUU="} + {"keypackage": "AAACAEEES5svZvBIGyrEUt9I8grzrdFagYx7WtOo9azjgGhhgj7yfUkH02xaDKIG4+fLT2ctPoBGuiljMlxEBZVTCT16dQEAP+gwgj/kMII/iaADAgECAgEqMAoGCCqGSM49BAMCMCoxEzARBgNVBAoMCkNyeXB0by5jb20xEzARBgNVBAMMCkNyeXB0by5jb20wIhgPMTk3MDAxMDEwMDAwMDBaGA8yMDIwMDgyNDEwNDc0OFowKjETMBEGA1UECgwKQ3J5cHRvLmNvbTETMBEGA1UEAwwKQ3J5cHRvLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEubL2bwSBsqxFLfSPIK863RWoGMe1rTqPWs44BoYYI+8n1JB9NsWgyiBuPny09nLT6ARropYzJcRAWVUwk9enWjgj6aMII+ljAeBgNVHREEFzAVgRNzZWN1cml0eUBjcnlwdG8uY29tMII+cgYJYIZIAYb4QgENBII+Y3siYm9keSI6WzEyMywzNCwxMDUsMTAwLDM0LDU4LDM0LDUxLDQ5LDU2LDUxLDUwLDUxLDU0LDU0LDU3LDQ5LDU3LDU0LDU0LDUyLDQ5LDU3LDU3LDUwLDUyLDUwLDUxLDQ4LDUxLDU2LDUxLDU1LDUxLDU3LDU0LDUzLDUzLDU2LDU0LDUxLDU0LDU1LDU0LDUzLDQ4LDM0LDQ0LDM0LDExNiwxMDUsMTA5LDEwMSwxMTUsMTE2LDk3LDEwOSwxMTIsMzQsNTgsMzQsNTAsNDgsNTAsNDgsNDUsNDgsNTMsNDUsNTAsNTQsODQsNDksNDgsNTgsNTIsNTUsNTgsNTMsNDgsNDYsNTUsNTEsNTIsNTMsNTUsNTQsMzQsNDQsMzQsMTE4LDEwMSwxMTQsMTE1LDEwNSwxMTEsMTEwLDM0LDU4LDUyLDQ0LDM0LDk3LDEwMCwxMTgsMTA1LDExNSwxMTEsMTE0LDEyMSw4NSw4Miw3NiwzNCw1OCwzNCwxMDQsMTE2LDExNiwxMTIsMTE1LDU4LDQ3LDQ3LDExNSwxMDEsOTksMTE3LDExNCwxMDUsMTE2LDEyMSw0NSw5OSwxMDEsMTEwLDExNiwxMDEsMTE0LDQ2LDEwNSwxMTAsMTE2LDEwMSwxMDgsNDYsOTksMTExLDEwOSwzNCw0NCwzNCw5NywxMDAsMTE4LDEwNSwxMTUsMTExLDExNCwxMjEsNzMsNjgsMTE1LDM0LDU4LDkxLDM0LDczLDc4LDg0LDY5LDc2LDQ1LDgzLDY1LDQ1LDQ4LDQ4LDUxLDUxLDUyLDM0LDkzLDQ0LDM0LDEwNSwxMTUsMTE4LDY5LDExMCw5OSwxMDgsOTcsMTE4LDEwMSw4MSwxMTcsMTExLDExNiwxMDEsODMsMTE2LDk3LDExNiwxMTcsMTE1LDM0LDU4LDM0LDgzLDg3LDk1LDcyLDY1LDgyLDY4LDY5LDc4LDczLDc4LDcxLDk1LDc4LDY5LDY5LDY4LDY5LDY4LDM0LDQ0LDM0LDEwNSwxMTUsMTE4LDY5LDExMCw5OSwxMDgsOTcsMTE4LDEwMSw4MSwxMTcsMTExLDExNiwxMDEsNjYsMTExLDEwMCwxMjEsMzQsNTgsMzQsNjUsMTAzLDY1LDY1LDY1LDc3LDg1LDc2LDY1LDY1LDY1LDc2LDY1LDY1LDExMSw2NSw2NSw2NSw2NSw2NSw2NSw3NCw1MSw5OCwxMDQsMTAzLDc5LDc5LDY1LDg1LDExMyw2OSw2OCwxMTcsMTE2LDkwLDc4LDEwNSw0Nyw3OSw3MSwxMDYsODUsNDMsMTAzLDgxLDExMCw4Miw4NSwxMDksMTIwLDEwMyw3Miw3Miw1Miw3NSw3NCw4NCw5OSw4MCw3OCw4OCwxMDAsNDcsNjgsMTE5LDU2LDY3LDY2LDEwMiw0Myw2NSw2NiwxMTksNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjYsMTE5LDY1LDY1LDY1LDY1LDY1LDY1LDY1LDY1LDY1LDEwMiw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw3NCw0OSw3Miw3MCw4Miw4Myw1MywxMTksODMsNDMsODAsNzIsNDksMTA0LDY4LDc4LDEyMCwxMDUsMTExLDEwOSw3Nyw5MCw3NCwxMDMsMTEwLDU2LDEyMiwxMTAsMTA2LDczLDEwMyw4MCwxMTgsODIsODcsOTgsOTksNTAsNzUsNzYsNTcsMTIwLDExOSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2Nyw5Nyw0Nyw1Niw0MywxMTcsODIsNTUsMTA0LDczLDU1LDY3LDEyMSwxMTgsNzIsNjksMTA5LDQ4LDExNSwxMTEsNzgsODQsNzIsMTA0LDEyMiw2OSw3NCwxMDIsMTA3LDQ5LDEwMywxMTQsNzgsMTExLDY2LDExNyw4NSwxMTMsODEsNTcsMTAxLDc4LDcxLDEwMyw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2Niw3NiwxMDksMTIxLDU3LDEwOSw1Niw2OSwxMDMsOTgsNzUsMTE1LDgyLDgzLDUxLDQ4LDEwNiwxMjEsNjcsMTE4LDc5LDExNiw0OCw4NiwxMTMsNjYsMTA2LDcyLDExNiw5Nyw0OCw1NCwxMDYsNDksMTE0LDc5LDc5LDY1LDk3LDcxLDcxLDY3LDgwLDExOCw3NCw1Nyw4Myw4MSwxMDIsODQsOTgsNzAsMTExLDc3LDExMSwxMDMsOTgsMTA2LDUzLDU2LDExNiw4MCw5MCwxMjEsNDgsNDMsMTAzLDY5LDk3LDU0LDc1LDg3LDc3LDEyMSw4OCw2OSw4MSw3MCwxMDgsODYsNzcsNzQsODAsODgsMTEyLDQ5LDM0LDEyNV0sInNpZ25hdHVyZSI6WzE2MCw0MSwxOTEsMjUsMTkzLDIyOCwyOCwxMzYsMjA5LDY3LDIzMSwxNTgsMTY4LDIzNyw5LDE4NSw0MCw1OCwxMzcsNDksMTA1LDE3Miw1LDIwNCwyMzQsODEsODMsMTMwLDE1MywxMjgsMTQ1LDI1NCwxNjMsNTcsMTU1LDEsMjI0LDEyLDExNCwyMTksNTIsMjA2LDk2LDI0MSwxNzgsMTQsODMsMzAsMTQwLDIyOCwyMTcsODEsMjYsMTczLDIyMywyLDE4MywxMDIsMTAzLDIzMSwyMjEsMTQ2LDU4LDE2NSwyMzgsMTI1LDY1LDE3OCw2NSwyMTQsMjA3LDQzLDg1LDEzMywyMTUsMjYsMTE2LDE3NSwxNDIsMjQwLDc0LDE5NywxMjAsMTQzLDEyMywyMiwyOCw0LDEwNiw2Niw3NiwyMzIsMjA3LDE2OCw5OSw1LDIxNCwxNTIsMTEwLDE0Miw4Niw3MywyNCw5MCwyMzYsMTMyLDIyNCw4OSw4LDIzMywxNzgsMzksMzgsMTg3LDIyNiwxNTMsMzAsNDQsMTQxLDU1LDE1LDE3OSwyMDUsMTY1LDIwLDI2LDIxMCw1Miw4NSwyMTUsNjcsMTQxLDQzLDE3MCw4NywxMjMsNzYsMTIyLDE4LDUxLDExNywyMjksNjQsMTA0LDM2LDk3LDExNSwxMDMsMTY0LDE3Miw4Nyw4NywxNzgsMTIyLDE3MSwxMzMsMTIsNTgsMTE0LDE0Niw1NiwyMTgsMjQ3LDEyNiwxOTYsNjksMjExLDE0MiwxNTQsMTg3LDE5MywxOCwxMjQsMTYzLDIzNCwxNzgsMjQ0LDY4LDQ2LDI4LDEwLDE0Myw3NiwxOTQsMjUsMjA5LDg5LDIxMCwyMzgsMTY0LDE4MCwwLDExNSwxMTksMTM2LDI0MiwyNDksMjIzLDEwMyw1NywyMSwxODIsMTMyLDYxLDg0LDUwLDIxNyw4MSw4NiwyMDAsMTMsMTQ1LDAsMTI4LDEyOCw2NiwyMTcsMjEzLDEwMCwyMDksMTcxLDIsMTQxLDIyOCwyMjYsMTU5LDE0NiwyMDQsMTI4LDEzNywxMTEsOTEsMjIzLDE5OSwyMTEsMTI1LDM2LDI5LDEwNCwyNTUsMjE2LDE3MCwxMzEsMTE1LDIwNCwxNTIsMTA1LDE0NywxMTAsMzcsMjAxLDIyNywyMDUsNywxNjQsNjFdLCJzaWduaW5nX2NlcnQiOls0NSw0NSw0NSw0NSw0NSw2Niw2OSw3MSw3Myw3OCwzNyw1MCw0OCw2Nyw2OSw4Miw4NCw3Myw3MCw3Myw2Nyw2NSw4NCw2OSw0NSw0NSw0NSw0NSw0NSwzNyw0OCw2NSw3Nyw3Myw3Myw2OSwxMTEsODQsNjcsNjcsNjUsMTE5LDEwOSwxMDMsNjUsMTE5LDczLDY2LDY1LDEwMyw3Myw3NCw2NSw3OCw2OSw3MiwxMDAsMTA4LDQ4LDEyMSwxMTEsNTUsNjcsODcsNzcsNjUsNDgsNzEsNjcsODMsMTEzLDcxLDgzLDczLDk4LDUxLDY4LDgxLDY5LDY2LDY3LDExOSw4NSw2NSw3Nyw3Miw1MiwxMjAsNjcsMTIyLDY1LDc0LDY2LDEwMyw3OCw4NiwzNyw0OCw2NSw2Niw2NSw4OSw4NCw2NSwxMDgsODYsODQsNzcsODEsMTE1LDExOSw2Nyw4MSw4OSw2OCw4Niw4MSw4MSw3Myw2OCw2NSw3NCw2OCw4MSw4NCw2OSw4NSw3Nyw2Niw3Myw3MSw2NSw0OSw4NSw2OSw2NiwxMTksMTE5LDc2LDg1LDUwLDcwLDExNywxMDAsNzEsNjksMTAzLDgxLDUwLDEyMCwxMDQsOTksMTA5LDY5LDEyMCw3MSwxMDYsNjUsODksNjYsMTAzLDc4LDg2LDM3LDQ4LDY1LDY2LDY1LDExMSw3Nyw2OSw4NSwxMDgsMTE3LDEwMCw3MSw4NiwxMTUsNzMsNjksNzgsMTE4LDk5LDExMCw2NiwxMTgsOTksMTA5LDcwLDQ4LDk3LDg3LDU3LDExNyw3Nyw4NCw2NSwxMTksNzYsMTAzLDg5LDY4LDg2LDgxLDgxLDY4LDY4LDY3LDEwMCw3NCw5OCwxMTAsODIsMTA4LDk4LDY3LDY2LDg0LDgyLDQ5LDEwMywxMDMsODEsODgsODIsNDgsOTAsODgsNzgsNDgsMzcsNDgsNjUsODksODgsODIsMTEyLDk4LDUwLDUyLDEwMyw4NSwxMDksODYsMTE5LDk4LDUxLDc0LDQ4LDczLDcwLDc4LDExMiw5MCw1MCw1MywxMTIsOTgsMTA5LDk5LDEwMyw4MSw0OCw2OSwxMTksNzIsMTA0LDk5LDc4LDc3LDg0LDg5LDEyMCw3Nyw4NCw3MywxMjEsNzcsNjgsMTA3LDEyMiw3OCwxMDYsODUsNTIsODcsMTA0LDk5LDc4LDc3LDEwNiw4OSwxMjAsNzcsODQsNzMsMTE5LDM3LDQ4LDY1LDc3LDY4LDEwNywxMjIsNzgsMTA2LDg1LDUyLDg3LDEwNiw2Niw1NSw3Nyw4MSwxMTUsMTE5LDY3LDgxLDg5LDY4LDg2LDgxLDgxLDcxLDY5LDExOSw3NCw4Niw4NSwxMjIsNjksNzYsNzcsNjUsMTA3LDcxLDY1LDQ5LDg1LDY5LDY3LDY1LDExOSw2Nyw4MSw0OCw2OSwxMjAsNzAsNjgsNjUsODMsNjYsMTAzLDc4LDg2LDY2LDY1LDk5LDc3LDY3LDQ5LDc4LDEwNCwzNyw0OCw2NSw5OCwxMTAsODIsMTA0LDczLDY5LDc4LDExNSw4OSw4OCw3NCwxMDQsNzcsODIsMTExLDExOSw3MSw2NSw4OSw2OCw4Niw4MSw4MSw3NSw2OCw2Niw3MCw3NCw5OCwxMTAsODIsMTA4LDk4LDY3LDY2LDY4LDk4LDUxLDc0LDExOSw5OCw1MSw3NCwxMDQsMTAwLDcxLDEwOCwxMTgsOTgsMTA2LDY5LDExNiw3Nyw2NywxMTUsNzEsNjUsNDksODUsNjksNjUsMTE5LDExOSwxMDcsMzcsNDgsNjUsODMsODcsNTMsNDgsOTAsODcsMTE5LDEwMyw4NSw0OCwxMDAsODksNzMsNjksNzAsNDgsMTAwLDcxLDg2LDEyMiwxMDAsNzEsNzAsNDgsOTcsODcsNTcsMTE3LDczLDcwLDc0LDEwOCw5OSw3MSw1NywxMjEsMTAwLDY3LDY2LDg0LDk3LDg3LDEwMCwxMTcsOTcsODcsNTMsMTEwLDc3LDczLDczLDY2LDczLDEwNiw2NSw3OCw2NiwxMDMsMTA3LDExMywxMDQsMTA3LDEwNSw3MSwzNyw0OCw2NSw1NywxMTksNDgsNjYsNjUsODEsNjksNzAsNjUsNjUsNzksNjcsNjUsODEsNTYsNjUsNzcsNzMsNzMsNjYsNjcsMTAzLDc1LDY3LDY1LDgxLDY5LDY1LDExMyw4OCwxMTEsMTE2LDUyLDc5LDkwLDExNywxMTIsMTA0LDgyLDU2LDExMCwxMTcsMTAwLDcwLDExNCw2NSw3MCwxMDUsOTcsNzEsMTIwLDEyMCwxMDcsMTAzLDEwOSw5Nyw0Nyw2OSwxMTUsNDcsNjYsNjUsMzcsNTAsNjYsMTE2LDM3LDQ4LDY1LDk4LDEwMSw2Nyw4NCw4NSw4Miw0OSw0OCw1NCw2NSw3Niw0OSw2OSw3OCw5OSw4Nyw2NSw1Miw3MCw4OCw1MSw3NSwzNyw1MCw2Niw2OSw1Nyw2Niw2Niw3Niw0OCw0Nyw1NSw4OCw1MywxMTQsMTA2LDUzLDExMCw3MywxMDMsODgsNDcsODIsNDcsNDksMTE3LDk4LDEwNCwxMDcsNzUsODcsMTE5LDU3LDEwMywxMDIsMTEzLDgwLDcxLDUxLDc1LDEwMSw2NSwxMTYsNzMsMTAwLDM3LDQ4LDY1LDk5LDExOCw0NywxMTcsODQsNzksNDksMTIxLDg4LDExOCw1Myw0OCwxMTgsMTEzLDk3LDgwLDExOCw2OSw0OSw2Nyw4Miw2NywxMDQsMTE4LDEyMiwxMDAsODMsNDcsOTAsNjksNjYsMTEzLDgxLDUzLDExMSw4NiwxMTgsNzYsODQsODAsOTAsNTEsODYsNjksMTA1LDk5LDgxLDEwNiwxMDgsMTIxLDExNiw3NSwxMDMsNzgsNTcsOTksNzYsMTEwLDEyMCw5OCwxMTksMTE2LDExNywxMTgsMzcsNDgsNjUsNzYsODUsNzUsNTUsMTAxLDEyMSw4Miw4MCwxMDIsNzQsODcsNDcsMTA3LDExNSwxMDAsMTAwLDc5LDEyMiw4MCw1Niw4Niw2Niw2NiwxMTAsMTA1LDExMSwxMDgsODksMTEwLDgyLDY3LDY4LDUwLDEwNiwxMTQsNzcsODIsOTAsNTYsMTEwLDY2LDc3LDUwLDkwLDg3LDg5LDExOSwxMTAsODgsMTEwLDExOSw4OSwxMDEsNzksNjUsNzIsODYsMzcsNTAsNjYsODcsNTcsMTE2LDc5LDEwNCw2NSwzNyw0OCw2NSw3MywxMDksMTE5LDgyLDExOSw3NSw3MCw0Nyw1Nyw1MywxMjEsNjUsMTE1LDg2LDExOSwxMDAsNTAsNDksMTE0LDEyMSw3Miw3Nyw3NCw2Niw5OSw3MSw3Miw1NSw0OCwxMTMsNzYsOTcsMTAzLDkwLDU1LDg0LDExNiwxMjEsMTE2LDM3LDUwLDY2LDM3LDUwLDY2LDExMyw3OSw0Nyw1NCwzNyw1MCw2Niw3NSw2NSw4OCw3NCwxMTcsNzUsMTE5LDkwLDExMywxMDYsODIsMTA4LDY5LDExNiw4Myw2OSwxMjIsNTYsMzcsNDgsNjUsMTAzLDkwLDgxLDEwMSw3MCwxMDIsODYsODksMTAzLDk5LDExOSw4MywxMDIsMTExLDU3LDU0LDExMSw4Myw3Nyw2NSwxMjIsODYsMTE0LDU1LDg2LDQ4LDc2LDU0LDcyLDgzLDY4LDc2LDgyLDExMCwxMTIsOTgsNTQsMTIwLDEyMCwxMDksOTgsODAsMTAwLDExMyw3OCwxMTEsMTA4LDUyLDExNiw4MSw3Myw2OCw2NSw4MSw2NSw2NiwxMTEsNTIsNzEsMTA3LDc3LDczLDcxLDEwNCwzNyw0OCw2NSw3Nyw2Niw1Niw3MSw2NSw0OSw4NSwxMDAsNzMsMTE5LDgxLDg5LDc3LDY2LDk3LDY1LDcwLDcyLDEwNCw2OCwxMDEsNTEsOTcsMTA5LDEwMiwxMTQsMTIyLDgxLDExNCw1MSw1Myw2Nyw3OCwzNyw1MCw2NiwxMTUsNDksMTAyLDY4LDExNyw3Miw2NSw4Niw2OSw1Niw3Nyw2NSw1Miw3MSw2NSw0OSw4NSwxMDAsNjgsMTE5LDY5LDY2LDQ3LDExOSw4MSw2OSw2NSwxMTksNzMsNzEsMzcsNDgsNjUsMTE5LDY4LDY1LDc3LDY2LDEwMyw3OCw4Niw3Miw4Miw3Nyw2Niw2NSwxMDIsNTYsNjksNjUsMTA2LDY1LDY1LDc3LDcxLDY1LDcxLDY1LDQ5LDg1LDEwMCw3MiwxMTksODIsOTAsNzcsNzAsOTksMTE5LDg2LDk3LDY2LDg0LDExMSw3MCw3MSw3MSw4NCw1MCwxMDQsNDgsMTAwLDcyLDY1LDU0LDc2LDEyMSw1Nyw0OCw5OSwxMTAsODYsMTIyLDEwMCw3MSw4NiwxMDcsMzcsNDgsNjUsOTksNTAsODYsMTIxLDEwMCwxMDksMTA4LDEwNiw5MCw4OCw3NywxMTcsOTcsODcsNTMsNDgsOTAsODcsMTE5LDExNyw4OSw1MCw1NywxMTYsNzYsNTAsNzgsMTE4LDk4LDExMCw4MiwxMDgsOTgsMTEwLDgxLDExOCw4MSw0OSw3NCw3Nyw3Niw0OSw3OCw3Miw4Nyw2Nyw1Nyw2NiwxMDAsNzIsODIsMTA4LDk5LDUxLDgyLDEwNCwxMDAsNzEsMTA4LDExOCw5OCwxMDgsNzQsMTA4LDM3LDQ4LDY1LDk5LDcxLDU3LDEyMSwxMDAsNzAsNzgsMTEyLDkwLDUwLDUzLDExMiw5OCwxMDksMTAwLDY4LDgxLDgzLDUzLDEwNiw5OSwxMDksMTE5LDExOSw2OCw4MSw4OSw3NCw3NSwxMTEsOTAsNzMsMTA0LDExOCw5OSw3OCw2NSw4MSw2OSw3Niw2Niw4MSw2NSw2OCwxMDMsMTAzLDcxLDY2LDY1LDcxLDk5LDczLDExNiwxMDQsMTE2LDk5LDc1LDU3LDczLDg2LDgyLDEyMiw1MiwxMTQsMzcsNDgsNjUsODIsMTEzLDM3LDUwLDY2LDkwLDc1LDY5LDM3LDUwLDY2LDU1LDEwNyw1Myw0OCw0Nyw3OSwxMjAsODUsMTE1LDEwOSw4Nyw1Niw5Nyw5NywxMTgsNzksMTIyLDc1LDk4LDQ4LDEwNSw2NywxMjAsNDgsNTUsODksODEsNTcsMTE0LDEyMiwxMDUsNTMsMTEwLDg1LDU1LDUxLDExNiw3Nyw2OSw1MCwxMjEsNzEsODIsNzYsMTIyLDEwNCw4Myw4NiwxMDUsNzAsMTE1LDQ3LDc2LDExMiw3MCw5Nyw1NywzNyw0OCw2NSwxMDgsMTEyLDgxLDc2LDU0LDc0LDc2LDQ5LDk3LDgxLDExOSwxMDksNjgsODIsNTUsNTIsODQsMTIwLDg5LDcxLDY2LDY1LDczLDEwNSw1MywxMDIsNTIsNzMsNTMsODQsNzQsMTExLDY3LDY3LDY5LDExMyw4Miw3MiwxMjIsNTcsNDksMTA3LDExMiw3MSw1NCw4NSwxMTgsMTIxLDExMCw1MCwxMTYsNzYsMTA5LDExMCw3MywxMDAsNzQsOTgsODAsNjksNTIsMTE4LDg5LDExOCwzNyw0OCw2NSw4Nyw3NiwxMTQsMTE2LDg4LDg4LDEwMiw3MCw2Niw4Myw4Myw4MCw2OCw1Miw2NSwxMDIsMTEwLDU1LDM3LDUwLDY2LDUxLDQ3LDg4LDg1LDEwMywxMDMsNjUsMTA4LDk5LDU1LDExMSw2Nyw4NCwxMDUsMTIyLDc5LDEwMiw5OCw5OCwxMTYsNzksNzAsMTA4LDg5LDY1LDUyLDEwMyw1Myw3NSw5OSw4OSwxMDMsODMsNDksNzQsNTAsOTAsNjUsMTAxLDc3LDgxLDExMyw5OCw4NSwxMDAsMzcsNDgsNjUsOTAsMTE1LDEwMSw5MCw2Nyw5OSw5Nyw5MCw5MCw5MCwxMTAsNTQsNTMsMTE2LDEwMCwxMTMsMTAxLDEwMSw1Niw4NSw4OCw5MCwxMDgsNjgsMTE4LDEyMCw0OCwzNyw1MCw2Niw3OCwxMDAsNzksNDgsNzYsODIsMzcsNTAsNjYsNTMsMTEyLDcwLDEyMSwzNyw1MCw2NiwxMDYsMTE3LDc3LDQ4LDExOSw4Nyw5OCwxMTcsNTMsNTcsNzcsMTE4LDEyMiw5OSwxMDksODQsODgsOTgsMTA2LDExNSwxMDUsNTUsNzIsODksMzcsNDgsNjUsNTQsMTIyLDEwMCw1Myw1MSw4OSwxMTMsNTMsNzUsNTAsNTIsNTIsMTAyLDExOSw3MCw3Miw4Miw4MSw1NiwxMDEsNzksNjYsNDgsNzMsODcsNjYsMzcsNTAsNjYsNTIsODAsMTAyLDc3LDU1LDcwLDEwMSw2NSw2NSwxMTIsOTAsMTE4LDEwOCwxMDIsMTEzLDEwOCw3NSw3OSwxMDgsNzYsOTksOTAsNzYsNTAsMTE3LDEyMSw4NiwxMDksMTIyLDgyLDEwNywxMjEsODIsNTMsMTIxLDg3LDU1LDM3LDQ4LDY1LDUwLDExNywxMTEsNTcsMTA5LDEwMSwxMDQsODgsNTIsNTIsNjcsMTA1LDgwLDc0LDUwLDEwMiwxMTUsMTAxLDU3LDg5LDU0LDEwMSw4MSwxMTYsOTksMTAyLDY5LDEwNCw3Nyw4MCwxMDcsMTA5LDcyLDg4LDczLDQ4LDQ5LDExNSw3OCwzNyw1MCw2Niw3NSwxMTksODAsOTgsMTEyLDY1LDUxLDU3LDM3LDUwLDY2LDEyMCw3OSwxMTUsODMsMTE2LDEwNiwxMDQsODAsNTcsNzgsNDksODksNDksOTcsNTAsMzcsNDgsNjUsMTE2LDgxLDY1LDg2LDExMSwzNyw1MCw2NiwxMjEsODYsMTAzLDc2LDEwMyw4Niw1MCw3MiwxMTksMTE1LDU1LDUxLDcwLDk5LDQ4LDExMSw1MSwxMTksNjcsNTUsNTYsMTEzLDgwLDY5LDY1LDM3LDUwLDY2LDExOCw1MCw5Nyw4MiwxMTUsNDcsNjYsMTAxLDUxLDkwLDcwLDY4LDEwMyw2OCwxMjEsMTAzLDEwNCw5OSw0Nyw0OSwxMDIsMTAzLDg1LDM3LDUwLDY2LDU1LDY3LDM3LDUwLDY2LDgwLDU0LDEwNyw5OCwxMTMsMzcsNDgsNjUsMTAwLDUyLDExMiwxMTEsMTIxLDk4LDU0LDczLDg3LDU2LDc1LDY3LDc0LDk4LDEyMCwxMDIsNzcsNzQsMTE4LDEwNywxMTEsMTE0LDEwMCw3OCw3OSwxMDMsNzksODUsODUsMTIwLDExMCwxMDAsODAsNzIsNjksMTA1LDQ3LDExNiw5OCw0Nyw4NSw1NSwxMTcsNzYsMTA2LDc2LDc5LDEwMyw4MCw2NSwzNyw1MSw2OCwzNyw1MSw2OCwzNyw0OCw2NSw0NSw0NSw0NSw0NSw0NSw2OSw3OCw2OCwzNyw1MCw0OCw2Nyw2OSw4Miw4NCw3Myw3MCw3Myw2Nyw2NSw4NCw2OSw0NSw0NSw0NSw0NSw0NSwzNyw0OCw2NSw0NSw0NSw0NSw0NSw0NSw2Niw2OSw3MSw3Myw3OCwzNyw1MCw0OCw2Nyw2OSw4Miw4NCw3Myw3MCw3Myw2Nyw2NSw4NCw2OSw0NSw0NSw0NSw0NSw0NSwzNyw0OCw2NSw3Nyw3Myw3Myw3MCw4MywxMjIsNjcsNjcsNjUsNTUsNzksMTAzLDY1LDExOSw3Myw2Niw2NSwxMDMsNzMsNzQsNjUsNzgsNjksNzIsMTAwLDEwOCw0OCwxMjEsMTExLDU1LDY3LDg1LDc3LDY1LDQ4LDcxLDY3LDgzLDExMyw3MSw4Myw3Myw5OCw1MSw2OCw4MSw2OSw2Niw2NywxMTksODUsNjUsNzcsNzIsNTIsMTIwLDY3LDEyMiw2NSw3NCw2NiwxMDMsNzgsODYsMzcsNDgsNjUsNjYsNjUsODksODQsNjUsMTA4LDg2LDg0LDc3LDgxLDExNSwxMTksNjcsODEsODksNjgsODYsODEsODEsNzMsNjgsNjUsNzQsNjgsODEsODQsNjksODUsNzcsNjYsNzMsNzEsNjUsNDksODUsNjksNjYsMTE5LDExOSw3Niw4NSw1MCw3MCwxMTcsMTAwLDcxLDY5LDEwMyw4MSw1MCwxMjAsMTA0LDk5LDEwOSw2OSwxMjAsNzEsMTA2LDY1LDg5LDY2LDEwMyw3OCw4NiwzNyw0OCw2NSw2Niw2NSwxMTEsNzcsNjksODUsMTA4LDExNywxMDAsNzEsODYsMTE1LDczLDY5LDc4LDExOCw5OSwxMTAsNjYsMTE4LDk5LDEwOSw3MCw0OCw5Nyw4Nyw1NywxMTcsNzcsODQsNjUsMTE5LDc2LDEwMyw4OSw2OCw4Niw4MSw4MSw2OCw2OCw2NywxMDAsNzQsOTgsMTEwLDgyLDEwOCw5OCw2Nyw2Niw4NCw4Miw0OSwxMDMsMTAzLDgxLDg4LDgyLDQ4LDkwLDg4LDc4LDQ4LDM3LDQ4LDY1LDg5LDg4LDgyLDExMiw5OCw1MCw1MiwxMDMsODUsMTA5LDg2LDExOSw5OCw1MSw3NCw0OCw3Myw3MCw3OCwxMTIsOTAsNTAsNTMsMTEyLDk4LDEwOSw5OSwxMDMsODEsNDgsNjksMTE5LDczLDY2LDk5LDc4LDc3LDg0LDg5LDEyMCw3Nyw4NCw2OSw0OCw3Nyw4NCw4NSwxMjIsNzgsMTIyLDc3LDEyMCw4NywxMDQsMTAzLDgwLDc3LDEwNiw2NSw0OCw3OSw4NCw2OSwxMjEsMzcsNDgsNjUsNzcsMTIyLDY5LDEyMSw3NywxMjIsODUsNTMsNzgsODQsMTA4LDk3LDc3LDcyLDUyLDEyMCw2NywxMjIsNjUsNzQsNjYsMTAzLDc4LDg2LDY2LDY1LDg5LDg0LDY1LDEwOCw4Niw4NCw3Nyw4MSwxMTUsMTE5LDY3LDgxLDg5LDY4LDg2LDgxLDgxLDczLDY4LDY1LDc0LDY4LDgxLDg0LDY5LDg1LDc3LDY2LDczLDcxLDY1LDQ5LDg1LDY5LDY2LDExOSwxMTksNzYsMzcsNDgsNjUsODUsNTAsNzAsMTE3LDEwMCw3MSw2OSwxMDMsODEsNTAsMTIwLDEwNCw5OSwxMDksNjksMTIwLDcxLDEwNiw2NSw4OSw2NiwxMDMsNzgsODYsNjYsNjUsMTExLDc3LDY5LDg1LDEwOCwxMTcsMTAwLDcxLDg2LDExNSw3Myw2OSw3OCwxMTgsOTksMTEwLDY2LDExOCw5OSwxMDksNzAsNDgsOTcsODcsNTcsMTE3LDc3LDg0LDY1LDExOSw3NiwxMDMsODksNjgsODYsODEsODEsNjgsMzcsNDgsNjUsNjgsNjcsMTAwLDc0LDk4LDExMCw4MiwxMDgsOTgsNjcsNjYsODQsODIsNDksMTAzLDEwMyw4MSw4OCw4Miw0OCw5MCw4OCw3OCw0OCw4OSw4OCw4MiwxMTIsOTgsNTAsNTIsMTAzLDg1LDEwOSw4NiwxMTksOTgsNTEsNzQsNDgsNzMsNzAsNzgsMTEyLDkwLDUwLDUzLDExMiw5OCwxMDksOTksMTAzLDgxLDQ4LDY5LDExOSwxMDMsMTAzLDcxLDEwNSw3Nyw2NSw0OCw3MSwzNyw0OCw2NSw2Nyw4MywxMTMsNzEsODMsNzMsOTgsNTEsNjgsODEsNjksNjYsNjUsODEsODUsNjUsNjUsNTIsNzMsNjYsMTA2LDExOSw2NSwxMTksMTAzLDEwMyw3MSw3NSw2NSwxMTEsNzMsNjYsMTAzLDgxLDY3LDEwMiw4MCw3MSw4MiwzNyw1MCw2NiwxMTYsODgsOTksNTYsMTE3LDQ5LDY5LDExNiw3NCwxMjIsNzYsNjUsNDksNDgsNzAsMTAxLDExNyw0OSw4NywxMDMsMzcsNTAsNjYsMTEyLDU1LDEwMSwzNyw0OCw2NSw3NiwxMDksODMsODIsMTA5LDEwMSw5Nyw2Nyw3Miw5OCwxMDcsODEsNDksODQsNzAsNTEsNzgsMTE5LDEwOCw1MSw4MiwxMDksMTEyLDExMyw4OCwxMDcsMTAxLDcxLDEyMiw3OCw3NiwxMDAsNTQsNTcsODEsODUsMTEwLDg3LDExMSwxMTgsODksMTIxLDg2LDgzLDExMCwxMDAsNjksNzcsMTIxLDg5LDk5LDUxLDExNSw3MiwxMDEsOTksNzEsMTAzLDEwMiwxMDUsMTEwLDY5LDEwMSwxMDQsMzcsNDgsNjUsMTE0LDEwMyw2Niw3NCw4Myw2OSwxMDAsMTE1LDgzLDc0LDU3LDcwLDExMiw5Nyw3MCwxMDAsMTAxLDExNSwxMDYsMTE1LDEyMCwxMTMsMTIyLDcxLDgyLDk3LDUwLDQ4LDgwLDg5LDEwMCwxMTAsMTEwLDEwMiw4Nyw5OSw2Nyw4NCwxMTgsNzAsMTExLDExNywxMDgsMTEyLDk4LDcwLDgyLDUyLDg2LDY2LDExNyw4OCwxMTAsMTEwLDg2LDc2LDg2LDEyMiwxMDcsODUsMTE4LDEwOCw4OCw4NCwzNyw0OCw2NSw3Niw0Nyw4NCw2NSwxMTAsMTAwLDU2LDExMCw3Myw5MCwxMDcsNDgsMTIyLDkwLDEwNyw3MCw3NCw1NSw4MCw1Myw3NiwxMTYsMTAxLDgwLDExOCwxMjEsMTA3LDEwNyw5NywxMTQsNTUsNzYsOTksODMsODEsNzksNTYsNTMsMTE5LDExNiw5OSw4MSwxMDEsNDgsODIsNDksODIsOTcsMTAyLDQ3LDExNSw4MSw1NCwxMTksODksNzUsOTcsNzUsMTA5LDcwLDEwMyw2Nyw3MSwxMDEsMzcsNDgsNjUsNzgsMTEyLDY5LDc0LDg1LDEwOSwxMDMsNTIsMTA3LDExNiw5NywxMDgsNTIsMTEzLDEwMyw3Myw2NSwxMjAsMTA3LDM3LDUwLDY2LDgxLDcyLDg1LDEyMCw4MSw2OSw1Miw1MCwxMTUsMTIwLDg2LDEwNSw3OCw1MywxMDksMTEzLDEwMywxMDgsNjYsNDgsODEsNzQsMTAwLDg1LDExMSwxMTYsNDcsMTExLDU3LDk3LDQ3LDg2LDQ3LDEwOSw3NywxMDEsNzIsNTYsNzUsMTE4LDc5LDY1LDEwNSw4MSwzNyw0OCw2NSw5OCwxMjEsMTA1LDExMCwxMDcsNzgsMTEwLDEwMCwxMTAsMzcsNTAsNjYsNjYsMTAzLDEwNyw1MywxMTUsODMsODYsNTMsNjgsNzAsMTAzLDcwLDQ4LDY4LDEwMiwxMDIsODYsMTEzLDEwOSw4Niw3Nyw5OCwxMDgsMTE2LDUzLDExMiw1MSwxMDYsODAsMTE2LDczLDEwOSwxMjIsNjYsNzMsNzIsNDgsODEsODEsMTE0LDg4LDc0LDExMyw1MSw1Nyw2NSw4NCw1Niw5OSw4MiwxMTksODAsNTMsNzIsMzcsNDgsNjUsOTcsMTAyLDExNyw4NiwxMDEsNzYsNzIsOTksNjgsMTE1LDgyLDExMiw1NCwxMDQsMTExLDEwOCw1Miw4MCwzNyw1MCw2Niw5MCw3MCw3MywxMDQsMTE3LDU2LDEwOSwxMDksOTgsNzMsNDksMTE3LDQ4LDEwNCw3Miw1MSw4Nyw0Nyw0OCw2Nyw1MCw2NiwxMTcsODksODgsNjYsNTMsODAsNjcsMzcsNTAsNjYsNTMsMTA1LDEyMiw3MCw3MCwxMDQsNDcsMTEwLDgwLDQ4LDEwOCw5OSw1MCw3NiwxMDIsMzcsNDgsNjUsNTQsMTE0LDY5LDc2LDc5LDU3LDc2LDkwLDEwMCwxMTAsNzksMTA0LDExMiw3Niw0OSw2OSwxMjAsNzAsNzksMTEzLDU3LDcyLDQ3LDY2LDU2LDExNiw4MCw4MSw1Niw1Miw4NCw1MSw4MywxMDMsOTgsNTIsMTEwLDY1LDEwNSwxMDIsNjgsOTcsOTgsNzgsMTE2LDQ3LDEyMiwxMTcsNTQsNzcsMTA5LDY3LDcxLDExMSw1Myw4NSw1NiwxMDgsMTE5LDY5LDcwLDExNiw3MSw3NywzNyw0OCw2NSw4MiwxMTEsNzksOTcsODgsNTIsNjUsODMsMzcsNTAsNjYsNTcsNDgsNTcsMTIwLDQ4LDQ4LDEwOCw4OSwxMTAsMTA5LDExNiwxMTksMTE1LDY4LDg2LDg3LDExOCw1NywxMTgsNjYsMTA1LDc0LDY3LDg4LDgyLDExNSw2Nyw2NSwxMTksNjksNjUsNjUsOTcsNzksNjYsMTIxLDg0LDY3LDY2LDEyMCwxMDYsNjYsMTAzLDY2LDEwMyw3OCw4Niw3Miw4Miw1Niw2OSw4Nyw4NCw2Niw4OCwzNyw0OCw2NSw3Nyw3MCw4NywxMDMsODUsNTQsNjYsODIsMTA0LDEwNyw1NywxMTEsMTAwLDcyLDgyLDExOSw3OSwxMDUsNTYsMTE4LDEwMCw3Miw3NCw0OSw5OSw1MSw4MiwxMDgsOTAsNzIsNzgsMTA4LDk5LDExMCw5MCwxMTIsODksNTAsODYsMTIyLDc2LDEwOSwxMDgsMTE3LDEwMCw3MSw4NiwxMTUsNzYsMTA5LDc4LDExOCw5OCw4Myw1NywxMDYsOTgsNTAsNTMsNDgsOTAsODcsNTMsNDgsMzcsNDgsNjUsNzYsNDgsNzgsODMsODQsNjcsNTcsODQsODIsNDksMTAzLDExOCw4MSw4OCw4Miw0OCw5MCw4OCw3OCw0OCw4OSw4OCw4MiwxMTIsOTgsNTAsNTMsODMsOTAsODgsNjYsMTE4LDk5LDExMCw4Miw4NCw5Nyw4NywxMDAsMTE3LDk3LDg3LDUzLDExMCw4MSw0OCw2OSwxMTcsODksNTEsNzQsMTE1LDc3LDY2LDQ4LDcxLDY1LDQ5LDg1LDEwMCw2OCwxMDMsODEsODcsMzcsNDgsNjUsNjYsNjYsODIsNTIsODEsNTEsMTE2LDUwLDExMiwxMTAsNTQsNTYsNDgsNzUsNTcsMzcsNTAsNjYsODEsMTA2LDEwMiwxMTQsNzgsODgsMTE5LDU1LDEwNCwxMTksNzAsODIsODAsNjgsNjUsMTAyLDY2LDEwMyw3OCw4Niw3Miw4Myw3Nyw2OSw3MSw2OCw2NSw4NywxMDMsNjYsODIsNTIsODEsNTEsMTE2LDUwLDExMiwxMTAsNTQsNTYsNDgsNzUsNTcsMzcsNTAsNjYsODEsMTA2LDEwMiwxMTQsMzcsNDgsNjUsNzgsODgsMTE5LDU1LDEwNCwxMTksNzAsODIsODAsNjgsNjUsNzksNjYsMTAzLDc4LDg2LDcyLDgxLDU2LDY2LDY1LDEwMiw1Niw2OSw2Niw2NSw3Nyw2Nyw2NSw4MSw4OSwxMTksNjksMTAzLDg5LDY4LDg2LDgyLDQ4LDg0LDY1LDgxLDcyLDQ3LDY2LDY1LDEwMywxMTksNjYsMTAzLDY5LDY2LDQ3LDExOSw3Myw2Niw2NSw2OCw2NSw3OCw2NiwxMDMsMTA3LDExMywzNyw0OCw2NSwxMDQsMTA3LDEwNSw3MSw1NywxMTksNDgsNjYsNjUsODEsMTE1LDcwLDY1LDY1LDc5LDY3LDY1LDg5LDY5LDY1LDEwMSw3MCw1NiwxMTYsODksNzcsODgsNzMsNjcsMTE4LDgxLDExMywxMDEsODgsODksODEsNzMsODQsMTA3LDg2LDUwLDExMSw3Niw3NCwxMTUsMTEyLDU0LDc0LDUyLDc0LDY1LDExMyw3NCw5Nyw5OCw3Miw4NywxMjAsODksNzQsNzIsNzEsMTA1LDExNCwzNyw0OCw2NSw3Myw2OSwxMTMsMTE3LDk5LDgyLDEwNSw3NCw4Myw4MywxMjAsMzcsNTAsNjYsNzIsMTA2LDczLDc0LDY5LDg1LDg2LDk3LDEwNiw1Niw2OSw0OCw4MSwxMDYsNjksMTE3LDEwMCw1NCw4OSw1MywxMDgsNzgsMTA5LDg4LDEwOCw5OSwxMDYsMTEzLDgyLDg4LDk3LDY3LDgwLDc5LDExMyw3NSw0OCwxMDEsNzEsODIsMTIyLDU0LDEwNCwxMDUsMzcsNTAsNjYsMTE0LDEwNSwxMTIsNzcsMTE2LDgwLDkwLDM3LDQ4LDY1LDExNSw3MCw3OCw5Nyw2NiwxMTksNzYsODEsODYsODYsNTcsNDgsNTMsODMsNjgsMTA2LDY1LDEyMiw2OCwxMjIsNzgsNzMsNjgsMTEwLDExNCw5OSwxMTAsODgsMTIxLDY2LDUyLDEwMyw5OSw2OCw3MCw2NywxMTgsMTE5LDY4LDcwLDc1LDc1LDEwMyw3Niw4MiwxMDYsNzksNjYsNDcsODcsNjUsMTEzLDEwMywxMTUsOTksNjgsODUsMTExLDcxLDExMyw1Myw5MCw4NiwxMDUsMzcsNDgsNjUsMTIyLDc2LDg1LDEyMiw4NCwxMTMsMTA1LDgxLDgwLDEwOSw4NSw3Niw2NSw4MSw5Nyw2Niw1Nyw5OSw1NCw3OSwxMTYsMTA1LDU0LDExNSwxMTAsNjksNzAsNzQsMTA1LDY3LDgxLDU0LDU1LDc0LDc2LDEyMSw4Nyw0Nyw2OSw1Niw1MSw0NywxMDIsMTE0LDEyMiw2NywxMDksNzksNTMsODIsMTE3LDU0LDg3LDEwNiw4NSw1MiwxMTYsMTA5LDExNSwxMDksMTIxLDU2LDgyLDk3LDM3LDQ4LDY1LDg1LDEwMCw1Miw2NSw4MCw3NSw0OCwxMTksOTAsODQsNzEsMTE2LDEwMiw4MCw4OCw4NSw1NSwxMTksMzcsNTAsNjYsNzMsNjYsMTAwLDcxLDUzLDY5LDEyMiw0OCwxMDcsNjksNDksMTEzLDEyMiwxMjAsNzEsODEsOTcsNzYsNTIsMTAzLDczLDc4LDc0LDQ5LDEyMiw3NywxMjEsMTA4LDEwMSw2OCwxMTAsOTgsMTE3LDgzLDU2LDg1LDEwNSw5OSwxMDYsNzQsMTA1LDEwNiwxMTgsMTEzLDY1LDM3LDQ4LDY1LDQ5LDUzLDUwLDgzLDExMyw0OCw1Miw1Nyw2OSw4Myw2OCwxMjIsMzcsNTAsNjYsNDksMTE0LDgyLDcxLDk5LDUwLDc4LDg2LDY5LDExMywxMDQsNDksNzUsOTcsNzEsODgsMTA5LDExNiw4OCwxMTgsMTEzLDEyMCw4OCw5OSw4NCw2NiwzNyw1MCw2Niw3NiwxMDYsMTIxLDUzLDY2LDExOSw1MCwxMDcsMTAxLDQ4LDExOCw1NiwxMDUsNzEsMTEwLDEwMyw3MCw2Niw4MCwxMTMsNjcsODQsODYsNjYsMzcsNDgsNjUsNTEsMTExLDExMiw1Myw3NSw2Niw3MSw1MSw4MiwxMDYsOTgsNzAsNTQsODIsODIsODMsMTIyLDExOSwxMjIsMTE3LDg3LDEwMiw3Niw1NSw4MSw2OSwxMTQsNzgsNjcsNTYsODcsNjksMTIxLDUzLDEyMSw2OCw4Niw2NSw4MiwxMjIsODQsNjUsNTMsMzcsNTAsNjYsMTIwLDEwOSw2Niw5OSw1MSw1Niw1NiwxMTgsNTcsNjgsMTA5LDUwLDQ5LDcyLDcxLDEwMiw5OSw2Nyw1Niw3OSwzNyw0OCw2NSw2OCw2OCwzNyw1MCw2NiwxMDMsODQsNTcsMTE1LDgzLDExMiwxMTUsMTE1LDExMyw0OCw5NywxMTUsOTksMTA5LDExOCw3Miw1Miw1Nyw3Nyw3OSwxMDMsMTA2LDExNiw0OSwxMjEsMTExLDEyMSwxMTUsNzYsMTE2LDEwMCw2NywxMTYsNzQsODcsNDcsNTcsNzAsOTAsMTEyLDExMSw3OSwxMjEsMTEyLDk3LDcyLDEyMCw0OCw4MiwzNyw1MCw2NiwxMDksNzQsODQsNzYsMTE5LDgwLDg4LDg2LDc3LDExNCwxMTgsMzcsNDgsNjUsNjgsOTcsODYsMTIyLDg3LDEwNCw1Myw5NywxMDUsNjksMTIwLDM3LDUwLDY2LDEwNSwxMDAsMTA3LDgzLDcxLDc3LDExMCw4OCwzNyw0OCw2NSw0NSw0NSw0NSw0NSw0NSw2OSw3OCw2OCwzNyw1MCw0OCw2Nyw2OSw4Miw4NCw3Myw3MCw3Myw2Nyw2NSw4NCw2OSw0NSw0NSw0NSw0NSw0NSwzNyw0OCw2NV19MAoGCCqGSM49BAMCA0kAMEYCIQDNDp24Qkw4EK0zTdvry2HORqKVAbu4lCq9TfY/PaCfawIhAJnUWTdxg/W+ZugcKVbHn0pKEQOc8CI6J4Mg8W22U6urACEAAQACAQAAAgADAgACAAMAEAAAAAAAAAAAAAAAAF9DmtQASDBGAiEAjLsiYjbHm1IUkEctJwdF9GJxIX8ezxiNOoOHAmI4uQwCIQCFTudVdUoI1oc5p3Q81nGIa6+ci8a60mvJJdNCWmyBPw=="} ] } } diff --git a/docker/config/devnet/tendermint/genesis.json b/docker/config/devnet/tendermint/genesis.json index 99caf0e9c..01779e9d7 100644 --- a/docker/config/devnet/tendermint/genesis.json +++ b/docker/config/devnet/tendermint/genesis.json @@ -1,5 +1,5 @@ { - "app_hash": "6101EB4F69CCB1AEF735C9780C01264ADAA6972C44DE49A57B5662F9CEA753BC", + "app_hash": "A678257B5C445E8CB9685900F9B356470D0B82A07AFC42222A12E1BADE1392AE", "app_state": { "council_nodes": { "0x45c1851c2f0dc6138935857b9e23b173185fea15": [ @@ -9,7 +9,7 @@ "type": "tendermint/PubKeyEd25519", "value": "FF5JxhRrCUNLj6UZmYdjv/AWgSWUeiomeOMeJG71owE=" }, - {"keypackage": "RklYTUU="} + {"keypackage": "AAACAEEES5svZvBIGyrEUt9I8grzrdFagYx7WtOo9azjgGhhgj7yfUkH02xaDKIG4+fLT2ctPoBGuiljMlxEBZVTCT16dQEAP+gwgj/kMII/iaADAgECAgEqMAoGCCqGSM49BAMCMCoxEzARBgNVBAoMCkNyeXB0by5jb20xEzARBgNVBAMMCkNyeXB0by5jb20wIhgPMTk3MDAxMDEwMDAwMDBaGA8yMDIwMDgyNDEwNDc0OFowKjETMBEGA1UECgwKQ3J5cHRvLmNvbTETMBEGA1UEAwwKQ3J5cHRvLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEubL2bwSBsqxFLfSPIK863RWoGMe1rTqPWs44BoYYI+8n1JB9NsWgyiBuPny09nLT6ARropYzJcRAWVUwk9enWjgj6aMII+ljAeBgNVHREEFzAVgRNzZWN1cml0eUBjcnlwdG8uY29tMII+cgYJYIZIAYb4QgENBII+Y3siYm9keSI6WzEyMywzNCwxMDUsMTAwLDM0LDU4LDM0LDUxLDQ5LDU2LDUxLDUwLDUxLDU0LDU0LDU3LDQ5LDU3LDU0LDU0LDUyLDQ5LDU3LDU3LDUwLDUyLDUwLDUxLDQ4LDUxLDU2LDUxLDU1LDUxLDU3LDU0LDUzLDUzLDU2LDU0LDUxLDU0LDU1LDU0LDUzLDQ4LDM0LDQ0LDM0LDExNiwxMDUsMTA5LDEwMSwxMTUsMTE2LDk3LDEwOSwxMTIsMzQsNTgsMzQsNTAsNDgsNTAsNDgsNDUsNDgsNTMsNDUsNTAsNTQsODQsNDksNDgsNTgsNTIsNTUsNTgsNTMsNDgsNDYsNTUsNTEsNTIsNTMsNTUsNTQsMzQsNDQsMzQsMTE4LDEwMSwxMTQsMTE1LDEwNSwxMTEsMTEwLDM0LDU4LDUyLDQ0LDM0LDk3LDEwMCwxMTgsMTA1LDExNSwxMTEsMTE0LDEyMSw4NSw4Miw3NiwzNCw1OCwzNCwxMDQsMTE2LDExNiwxMTIsMTE1LDU4LDQ3LDQ3LDExNSwxMDEsOTksMTE3LDExNCwxMDUsMTE2LDEyMSw0NSw5OSwxMDEsMTEwLDExNiwxMDEsMTE0LDQ2LDEwNSwxMTAsMTE2LDEwMSwxMDgsNDYsOTksMTExLDEwOSwzNCw0NCwzNCw5NywxMDAsMTE4LDEwNSwxMTUsMTExLDExNCwxMjEsNzMsNjgsMTE1LDM0LDU4LDkxLDM0LDczLDc4LDg0LDY5LDc2LDQ1LDgzLDY1LDQ1LDQ4LDQ4LDUxLDUxLDUyLDM0LDkzLDQ0LDM0LDEwNSwxMTUsMTE4LDY5LDExMCw5OSwxMDgsOTcsMTE4LDEwMSw4MSwxMTcsMTExLDExNiwxMDEsODMsMTE2LDk3LDExNiwxMTcsMTE1LDM0LDU4LDM0LDgzLDg3LDk1LDcyLDY1LDgyLDY4LDY5LDc4LDczLDc4LDcxLDk1LDc4LDY5LDY5LDY4LDY5LDY4LDM0LDQ0LDM0LDEwNSwxMTUsMTE4LDY5LDExMCw5OSwxMDgsOTcsMTE4LDEwMSw4MSwxMTcsMTExLDExNiwxMDEsNjYsMTExLDEwMCwxMjEsMzQsNTgsMzQsNjUsMTAzLDY1LDY1LDY1LDc3LDg1LDc2LDY1LDY1LDY1LDc2LDY1LDY1LDExMSw2NSw2NSw2NSw2NSw2NSw2NSw3NCw1MSw5OCwxMDQsMTAzLDc5LDc5LDY1LDg1LDExMyw2OSw2OCwxMTcsMTE2LDkwLDc4LDEwNSw0Nyw3OSw3MSwxMDYsODUsNDMsMTAzLDgxLDExMCw4Miw4NSwxMDksMTIwLDEwMyw3Miw3Miw1Miw3NSw3NCw4NCw5OSw4MCw3OCw4OCwxMDAsNDcsNjgsMTE5LDU2LDY3LDY2LDEwMiw0Myw2NSw2NiwxMTksNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjUsNjYsMTE5LDY1LDY1LDY1LDY1LDY1LDY1LDY1LDY1LDY1LDEwMiw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw3NCw0OSw3Miw3MCw4Miw4Myw1MywxMTksODMsNDMsODAsNzIsNDksMTA0LDY4LDc4LDEyMCwxMDUsMTExLDEwOSw3Nyw5MCw3NCwxMDMsMTEwLDU2LDEyMiwxMTAsMTA2LDczLDEwMyw4MCwxMTgsODIsODcsOTgsOTksNTAsNzUsNzYsNTcsMTIwLDExOSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2Nyw5Nyw0Nyw1Niw0MywxMTcsODIsNTUsMTA0LDczLDU1LDY3LDEyMSwxMTgsNzIsNjksMTA5LDQ4LDExNSwxMTEsNzgsODQsNzIsMTA0LDEyMiw2OSw3NCwxMDIsMTA3LDQ5LDEwMywxMTQsNzgsMTExLDY2LDExNyw4NSwxMTMsODEsNTcsMTAxLDc4LDcxLDEwMyw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2NSw2Niw3NiwxMDksMTIxLDU3LDEwOSw1Niw2OSwxMDMsOTgsNzUsMTE1LDgyLDgzLDUxLDQ4LDEwNiwxMjEsNjcsMTE4LDc5LDExNiw0OCw4NiwxMTMsNjYsMTA2LDcyLDExNiw5Nyw0OCw1NCwxMDYsNDksMTE0LDc5LDc5LDY1LDk3LDcxLDcxLDY3LDgwLDExOCw3NCw1Nyw4Myw4MSwxMDIsODQsOTgsNzAsMTExLDc3LDExMSwxMDMsOTgsMTA2LDUzLDU2LDExNiw4MCw5MCwxMjEsNDgsNDMsMTAzLDY5LDk3LDU0LDc1LDg3LDc3LDEyMSw4OCw2OSw4MSw3MCwxMDgsODYsNzcsNzQsODAsODgsMTEyLDQ5LDM0LDEyNV0sInNpZ25hdHVyZSI6WzE2MCw0MSwxOTEsMjUsMTkzLDIyOCwyOCwxMzYsMjA5LDY3LDIzMSwxNTgsMTY4LDIzNyw5LDE4NSw0MCw1OCwxMzcsNDksMTA1LDE3Miw1LDIwNCwyMzQsODEsODMsMTMwLDE1MywxMjgsMTQ1LDI1NCwxNjMsNTcsMTU1LDEsMjI0LDEyLDExNCwyMTksNTIsMjA2LDk2LDI0MSwxNzgsMTQsODMsMzAsMTQwLDIyOCwyMTcsODEsMjYsMTczLDIyMywyLDE4MywxMDIsMTAzLDIzMSwyMjEsMTQ2LDU4LDE2NSwyMzgsMTI1LDY1LDE3OCw2NSwyMTQsMjA3LDQzLDg1LDEzMywyMTUsMjYsMTE2LDE3NSwxNDIsMjQwLDc0LDE5NywxMjAsMTQzLDEyMywyMiwyOCw0LDEwNiw2Niw3NiwyMzIsMjA3LDE2OCw5OSw1LDIxNCwxNTIsMTEwLDE0Miw4Niw3MywyNCw5MCwyMzYsMTMyLDIyNCw4OSw4LDIzMywxNzgsMzksMzgsMTg3LDIyNiwxNTMsMzAsNDQsMTQxLDU1LDE1LDE3OSwyMDUsMTY1LDIwLDI2LDIxMCw1Miw4NSwyMTUsNjcsMTQxLDQzLDE3MCw4NywxMjMsNzYsMTIyLDE4LDUxLDExNywyMjksNjQsMTA0LDM2LDk3LDExNSwxMDMsMTY0LDE3Miw4Nyw4NywxNzgsMTIyLDE3MSwxMzMsMTIsNTgsMTE0LDE0Niw1NiwyMTgsMjQ3LDEyNiwxOTYsNjksMjExLDE0MiwxNTQsMTg3LDE5MywxOCwxMjQsMTYzLDIzNCwxNzgsMjQ0LDY4LDQ2LDI4LDEwLDE0Myw3NiwxOTQsMjUsMjA5LDg5LDIxMCwyMzgsMTY0LDE4MCwwLDExNSwxMTksMTM2LDI0MiwyNDksMjIzLDEwMyw1NywyMSwxODIsMTMyLDYxLDg0LDUwLDIxNyw4MSw4NiwyMDAsMTMsMTQ1LDAsMTI4LDEyOCw2NiwyMTcsMjEzLDEwMCwyMDksMTcxLDIsMTQxLDIyOCwyMjYsMTU5LDE0NiwyMDQsMTI4LDEzNywxMTEsOTEsMjIzLDE5OSwyMTEsMTI1LDM2LDI5LDEwNCwyNTUsMjE2LDE3MCwxMzEsMTE1LDIwNCwxNTIsMTA1LDE0NywxMTAsMzcsMjAxLDIyNywyMDUsNywxNjQsNjFdLCJzaWduaW5nX2NlcnQiOls0NSw0NSw0NSw0NSw0NSw2Niw2OSw3MSw3Myw3OCwzNyw1MCw0OCw2Nyw2OSw4Miw4NCw3Myw3MCw3Myw2Nyw2NSw4NCw2OSw0NSw0NSw0NSw0NSw0NSwzNyw0OCw2NSw3Nyw3Myw3Myw2OSwxMTEsODQsNjcsNjcsNjUsMTE5LDEwOSwxMDMsNjUsMTE5LDczLDY2LDY1LDEwMyw3Myw3NCw2NSw3OCw2OSw3MiwxMDAsMTA4LDQ4LDEyMSwxMTEsNTUsNjcsODcsNzcsNjUsNDgsNzEsNjcsODMsMTEzLDcxLDgzLDczLDk4LDUxLDY4LDgxLDY5LDY2LDY3LDExOSw4NSw2NSw3Nyw3Miw1MiwxMjAsNjcsMTIyLDY1LDc0LDY2LDEwMyw3OCw4NiwzNyw0OCw2NSw2Niw2NSw4OSw4NCw2NSwxMDgsODYsODQsNzcsODEsMTE1LDExOSw2Nyw4MSw4OSw2OCw4Niw4MSw4MSw3Myw2OCw2NSw3NCw2OCw4MSw4NCw2OSw4NSw3Nyw2Niw3Myw3MSw2NSw0OSw4NSw2OSw2NiwxMTksMTE5LDc2LDg1LDUwLDcwLDExNywxMDAsNzEsNjksMTAzLDgxLDUwLDEyMCwxMDQsOTksMTA5LDY5LDEyMCw3MSwxMDYsNjUsODksNjYsMTAzLDc4LDg2LDM3LDQ4LDY1LDY2LDY1LDExMSw3Nyw2OSw4NSwxMDgsMTE3LDEwMCw3MSw4NiwxMTUsNzMsNjksNzgsMTE4LDk5LDExMCw2NiwxMTgsOTksMTA5LDcwLDQ4LDk3LDg3LDU3LDExNyw3Nyw4NCw2NSwxMTksNzYsMTAzLDg5LDY4LDg2LDgxLDgxLDY4LDY4LDY3LDEwMCw3NCw5OCwxMTAsODIsMTA4LDk4LDY3LDY2LDg0LDgyLDQ5LDEwMywxMDMsODEsODgsODIsNDgsOTAsODgsNzgsNDgsMzcsNDgsNjUsODksODgsODIsMTEyLDk4LDUwLDUyLDEwMyw4NSwxMDksODYsMTE5LDk4LDUxLDc0LDQ4LDczLDcwLDc4LDExMiw5MCw1MCw1MywxMTIsOTgsMTA5LDk5LDEwMyw4MSw0OCw2OSwxMTksNzIsMTA0LDk5LDc4LDc3LDg0LDg5LDEyMCw3Nyw4NCw3MywxMjEsNzcsNjgsMTA3LDEyMiw3OCwxMDYsODUsNTIsODcsMTA0LDk5LDc4LDc3LDEwNiw4OSwxMjAsNzcsODQsNzMsMTE5LDM3LDQ4LDY1LDc3LDY4LDEwNywxMjIsNzgsMTA2LDg1LDUyLDg3LDEwNiw2Niw1NSw3Nyw4MSwxMTUsMTE5LDY3LDgxLDg5LDY4LDg2LDgxLDgxLDcxLDY5LDExOSw3NCw4Niw4NSwxMjIsNjksNzYsNzcsNjUsMTA3LDcxLDY1LDQ5LDg1LDY5LDY3LDY1LDExOSw2Nyw4MSw0OCw2OSwxMjAsNzAsNjgsNjUsODMsNjYsMTAzLDc4LDg2LDY2LDY1LDk5LDc3LDY3LDQ5LDc4LDEwNCwzNyw0OCw2NSw5OCwxMTAsODIsMTA0LDczLDY5LDc4LDExNSw4OSw4OCw3NCwxMDQsNzcsODIsMTExLDExOSw3MSw2NSw4OSw2OCw4Niw4MSw4MSw3NSw2OCw2Niw3MCw3NCw5OCwxMTAsODIsMTA4LDk4LDY3LDY2LDY4LDk4LDUxLDc0LDExOSw5OCw1MSw3NCwxMDQsMTAwLDcxLDEwOCwxMTgsOTgsMTA2LDY5LDExNiw3Nyw2NywxMTUsNzEsNjUsNDksODUsNjksNjUsMTE5LDExOSwxMDcsMzcsNDgsNjUsODMsODcsNTMsNDgsOTAsODcsMTE5LDEwMyw4NSw0OCwxMDAsODksNzMsNjksNzAsNDgsMTAwLDcxLDg2LDEyMiwxMDAsNzEsNzAsNDgsOTcsODcsNTcsMTE3LDczLDcwLDc0LDEwOCw5OSw3MSw1NywxMjEsMTAwLDY3LDY2LDg0LDk3LDg3LDEwMCwxMTcsOTcsODcsNTMsMTEwLDc3LDczLDczLDY2LDczLDEwNiw2NSw3OCw2NiwxMDMsMTA3LDExMywxMDQsMTA3LDEwNSw3MSwzNyw0OCw2NSw1NywxMTksNDgsNjYsNjUsODEsNjksNzAsNjUsNjUsNzksNjcsNjUsODEsNTYsNjUsNzcsNzMsNzMsNjYsNjcsMTAzLDc1LDY3LDY1LDgxLDY5LDY1LDExMyw4OCwxMTEsMTE2LDUyLDc5LDkwLDExNywxMTIsMTA0LDgyLDU2LDExMCwxMTcsMTAwLDcwLDExNCw2NSw3MCwxMDUsOTcsNzEsMTIwLDEyMCwxMDcsMTAzLDEwOSw5Nyw0Nyw2OSwxMTUsNDcsNjYsNjUsMzcsNTAsNjYsMTE2LDM3LDQ4LDY1LDk4LDEwMSw2Nyw4NCw4NSw4Miw0OSw0OCw1NCw2NSw3Niw0OSw2OSw3OCw5OSw4Nyw2NSw1Miw3MCw4OCw1MSw3NSwzNyw1MCw2Niw2OSw1Nyw2Niw2Niw3Niw0OCw0Nyw1NSw4OCw1MywxMTQsMTA2LDUzLDExMCw3MywxMDMsODgsNDcsODIsNDcsNDksMTE3LDk4LDEwNCwxMDcsNzUsODcsMTE5LDU3LDEwMywxMDIsMTEzLDgwLDcxLDUxLDc1LDEwMSw2NSwxMTYsNzMsMTAwLDM3LDQ4LDY1LDk5LDExOCw0NywxMTcsODQsNzksNDksMTIxLDg4LDExOCw1Myw0OCwxMTgsMTEzLDk3LDgwLDExOCw2OSw0OSw2Nyw4Miw2NywxMDQsMTE4LDEyMiwxMDAsODMsNDcsOTAsNjksNjYsMTEzLDgxLDUzLDExMSw4NiwxMTgsNzYsODQsODAsOTAsNTEsODYsNjksMTA1LDk5LDgxLDEwNiwxMDgsMTIxLDExNiw3NSwxMDMsNzgsNTcsOTksNzYsMTEwLDEyMCw5OCwxMTksMTE2LDExNywxMTgsMzcsNDgsNjUsNzYsODUsNzUsNTUsMTAxLDEyMSw4Miw4MCwxMDIsNzQsODcsNDcsMTA3LDExNSwxMDAsMTAwLDc5LDEyMiw4MCw1Niw4Niw2Niw2NiwxMTAsMTA1LDExMSwxMDgsODksMTEwLDgyLDY3LDY4LDUwLDEwNiwxMTQsNzcsODIsOTAsNTYsMTEwLDY2LDc3LDUwLDkwLDg3LDg5LDExOSwxMTAsODgsMTEwLDExOSw4OSwxMDEsNzksNjUsNzIsODYsMzcsNTAsNjYsODcsNTcsMTE2LDc5LDEwNCw2NSwzNyw0OCw2NSw3MywxMDksMTE5LDgyLDExOSw3NSw3MCw0Nyw1Nyw1MywxMjEsNjUsMTE1LDg2LDExOSwxMDAsNTAsNDksMTE0LDEyMSw3Miw3Nyw3NCw2Niw5OSw3MSw3Miw1NSw0OCwxMTMsNzYsOTcsMTAzLDkwLDU1LDg0LDExNiwxMjEsMTE2LDM3LDUwLDY2LDM3LDUwLDY2LDExMyw3OSw0Nyw1NCwzNyw1MCw2Niw3NSw2NSw4OCw3NCwxMTcsNzUsMTE5LDkwLDExMywxMDYsODIsMTA4LDY5LDExNiw4Myw2OSwxMjIsNTYsMzcsNDgsNjUsMTAzLDkwLDgxLDEwMSw3MCwxMDIsODYsODksMTAzLDk5LDExOSw4MywxMDIsMTExLDU3LDU0LDExMSw4Myw3Nyw2NSwxMjIsODYsMTE0LDU1LDg2LDQ4LDc2LDU0LDcyLDgzLDY4LDc2LDgyLDExMCwxMTIsOTgsNTQsMTIwLDEyMCwxMDksOTgsODAsMTAwLDExMyw3OCwxMTEsMTA4LDUyLDExNiw4MSw3Myw2OCw2NSw4MSw2NSw2NiwxMTEsNTIsNzEsMTA3LDc3LDczLDcxLDEwNCwzNyw0OCw2NSw3Nyw2Niw1Niw3MSw2NSw0OSw4NSwxMDAsNzMsMTE5LDgxLDg5LDc3LDY2LDk3LDY1LDcwLDcyLDEwNCw2OCwxMDEsNTEsOTcsMTA5LDEwMiwxMTQsMTIyLDgxLDExNCw1MSw1Myw2Nyw3OCwzNyw1MCw2NiwxMTUsNDksMTAyLDY4LDExNyw3Miw2NSw4Niw2OSw1Niw3Nyw2NSw1Miw3MSw2NSw0OSw4NSwxMDAsNjgsMTE5LDY5LDY2LDQ3LDExOSw4MSw2OSw2NSwxMTksNzMsNzEsMzcsNDgsNjUsMTE5LDY4LDY1LDc3LDY2LDEwMyw3OCw4Niw3Miw4Miw3Nyw2Niw2NSwxMDIsNTYsNjksNjUsMTA2LDY1LDY1LDc3LDcxLDY1LDcxLDY1LDQ5LDg1LDEwMCw3MiwxMTksODIsOTAsNzcsNzAsOTksMTE5LDg2LDk3LDY2LDg0LDExMSw3MCw3MSw3MSw4NCw1MCwxMDQsNDgsMTAwLDcyLDY1LDU0LDc2LDEyMSw1Nyw0OCw5OSwxMTAsODYsMTIyLDEwMCw3MSw4NiwxMDcsMzcsNDgsNjUsOTksNTAsODYsMTIxLDEwMCwxMDksMTA4LDEwNiw5MCw4OCw3NywxMTcsOTcsODcsNTMsNDgsOTAsODcsMTE5LDExNyw4OSw1MCw1NywxMTYsNzYsNTAsNzgsMTE4LDk4LDExMCw4MiwxMDgsOTgsMTEwLDgxLDExOCw4MSw0OSw3NCw3Nyw3Niw0OSw3OCw3Miw4Nyw2Nyw1Nyw2NiwxMDAsNzIsODIsMTA4LDk5LDUxLDgyLDEwNCwxMDAsNzEsMTA4LDExOCw5OCwxMDgsNzQsMTA4LDM3LDQ4LDY1LDk5LDcxLDU3LDEyMSwxMDAsNzAsNzgsMTEyLDkwLDUwLDUzLDExMiw5OCwxMDksMTAwLDY4LDgxLDgzLDUzLDEwNiw5OSwxMDksMTE5LDExOSw2OCw4MSw4OSw3NCw3NSwxMTEsOTAsNzMsMTA0LDExOCw5OSw3OCw2NSw4MSw2OSw3Niw2Niw4MSw2NSw2OCwxMDMsMTAzLDcxLDY2LDY1LDcxLDk5LDczLDExNiwxMDQsMTE2LDk5LDc1LDU3LDczLDg2LDgyLDEyMiw1MiwxMTQsMzcsNDgsNjUsODIsMTEzLDM3LDUwLDY2LDkwLDc1LDY5LDM3LDUwLDY2LDU1LDEwNyw1Myw0OCw0Nyw3OSwxMjAsODUsMTE1LDEwOSw4Nyw1Niw5Nyw5NywxMTgsNzksMTIyLDc1LDk4LDQ4LDEwNSw2NywxMjAsNDgsNTUsODksODEsNTcsMTE0LDEyMiwxMDUsNTMsMTEwLDg1LDU1LDUxLDExNiw3Nyw2OSw1MCwxMjEsNzEsODIsNzYsMTIyLDEwNCw4Myw4NiwxMDUsNzAsMTE1LDQ3LDc2LDExMiw3MCw5Nyw1NywzNyw0OCw2NSwxMDgsMTEyLDgxLDc2LDU0LDc0LDc2LDQ5LDk3LDgxLDExOSwxMDksNjgsODIsNTUsNTIsODQsMTIwLDg5LDcxLDY2LDY1LDczLDEwNSw1MywxMDIsNTIsNzMsNTMsODQsNzQsMTExLDY3LDY3LDY5LDExMyw4Miw3MiwxMjIsNTcsNDksMTA3LDExMiw3MSw1NCw4NSwxMTgsMTIxLDExMCw1MCwxMTYsNzYsMTA5LDExMCw3MywxMDAsNzQsOTgsODAsNjksNTIsMTE4LDg5LDExOCwzNyw0OCw2NSw4Nyw3NiwxMTQsMTE2LDg4LDg4LDEwMiw3MCw2Niw4Myw4Myw4MCw2OCw1Miw2NSwxMDIsMTEwLDU1LDM3LDUwLDY2LDUxLDQ3LDg4LDg1LDEwMywxMDMsNjUsMTA4LDk5LDU1LDExMSw2Nyw4NCwxMDUsMTIyLDc5LDEwMiw5OCw5OCwxMTYsNzksNzAsMTA4LDg5LDY1LDUyLDEwMyw1Myw3NSw5OSw4OSwxMDMsODMsNDksNzQsNTAsOTAsNjUsMTAxLDc3LDgxLDExMyw5OCw4NSwxMDAsMzcsNDgsNjUsOTAsMTE1LDEwMSw5MCw2Nyw5OSw5Nyw5MCw5MCw5MCwxMTAsNTQsNTMsMTE2LDEwMCwxMTMsMTAxLDEwMSw1Niw4NSw4OCw5MCwxMDgsNjgsMTE4LDEyMCw0OCwzNyw1MCw2Niw3OCwxMDAsNzksNDgsNzYsODIsMzcsNTAsNjYsNTMsMTEyLDcwLDEyMSwzNyw1MCw2NiwxMDYsMTE3LDc3LDQ4LDExOSw4Nyw5OCwxMTcsNTMsNTcsNzcsMTE4LDEyMiw5OSwxMDksODQsODgsOTgsMTA2LDExNSwxMDUsNTUsNzIsODksMzcsNDgsNjUsNTQsMTIyLDEwMCw1Myw1MSw4OSwxMTMsNTMsNzUsNTAsNTIsNTIsMTAyLDExOSw3MCw3Miw4Miw4MSw1NiwxMDEsNzksNjYsNDgsNzMsODcsNjYsMzcsNTAsNjYsNTIsODAsMTAyLDc3LDU1LDcwLDEwMSw2NSw2NSwxMTIsOTAsMTE4LDEwOCwxMDIsMTEzLDEwOCw3NSw3OSwxMDgsNzYsOTksOTAsNzYsNTAsMTE3LDEyMSw4NiwxMDksMTIyLDgyLDEwNywxMjEsODIsNTMsMTIxLDg3LDU1LDM3LDQ4LDY1LDUwLDExNywxMTEsNTcsMTA5LDEwMSwxMDQsODgsNTIsNTIsNjcsMTA1LDgwLDc0LDUwLDEwMiwxMTUsMTAxLDU3LDg5LDU0LDEwMSw4MSwxMTYsOTksMTAyLDY5LDEwNCw3Nyw4MCwxMDcsMTA5LDcyLDg4LDczLDQ4LDQ5LDExNSw3OCwzNyw1MCw2Niw3NSwxMTksODAsOTgsMTEyLDY1LDUxLDU3LDM3LDUwLDY2LDEyMCw3OSwxMTUsODMsMTE2LDEwNiwxMDQsODAsNTcsNzgsNDksODksNDksOTcsNTAsMzcsNDgsNjUsMTE2LDgxLDY1LDg2LDExMSwzNyw1MCw2NiwxMjEsODYsMTAzLDc2LDEwMyw4Niw1MCw3MiwxMTksMTE1LDU1LDUxLDcwLDk5LDQ4LDExMSw1MSwxMTksNjcsNTUsNTYsMTEzLDgwLDY5LDY1LDM3LDUwLDY2LDExOCw1MCw5Nyw4MiwxMTUsNDcsNjYsMTAxLDUxLDkwLDcwLDY4LDEwMyw2OCwxMjEsMTAzLDEwNCw5OSw0Nyw0OSwxMDIsMTAzLDg1LDM3LDUwLDY2LDU1LDY3LDM3LDUwLDY2LDgwLDU0LDEwNyw5OCwxMTMsMzcsNDgsNjUsMTAwLDUyLDExMiwxMTEsMTIxLDk4LDU0LDczLDg3LDU2LDc1LDY3LDc0LDk4LDEyMCwxMDIsNzcsNzQsMTE4LDEwNywxMTEsMTE0LDEwMCw3OCw3OSwxMDMsNzksODUsODUsMTIwLDExMCwxMDAsODAsNzIsNjksMTA1LDQ3LDExNiw5OCw0Nyw4NSw1NSwxMTcsNzYsMTA2LDc2LDc5LDEwMyw4MCw2NSwzNyw1MSw2OCwzNyw1MSw2OCwzNyw0OCw2NSw0NSw0NSw0NSw0NSw0NSw2OSw3OCw2OCwzNyw1MCw0OCw2Nyw2OSw4Miw4NCw3Myw3MCw3Myw2Nyw2NSw4NCw2OSw0NSw0NSw0NSw0NSw0NSwzNyw0OCw2NSw0NSw0NSw0NSw0NSw0NSw2Niw2OSw3MSw3Myw3OCwzNyw1MCw0OCw2Nyw2OSw4Miw4NCw3Myw3MCw3Myw2Nyw2NSw4NCw2OSw0NSw0NSw0NSw0NSw0NSwzNyw0OCw2NSw3Nyw3Myw3Myw3MCw4MywxMjIsNjcsNjcsNjUsNTUsNzksMTAzLDY1LDExOSw3Myw2Niw2NSwxMDMsNzMsNzQsNjUsNzgsNjksNzIsMTAwLDEwOCw0OCwxMjEsMTExLDU1LDY3LDg1LDc3LDY1LDQ4LDcxLDY3LDgzLDExMyw3MSw4Myw3Myw5OCw1MSw2OCw4MSw2OSw2Niw2NywxMTksODUsNjUsNzcsNzIsNTIsMTIwLDY3LDEyMiw2NSw3NCw2NiwxMDMsNzgsODYsMzcsNDgsNjUsNjYsNjUsODksODQsNjUsMTA4LDg2LDg0LDc3LDgxLDExNSwxMTksNjcsODEsODksNjgsODYsODEsODEsNzMsNjgsNjUsNzQsNjgsODEsODQsNjksODUsNzcsNjYsNzMsNzEsNjUsNDksODUsNjksNjYsMTE5LDExOSw3Niw4NSw1MCw3MCwxMTcsMTAwLDcxLDY5LDEwMyw4MSw1MCwxMjAsMTA0LDk5LDEwOSw2OSwxMjAsNzEsMTA2LDY1LDg5LDY2LDEwMyw3OCw4NiwzNyw0OCw2NSw2Niw2NSwxMTEsNzcsNjksODUsMTA4LDExNywxMDAsNzEsODYsMTE1LDczLDY5LDc4LDExOCw5OSwxMTAsNjYsMTE4LDk5LDEwOSw3MCw0OCw5Nyw4Nyw1NywxMTcsNzcsODQsNjUsMTE5LDc2LDEwMyw4OSw2OCw4Niw4MSw4MSw2OCw2OCw2NywxMDAsNzQsOTgsMTEwLDgyLDEwOCw5OCw2Nyw2Niw4NCw4Miw0OSwxMDMsMTAzLDgxLDg4LDgyLDQ4LDkwLDg4LDc4LDQ4LDM3LDQ4LDY1LDg5LDg4LDgyLDExMiw5OCw1MCw1MiwxMDMsODUsMTA5LDg2LDExOSw5OCw1MSw3NCw0OCw3Myw3MCw3OCwxMTIsOTAsNTAsNTMsMTEyLDk4LDEwOSw5OSwxMDMsODEsNDgsNjksMTE5LDczLDY2LDk5LDc4LDc3LDg0LDg5LDEyMCw3Nyw4NCw2OSw0OCw3Nyw4NCw4NSwxMjIsNzgsMTIyLDc3LDEyMCw4NywxMDQsMTAzLDgwLDc3LDEwNiw2NSw0OCw3OSw4NCw2OSwxMjEsMzcsNDgsNjUsNzcsMTIyLDY5LDEyMSw3NywxMjIsODUsNTMsNzgsODQsMTA4LDk3LDc3LDcyLDUyLDEyMCw2NywxMjIsNjUsNzQsNjYsMTAzLDc4LDg2LDY2LDY1LDg5LDg0LDY1LDEwOCw4Niw4NCw3Nyw4MSwxMTUsMTE5LDY3LDgxLDg5LDY4LDg2LDgxLDgxLDczLDY4LDY1LDc0LDY4LDgxLDg0LDY5LDg1LDc3LDY2LDczLDcxLDY1LDQ5LDg1LDY5LDY2LDExOSwxMTksNzYsMzcsNDgsNjUsODUsNTAsNzAsMTE3LDEwMCw3MSw2OSwxMDMsODEsNTAsMTIwLDEwNCw5OSwxMDksNjksMTIwLDcxLDEwNiw2NSw4OSw2NiwxMDMsNzgsODYsNjYsNjUsMTExLDc3LDY5LDg1LDEwOCwxMTcsMTAwLDcxLDg2LDExNSw3Myw2OSw3OCwxMTgsOTksMTEwLDY2LDExOCw5OSwxMDksNzAsNDgsOTcsODcsNTcsMTE3LDc3LDg0LDY1LDExOSw3NiwxMDMsODksNjgsODYsODEsODEsNjgsMzcsNDgsNjUsNjgsNjcsMTAwLDc0LDk4LDExMCw4MiwxMDgsOTgsNjcsNjYsODQsODIsNDksMTAzLDEwMyw4MSw4OCw4Miw0OCw5MCw4OCw3OCw0OCw4OSw4OCw4MiwxMTIsOTgsNTAsNTIsMTAzLDg1LDEwOSw4NiwxMTksOTgsNTEsNzQsNDgsNzMsNzAsNzgsMTEyLDkwLDUwLDUzLDExMiw5OCwxMDksOTksMTAzLDgxLDQ4LDY5LDExOSwxMDMsMTAzLDcxLDEwNSw3Nyw2NSw0OCw3MSwzNyw0OCw2NSw2Nyw4MywxMTMsNzEsODMsNzMsOTgsNTEsNjgsODEsNjksNjYsNjUsODEsODUsNjUsNjUsNTIsNzMsNjYsMTA2LDExOSw2NSwxMTksMTAzLDEwMyw3MSw3NSw2NSwxMTEsNzMsNjYsMTAzLDgxLDY3LDEwMiw4MCw3MSw4MiwzNyw1MCw2NiwxMTYsODgsOTksNTYsMTE3LDQ5LDY5LDExNiw3NCwxMjIsNzYsNjUsNDksNDgsNzAsMTAxLDExNyw0OSw4NywxMDMsMzcsNTAsNjYsMTEyLDU1LDEwMSwzNyw0OCw2NSw3NiwxMDksODMsODIsMTA5LDEwMSw5Nyw2Nyw3Miw5OCwxMDcsODEsNDksODQsNzAsNTEsNzgsMTE5LDEwOCw1MSw4MiwxMDksMTEyLDExMyw4OCwxMDcsMTAxLDcxLDEyMiw3OCw3NiwxMDAsNTQsNTcsODEsODUsMTEwLDg3LDExMSwxMTgsODksMTIxLDg2LDgzLDExMCwxMDAsNjksNzcsMTIxLDg5LDk5LDUxLDExNSw3MiwxMDEsOTksNzEsMTAzLDEwMiwxMDUsMTEwLDY5LDEwMSwxMDQsMzcsNDgsNjUsMTE0LDEwMyw2Niw3NCw4Myw2OSwxMDAsMTE1LDgzLDc0LDU3LDcwLDExMiw5Nyw3MCwxMDAsMTAxLDExNSwxMDYsMTE1LDEyMCwxMTMsMTIyLDcxLDgyLDk3LDUwLDQ4LDgwLDg5LDEwMCwxMTAsMTEwLDEwMiw4Nyw5OSw2Nyw4NCwxMTgsNzAsMTExLDExNywxMDgsMTEyLDk4LDcwLDgyLDUyLDg2LDY2LDExNyw4OCwxMTAsMTEwLDg2LDc2LDg2LDEyMiwxMDcsODUsMTE4LDEwOCw4OCw4NCwzNyw0OCw2NSw3Niw0Nyw4NCw2NSwxMTAsMTAwLDU2LDExMCw3Myw5MCwxMDcsNDgsMTIyLDkwLDEwNyw3MCw3NCw1NSw4MCw1Myw3NiwxMTYsMTAxLDgwLDExOCwxMjEsMTA3LDEwNyw5NywxMTQsNTUsNzYsOTksODMsODEsNzksNTYsNTMsMTE5LDExNiw5OSw4MSwxMDEsNDgsODIsNDksODIsOTcsMTAyLDQ3LDExNSw4MSw1NCwxMTksODksNzUsOTcsNzUsMTA5LDcwLDEwMyw2Nyw3MSwxMDEsMzcsNDgsNjUsNzgsMTEyLDY5LDc0LDg1LDEwOSwxMDMsNTIsMTA3LDExNiw5NywxMDgsNTIsMTEzLDEwMyw3Myw2NSwxMjAsMTA3LDM3LDUwLDY2LDgxLDcyLDg1LDEyMCw4MSw2OSw1Miw1MCwxMTUsMTIwLDg2LDEwNSw3OCw1MywxMDksMTEzLDEwMywxMDgsNjYsNDgsODEsNzQsMTAwLDg1LDExMSwxMTYsNDcsMTExLDU3LDk3LDQ3LDg2LDQ3LDEwOSw3NywxMDEsNzIsNTYsNzUsMTE4LDc5LDY1LDEwNSw4MSwzNyw0OCw2NSw5OCwxMjEsMTA1LDExMCwxMDcsNzgsMTEwLDEwMCwxMTAsMzcsNTAsNjYsNjYsMTAzLDEwNyw1MywxMTUsODMsODYsNTMsNjgsNzAsMTAzLDcwLDQ4LDY4LDEwMiwxMDIsODYsMTEzLDEwOSw4Niw3Nyw5OCwxMDgsMTE2LDUzLDExMiw1MSwxMDYsODAsMTE2LDczLDEwOSwxMjIsNjYsNzMsNzIsNDgsODEsODEsMTE0LDg4LDc0LDExMyw1MSw1Nyw2NSw4NCw1Niw5OSw4MiwxMTksODAsNTMsNzIsMzcsNDgsNjUsOTcsMTAyLDExNyw4NiwxMDEsNzYsNzIsOTksNjgsMTE1LDgyLDExMiw1NCwxMDQsMTExLDEwOCw1Miw4MCwzNyw1MCw2Niw5MCw3MCw3MywxMDQsMTE3LDU2LDEwOSwxMDksOTgsNzMsNDksMTE3LDQ4LDEwNCw3Miw1MSw4Nyw0Nyw0OCw2Nyw1MCw2NiwxMTcsODksODgsNjYsNTMsODAsNjcsMzcsNTAsNjYsNTMsMTA1LDEyMiw3MCw3MCwxMDQsNDcsMTEwLDgwLDQ4LDEwOCw5OSw1MCw3NiwxMDIsMzcsNDgsNjUsNTQsMTE0LDY5LDc2LDc5LDU3LDc2LDkwLDEwMCwxMTAsNzksMTA0LDExMiw3Niw0OSw2OSwxMjAsNzAsNzksMTEzLDU3LDcyLDQ3LDY2LDU2LDExNiw4MCw4MSw1Niw1Miw4NCw1MSw4MywxMDMsOTgsNTIsMTEwLDY1LDEwNSwxMDIsNjgsOTcsOTgsNzgsMTE2LDQ3LDEyMiwxMTcsNTQsNzcsMTA5LDY3LDcxLDExMSw1Myw4NSw1NiwxMDgsMTE5LDY5LDcwLDExNiw3MSw3NywzNyw0OCw2NSw4MiwxMTEsNzksOTcsODgsNTIsNjUsODMsMzcsNTAsNjYsNTcsNDgsNTcsMTIwLDQ4LDQ4LDEwOCw4OSwxMTAsMTA5LDExNiwxMTksMTE1LDY4LDg2LDg3LDExOCw1NywxMTgsNjYsMTA1LDc0LDY3LDg4LDgyLDExNSw2Nyw2NSwxMTksNjksNjUsNjUsOTcsNzksNjYsMTIxLDg0LDY3LDY2LDEyMCwxMDYsNjYsMTAzLDY2LDEwMyw3OCw4Niw3Miw4Miw1Niw2OSw4Nyw4NCw2Niw4OCwzNyw0OCw2NSw3Nyw3MCw4NywxMDMsODUsNTQsNjYsODIsMTA0LDEwNyw1NywxMTEsMTAwLDcyLDgyLDExOSw3OSwxMDUsNTYsMTE4LDEwMCw3Miw3NCw0OSw5OSw1MSw4MiwxMDgsOTAsNzIsNzgsMTA4LDk5LDExMCw5MCwxMTIsODksNTAsODYsMTIyLDc2LDEwOSwxMDgsMTE3LDEwMCw3MSw4NiwxMTUsNzYsMTA5LDc4LDExOCw5OCw4Myw1NywxMDYsOTgsNTAsNTMsNDgsOTAsODcsNTMsNDgsMzcsNDgsNjUsNzYsNDgsNzgsODMsODQsNjcsNTcsODQsODIsNDksMTAzLDExOCw4MSw4OCw4Miw0OCw5MCw4OCw3OCw0OCw4OSw4OCw4MiwxMTIsOTgsNTAsNTMsODMsOTAsODgsNjYsMTE4LDk5LDExMCw4Miw4NCw5Nyw4NywxMDAsMTE3LDk3LDg3LDUzLDExMCw4MSw0OCw2OSwxMTcsODksNTEsNzQsMTE1LDc3LDY2LDQ4LDcxLDY1LDQ5LDg1LDEwMCw2OCwxMDMsODEsODcsMzcsNDgsNjUsNjYsNjYsODIsNTIsODEsNTEsMTE2LDUwLDExMiwxMTAsNTQsNTYsNDgsNzUsNTcsMzcsNTAsNjYsODEsMTA2LDEwMiwxMTQsNzgsODgsMTE5LDU1LDEwNCwxMTksNzAsODIsODAsNjgsNjUsMTAyLDY2LDEwMyw3OCw4Niw3Miw4Myw3Nyw2OSw3MSw2OCw2NSw4NywxMDMsNjYsODIsNTIsODEsNTEsMTE2LDUwLDExMiwxMTAsNTQsNTYsNDgsNzUsNTcsMzcsNTAsNjYsODEsMTA2LDEwMiwxMTQsMzcsNDgsNjUsNzgsODgsMTE5LDU1LDEwNCwxMTksNzAsODIsODAsNjgsNjUsNzksNjYsMTAzLDc4LDg2LDcyLDgxLDU2LDY2LDY1LDEwMiw1Niw2OSw2Niw2NSw3Nyw2Nyw2NSw4MSw4OSwxMTksNjksMTAzLDg5LDY4LDg2LDgyLDQ4LDg0LDY1LDgxLDcyLDQ3LDY2LDY1LDEwMywxMTksNjYsMTAzLDY5LDY2LDQ3LDExOSw3Myw2Niw2NSw2OCw2NSw3OCw2NiwxMDMsMTA3LDExMywzNyw0OCw2NSwxMDQsMTA3LDEwNSw3MSw1NywxMTksNDgsNjYsNjUsODEsMTE1LDcwLDY1LDY1LDc5LDY3LDY1LDg5LDY5LDY1LDEwMSw3MCw1NiwxMTYsODksNzcsODgsNzMsNjcsMTE4LDgxLDExMywxMDEsODgsODksODEsNzMsODQsMTA3LDg2LDUwLDExMSw3Niw3NCwxMTUsMTEyLDU0LDc0LDUyLDc0LDY1LDExMyw3NCw5Nyw5OCw3Miw4NywxMjAsODksNzQsNzIsNzEsMTA1LDExNCwzNyw0OCw2NSw3Myw2OSwxMTMsMTE3LDk5LDgyLDEwNSw3NCw4Myw4MywxMjAsMzcsNTAsNjYsNzIsMTA2LDczLDc0LDY5LDg1LDg2LDk3LDEwNiw1Niw2OSw0OCw4MSwxMDYsNjksMTE3LDEwMCw1NCw4OSw1MywxMDgsNzgsMTA5LDg4LDEwOCw5OSwxMDYsMTEzLDgyLDg4LDk3LDY3LDgwLDc5LDExMyw3NSw0OCwxMDEsNzEsODIsMTIyLDU0LDEwNCwxMDUsMzcsNTAsNjYsMTE0LDEwNSwxMTIsNzcsMTE2LDgwLDkwLDM3LDQ4LDY1LDExNSw3MCw3OCw5Nyw2NiwxMTksNzYsODEsODYsODYsNTcsNDgsNTMsODMsNjgsMTA2LDY1LDEyMiw2OCwxMjIsNzgsNzMsNjgsMTEwLDExNCw5OSwxMTAsODgsMTIxLDY2LDUyLDEwMyw5OSw2OCw3MCw2NywxMTgsMTE5LDY4LDcwLDc1LDc1LDEwMyw3Niw4MiwxMDYsNzksNjYsNDcsODcsNjUsMTEzLDEwMywxMTUsOTksNjgsODUsMTExLDcxLDExMyw1Myw5MCw4NiwxMDUsMzcsNDgsNjUsMTIyLDc2LDg1LDEyMiw4NCwxMTMsMTA1LDgxLDgwLDEwOSw4NSw3Niw2NSw4MSw5Nyw2Niw1Nyw5OSw1NCw3OSwxMTYsMTA1LDU0LDExNSwxMTAsNjksNzAsNzQsMTA1LDY3LDgxLDU0LDU1LDc0LDc2LDEyMSw4Nyw0Nyw2OSw1Niw1MSw0NywxMDIsMTE0LDEyMiw2NywxMDksNzksNTMsODIsMTE3LDU0LDg3LDEwNiw4NSw1MiwxMTYsMTA5LDExNSwxMDksMTIxLDU2LDgyLDk3LDM3LDQ4LDY1LDg1LDEwMCw1Miw2NSw4MCw3NSw0OCwxMTksOTAsODQsNzEsMTE2LDEwMiw4MCw4OCw4NSw1NSwxMTksMzcsNTAsNjYsNzMsNjYsMTAwLDcxLDUzLDY5LDEyMiw0OCwxMDcsNjksNDksMTEzLDEyMiwxMjAsNzEsODEsOTcsNzYsNTIsMTAzLDczLDc4LDc0LDQ5LDEyMiw3NywxMjEsMTA4LDEwMSw2OCwxMTAsOTgsMTE3LDgzLDU2LDg1LDEwNSw5OSwxMDYsNzQsMTA1LDEwNiwxMTgsMTEzLDY1LDM3LDQ4LDY1LDQ5LDUzLDUwLDgzLDExMyw0OCw1Miw1Nyw2OSw4Myw2OCwxMjIsMzcsNTAsNjYsNDksMTE0LDgyLDcxLDk5LDUwLDc4LDg2LDY5LDExMywxMDQsNDksNzUsOTcsNzEsODgsMTA5LDExNiw4OCwxMTgsMTEzLDEyMCw4OCw5OSw4NCw2NiwzNyw1MCw2Niw3NiwxMDYsMTIxLDUzLDY2LDExOSw1MCwxMDcsMTAxLDQ4LDExOCw1NiwxMDUsNzEsMTEwLDEwMyw3MCw2Niw4MCwxMTMsNjcsODQsODYsNjYsMzcsNDgsNjUsNTEsMTExLDExMiw1Myw3NSw2Niw3MSw1MSw4MiwxMDYsOTgsNzAsNTQsODIsODIsODMsMTIyLDExOSwxMjIsMTE3LDg3LDEwMiw3Niw1NSw4MSw2OSwxMTQsNzgsNjcsNTYsODcsNjksMTIxLDUzLDEyMSw2OCw4Niw2NSw4MiwxMjIsODQsNjUsNTMsMzcsNTAsNjYsMTIwLDEwOSw2Niw5OSw1MSw1Niw1NiwxMTgsNTcsNjgsMTA5LDUwLDQ5LDcyLDcxLDEwMiw5OSw2Nyw1Niw3OSwzNyw0OCw2NSw2OCw2OCwzNyw1MCw2NiwxMDMsODQsNTcsMTE1LDgzLDExMiwxMTUsMTE1LDExMyw0OCw5NywxMTUsOTksMTA5LDExOCw3Miw1Miw1Nyw3Nyw3OSwxMDMsMTA2LDExNiw0OSwxMjEsMTExLDEyMSwxMTUsNzYsMTE2LDEwMCw2NywxMTYsNzQsODcsNDcsNTcsNzAsOTAsMTEyLDExMSw3OSwxMjEsMTEyLDk3LDcyLDEyMCw0OCw4MiwzNyw1MCw2NiwxMDksNzQsODQsNzYsMTE5LDgwLDg4LDg2LDc3LDExNCwxMTgsMzcsNDgsNjUsNjgsOTcsODYsMTIyLDg3LDEwNCw1Myw5NywxMDUsNjksMTIwLDM3LDUwLDY2LDEwNSwxMDAsMTA3LDgzLDcxLDc3LDExMCw4OCwzNyw0OCw2NSw0NSw0NSw0NSw0NSw0NSw2OSw3OCw2OCwzNyw1MCw0OCw2Nyw2OSw4Miw4NCw3Myw3MCw3Myw2Nyw2NSw4NCw2OSw0NSw0NSw0NSw0NSw0NSwzNyw0OCw2NV19MAoGCCqGSM49BAMCA0kAMEYCIQDNDp24Qkw4EK0zTdvry2HORqKVAbu4lCq9TfY/PaCfawIhAJnUWTdxg/W+ZugcKVbHn0pKEQOc8CI6J4Mg8W22U6urACEAAQACAQAAAgADAgACAAMAEAAAAAAAAAAAAAAAAF9DmtQASDBGAiEAjLsiYjbHm1IUkEctJwdF9GJxIX8ezxiNOoOHAmI4uQwCIQCFTudVdUoI1oc5p3Q81nGIa6+ci8a60mvJJdNCWmyBPw=="} ] }, "distribution": { diff --git a/integration-tests/bot/chainbinding.py b/integration-tests/bot/chainbinding.py index 375b93915..2505b2a58 100644 --- a/integration-tests/bot/chainbinding.py +++ b/integration-tests/bot/chainbinding.py @@ -57,7 +57,7 @@ def __del__(self): dll.cro_destroy_jsonrpc(self._p) def call(self, req): - rsp = ctypes.create_string_buffer(10240) + rsp = ctypes.create_string_buffer(102400) retcode = dll.cro_run_jsonrpc(self._p, req.encode(), rsp, len(rsp), None) assert retcode == 0, rsp.value return rsp.value diff --git a/integration-tests/bot/chainbot.py b/integration-tests/bot/chainbot.py index 308045c8b..d72753f81 100755 --- a/integration-tests/bot/chainbot.py +++ b/integration-tests/bot/chainbot.py @@ -173,6 +173,10 @@ def extract_enckey(s): def app_state_cfg(cfg): + mock_keypackage = open(os.path.join( + os.path.dirname(__file__), + '../../chain-tx-enclave-next/mls/tests/test_vectors/keypackage.bin' + ), 'rb').read() return { "distribution": gen_distribution(cfg), "required_council_node_stake": "100000000", # 10 coins @@ -203,7 +207,7 @@ def app_state_cfg(cfg): 'type': 'tendermint/PubKeyEd25519', 'value': SigningKey(node['validator_seed']).pub_key_base64(), }, - {'keypackage': "RklYTUU="} # FIXME: to be designed and implemented + {'keypackage': base64.b64encode(mock_keypackage).decode()} # FIXME: to be designed and implemented ] for node in cfg['nodes'] if node['bonded_coin'] > 0 }, diff --git a/test-common/src/block_generator.rs b/test-common/src/block_generator.rs index 54d783034..39c636d2d 100644 --- a/test-common/src/block_generator.rs +++ b/test-common/src/block_generator.rs @@ -27,9 +27,7 @@ use chain_core::init::config::NetworkParameters; use chain_core::init::{ address::RedeemAddress, coin::Coin, config::InitConfig, network::Network, params, }; -use chain_core::state::account::{ - ConfidentialInit, CouncilNode, StakedStateAddress, StakedStateDestination, -}; +use chain_core::state::account::{CouncilNode, StakedStateAddress, StakedStateDestination}; use chain_core::state::tendermint::{ TendermintValidatorAddress, TendermintValidatorPubKey, TendermintVotePower, }; @@ -46,6 +44,8 @@ use tendermint::block::BlockIDFlag::BlockIDFlagCommit; use tendermint::block::{CommitSig, CommitSigs}; use tendermint::hash::Algorithm; +use crate::chain_env::mock_confidential_init; + lazy_static! { static ref DEFAULT_NODES: Vec = vec![Node::new( 0, @@ -96,9 +96,7 @@ impl Node { name: self.name.clone(), security_contact: Some(format!("{}@example.com", self.name)), consensus_pubkey: self.tendermint_pub_key(), - confidential_init: ConfidentialInit { - keypackage: b"FIXME".to_vec(), - }, + confidential_init: mock_confidential_init(), } } @@ -258,9 +256,7 @@ impl TestnetSpec { node.name.clone(), None, node.tendermint_pub_key(), - ConfidentialInit { - keypackage: b"FIXME".to_vec(), - }, + mock_confidential_init(), ), ); } @@ -285,15 +281,15 @@ impl TestnetSpec { .duration_since(Time::unix_epoch()) .expect("invalid genesis time") .as_secs(); - let (accounts, rewards_pool, nodes) = config + let genesis_state = config .validate_config_get_genesis(genesis_seconds) .expect("distribution validation error"); - let account_root = put_stakings(&mut store, 0, accounts.iter()).unwrap(); + let account_root = put_stakings(&mut store, 0, genesis_state.accounts.iter()).unwrap(); let network_params = NetworkParameters::Genesis(config.network_params.clone()); let app_hash = compute_app_hash( &MerkleTree::empty(), &account_root, - &rewards_pool, + &genesis_state.rewards_pool, &network_params, ); @@ -334,7 +330,11 @@ impl TestnetSpec { &StakingGetter::new(&store, 0), network_params.get_required_council_node_stake(), network_params.get_max_validators(), - &nodes.iter().map(|(addr, _)| *addr).collect::>(), + &genesis_state + .validators + .iter() + .map(|(addr, _)| *addr) + .collect::>(), ); let state = ChainNodeState::genesis( @@ -342,9 +342,10 @@ impl TestnetSpec { genesis_seconds, self.max_evidence_age, account_root, - rewards_pool, + genesis_state.rewards_pool, network_params, staking_table, + genesis_state.isv_svn, ); (genesis, state) diff --git a/test-common/src/chain_env.rs b/test-common/src/chain_env.rs index d5c7f3107..e74a89a02 100644 --- a/test-common/src/chain_env.rs +++ b/test-common/src/chain_env.rs @@ -49,6 +49,8 @@ lazy_static! { .map(|s| TendermintValidatorPubKey::from_base64(*s).unwrap()) .collect(); } +pub const KEYPACKAGE_VECTOR: &[u8] = + include_bytes!("../../chain-tx-enclave-next/mls/tests/test_vectors/keypackage.bin"); pub fn get_account( account_address: &StakedStateAddress, @@ -109,6 +111,21 @@ pub fn get_init_network_params(expansion_cap: Coin) -> InitNetworkParameters { } } +pub fn mock_council_node(consensus_pubkey: TendermintValidatorPubKey) -> CouncilNode { + CouncilNode::new_with_details( + "no-name".to_string(), + None, + consensus_pubkey, + mock_confidential_init(), + ) +} + +pub fn mock_confidential_init() -> ConfidentialInit { + ConfidentialInit { + keypackage: KEYPACKAGE_VECTOR.to_vec(), + } +} + pub fn get_nodes( addresses: &[Account], ) -> BTreeMap< @@ -129,9 +146,7 @@ pub fn get_nodes( acct.name.clone(), None, acct.validator_pub_key.clone(), - ConfidentialInit { - keypackage: b"FIXME".to_vec(), - }, + mock_confidential_init(), ), ) }) @@ -228,15 +243,15 @@ impl ChainEnv { ); let timestamp = Timestamp::new(); - let (states, rewards_pool_state, council_nodes) = init_config + let genesis_state = init_config .validate_config_get_genesis(timestamp.get_seconds().try_into().unwrap()) .expect("Error while validating distribution"); - let new_account_root = storage.put_stakings(0, &states); + let new_account_root = storage.put_stakings(0, &genesis_state.accounts); let genesis_app_hash = compute_app_hash( &MerkleTree::empty(), &new_account_root, - &rewards_pool_state, + &genesis_state.rewards_pool, &NetworkParameters::Genesis(init_network_params), ); ( @@ -247,7 +262,7 @@ impl ChainEnv { timestamp, init_config, max_evidence_age: 172_800, - council_nodes, + council_nodes: genesis_state.validators, accounts, }, storage,