diff --git a/circuits/license/tests/prove_verify_license_circuit.rs b/circuits/license/tests/prove_verify_license_circuit.rs index e33665b293..9d7d1ddabf 100644 --- a/circuits/license/tests/prove_verify_license_circuit.rs +++ b/circuits/license/tests/prove_verify_license_circuit.rs @@ -6,7 +6,10 @@ use dusk_plonk::prelude::*; use dusk_poseidon::{Domain, Hash}; -use execution_core::{JubJubAffine, PublicKey, SecretKey, GENERATOR_EXTENDED}; +use execution_core::{ + transfer::phoenix::{PublicKey, SecretKey}, + JubJubAffine, GENERATOR_EXTENDED, +}; use ff::Field; use license_circuits::{Error, LicenseCircuit, DEPTH}; use poseidon_merkle::{Item, Opening, Tree}; diff --git a/consensus/src/aggregator.rs b/consensus/src/aggregator.rs index 0c10cecf67..c39909ae92 100644 --- a/consensus/src/aggregator.rs +++ b/consensus/src/aggregator.rs @@ -7,7 +7,9 @@ use crate::user::cluster::Cluster; use crate::user::committee::Committee; use dusk_bytes::Serializable; -use execution_core::{BlsSigError, BlsSignature}; +use execution_core::signatures::bls::{ + Error as BlsSigError, MultisigSignature as BlsMultisigSignature, +}; use node_data::bls::{PublicKey, PublicKeyBytes}; use node_data::ledger::{to_str, StepVotes}; use node_data::message::payload::Vote; @@ -184,12 +186,12 @@ impl fmt::Display for Aggregator { #[derive(Default)] pub(super) struct AggrSignature { - data: Option, + data: Option, } impl AggrSignature { pub fn add(&mut self, data: &[u8; 48]) -> Result<(), BlsSigError> { - let sig = BlsSignature::from_bytes(data)?; + let sig = BlsMultisigSignature::from_bytes(data)?; let aggr_sig = match self.data { Some(data) => data.aggregate(&[sig]), @@ -215,7 +217,9 @@ mod tests { use crate::user::provisioners::{Provisioners, DUSK}; use crate::user::sortition::Config; use dusk_bytes::DeserializableSlice; - use execution_core::{BlsPublicKey, BlsSecretKey}; + use execution_core::signatures::bls::{ + PublicKey as BlsPublicKey, SecretKey as BlsSecretKey, + }; use hex::FromHex; use node_data::ledger::{Header, Seed}; use std::collections::HashMap; diff --git a/consensus/src/commons.rs b/consensus/src/commons.rs index bb3c641e64..3193cb493c 100644 --- a/consensus/src/commons.rs +++ b/consensus/src/commons.rs @@ -14,7 +14,9 @@ use std::collections::HashMap; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use thiserror::Error; -use execution_core::{BlsSecretKey, BlsSigError}; +use execution_core::signatures::bls::{ + Error as BlsSigError, SecretKey as BlsSecretKey, +}; use node_data::bls::PublicKey; use node_data::message::{AsyncQueue, Message, Payload}; use node_data::StepName; @@ -91,8 +93,6 @@ pub enum StepSigError { VoteSetTooSmall, #[error("Verification error {0}")] VerificationFailed(BlsSigError), - #[error("Empty Apk instance")] - EmptyApk, #[error("Invalid Type")] InvalidType, } diff --git a/consensus/src/proposal/block_generator.rs b/consensus/src/proposal/block_generator.rs index f6812c0afc..d38f8c2c03 100644 --- a/consensus/src/proposal/block_generator.rs +++ b/consensus/src/proposal/block_generator.rs @@ -41,7 +41,7 @@ impl Generator { // Sign seed let seed = ru .secret_key - .sign(ru.pubkey_bls.inner(), &ru.seed().inner()[..]) + .sign_multisig(ru.pubkey_bls.inner(), &ru.seed().inner()[..]) .to_bytes(); let start = Instant::now(); diff --git a/consensus/src/quorum/verifiers.rs b/consensus/src/quorum/verifiers.rs index 2f3b0919de..5693cb937a 100644 --- a/consensus/src/quorum/verifiers.rs +++ b/consensus/src/quorum/verifiers.rs @@ -18,7 +18,10 @@ use crate::user::sortition; use crate::config::CONSENSUS_MAX_ITER; use dusk_bytes::Serializable as BytesSerializable; -use execution_core::{BlsAggPublicKey, BlsSignature}; +use execution_core::signatures::bls::{ + MultisigPublicKey as BlsMultisigPublicKey, + MultisigSignature as BlsMultisigSignature, +}; use tokio::sync::RwLock; pub async fn verify_step_votes( @@ -131,18 +134,10 @@ pub fn verify_votes( } impl Cluster { - fn aggregate_pks(&self) -> Result { + fn aggregate_pks(&self) -> Result { let pks: Vec<_> = self.iter().map(|(pubkey, _)| *pubkey.inner()).collect(); - - match pks.split_first() { - Some((first, rest)) => { - let mut apk = BlsAggPublicKey::from(first); - apk.aggregate(rest)?; - Ok(apk) - } - None => Err(StepSigError::EmptyApk), - } + Ok(BlsMultisigPublicKey::aggregate(&pks)?) } pub fn to_voters(self) -> Vec { @@ -154,7 +149,7 @@ fn verify_step_signature( header: &ConsensusHeader, step: StepName, vote: &Vote, - apk: BlsAggPublicKey, + apk: BlsMultisigPublicKey, signature: &[u8; 48], ) -> Result<(), StepSigError> { // Compile message to verify @@ -164,7 +159,7 @@ fn verify_step_signature( StepName::Proposal => Err(StepSigError::InvalidType)?, }; - let sig = BlsSignature::from_bytes(signature)?; + let sig = BlsMultisigSignature::from_bytes(signature)?; let mut msg = header.signable(); msg.extend_from_slice(sign_seed); vote.write(&mut msg).expect("Writing to vec should succeed"); diff --git a/consensus/src/user/sortition.rs b/consensus/src/user/sortition.rs index 92c10856c5..aecaf8a92c 100644 --- a/consensus/src/user/sortition.rs +++ b/consensus/src/user/sortition.rs @@ -100,7 +100,9 @@ mod tests { use crate::user::provisioners::{Provisioners, DUSK}; use crate::user::sortition::Config; use dusk_bytes::DeserializableSlice; - use execution_core::{BlsPublicKey, BlsSecretKey}; + use execution_core::signatures::bls::{ + PublicKey as BlsPublicKey, SecretKey as BlsSecretKey, + }; use node_data::ledger::Seed; diff --git a/contracts/alice/src/state.rs b/contracts/alice/src/state.rs index e4f87bd4bd..7d1c9287c5 100644 --- a/contracts/alice/src/state.rs +++ b/contracts/alice/src/state.rs @@ -4,8 +4,7 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use execution_core::transfer::Withdraw; -use rusk_abi::TRANSFER_CONTRACT; +use execution_core::transfer::{withdraw::Withdraw, TRANSFER_CONTRACT}; /// Alice contract. #[derive(Debug, Clone)] diff --git a/contracts/host_fn/src/lib.rs b/contracts/host_fn/src/lib.rs index 3da18959ae..bece500144 100644 --- a/contracts/host_fn/src/lib.rs +++ b/contracts/host_fn/src/lib.rs @@ -13,8 +13,14 @@ use alloc::vec::Vec; use dusk_bytes::Serializable; use execution_core::{ - BlsPublicKey, BlsScalar, BlsSignature, PublicKey, SchnorrPublicKey, - SchnorrSignature, + signatures::{ + bls::{PublicKey as BlsPublicKey, Signature as BlsSignature}, + schnorr::{ + PublicKey as SchnorrPublicKey, Signature as SchnorrSignature, + }, + }, + transfer::phoenix::PublicKey as PhoenixPublicKey, + BlsScalar, }; static mut STATE: HostFnTest = HostFnTest; @@ -62,11 +68,11 @@ impl HostFnTest { rusk_abi::block_height() } - pub fn owner(&self) -> PublicKey { + pub fn owner(&self) -> PhoenixPublicKey { rusk_abi::self_owner() } - pub fn owner_raw(&self) -> [u8; PublicKey::SIZE] { + pub fn owner_raw(&self) -> [u8; PhoenixPublicKey::SIZE] { rusk_abi::self_owner_raw() } } diff --git a/contracts/license/tests/license.rs b/contracts/license/tests/license.rs index 374097d28c..942f1296c0 100644 --- a/contracts/license/tests/license.rs +++ b/contracts/license/tests/license.rs @@ -21,10 +21,10 @@ use zk_citadel::license::{ }; use execution_core::{ - BlsScalar, JubJubAffine, PublicKey, SecretKey, StealthAddress, ViewKey, - GENERATOR_EXTENDED, + transfer::phoenix::{PublicKey, SecretKey, StealthAddress, ViewKey}, + BlsScalar, ContractId, JubJubAffine, GENERATOR_EXTENDED, }; -use rusk_abi::{ContractData, ContractId, Session}; +use rusk_abi::{ContractData, Session}; use rusk_profile::get_common_reference_string; #[path = "../src/license_types.rs"] diff --git a/contracts/stake/benches/get_provisioners.rs b/contracts/stake/benches/get_provisioners.rs index ec75f2d4fc..100ba698b2 100644 --- a/contracts/stake/benches/get_provisioners.rs +++ b/contracts/stake/benches/get_provisioners.rs @@ -5,12 +5,14 @@ // Copyright (c) DUSK NETWORK. All rights reserved. use criterion::{criterion_group, criterion_main, Criterion}; -use execution_core::{stake::StakeData, BlsPublicKey, BlsSecretKey}; +use execution_core::{ + stake::{StakeData, STAKE_CONTRACT}, + transfer::TRANSFER_CONTRACT, + BlsPublicKey, BlsSecretKey, +}; use rand::rngs::StdRng; use rand::{CryptoRng, RngCore, SeedableRng}; -use rusk_abi::{ - ContractData, PiecrustError, Session, STAKE_CONTRACT, TRANSFER_CONTRACT, VM, -}; +use rusk_abi::{ContractData, PiecrustError, Session, VM}; use std::sync::mpsc; const SAMPLE_SIZE: usize = 10; diff --git a/contracts/stake/src/lib.rs b/contracts/stake/src/lib.rs index a23e74853e..dba88e1d8a 100644 --- a/contracts/stake/src/lib.rs +++ b/contracts/stake/src/lib.rs @@ -10,7 +10,7 @@ extern crate alloc; -use rusk_abi::dusk::*; +use execution_core::{dusk, transfer::TRANSFER_CONTRACT, Dusk}; mod state; use state::StakeState; @@ -128,10 +128,10 @@ unsafe fn set_burnt_amount(arg_len: u32) -> u32 { /// Asserts the call is made via the transfer contract. /// /// # Panics -/// When the `caller` is not [`rusk_abi::TRANSFER_CONTRACT`]. +/// When the `caller` is not [`TRANSFER_CONTRACT`]. fn assert_transfer_caller() { const PANIC_MSG: &str = "Can only be called from the transfer contract"; - if rusk_abi::caller().expect(PANIC_MSG) != rusk_abi::TRANSFER_CONTRACT { + if rusk_abi::caller().expect(PANIC_MSG) != TRANSFER_CONTRACT { panic!("{PANIC_MSG}"); } } diff --git a/contracts/stake/src/state.rs b/contracts/stake/src/state.rs index fe4e26da69..84cb602df5 100644 --- a/contracts/stake/src/state.rs +++ b/contracts/stake/src/state.rs @@ -10,13 +10,13 @@ use core::cmp::min; use dusk_bytes::Serializable; use execution_core::{ + signatures::bls::PublicKey as BlsPublicKey, stake::{ next_epoch, Stake, StakeAmount, StakeData, StakeEvent, Withdraw, EPOCH, - STAKE_WARNINGS, + STAKE_CONTRACT, STAKE_WARNINGS, }, - BlsPublicKey, + transfer::TRANSFER_CONTRACT, }; -use rusk_abi::{STAKE_CONTRACT, TRANSFER_CONTRACT}; use crate::*; diff --git a/contracts/stake/tests/common/assert.rs b/contracts/stake/tests/common/assert.rs index 52809171a7..322ea3e684 100644 --- a/contracts/stake/tests/common/assert.rs +++ b/contracts/stake/tests/common/assert.rs @@ -7,8 +7,9 @@ use dusk_bytes::Serializable; use rkyv::{check_archived_root, Deserialize, Infallible}; -use execution_core::{stake::StakeEvent, BlsPublicKey}; -use rusk_abi::Event; +use execution_core::{ + signatures::bls::PublicKey as BlsPublicKey, stake::StakeEvent, Event, +}; pub fn assert_event( events: &Vec, diff --git a/contracts/stake/tests/common/init.rs b/contracts/stake/tests/common/init.rs index a4eba73967..e129149b29 100644 --- a/contracts/stake/tests/common/init.rs +++ b/contracts/stake/tests/common/init.rs @@ -6,9 +6,16 @@ use rand::{CryptoRng, RngCore}; -use execution_core::{JubJubScalar, Note, PublicKey}; +use execution_core::{ + stake::STAKE_CONTRACT, + transfer::{ + phoenix::{Note, PublicKey as PhoenixPublicKey}, + TRANSFER_CONTRACT, + }, + JubJubScalar, +}; use ff::Field; -use rusk_abi::{ContractData, Session, STAKE_CONTRACT, TRANSFER_CONTRACT, VM}; +use rusk_abi::{ContractData, Session, VM}; use crate::common::utils::update_root; @@ -20,7 +27,7 @@ const POINT_LIMIT: u64 = 0x100_000_000; pub fn instantiate( rng: &mut Rng, vm: &VM, - pk: &PublicKey, + pk: &PhoenixPublicKey, genesis_value: u64, ) -> Session { let transfer_bytecode = include_bytes!( diff --git a/contracts/stake/tests/common/utils.rs b/contracts/stake/tests/common/utils.rs index 875e5e89c5..9eb5b78fb5 100644 --- a/contracts/stake/tests/common/utils.rs +++ b/contracts/stake/tests/common/utils.rs @@ -15,16 +15,20 @@ use rand::rngs::StdRng; use rand::SeedableRng; use execution_core::{ + signatures::schnorr::SecretKey as SchnorrSecretKey, transfer::{ - ContractCall, ContractExec, Fee, PhoenixPayload, Transaction, TreeLeaf, - TRANSFER_TREE_DEPTH, + contract_exec::{ContractCall, ContractExec}, + phoenix::{ + value_commitment, Fee, Note, Payload as PhoenixPayload, + PublicKey as PhoenixPublicKey, SecretKey as PhoenixSecretKey, + Sender, TreeLeaf, TxSkeleton, ViewKey as PhoenixViewKey, + NOTES_TREE_DEPTH, + }, + Transaction, TRANSFER_CONTRACT, }, - value_commitment, JubJubScalar, Note, PublicKey, SchnorrSecretKey, - SecretKey, Sender, TxSkeleton, ViewKey, -}; -use rusk_abi::{ - CallReceipt, ContractError, PiecrustError, Session, TRANSFER_CONTRACT, + ContractError, JubJubScalar, }; +use rusk_abi::{CallReceipt, PiecrustError, Session}; const POINT_LIMIT: u64 = 0x100000000; @@ -83,7 +87,7 @@ pub fn root(session: &mut Session) -> Result { pub fn opening( session: &mut Session, pos: u64, -) -> Result>, PiecrustError> { +) -> Result>, PiecrustError> { session .call(TRANSFER_CONTRACT, "opening", &pos, POINT_LIMIT) .map(|r| r.data) @@ -113,7 +117,7 @@ pub fn prover_verifier(input_notes: usize) -> (Prover, Verifier) { } pub fn filter_notes_owned_by>( - vk: ViewKey, + vk: PhoenixViewKey, iter: I, ) -> Vec { iter.into_iter() @@ -163,8 +167,8 @@ pub fn execute( /// input note positions in the transaction tree and the new output-notes. pub fn create_transaction( session: &mut Session, - sender_sk: &SecretKey, - receiver_pk: &PublicKey, + sender_sk: &PhoenixSecretKey, + receiver_pk: &PhoenixPublicKey, gas_limit: u64, gas_price: u64, input_pos: [u64; I], @@ -174,8 +178,8 @@ pub fn create_transaction( contract_call: Option, ) -> Transaction { let mut rng = StdRng::seed_from_u64(0xfeeb); - let sender_vk = ViewKey::from(sender_sk); - let sender_pk = PublicKey::from(sender_sk); + let sender_vk = PhoenixViewKey::from(sender_sk); + let sender_pk = PhoenixPublicKey::from(sender_sk); // Create the transaction payload: @@ -344,7 +348,7 @@ pub fn create_transaction( let sig_b = schnorr_sk_b.sign(&mut rng, payload_hash); // Build the circuit - let circuit: TxCircuit = TxCircuit::new( + let circuit: TxCircuit = TxCircuit::new( tx_input_notes .try_into() .expect("The input notes should be the correct ammount"), diff --git a/contracts/stake/tests/events.rs b/contracts/stake/tests/events.rs index 754baaab0f..fa8183beed 100644 --- a/contracts/stake/tests/events.rs +++ b/contracts/stake/tests/events.rs @@ -10,11 +10,17 @@ use rand::rngs::StdRng; use rand::SeedableRng; use execution_core::{ - stake::{StakeAmount, StakeData}, - BlsPublicKey, BlsSecretKey, PublicKey, SecretKey, + dusk, + signatures::bls::{PublicKey as BlsPublicKey, SecretKey as BlsSecretKey}, + stake::{StakeAmount, StakeData, STAKE_CONTRACT}, + transfer::{ + phoenix::{ + PublicKey as PhoenixPublicKey, SecretKey as PhoenixSecretKey, + }, + TRANSFER_CONTRACT, + }, }; -use rusk_abi::dusk::dusk; -use rusk_abi::{PiecrustError, STAKE_CONTRACT, TRANSFER_CONTRACT}; +use rusk_abi::PiecrustError; use crate::common::assert::assert_event; use crate::common::init::instantiate; @@ -28,8 +34,8 @@ fn reward_slash() -> Result<(), PiecrustError> { let vm = &mut rusk_abi::new_ephemeral_vm() .expect("Creating ephemeral VM should work"); - let sk = SecretKey::random(rng); - let pk = PublicKey::from(&sk); + let sk = PhoenixSecretKey::random(rng); + let pk = PhoenixPublicKey::from(&sk); let stake_sk = BlsSecretKey::random(rng); let stake_pk = BlsPublicKey::from(&stake_sk); @@ -117,8 +123,8 @@ fn stake_hard_slash() -> Result<(), PiecrustError> { let vm = &mut rusk_abi::new_ephemeral_vm() .expect("Creating ephemeral VM should work"); - let sk = SecretKey::random(rng); - let pk = PublicKey::from(&sk); + let sk = PhoenixSecretKey::random(rng); + let pk = PhoenixPublicKey::from(&sk); let stake_sk = BlsSecretKey::random(rng); let stake_pk = BlsPublicKey::from(&stake_sk); diff --git a/contracts/stake/tests/stake.rs b/contracts/stake/tests/stake.rs index 43f2bd80d5..4188a9b76f 100644 --- a/contracts/stake/tests/stake.rs +++ b/contracts/stake/tests/stake.rs @@ -11,12 +11,19 @@ use rand::rngs::StdRng; use rand::SeedableRng; use execution_core::{ - stake::{Stake, StakeData, Withdraw as StakeWithdraw}, - transfer::{ContractCall, Withdraw, WithdrawReceiver, WithdrawReplayToken}, - BlsPublicKey, BlsSecretKey, JubJubScalar, PublicKey, SecretKey, ViewKey, + dusk, + signatures::bls::{PublicKey as BlsPublicKey, SecretKey as BlsSecretKey}, + stake::{Stake, StakeData, Withdraw as StakeWithdraw, STAKE_CONTRACT}, + transfer::{ + contract_exec::ContractCall, + phoenix::{ + PublicKey as PhoenixPublicKey, SecretKey as PhoenixSecretKey, + ViewKey as PhoenixViewKey, + }, + withdraw::{Withdraw, WithdrawReceiver, WithdrawReplayToken}, + }, + JubJubScalar, LUX, }; -use rusk_abi::dusk::{dusk, LUX}; -use rusk_abi::STAKE_CONTRACT; use crate::common::assert::assert_event; use crate::common::init::instantiate; @@ -39,9 +46,9 @@ fn stake_withdraw_unstake() { let vm = &mut rusk_abi::new_ephemeral_vm() .expect("Creating ephemeral VM should work"); - let phoenix_sender_sk = SecretKey::random(rng); - let phoenix_sender_vk = ViewKey::from(&phoenix_sender_sk); - let phoenix_sender_pk = PublicKey::from(&phoenix_sender_sk); + let phoenix_sender_sk = PhoenixSecretKey::random(rng); + let phoenix_sender_vk = PhoenixViewKey::from(&phoenix_sender_sk); + let phoenix_sender_pk = PhoenixPublicKey::from(&phoenix_sender_sk); let stake_sk = BlsSecretKey::random(rng); let stake_pk = BlsPublicKey::from(&stake_sk); @@ -70,7 +77,7 @@ fn stake_withdraw_unstake() { .expect("Should serialize Stake correctly") .to_vec(); let contract_call = Some(ContractCall { - contract: STAKE_CONTRACT.to_bytes(), + contract: STAKE_CONTRACT, fn_name: String::from("stake"), fn_args: stake_bytes, }); @@ -183,7 +190,7 @@ fn stake_withdraw_unstake() { let withdraw = Withdraw::new( rng, ¬e_sk, - STAKE_CONTRACT.to_bytes(), + STAKE_CONTRACT, REWARD_AMOUNT, WithdrawReceiver::Phoenix(address), WithdrawReplayToken::Phoenix(vec![ @@ -198,7 +205,7 @@ fn stake_withdraw_unstake() { .to_vec(); let contract_call = Some(ContractCall { - contract: STAKE_CONTRACT.to_bytes(), + contract: STAKE_CONTRACT, fn_name: String::from("withdraw"), fn_args: withdraw_bytes, }); @@ -292,7 +299,7 @@ fn stake_withdraw_unstake() { let withdraw = Withdraw::new( rng, ¬e_sk, - STAKE_CONTRACT.to_bytes(), + STAKE_CONTRACT, INITIAL_STAKE, WithdrawReceiver::Phoenix(address), WithdrawReplayToken::Phoenix(vec![ @@ -309,7 +316,7 @@ fn stake_withdraw_unstake() { .to_vec(); let contract_call = Some(ContractCall { - contract: STAKE_CONTRACT.to_bytes(), + contract: STAKE_CONTRACT, fn_name: String::from("unstake"), fn_args: unstake_bytes, }); diff --git a/contracts/transfer/src/error.rs b/contracts/transfer/src/error.rs index e557d10d32..01cfdf9e3b 100644 --- a/contracts/transfer/src/error.rs +++ b/contracts/transfer/src/error.rs @@ -5,7 +5,7 @@ // Copyright (c) DUSK NETWORK. All rights reserved. use core::fmt; -use execution_core::PhoenixError; +use execution_core::transfer::phoenix::Error as PhoenixError; #[derive(Debug, Clone)] pub enum Error { diff --git a/contracts/transfer/src/lib.rs b/contracts/transfer/src/lib.rs index a90eb237b9..dc2a13d24f 100644 --- a/contracts/transfer/src/lib.rs +++ b/contracts/transfer/src/lib.rs @@ -16,7 +16,7 @@ mod transitory; mod tree; mod verifier_data; -use rusk_abi::STAKE_CONTRACT; +use execution_core::stake::STAKE_CONTRACT; use state::TransferState; static mut STATE: TransferState = TransferState::new(); diff --git a/contracts/transfer/src/state.rs b/contracts/transfer/src/state.rs index e124a45a0f..f791164601 100644 --- a/contracts/transfer/src/state.rs +++ b/contracts/transfer/src/state.rs @@ -15,15 +15,22 @@ use alloc::vec::Vec; use dusk_bytes::Serializable; use poseidon_merkle::Opening as PoseidonOpening; use ringbuffer::{ConstGenericRingBuffer, RingBuffer}; -use rusk_abi::{ContractError, ContractId, STAKE_CONTRACT, TRANSFER_CONTRACT}; use execution_core::{ + signatures::bls::PublicKey as AccountPublicKey, + stake::STAKE_CONTRACT, transfer::{ - AccountData, MoonlightTransaction, PhoenixTransaction, Transaction, - TreeLeaf, Withdraw, WithdrawReceiver, WithdrawReplayToken, - WithdrawSignature, TRANSFER_TREE_DEPTH, + moonlight::{AccountData, Transaction as MoonlightTransaction}, + phoenix::{ + Note, Sender, Transaction as PhoenixTransaction, TreeLeaf, + NOTES_TREE_DEPTH, + }, + withdraw::{ + Withdraw, WithdrawReceiver, WithdrawReplayToken, WithdrawSignature, + }, + Transaction, TRANSFER_CONTRACT, }, - BlsPublicKey, BlsScalar, Note, Sender, + BlsScalar, ContractError, ContractId, }; use crate::transitory; @@ -59,7 +66,7 @@ pub struct TransferState { // NOTE: we should never remove entries from this list, since the entries // contain the nonce of the given account. Doing so opens the account // up to replay attacks. - accounts: BTreeMap<[u8; BlsPublicKey::SIZE], AccountData>, + accounts: BTreeMap<[u8; AccountPublicKey::SIZE], AccountData>, contract_balances: BTreeMap, } @@ -118,10 +125,7 @@ impl TransferState { panic!("Invalid signature"); } - let sender = contract_fn_sender( - fn_name, - ContractId::from_bytes(*contract), - ); + let sender = contract_fn_sender(fn_name, *contract); let note = Note::transparent_stealth(*address, value, sender); self.push_note_current_height(note); @@ -184,21 +188,21 @@ impl TransferState { /// This can only be called by the contract specified, and only if said /// contract has enough balance. pub fn withdraw(&mut self, withdraw: Withdraw) { - let contract = ContractId::from_bytes(*withdraw.contract()); + let contract = withdraw.contract(); let caller = rusk_abi::caller() .expect("A withdrawal must happen in the context of a transaction"); - if contract != caller { + if *contract != caller { panic!("The \"withdraw\" function can only be called by the contract specified in the payload"); } let value = withdraw.value(); - if self.contract_balance(&contract) < value { + if self.contract_balance(contract) < value { panic!("The contract doesn't have enough balance"); } - self.sub_contract_balance(&contract, value) + self.sub_contract_balance(contract, value) .expect("Subtracting balance from contract should succeed"); self.mint_withdrawal("WITHDRAW", withdraw); @@ -224,7 +228,7 @@ impl TransferState { panic!("Only the first contract call can be a conversion"); } - if *convert.contract() != TRANSFER_CONTRACT.to_bytes() { + if *convert.contract() != TRANSFER_CONTRACT { panic!("The conversion must target the transfer contract"); } @@ -366,11 +370,8 @@ impl TransferState { // perform contract call if present let mut result = Ok(Vec::new()); if let Some(call) = tx.call() { - result = rusk_abi::call_raw( - ContractId::from_bytes(call.contract), - &call.fn_name, - &call.fn_args, - ); + result = + rusk_abi::call_raw(call.contract, &call.fn_name, &call.fn_args); } result @@ -421,7 +422,7 @@ impl TransferState { // the balance, increment the nonce, and rely on `refund` to be called // after a successful exit. let from_bytes = from.to_bytes(); // TODO: this is expensive. maybe we should address the - // fact that `BlsPublicKey` doesn't impl `Ord` + // fact that `AccountPublicKey` doesn't impl `Ord` // so we can just use it directly as a key in the // `BTreeMap` @@ -469,11 +470,8 @@ impl TransferState { // perform contract call if present let mut result = Ok(Vec::new()); if let Some(call) = tx.call() { - result = rusk_abi::call_raw( - ContractId::from_bytes(call.contract), - &call.fn_name, - &call.fn_args, - ); + result = + rusk_abi::call_raw(call.contract, &call.fn_name, &call.fn_args); } result @@ -582,7 +580,7 @@ impl TransferState { pub fn opening( &self, pos: u64, - ) -> Option> { + ) -> Option> { self.tree.opening(pos) } @@ -598,7 +596,7 @@ impl TransferState { .collect() } - pub fn account(&self, key: &BlsPublicKey) -> AccountData { + pub fn account(&self, key: &AccountPublicKey) -> AccountData { let key_bytes = key.to_bytes(); self.accounts .get(&key_bytes) @@ -606,13 +604,13 @@ impl TransferState { .unwrap_or(EMPTY_ACCOUNT) } - pub fn add_account_balance(&mut self, key: &BlsPublicKey, value: u64) { + pub fn add_account_balance(&mut self, key: &AccountPublicKey, value: u64) { let key_bytes = key.to_bytes(); let account = self.accounts.entry(key_bytes).or_insert(EMPTY_ACCOUNT); account.balance = account.balance.saturating_add(value); } - pub fn sub_account_balance(&mut self, key: &BlsPublicKey, value: u64) { + pub fn sub_account_balance(&mut self, key: &AccountPublicKey, value: u64) { let key_bytes = key.to_bytes(); if let Some(account) = self.accounts.get_mut(&key_bytes) { account.balance = account.balance.saturating_sub(value); diff --git a/contracts/transfer/src/transitory.rs b/contracts/transfer/src/transitory.rs index 75297445ad..69186235c4 100644 --- a/contracts/transfer/src/transitory.rs +++ b/contracts/transfer/src/transitory.rs @@ -4,10 +4,12 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use rusk_abi::ContractId; - -use execution_core::transfer::{ - MoonlightTransaction, PhoenixTransaction, Transaction, +use execution_core::{ + transfer::{ + moonlight::Transaction as MoonlightTransaction, + phoenix::Transaction as PhoenixTransaction, Transaction, + }, + ContractId, }; /// The state of a deposit while a transaction is executing. @@ -62,8 +64,8 @@ pub fn put_transaction(tx: impl Into) { if d > 0 { let contract = tx .call() - .map(|call| ContractId::from_bytes(call.contract)) - .expect("There must be a contract when depositing funds"); + .expect("There must be a contract when depositing funds") + .contract; // When a transaction is initially inserted, any deposit is // available for pick up. diff --git a/contracts/transfer/src/tree.rs b/contracts/transfer/src/tree.rs index 358bca88b2..958d51a633 100644 --- a/contracts/transfer/src/tree.rs +++ b/contracts/transfer/src/tree.rs @@ -11,12 +11,12 @@ use poseidon_merkle::{ }; use execution_core::{ - transfer::{TreeLeaf, TRANSFER_TREE_DEPTH}, - BlsScalar, Note, + transfer::phoenix::{Note, TreeLeaf, NOTES_TREE_DEPTH}, + BlsScalar, }; pub struct Tree { - tree: PoseidonTree<(), TRANSFER_TREE_DEPTH>, + tree: PoseidonTree<(), NOTES_TREE_DEPTH>, // Since `dusk-merkle` does not include data blocks with the tree, we do it // here. leaves: Vec, @@ -95,7 +95,7 @@ impl Tree { pub fn opening( &self, pos: u64, - ) -> Option> { + ) -> Option> { self.tree.opening(pos) } diff --git a/contracts/transfer/tests/common.rs b/contracts/transfer/tests/common.rs index efb3c90b00..85a5a99c73 100644 --- a/contracts/transfer/tests/common.rs +++ b/contracts/transfer/tests/common.rs @@ -7,22 +7,29 @@ use std::sync::mpsc; use execution_core::{ + signatures::{ + bls::{PublicKey as AccountPublicKey, SecretKey as AccountSecretKey}, + schnorr::SecretKey as SchnorrSecretKey, + }, transfer::{ - AccountData, ContractCall, Fee, MoonlightPayload, MoonlightTransaction, - PhoenixPayload, PhoenixTransaction, Transaction, TreeLeaf, - TRANSFER_TREE_DEPTH, + contract_exec::{ContractCall, ContractExec}, + moonlight::{ + AccountData, Payload as MoonlightPayload, + Transaction as MoonlightTransaction, + }, + phoenix::{ + value_commitment, Fee, Note, Payload as PhoenixPayload, PublicKey, + SecretKey, Sender, Transaction as PhoenixTransaction, TreeLeaf, + TxSkeleton, ViewKey, NOTES_TREE_DEPTH, + }, + Transaction, TRANSFER_CONTRACT, }, - value_commitment, BlsPublicKey, BlsScalar, BlsSecretKey, JubJubScalar, - Note, PublicKey, SchnorrSecretKey, SecretKey, Sender, TxSkeleton, ViewKey, -}; -use rusk_abi::{ - CallReceipt, ContractError, ContractId, PiecrustError, Session, - TRANSFER_CONTRACT, + BlsScalar, ContractError, ContractId, JubJubScalar, }; +use rusk_abi::{CallReceipt, PiecrustError, Session}; use dusk_bytes::Serializable; use dusk_plonk::prelude::*; -use execution_core::transfer::ContractExec; use ff::Field; use phoenix_circuits::transaction::{TxCircuit, TxInputNote, TxOutputNote}; use poseidon_merkle::Opening as PoseidonOpening; @@ -91,7 +98,7 @@ pub fn root(session: &mut Session) -> Result { pub fn account( session: &mut Session, - pk: &BlsPublicKey, + pk: &AccountPublicKey, ) -> Result { session .call(TRANSFER_CONTRACT, "account", pk, GAS_LIMIT) @@ -110,7 +117,7 @@ pub fn contract_balance( pub fn opening( session: &mut Session, pos: u64, -) -> Result>, PiecrustError> { +) -> Result>, PiecrustError> { session .call(TRANSFER_CONTRACT, "opening", &pos, GAS_LIMIT) .map(|r| r.data) @@ -197,8 +204,8 @@ pub fn filter_notes_owned_by>( } pub fn create_moonlight_transaction( - from_sk: &BlsSecretKey, - to: Option, + from_sk: &AccountSecretKey, + to: Option, value: u64, deposit: u64, gas_limit: u64, @@ -206,10 +213,8 @@ pub fn create_moonlight_transaction( nonce: u64, exec: Option>, ) -> MoonlightTransaction { - let from = BlsPublicKey::from(from_sk); - let payload = MoonlightPayload { - from, + from: AccountPublicKey::from(from_sk), to, value, deposit, @@ -220,7 +225,7 @@ pub fn create_moonlight_transaction( }; let digest = payload.to_hash_input_bytes(); - let signature = from_sk.sign(&from, &digest); + let signature = from_sk.sign(&digest); MoonlightTransaction::new(payload, signature) } @@ -409,7 +414,7 @@ pub fn create_phoenix_transaction( let sig_b = schnorr_sk_b.sign(&mut rng, payload_hash); // Build the circuit - let circuit: TxCircuit = TxCircuit::new( + let circuit: TxCircuit = TxCircuit::new( tx_input_notes .try_into() .expect("The input notes should be the correct ammount"), diff --git a/contracts/transfer/tests/transfer.rs b/contracts/transfer/tests/transfer.rs index 3207477304..17483262b4 100644 --- a/contracts/transfer/tests/transfer.rs +++ b/contracts/transfer/tests/transfer.rs @@ -19,15 +19,22 @@ use rand::rngs::StdRng; use rand::{CryptoRng, RngCore, SeedableRng}; use execution_core::{ + dusk, + signatures::bls::{ + PublicKey as AccountPublicKey, SecretKey as AccountSecretKey, + }, transfer::{ - ContractCall, ContractExec, Withdraw, WithdrawReceiver, - WithdrawReplayToken, + contract_exec::{ContractCall, ContractExec}, + phoenix::{ + Note, PublicKey as PhoenixPublicKey, SecretKey as PhoenixSecretKey, + ViewKey as PhoenixViewKey, + }, + withdraw::{Withdraw, WithdrawReceiver, WithdrawReplayToken}, + TRANSFER_CONTRACT, }, - BlsPublicKey, BlsSecretKey, JubJubScalar, Note, PublicKey, SecretKey, - ViewKey, + ContractId, JubJubScalar, LUX, }; -use rusk_abi::dusk::{dusk, LUX}; -use rusk_abi::{ContractData, ContractId, Session, TRANSFER_CONTRACT, VM}; +use rusk_abi::{ContractData, Session, VM}; const PHOENIX_GENESIS_VALUE: u64 = dusk(1_000.0); const MOONLIGHT_GENESIS_VALUE: u64 = dusk(1_000.0); @@ -52,8 +59,8 @@ const OWNER: [u8; 32] = [0; 32]; fn instantiate( rng: &mut Rng, vm: &VM, - phoenix_pk: &PublicKey, - moonlight_pk: &BlsPublicKey, + phoenix_pk: &PhoenixPublicKey, + moonlight_pk: &AccountPublicKey, ) -> Session { let transfer_bytecode = include_bytes!( "../../../target/dusk/wasm64-unknown-unknown/release/transfer_contract.wasm" @@ -143,13 +150,14 @@ fn phoenix_transfer() { let vm = &mut rusk_abi::new_ephemeral_vm() .expect("Creating ephemeral VM should work"); - let phoenix_sender_sk = SecretKey::random(rng); - let phoenix_sender_pk = PublicKey::from(&phoenix_sender_sk); + let phoenix_sender_sk = PhoenixSecretKey::random(rng); + let phoenix_sender_pk = PhoenixPublicKey::from(&phoenix_sender_sk); - let phoenix_receiver_pk = PublicKey::from(&SecretKey::random(rng)); + let phoenix_receiver_pk = + PhoenixPublicKey::from(&PhoenixSecretKey::random(rng)); - let moonlight_sk = BlsSecretKey::random(rng); - let moonlight_pk = BlsPublicKey::from(&moonlight_sk); + let moonlight_sk = AccountSecretKey::random(rng); + let moonlight_pk = AccountPublicKey::from(&moonlight_sk); let session = &mut instantiate(rng, vm, &phoenix_sender_pk, &moonlight_pk); @@ -229,12 +237,13 @@ fn moonlight_transfer() { let vm = &mut rusk_abi::new_ephemeral_vm() .expect("Creating ephemeral VM should work"); - let phoenix_pk = PublicKey::from(&SecretKey::random(rng)); + let phoenix_pk = PhoenixPublicKey::from(&PhoenixSecretKey::random(rng)); - let moonlight_sender_sk = BlsSecretKey::random(rng); - let moonlight_sender_pk = BlsPublicKey::from(&moonlight_sender_sk); + let moonlight_sender_sk = AccountSecretKey::random(rng); + let moonlight_sender_pk = AccountPublicKey::from(&moonlight_sender_sk); - let moonlight_receiver_pk = BlsPublicKey::from(&BlsSecretKey::random(rng)); + let moonlight_receiver_pk = + AccountPublicKey::from(&AccountSecretKey::random(rng)); let session = &mut instantiate(rng, vm, &phoenix_pk, &moonlight_sender_pk); @@ -294,11 +303,11 @@ fn phoenix_alice_ping() { let vm = &mut rusk_abi::new_ephemeral_vm() .expect("Creating ephemeral VM should work"); - let phoenix_sender_sk = SecretKey::random(rng); - let phoenix_sender_pk = PublicKey::from(&phoenix_sender_sk); + let phoenix_sender_sk = PhoenixSecretKey::random(rng); + let phoenix_sender_pk = PhoenixPublicKey::from(&phoenix_sender_sk); - let moonlight_sk = BlsSecretKey::random(rng); - let moonlight_pk = BlsPublicKey::from(&moonlight_sk); + let moonlight_sk = AccountSecretKey::random(rng); + let moonlight_pk = AccountPublicKey::from(&moonlight_sk); let session = &mut instantiate(rng, vm, &phoenix_sender_pk, &moonlight_pk); @@ -315,7 +324,7 @@ fn phoenix_alice_ping() { let is_obfuscated = false; let deposit = 0; let contract_call = Some(ContractCall { - contract: ALICE_ID.to_bytes(), + contract: ALICE_ID, fn_name: String::from("ping"), fn_args: vec![], }); @@ -358,10 +367,10 @@ fn moonlight_alice_ping() { let vm = &mut rusk_abi::new_ephemeral_vm() .expect("Creating ephemeral VM should work"); - let phoenix_pk = PublicKey::from(&SecretKey::random(rng)); + let phoenix_pk = PhoenixPublicKey::from(&PhoenixSecretKey::random(rng)); - let moonlight_sk = BlsSecretKey::random(rng); - let moonlight_pk = BlsPublicKey::from(&moonlight_sk); + let moonlight_sk = AccountSecretKey::random(rng); + let moonlight_pk = AccountPublicKey::from(&moonlight_sk); let session = &mut instantiate(rng, vm, &phoenix_pk, &moonlight_pk); @@ -369,7 +378,7 @@ fn moonlight_alice_ping() { .expect("Getting the sender account should succeed"); let contract_call = Some(ContractCall { - contract: ALICE_ID.to_bytes(), + contract: ALICE_ID, fn_name: String::from("ping"), fn_args: vec![], }); @@ -416,12 +425,12 @@ fn phoenix_deposit_and_withdraw() { let vm = &mut rusk_abi::new_ephemeral_vm() .expect("Creating ephemeral VM should work"); - let phoenix_sender_sk = SecretKey::random(rng); - let phoenix_sender_vk = ViewKey::from(&phoenix_sender_sk); - let phoenix_sender_pk = PublicKey::from(&phoenix_sender_sk); + let phoenix_sender_sk = PhoenixSecretKey::random(rng); + let phoenix_sender_vk = PhoenixViewKey::from(&phoenix_sender_sk); + let phoenix_sender_pk = PhoenixPublicKey::from(&phoenix_sender_sk); - let moonlight_sk = BlsSecretKey::random(rng); - let moonlight_pk = BlsPublicKey::from(&moonlight_sk); + let moonlight_sk = AccountSecretKey::random(rng); + let moonlight_pk = AccountPublicKey::from(&moonlight_sk); let session = &mut instantiate(rng, vm, &phoenix_sender_pk, &moonlight_pk); @@ -438,7 +447,7 @@ fn phoenix_deposit_and_withdraw() { let is_obfuscated = false; let deposit_value = PHOENIX_GENESIS_VALUE / 2; let contract_call = Some(ContractCall { - contract: ALICE_ID.to_bytes(), + contract: ALICE_ID, fn_name: String::from("deposit"), fn_args: deposit_value.to_bytes().into(), }); @@ -471,7 +480,7 @@ fn phoenix_deposit_and_withdraw() { + tx.payload().tx_skeleton.deposit + tx.payload().tx_skeleton.max_fee + tx.payload().tx_skeleton.outputs[1] - .value(Some(&ViewKey::from(&phoenix_sender_sk))) + .value(Some(&PhoenixViewKey::from(&phoenix_sender_sk))) .unwrap() ); assert_eq!( @@ -513,7 +522,7 @@ fn phoenix_deposit_and_withdraw() { let withdraw = Withdraw::new( rng, ¬e_sk, - ALICE_ID.to_bytes(), + ALICE_ID, PHOENIX_GENESIS_VALUE / 2, WithdrawReceiver::Phoenix(address), WithdrawReplayToken::Phoenix(vec![ @@ -529,7 +538,7 @@ fn phoenix_deposit_and_withdraw() { let is_obfuscated = false; let deposit_value = 0; let contract_call = Some(ContractCall { - contract: ALICE_ID.to_bytes(), + contract: ALICE_ID, fn_name: String::from("withdraw"), fn_args: rkyv::to_bytes::<_, 1024>(&withdraw) .expect("should serialize Mint correctly") @@ -570,12 +579,12 @@ fn phoenix_to_moonlight_swap() { let rng = &mut StdRng::seed_from_u64(0xfeeb); - let phoenix_sk = SecretKey::random(rng); - let phoenix_vk = ViewKey::from(&phoenix_sk); - let phoenix_pk = PublicKey::from(&phoenix_sk); + let phoenix_sk = PhoenixSecretKey::random(rng); + let phoenix_vk = PhoenixViewKey::from(&phoenix_sk); + let phoenix_pk = PhoenixPublicKey::from(&phoenix_sk); - let moonlight_sk = BlsSecretKey::random(rng); - let moonlight_pk = BlsPublicKey::from(&moonlight_sk); + let moonlight_sk = AccountSecretKey::random(rng); + let moonlight_pk = AccountPublicKey::from(&moonlight_sk); let vm = &mut rusk_abi::new_ephemeral_vm() .expect("Creating ephemeral VM should work"); @@ -601,14 +610,14 @@ fn phoenix_to_moonlight_swap() { let convert = Withdraw::new( rng, &moonlight_sk, - TRANSFER_CONTRACT.to_bytes(), + TRANSFER_CONTRACT, SWAP_VALUE, WithdrawReceiver::Moonlight(moonlight_pk), WithdrawReplayToken::Phoenix(vec![notes[0].gen_nullifier(&phoenix_sk)]), ); let contract_call = ContractCall { - contract: TRANSFER_CONTRACT.to_bytes(), + contract: TRANSFER_CONTRACT, fn_name: String::from("convert"), fn_args: rkyv::to_bytes::<_, 1024>(&convert) .expect("should serialize conversion correctly") @@ -670,12 +679,12 @@ fn moonlight_to_phoenix_swap() { let rng = &mut StdRng::seed_from_u64(0xfeeb); - let phoenix_sk = SecretKey::random(rng); - let phoenix_vk = ViewKey::from(&phoenix_sk); - let phoenix_pk = PublicKey::from(&phoenix_sk); + let phoenix_sk = PhoenixSecretKey::random(rng); + let phoenix_vk = PhoenixViewKey::from(&phoenix_sk); + let phoenix_pk = PhoenixPublicKey::from(&phoenix_sk); - let moonlight_sk = BlsSecretKey::random(rng); - let moonlight_pk = BlsPublicKey::from(&moonlight_sk); + let moonlight_sk = AccountSecretKey::random(rng); + let moonlight_pk = AccountPublicKey::from(&moonlight_sk); let vm = &mut rusk_abi::new_ephemeral_vm() .expect("Creating ephemeral VM should work"); @@ -706,14 +715,14 @@ fn moonlight_to_phoenix_swap() { let convert = Withdraw::new( rng, ¬e_sk, - TRANSFER_CONTRACT.to_bytes(), + TRANSFER_CONTRACT, SWAP_VALUE, WithdrawReceiver::Phoenix(address), WithdrawReplayToken::Moonlight(nonce), ); let contract_call = ContractCall { - contract: TRANSFER_CONTRACT.to_bytes(), + contract: TRANSFER_CONTRACT, fn_name: String::from("convert"), fn_args: rkyv::to_bytes::<_, 1024>(&convert) .expect("should serialize conversion correctly") @@ -768,12 +777,12 @@ fn swap_wrong_contract_targeted() { let rng = &mut StdRng::seed_from_u64(0xfeeb); - let phoenix_sk = SecretKey::random(rng); - let phoenix_vk = ViewKey::from(&phoenix_sk); - let phoenix_pk = PublicKey::from(&phoenix_sk); + let phoenix_sk = PhoenixSecretKey::random(rng); + let phoenix_vk = PhoenixViewKey::from(&phoenix_sk); + let phoenix_pk = PhoenixPublicKey::from(&phoenix_sk); - let moonlight_sk = BlsSecretKey::random(rng); - let moonlight_pk = BlsPublicKey::from(&moonlight_sk); + let moonlight_sk = AccountSecretKey::random(rng); + let moonlight_pk = AccountPublicKey::from(&moonlight_sk); let vm = &mut rusk_abi::new_ephemeral_vm() .expect("Creating ephemeral VM should work"); @@ -804,15 +813,15 @@ fn swap_wrong_contract_targeted() { let convert = Withdraw::new( rng, ¬e_sk, - ALICE_ID.to_bytes(), /* this should be the transfer contract, but - * we're testing the "wrong target" case */ + ALICE_ID, /* this should be the transfer contract, but + * we're testing the "wrong target" case */ SWAP_VALUE, WithdrawReceiver::Phoenix(address), WithdrawReplayToken::Moonlight(nonce), ); let contract_call = ContractCall { - contract: TRANSFER_CONTRACT.to_bytes(), + contract: TRANSFER_CONTRACT, fn_name: String::from("convert"), fn_args: rkyv::to_bytes::<_, 1024>(&convert) .expect("should serialize conversion correctly") diff --git a/execution-core/CHANGELOG.md b/execution-core/CHANGELOG.md index b0d7d6a0cf..749cd0f514 100644 --- a/execution-core/CHANGELOG.md +++ b/execution-core/CHANGELOG.md @@ -7,90 +7,103 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -### Changed - -- Improved deserialization [#1963] -- Change payload to support contract deployment [#1882] - -- Re-export - - `dusk-bls12_381::BlsScalar` - - `dusk-jubjub::{ - JubJubAffine, - JubJubExtended, - JubJubScalar, - GENERATOR_EXTENDED, - GENERATOR_NUMS_EXTENDED - }` - - `bls12_381_bls::{ - Error as BlsSigError, - PublicKey as BlsPublicKey, - SecretKey as BlsSecretKey, - Signature as BlsSignature, - APK as BlsAggPublicKey - }` - - `jubjub_schnorr::{ - PublicKey as SchnorrPublicKey, - SecretKey as SchnorrSecretKey, - Signature as SchnorrSignature, - SignatureDouble as SchnorrSignatureDouble - }` - - `phoenix_core::{ - value_commitment, - Error as PhoenixError, - Note, - PublicKey, - SecretKey, - Sender, - StealthAddress, - TxSkeleton, - ViewKey, - NOTE_VAL_ENC_SIZE, - OUTPUT_NOTES - }` -- Add type-alias: - - `pub type NotePublicKey = SchnorrPublicKey` - - `pub type NoteSecretKey = SchnorrSecretKey` - - `pub type NoteSignature = SchnorrSignature` -- Add modules, types and functionality: - - `transfer::{ - AccountData, - ContractCall, - Fee, - MoonlightPayload, - MoonlightTransaction, - PhoenixPayload, - PhoenixTransaction, - TreeLeaf, - Withdraw, - WithdrawReceiver, - WithdrawSignature, - WithdrawSecretKey, - WithdrawReplayToken, - Transaction, - TRANSFER_TREE_DEPTH, - ContractId, - }` - - `stake::{ - Stake, - StakeAmount, - StakeData, - StakeEvent, - Withdraw, - EPOCH, - STAKE_WARNINGS, - next_epoch, - }` - ### Added -- Add `nonce` contract deploy transaction [#1884] - - -[#1963]: https://github.com/dusk-network/rusk/issues/1963 -[#1963]: https://github.com/dusk-network/rusk/issues/1856 -[#1884]: https://github.com/dusk-network/rusk/issues/1884 -[#1882]: https://github.com/dusk-network/rusk/issues/1882 -[#1723]: https://github.com/dusk-network/rusk/issues/1723 +- Add, types, type-alias, functionality, re-exports and modules: +```rust +dusk; +from_dusk; +Dusk; +LUX; +pub use dusk_bls12_381::BlsScalar; +pub use dusk_jubjub::{ + JubJubAffine, + JubJubExtended, + JubJubScalar, + GENERATOR_EXTENDED, + GENERATOR_NUMS_EXTENDED +}; +pub use piecrust_uplink::{ + ContractError, + ContractId, + Event, + StandardBufSerializer, + ARGBUF_LEN, + CONTRACT_ID_BYTES, +}; +signatures::{ + bls::{ + Error, + PublicKey, + SecretKey, + Signature, + APK as AggPublicKey, + }; + schnorr::{ + PublicKey, + SecretKey, + Signature, + SignatureDouble, + } +} +transfer::{ + contract_exec::{ + ContractBytecode; + ContractCall; + ContractExec; + }; + moonlight::{ + AccountData; + Payload; + Transaction; + }; + phoenix::{ + Fee; + Payload; + Transaction; + TreeLeaf; + NOTES_TREE_DEPTH; + TRANSCRIPT_LABEL; + pub use phoenix_core::{ + value_commitment, + Error as PhoenixError, + Note, + PublicKey, + SecretKey, + Sender, + StealthAddress, + TxSkeleton, + ViewKey, + NOTE_VAL_ENC_SIZE, + OUTPUT_NOTES + }; + pub type NotePublicKey = SchnorrPublicKey; + pub type NoteSecretKey = SchnorrSecretKey; + pub type NoteSignature = SchnorrSignature; + }; + withdraw::{ + Withdraw; + WithdrawReceiver; + WithdrawSignature; + WithdrawSecretKey; + WithdrawReplayToken; + }; + Transaction; + TRANSFER_CONTRACT; +}; +stake::{ + Stake; + StakeAmount; + StakeData; + StakeEvent; + Withdraw; + EPOCH; + STAKE_CONTRACT; + STAKE_WARNINGS; + next_epoch; +}; +licence::LICENSE_CONTRACT; +``` [Unreleased]: https://github.com/dusk-network/rusk/compare/execution-core-0.1.0...HEAD [0.1.0]: https://github.com/dusk-network/dusk-abi/releases/tag/execution-core-0.1.0 diff --git a/execution-core/Cargo.toml b/execution-core/Cargo.toml index 5bd1422f2d..6d3a3b75b5 100644 --- a/execution-core/Cargo.toml +++ b/execution-core/Cargo.toml @@ -7,9 +7,10 @@ edition = "2021" dusk-bls12_381 = { version = "0.13", default-features = false, features = ["rkyv-impl"] } dusk-jubjub = { version = "0.14", default-features = false, features = ["rkyv-impl"] } dusk-poseidon = "0.39" -bls12_381-bls = { version = "0.3", default-features = false, features = ["rkyv-impl"] } +bls12_381-bls = { version = "0.4", default-features = false, features = ["rkyv-impl"] } jubjub-schnorr = { version = "0.4", default-features = false, features = ["rkyv-impl"] } phoenix-core = { version = "0.30.0-rc", default-features = false, features = ["rkyv-impl", "alloc"] } +piecrust-uplink = { version = "0.16" } dusk-bytes = "0.1" rkyv = { version = "0.7", default-features = false, features = ["size_32"] } bytecheck = { version = "0.6", default-features = false } diff --git a/execution-core/src/bytecode.rs b/execution-core/src/bytecode.rs deleted file mode 100644 index ab1e745193..0000000000 --- a/execution-core/src/bytecode.rs +++ /dev/null @@ -1,53 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. -// -// Copyright (c) DUSK NETWORK. All rights reserved. - -//! Wrapper for a strip-able bytecode that we want to keep the integrity of. - -extern crate alloc; -use crate::reader::{read_arr, read_vec}; -use alloc::vec::Vec; -use bytecheck::CheckBytes; -use dusk_bytes::{Error as BytesError, Serializable}; -use rkyv::{Archive, Deserialize, Serialize}; - -#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)] -#[archive_attr(derive(CheckBytes))] -/// Holds bytes of bytecode and its hash. -pub struct Bytecode { - /// Hash of the bytecode bytes. - pub hash: [u8; 32], - /// Bytecode bytes. - pub bytes: Vec, -} - -impl Bytecode { - /// Provides contribution bytes for an external hash. - #[must_use] - pub fn to_hash_input_bytes(&self) -> Vec { - self.hash.to_vec() - } - - /// Serializes this object into a variable length buffer - #[must_use] - pub fn to_var_bytes(&self) -> Vec { - let mut bytes = Vec::new(); - bytes.extend(self.hash); - bytes.extend((self.bytes.len() as u64).to_bytes()); - bytes.extend(&self.bytes); - bytes - } - - /// Deserialize from a bytes buffer. - /// Resets buffer to a position after the bytes read. - /// - /// # Errors - /// Errors when the bytes are not available. - pub fn from_buf(buf: &mut &[u8]) -> Result { - let hash = read_arr::<32>(buf)?; - let bytes = read_vec(buf)?; - Ok(Self { hash, bytes }) - } -} diff --git a/rusk-abi/src/dusk.rs b/execution-core/src/dusk.rs similarity index 90% rename from rusk-abi/src/dusk.rs rename to execution-core/src/dusk.rs index 920b762748..721dccf3b3 100644 --- a/rusk-abi/src/dusk.rs +++ b/execution-core/src/dusk.rs @@ -20,11 +20,16 @@ pub const LUX: Dusk = dusk(1.0 / DUSK_UNIT); pub type Dusk = u64; /// Converts from floating point format to Dusk. +#[must_use] +#[allow(clippy::cast_possible_truncation)] +#[allow(clippy::cast_sign_loss)] pub const fn dusk(value: f64) -> Dusk { (value * DUSK_UNIT) as Dusk } /// Converts from Dusk to floating point format. +#[must_use] +#[allow(clippy::cast_precision_loss)] pub const fn from_dusk(dusk: Dusk) -> f64 { dusk as f64 / DUSK_UNIT } diff --git a/execution-core/src/lib.rs b/execution-core/src/lib.rs index 5697fbd942..1feccfde40 100644 --- a/execution-core/src/lib.rs +++ b/execution-core/src/lib.rs @@ -11,14 +11,17 @@ #![deny(rustdoc::broken_intra_doc_links)] #![deny(clippy::pedantic)] #![allow(clippy::module_name_repetitions)] +#![feature(const_fn_floating_point_arithmetic)] extern crate alloc; -pub mod bytecode; -pub mod reader; +pub mod license; pub mod stake; pub mod transfer; +mod dusk; +pub use dusk::{dusk, from_dusk, Dusk, LUX}; + // elliptic curve types pub use dusk_bls12_381::BlsScalar; pub use dusk_jubjub::{ @@ -26,27 +29,85 @@ pub use dusk_jubjub::{ GENERATOR_NUMS_EXTENDED, }; -// signature types -pub use bls12_381_bls::{ - Error as BlsSigError, PublicKey as BlsPublicKey, SecretKey as BlsSecretKey, - Signature as BlsSignature, APK as BlsAggPublicKey, -}; +/// Signatures used in the Dusk protocol. +pub mod signatures { + /// Types for the bls-signature scheme. + pub mod bls { + pub use bls12_381_bls::{ + Error, MultisigPublicKey, MultisigSignature, PublicKey, SecretKey, + Signature, + }; + } -pub use jubjub_schnorr::{ - PublicKey as SchnorrPublicKey, SecretKey as SchnorrSecretKey, - Signature as SchnorrSignature, SignatureDouble as SchnorrSignatureDouble, -}; + /// Types for the schnorr-signature scheme. + pub mod schnorr { + pub use jubjub_schnorr::{ + PublicKey, SecretKey, Signature, SignatureDouble, + }; + } +} -/// Secret key associated with a note. -pub type NoteSecretKey = SchnorrSecretKey; -/// Public key associated with a note. -pub type NotePublicKey = SchnorrPublicKey; -/// Signature to prove ownership of the note -pub type NoteSignature = SchnorrSignature; - -// phoenix types -pub use phoenix_core::{ - value_commitment, Error as PhoenixError, Note, PublicKey, SecretKey, - Sender, StealthAddress, TxSkeleton, ViewKey, NOTE_VAL_ENC_SIZE, - OUTPUT_NOTES, +pub use piecrust_uplink::{ + ContractError, ContractId, Event, StandardBufSerializer, ARGBUF_LEN, + CONTRACT_ID_BYTES, }; + +#[inline] +const fn reserved(b: u8) -> ContractId { + let mut bytes = [0u8; CONTRACT_ID_BYTES]; + bytes[0] = b; + ContractId::from_bytes(bytes) +} + +use alloc::string::String; +use alloc::vec::Vec; + +use dusk_bytes::{DeserializableSlice, Error as BytesError}; + +/// Reads vector from a buffer. +/// Resets buffer to a position after the bytes read. +/// +/// # Errors +/// When length or data could not be read. +fn read_vec(buf: &mut &[u8]) -> Result, BytesError> { + let len = usize::try_from(u64::from_reader(buf)?) + .map_err(|_| BytesError::InvalidData)?; + if buf.len() < len { + return Err(BytesError::InvalidData); + } + let bytes = buf[..len].into(); + *buf = &buf[len..]; + Ok(bytes) +} + +/// Reads string from a buffer. +/// Resets buffer to a position after the bytes read. +/// +/// # Errors +/// When length or data could not be read. +fn read_str(buf: &mut &[u8]) -> Result { + let len = usize::try_from(u64::from_reader(buf)?) + .map_err(|_| BytesError::InvalidData)?; + if buf.len() < len { + return Err(BytesError::InvalidData); + } + let str = String::from_utf8(buf[..len].into()) + .map_err(|_| BytesError::InvalidData)?; + *buf = &buf[len..]; + Ok(str) +} + +/// Reads array from a buffer. +/// Resets buffer to a position after the bytes read. +/// +/// # Errors +/// When length or data could not be read. +fn read_arr(buf: &mut &[u8]) -> Result<[u8; N], BytesError> { + if buf.len() < N { + return Err(BytesError::InvalidData); + } + let mut a = [0u8; N]; + a.copy_from_slice(&buf[..N]); + *buf = &buf[N..]; + Ok(a) +} diff --git a/rusk-abi/src/query.rs b/execution-core/src/license.rs similarity index 55% rename from rusk-abi/src/query.rs rename to execution-core/src/license.rs index 864bcd1a2a..5b503324c6 100644 --- a/rusk-abi/src/query.rs +++ b/execution-core/src/license.rs @@ -4,15 +4,9 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -mod public_input; -pub use public_input::*; +//! Types used by Dusk's license contract. -cfg_if::cfg_if! { - if #[cfg(feature = "host")] { - mod host; - pub use host::*; - } else { - mod hosted; - pub use hosted::*; - } -} +use crate::{reserved, ContractId}; + +/// ID of the genesis license contract +pub const LICENSE_CONTRACT: ContractId = reserved(0x3); diff --git a/execution-core/src/reader.rs b/execution-core/src/reader.rs deleted file mode 100644 index 058e09fd89..0000000000 --- a/execution-core/src/reader.rs +++ /dev/null @@ -1,64 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. -// -// Copyright (c) DUSK NETWORK. All rights reserved. - -//! Functions for reading variable length elements from bytes. - -extern crate alloc; -use alloc::string::String; -use alloc::vec::Vec; - -use dusk_bytes::Error::InvalidData; -use dusk_bytes::{DeserializableSlice, Error as BytesError}; - -/// Reads vector from a buffer. -/// Resets buffer to a position after the bytes read. -/// -/// # Errors -/// When length or data could not be read. -pub fn read_vec(buf: &mut &[u8]) -> Result, BytesError> { - let len = usize::try_from(u64::from_reader(buf)?) - .map_err(|_| BytesError::InvalidData)?; - if buf.len() < len { - return Err(InvalidData); - } - let bytes = buf[..len].into(); - *buf = &buf[len..]; - Ok(bytes) -} - -/// Reads string from a buffer. -/// Resets buffer to a position after the bytes read. -/// -/// # Errors -/// When length or data could not be read. -pub fn read_str(buf: &mut &[u8]) -> Result { - let len = usize::try_from(u64::from_reader(buf)?) - .map_err(|_| BytesError::InvalidData)?; - if buf.len() < len { - return Err(InvalidData); - } - let str = String::from_utf8(buf[..len].into()) - .map_err(|_| BytesError::InvalidData)?; - *buf = &buf[len..]; - Ok(str) -} - -/// Reads array from a buffer. -/// Resets buffer to a position after the bytes read. -/// -/// # Errors -/// When length or data could not be read. -pub fn read_arr( - buf: &mut &[u8], -) -> Result<[u8; N], BytesError> { - if buf.len() < N { - return Err(InvalidData); - } - let mut a = [0u8; N]; - a.copy_from_slice(&buf[..N]); - *buf = &buf[N..]; - Ok(a) -} diff --git a/execution-core/src/stake.rs b/execution-core/src/stake.rs index 003fbdca6e..3d5b722542 100644 --- a/execution-core/src/stake.rs +++ b/execution-core/src/stake.rs @@ -13,10 +13,17 @@ use dusk_bytes::{DeserializableSlice, Serializable, Write}; use rkyv::{Archive, Deserialize, Serialize}; use crate::{ - transfer::{Withdraw as TransferWithdraw, WithdrawReceiver}, - BlsPublicKey, BlsSecretKey, BlsSignature, + signatures::bls::{ + PublicKey as BlsPublicKey, SecretKey as BlsSecretKey, + Signature as BlsSignature, + }, + transfer::withdraw::{Withdraw as TransferWithdraw, WithdrawReceiver}, + ContractId, }; +/// ID of the genesis stake contract +pub const STAKE_CONTRACT: ContractId = crate::reserved(0x2); + /// Epoch used for stake operations pub const EPOCH: u64 = 2160; @@ -46,17 +53,15 @@ impl Stake { /// Create a new stake. #[must_use] pub fn new(sk: &BlsSecretKey, value: u64, nonce: u64) -> Self { - let account = BlsPublicKey::from(sk); - let mut stake = Stake { - account, + account: BlsPublicKey::from(sk), value, nonce, signature: BlsSignature::default(), }; let msg = stake.signature_message(); - stake.signature = sk.sign(&account, &msg); + stake.signature = sk.sign(&msg); stake } @@ -127,16 +132,14 @@ impl Withdraw { /// Create a new withdraw call. #[must_use] pub fn new(sk: &BlsSecretKey, withdraw: TransferWithdraw) -> Self { - let account = BlsPublicKey::from(sk); - let mut stake_withdraw = Withdraw { - account, + account: BlsPublicKey::from(sk), withdraw, signature: BlsSignature::default(), }; let msg = stake_withdraw.signature_message(); - stake_withdraw.signature = sk.sign(&account, &msg); + stake_withdraw.signature = sk.sign(&msg); stake_withdraw } diff --git a/execution-core/src/transfer.rs b/execution-core/src/transfer.rs index 2f830c7aca..62195629ca 100644 --- a/execution-core/src/transfer.rs +++ b/execution-core/src/transfer.rs @@ -7,600 +7,260 @@ //! Types related to Dusk's transfer contract that are shared across the //! network. -use alloc::string::String; use alloc::vec::Vec; -use core::cmp; - use bytecheck::CheckBytes; -use dusk_bytes::{DeserializableSlice, Error as BytesError, Serializable}; -use dusk_poseidon::{Domain, Hash}; -use ff::Field; -use rand::{CryptoRng, RngCore}; -use rkyv::{ - ser::serializers::AllocSerializer, Archive, Deserialize, Fallible, - Serialize, -}; +use dusk_bytes::{DeserializableSlice, Error as BytesError}; +use rkyv::{Archive, Deserialize, Serialize}; use crate::{ - BlsPublicKey, BlsScalar, BlsSecretKey, BlsSignature, JubJubScalar, Note, - PublicKey, SchnorrSecretKey, SchnorrSignature, Sender, StealthAddress, -}; - -mod transaction; -pub use transaction::{ - MoonlightPayload, MoonlightTransaction, PhoenixPayload, PhoenixTransaction, - Transaction, + signatures::bls::{ + PublicKey as AccountPublicKey, Signature as AccountSignature, + }, + BlsScalar, ContractId, }; -use crate::bytecode::Bytecode; -use crate::reader::{read_arr, read_str, read_vec}; - -/// Unique ID to identify a contract. -pub type ContractId = [u8; 32]; +pub mod contract_exec; +pub mod moonlight; +pub mod phoenix; +pub mod withdraw; -/// The depth of the transfer tree. -pub const TRANSFER_TREE_DEPTH: usize = 17; +/// ID of the genesis transfer contract +pub const TRANSFER_CONTRACT: ContractId = crate::reserved(0x1); -/// A leaf of the transfer tree. -#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)] -#[archive_attr(derive(CheckBytes))] -pub struct TreeLeaf { - /// The height of the block when the note was inserted in the tree. - pub block_height: u64, - /// The note inserted in the tree. - pub note: Note, -} - -/// A Moonlight account's information. -#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)] -#[archive_attr(derive(CheckBytes))] -pub struct AccountData { - /// Number used for replay protection. - pub nonce: u64, - /// Account balance. - pub balance: u64, -} +use contract_exec::{ContractCall, ContractDeploy}; +use moonlight::{ + Payload as MoonlightPayload, Transaction as MoonlightTransaction, +}; +use phoenix::{ + Note, Payload as PhoenixPayload, Sender, StealthAddress, + Transaction as PhoenixTransaction, +}; -/// Withdrawal information, proving the intent of a user to withdraw from a -/// contract. -/// -/// This structure is meant to be passed to a contract by a caller. The contract -/// is then responsible for calling `withdraw` in the transfer contract to -/// settle it, if it wants to allow the withdrawal. -/// -/// e.g. the stake contract uses it as a call argument for the `unstake` -/// function -#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize)] +/// The transaction used by the transfer contract. +#[derive(Debug, Clone, Archive, PartialEq, Eq, Serialize, Deserialize)] #[archive_attr(derive(CheckBytes))] -pub struct Withdraw { - contract: ContractId, - value: u64, - receiver: WithdrawReceiver, - token: WithdrawReplayToken, - signature: WithdrawSignature, +#[allow(clippy::large_enum_variant)] +pub enum Transaction { + /// A phoenix transaction. + Phoenix(PhoenixTransaction), + /// A moonlight transaction. + Moonlight(MoonlightTransaction), } -impl Withdraw { - /// Create a new contract withdrawal. - /// - /// # Panics - /// When the receiver does not match the secret key passed. +impl Transaction { + /// Create a new phoenix transaction. #[must_use] - pub fn new<'a, R: RngCore + CryptoRng>( - rng: &mut R, - sk: impl Into>, - contract: ContractId, - value: u64, - receiver: WithdrawReceiver, - token: WithdrawReplayToken, - ) -> Self { - let mut withdraw = Self { - contract, - value, - receiver, - token, - signature: WithdrawSignature::Moonlight(BlsSignature::default()), - }; - - let sk = sk.into(); - - match (&sk, &receiver) { - (WithdrawSecretKey::Phoenix(_), WithdrawReceiver::Moonlight(_)) => { - panic!("Moonlight receiver with phoenix signer"); - } - (WithdrawSecretKey::Moonlight(_), WithdrawReceiver::Phoenix(_)) => { - panic!("Phoenix receiver with moonlight signer"); - } - _ => {} - } - - let msg = withdraw.signature_message(); - - match sk { - WithdrawSecretKey::Phoenix(sk) => { - let digest = BlsScalar::hash_to_scalar(&msg); - let signature = sk.sign(rng, digest); - withdraw.signature = signature.into(); - } - WithdrawSecretKey::Moonlight(sk) => { - let pk = BlsPublicKey::from(sk); - let signature = sk.sign(&pk, &msg); - withdraw.signature = signature.into(); - } - } - - withdraw + pub fn phoenix(payload: PhoenixPayload, proof: impl Into>) -> Self { + Self::Phoenix(PhoenixTransaction::new(payload, proof)) } - /// The contract to withraw from. + /// Create a new moonlight transaction. #[must_use] - pub fn contract(&self) -> &ContractId { - &self.contract + pub fn moonlight( + payload: MoonlightPayload, + signature: AccountSignature, + ) -> Self { + Self::Moonlight(MoonlightTransaction::new(payload, signature)) } - /// The amount to withdraw. + /// Return the sender of the account for Moonlight transactions. #[must_use] - pub fn value(&self) -> u64 { - self.value + pub fn from(&self) -> Option<&AccountPublicKey> { + match self { + Self::Phoenix(_) => None, + Self::Moonlight(tx) => Some(&tx.payload.from), + } } - /// The receiver of the value. + /// Return the receiver of the transaction for Moonlight transactions, if it + /// exists. #[must_use] - pub fn receiver(&self) -> &WithdrawReceiver { - &self.receiver + pub fn to(&self) -> Option<&AccountPublicKey> { + match self { + Self::Phoenix(_) => None, + Self::Moonlight(tx) => tx.payload.to.as_ref(), + } } - /// The unique token to prevent replay. + /// Return the value transferred in a Moonlight transaction. #[must_use] - pub fn token(&self) -> &WithdrawReplayToken { - &self.token + pub fn value(&self) -> Option { + match self { + Self::Phoenix(_) => None, + Self::Moonlight(tx) => Some(tx.payload.value), + } } - /// Signature of the withdrawal. + /// Returns the nullifiers of the transaction, if the transaction is a + /// moonlight transaction, the result will be empty. #[must_use] - pub fn signature(&self) -> &WithdrawSignature { - &self.signature + pub fn nullifiers(&self) -> &[BlsScalar] { + match self { + Self::Phoenix(tx) => &tx.payload.tx_skeleton.nullifiers, + Self::Moonlight(_) => &[], + } } - /// Return the message that is used as the input to the signature. - /// - /// This message is *not* the one that is meant to be signed on making a - /// withdrawal. Instead it is meant to be used by structures wrapping - /// withdrawals to offer additional functionality. - /// - /// To see the signature message used to sign a withdrawal, see - /// [`stake::Withdraw::signature_message`][`crate::stake::Withdraw::signature_message`]. + /// Return the root of the UTXO tree for Phoenix transactions. #[must_use] - pub fn signature_message(&self) -> Vec { - let mut bytes = Vec::new(); - - bytes.extend(self.contract); - bytes.extend(self.value.to_bytes()); - - match self.receiver { - WithdrawReceiver::Phoenix(address) => { - bytes.extend(address.to_bytes()); - } - WithdrawReceiver::Moonlight(account) => { - bytes.extend(account.to_bytes()); - } + pub fn root(&self) -> Option<&BlsScalar> { + match self { + Self::Phoenix(tx) => Some(&tx.payload.tx_skeleton.root), + Self::Moonlight(_) => None, } + } - match &self.token { - WithdrawReplayToken::Phoenix(nullifiers) => { - for n in nullifiers { - bytes.extend(n.to_bytes()); - } - } - WithdrawReplayToken::Moonlight(nonce) => { - bytes.extend(nonce.to_bytes()); - } + /// Return the UTXO outputs of the transaction. + #[must_use] + pub fn outputs(&self) -> &[Note] { + match self { + Self::Phoenix(tx) => &tx.payload.tx_skeleton.outputs, + Self::Moonlight(_) => &[], } - - bytes } - /// Returns the message that should be "mixed in" as input for a signature - /// of an item that wraps a [`Withdraw`]. - /// - /// One example of this is [`stake::Withdraw`][`crate::stake::Withdraw`]. + /// Return the stealth address for returning funds for Phoenix transactions. #[must_use] - pub fn wrapped_signature_message(&self) -> Vec { - let mut bytes = self.signature_message(); - bytes.extend(self.signature.to_var_bytes()); - bytes + pub fn stealth_address(&self) -> Option<&StealthAddress> { + match self { + Self::Phoenix(tx) => Some(&tx.payload.fee.stealth_address), + Self::Moonlight(_) => None, + } } -} -/// The receiver of the [`Withdraw`] value. -#[derive(Debug, Clone, Copy, PartialEq, Archive, Serialize, Deserialize)] -#[archive_attr(derive(CheckBytes))] -pub enum WithdrawReceiver { - /// The stealth address to withdraw to, when the withdrawal is into Phoenix - /// notes. - Phoenix(StealthAddress), - /// The account to withdraw to, when the withdrawal is to a Moonlight - /// account. - Moonlight(BlsPublicKey), -} - -/// The token used for replay protection in a [`Withdraw`]. This is the same as -/// the encapsulating transaction's fields. -#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize)] -#[archive_attr(derive(CheckBytes))] -pub enum WithdrawReplayToken { - /// The nullifiers of the encapsulating Phoenix transaction, when the - /// transaction is paid for using Phoenix notes. - Phoenix(Vec), - /// The nonce of the encapsulating Moonlight transaction, when the - /// transaction is paid for using a Moonlight account. - Moonlight(u64), -} - -/// The secret key used for signing a [`Withdraw`]. -/// -/// When the withdrawal is into Phoenix notes, a [`SchnorrSecretKey`] should be -/// used. When the withdrawal is into a Moonlight account an -/// [`BlsSecretKey`] should be used. -#[derive(Debug, Clone, PartialEq)] -pub enum WithdrawSecretKey<'a> { - /// The secret key used to sign a withdrawal into Phoenix notes. - Phoenix(&'a SchnorrSecretKey), - /// The secret key used to sign a withdrawal into a Moonlight account. - Moonlight(&'a BlsSecretKey), -} - -impl<'a> From<&'a SchnorrSecretKey> for WithdrawSecretKey<'a> { - fn from(sk: &'a SchnorrSecretKey) -> Self { - Self::Phoenix(sk) + /// Returns the sender data for Phoenix transactions. + #[must_use] + pub fn sender(&self) -> Option<&Sender> { + match self { + Self::Phoenix(tx) => Some(&tx.payload.fee.sender), + Self::Moonlight(_) => None, + } } -} -impl<'a> From<&'a BlsSecretKey> for WithdrawSecretKey<'a> { - fn from(sk: &'a BlsSecretKey) -> Self { - Self::Moonlight(sk) + /// Returns the deposit of the transaction. + #[must_use] + pub fn deposit(&self) -> u64 { + match self { + Self::Phoenix(tx) => tx.payload.tx_skeleton.deposit, + Self::Moonlight(tx) => tx.payload.deposit, + } } -} -/// The signature used for a [`Withdraw`]. -#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize)] -#[archive_attr(derive(CheckBytes))] -pub enum WithdrawSignature { - /// A transaction withdrawing to Phoenix must sign using their - /// [`SchnorrSecretKey`]. - Phoenix(SchnorrSignature), - /// A transaction withdrawing to Moonlight - must sign using their - /// [`BlsSecretKey`]. - Moonlight(BlsSignature), -} - -impl WithdrawSignature { - fn to_var_bytes(&self) -> Vec { + /// Returns the gas limit of the transaction. + #[must_use] + pub fn gas_limit(&self) -> u64 { match self { - WithdrawSignature::Phoenix(sig) => sig.to_bytes().to_vec(), - WithdrawSignature::Moonlight(sig) => sig.to_bytes().to_vec(), + Self::Phoenix(tx) => tx.payload.fee.gas_limit, + Self::Moonlight(tx) => tx.payload.gas_limit, } } -} -impl From for WithdrawSignature { - fn from(sig: SchnorrSignature) -> Self { - Self::Phoenix(sig) + /// Returns the gas price of the transaction. + #[must_use] + pub fn gas_price(&self) -> u64 { + match self { + Self::Phoenix(tx) => tx.payload.fee.gas_price, + Self::Moonlight(tx) => tx.payload.gas_price, + } } -} -impl From for WithdrawSignature { - fn from(sig: BlsSignature) -> Self { - Self::Moonlight(sig) + /// Return the contract call data, if there is any. + #[must_use] + pub fn call(&self) -> Option<&ContractCall> { + match self { + Self::Phoenix(tx) => tx.call(), + Self::Moonlight(tx) => tx.call(), + } } -} - -/// Data for either contract call or contract deployment. -#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)] -#[archive_attr(derive(CheckBytes))] -pub enum ContractExec { - /// Data for a contract call. - Call(ContractCall), - /// Data for a contract deployment. - Deploy(ContractDeploy), -} -impl From for ContractExec { - fn from(c: ContractCall) -> Self { - ContractExec::Call(c) + /// Return the contract deploy data, if there is any. + #[must_use] + pub fn deploy(&self) -> Option<&ContractDeploy> { + match self { + Self::Phoenix(tx) => tx.deploy(), + Self::Moonlight(tx) => tx.deploy(), + } } -} -impl From for ContractExec { - fn from(d: ContractDeploy) -> Self { - ContractExec::Deploy(d) + /// Creates a modified clone of this transaction if it contains data for + /// deployment, clones all fields except for the bytecode' 'bytes' part. + /// Returns none if the transaction is not a deployment transaction. + #[must_use] + pub fn strip_off_bytecode(&self) -> Option { + Some(match self { + Transaction::Phoenix(tx) => { + Transaction::Phoenix(tx.strip_off_bytecode()?) + } + Transaction::Moonlight(tx) => { + Transaction::Moonlight(tx.strip_off_bytecode()?) + } + }) } -} -/// Data for performing a contract deployment -#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)] -#[archive_attr(derive(CheckBytes))] -pub struct ContractDeploy { - /// Bytecode of the contract to be deployed. - pub bytecode: Bytecode, - /// Owner of the contract to be deployed. - pub owner: Vec, - /// Constructor arguments of the deployed contract. - pub constructor_args: Option>, - /// Nonce for contract id uniqueness and vanity - pub nonce: u64, -} - -/// All the data the transfer-contract needs to perform a contract-call. -#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)] -#[archive_attr(derive(CheckBytes))] -pub struct ContractCall { - /// The unique ID of the contract to be called. - pub contract: ContractId, - /// The function of the contract that should be called. - pub fn_name: String, - /// The function arguments for the contract call, in bytes. - pub fn_args: Vec, -} - -// The size of the argument buffer in bytes as specified by piecrust-uplink -const ARGBUF_LEN: usize = 64 * 1024; - -impl ContractDeploy { - /// Serialize a `ContractDeploy` into a variable length byte buffer. + /// Serialize the transaction into a byte buffer. #[must_use] pub fn to_var_bytes(&self) -> Vec { let mut bytes = Vec::new(); - bytes.extend(&self.bytecode.to_var_bytes()); - - bytes.extend((self.owner.len() as u64).to_bytes()); - bytes.extend(&self.owner); - - match &self.constructor_args { - Some(constructor_args) => { + match self { + Self::Phoenix(tx) => { + bytes.push(0); + bytes.extend(tx.to_var_bytes()); + } + Self::Moonlight(tx) => { bytes.push(1); - bytes.extend((constructor_args.len() as u64).to_bytes()); - bytes.extend(constructor_args); + bytes.extend(tx.to_var_bytes()); } - None => bytes.push(0), } - bytes.extend(self.nonce.to_bytes()); - bytes } - /// Deserialize a `ContractDeploy` from a byte buffer. + /// Deserialize the transaction from a byte slice. /// /// # Errors /// Errors when the bytes are not canonical. pub fn from_slice(buf: &[u8]) -> Result { let mut buf = buf; - let bytecode = Bytecode::from_buf(&mut buf)?; - - let owner = read_vec(&mut buf)?; - - let constructor_args = match u8::from_reader(&mut buf)? { - 0 => None, - 1 => Some(read_vec(&mut buf)?), + Ok(match u8::from_reader(&mut buf)? { + 0 => Self::Phoenix(PhoenixTransaction::from_slice(buf)?), + 1 => Self::Moonlight(MoonlightTransaction::from_slice(buf)?), _ => return Err(BytesError::InvalidData), - }; - - let nonce = u64::from_reader(&mut buf)?; - - Ok(Self { - bytecode, - owner, - constructor_args, - nonce, }) } -} -impl ContractCall { - /// Creates a new contract call. + /// Return input bytes to hash the transaction. /// - /// # Errors - /// Errors if rkyv serialization fails. - pub fn new( - contract: impl Into<[u8; 32]>, - fn_name: impl Into, - fn_args: &impl Serialize>, - ) -> Result as Fallible>::Error> { - Ok(Self { - contract: contract.into(), - fn_name: fn_name.into(), - fn_args: rkyv::to_bytes::<_, ARGBUF_LEN>(fn_args)?.to_vec(), - }) - } - - /// Serialize a `ContractCall` into a variable length byte buffer. + /// Note: The result of this function is *only* meant to be used as an input + /// for hashing and *cannot* be used to deserialize the transaction again. #[must_use] - pub fn to_var_bytes(&self) -> Vec { - let mut bytes = Vec::new(); - - bytes.extend(self.contract); - - let fn_name_bytes = self.fn_name.as_bytes(); - bytes.extend((fn_name_bytes.len() as u64).to_bytes()); - bytes.extend(fn_name_bytes); - - bytes.extend((self.fn_args.len() as u64).to_bytes()); - bytes.extend(&self.fn_args); - - bytes - } - - /// Deserialize a `ContractCall` from a byte buffer. - /// - /// # Errors - /// Errors when the bytes are not canonical. - pub fn from_slice(buf: &[u8]) -> Result { - let mut buf = buf; - - let contract = read_arr::<32>(&mut buf)?; - - let fn_name = read_str(&mut buf)?; - - let fn_args = read_vec(&mut buf)?; - - Ok(Self { - contract, - fn_name, - fn_args, - }) - } -} - -/// The Fee structure -#[derive(Debug, Clone, Copy, Archive, Serialize, Deserialize)] -#[archive_attr(derive(CheckBytes))] -pub struct Fee { - /// Gas limit set for a phoenix transaction - pub gas_limit: u64, - /// Gas price set for a phoenix transaction - pub gas_price: u64, - /// Address to send the remainder note - pub stealth_address: StealthAddress, - /// Sender to use for the remainder - pub sender: Sender, -} - -impl PartialEq for Fee { - fn eq(&self, other: &Self) -> bool { - self.sender == other.sender && self.hash() == other.hash() - } -} - -impl Eq for Fee {} - -impl Fee { - /// Create a new Fee with inner randomness - #[must_use] - pub fn new( - rng: &mut R, - pk: &PublicKey, - gas_limit: u64, - gas_price: u64, - ) -> Self { - let r = JubJubScalar::random(&mut *rng); - - let sender_blinder = [ - JubJubScalar::random(&mut *rng), - JubJubScalar::random(&mut *rng), - ]; - - Self::deterministic(&r, pk, gas_limit, gas_price, &sender_blinder) - } - - /// Create a new Fee without inner randomness - #[must_use] - pub fn deterministic( - r: &JubJubScalar, - pk: &PublicKey, - gas_limit: u64, - gas_price: u64, - sender_blinder: &[JubJubScalar; 2], - ) -> Self { - let stealth_address = pk.gen_stealth_address(r); - let sender = - Sender::encrypt(stealth_address.note_pk(), pk, sender_blinder); - - Fee { - gas_limit, - gas_price, - stealth_address, - sender, + pub fn to_hash_input_bytes(&self) -> Vec { + match self { + Self::Phoenix(tx) => tx.to_hash_input_bytes(), + Self::Moonlight(tx) => tx.to_hash_input_bytes(), } } - /// Calculate the max-fee. - #[must_use] - pub fn max_fee(&self) -> u64 { - self.gas_limit * self.gas_price - } - - /// Return a hash represented by `H(gas_limit, gas_price, H([note_pk]))` + /// Create the unique transaction hash. #[must_use] pub fn hash(&self) -> BlsScalar { - let npk = self.stealth_address.note_pk().as_ref().to_hash_inputs(); - - let hash_inputs = [ - BlsScalar::from(self.gas_limit), - BlsScalar::from(self.gas_price), - npk[0], - npk[1], - ]; - Hash::digest(Domain::Other, &hash_inputs)[0] - } - - /// Generates a remainder from the fee and the given gas consumed. - /// - /// If there is a deposit, it means that the deposit hasn't been picked up - /// by the contract. In this case, it is added to the remainder note. - #[must_use] - pub fn gen_remainder_note( - &self, - gas_consumed: u64, - deposit: Option, - ) -> Note { - // Consuming more gas than the limit provided should never occur, and - // it's not the responsibility of the `Fee` to check that. - // Here defensively ensure it's not panicking, capping the gas consumed - // to the gas limit. - let gas_consumed = cmp::min(gas_consumed, self.gas_limit); - let gas_changes = (self.gas_limit - gas_consumed) * self.gas_price; - - Note::transparent_stealth( - self.stealth_address, - gas_changes + deposit.unwrap_or_default(), - self.sender, - ) + match self { + Self::Phoenix(tx) => tx.hash(), + Self::Moonlight(tx) => tx.hash(), + } } } -const SIZE: usize = 2 * u64::SIZE + StealthAddress::SIZE + Sender::SIZE; - -impl Serializable for Fee { - type Error = BytesError; - - /// Converts a Fee into it's byte representation - #[must_use] - fn to_bytes(&self) -> [u8; Self::SIZE] { - let mut buf = [0u8; Self::SIZE]; - - buf[..u64::SIZE].copy_from_slice(&self.gas_limit.to_bytes()); - let mut start = u64::SIZE; - buf[start..start + u64::SIZE] - .copy_from_slice(&self.gas_price.to_bytes()); - start += u64::SIZE; - buf[start..start + StealthAddress::SIZE] - .copy_from_slice(&self.stealth_address.to_bytes()); - start += StealthAddress::SIZE; - buf[start..start + Sender::SIZE] - .copy_from_slice(&self.sender.to_bytes()); - - buf +impl From for Transaction { + fn from(tx: PhoenixTransaction) -> Self { + Self::Phoenix(tx) } +} - /// Attempts to convert a byte representation of a fee into a `Fee`, - /// failing if the input is invalid - fn from_bytes(bytes: &[u8; Self::SIZE]) -> Result { - let mut reader = &bytes[..]; - - let gas_limit = u64::from_reader(&mut reader)?; - let gas_price = u64::from_reader(&mut reader)?; - let stealth_address = StealthAddress::from_reader(&mut reader)?; - let sender = Sender::from_reader(&mut reader)?; - - Ok(Fee { - gas_limit, - gas_price, - stealth_address, - sender, - }) +impl From for Transaction { + fn from(tx: MoonlightTransaction) -> Self { + Self::Moonlight(tx) } } diff --git a/execution-core/src/transfer/contract_exec.rs b/execution-core/src/transfer/contract_exec.rs new file mode 100644 index 0000000000..30762d149f --- /dev/null +++ b/execution-core/src/transfer/contract_exec.rs @@ -0,0 +1,214 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +//! Wrapper for a strip-able bytecode that we want to keep the integrity of. + +use alloc::string::String; +use alloc::vec::Vec; + +use bytecheck::CheckBytes; +use dusk_bytes::{DeserializableSlice, Error as BytesError, Serializable}; +use rkyv::{ + ser::serializers::AllocSerializer, Archive, Deserialize, Fallible, + Serialize, +}; + +use crate::{ContractId, ARGBUF_LEN}; + +/// Data for either contract call or contract deployment. +#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)] +#[archive_attr(derive(CheckBytes))] +pub enum ContractExec { + /// Data for a contract call. + Call(ContractCall), + /// Data for a contract deployment. + Deploy(ContractDeploy), +} + +impl From for ContractExec { + fn from(c: ContractCall) -> Self { + ContractExec::Call(c) + } +} + +impl From for ContractExec { + fn from(d: ContractDeploy) -> Self { + ContractExec::Deploy(d) + } +} + +/// Data for performing a contract deployment +#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)] +#[archive_attr(derive(CheckBytes))] +pub struct ContractDeploy { + /// Bytecode of the contract to be deployed. + pub bytecode: ContractBytecode, + /// Owner of the contract to be deployed. + pub owner: Vec, + /// Constructor arguments of the deployed contract. + pub constructor_args: Option>, + /// Nonce for contract id uniqueness and vanity + pub nonce: u64, +} + +/// All the data the transfer-contract needs to perform a contract-call. +#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)] +#[archive_attr(derive(CheckBytes))] +pub struct ContractCall { + /// The unique ID of the contract to be called. + pub contract: ContractId, + /// The function of the contract that should be called. + pub fn_name: String, + /// The function arguments for the contract call, in bytes. + pub fn_args: Vec, +} + +impl ContractDeploy { + /// Serialize a `ContractDeploy` into a variable length byte buffer. + #[must_use] + pub fn to_var_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + + bytes.extend(&self.bytecode.to_var_bytes()); + + bytes.extend((self.owner.len() as u64).to_bytes()); + bytes.extend(&self.owner); + + match &self.constructor_args { + Some(constructor_args) => { + bytes.push(1); + bytes.extend((constructor_args.len() as u64).to_bytes()); + bytes.extend(constructor_args); + } + None => bytes.push(0), + } + + bytes.extend(self.nonce.to_bytes()); + + bytes + } + + /// Deserialize a `ContractDeploy` from a byte buffer. + /// + /// # Errors + /// Errors when the bytes are not canonical. + pub fn from_slice(buf: &[u8]) -> Result { + let mut buf = buf; + + let bytecode = ContractBytecode::from_buf(&mut buf)?; + + let owner = crate::read_vec(&mut buf)?; + + let constructor_args = match u8::from_reader(&mut buf)? { + 0 => None, + 1 => Some(crate::read_vec(&mut buf)?), + _ => return Err(BytesError::InvalidData), + }; + + let nonce = u64::from_reader(&mut buf)?; + + Ok(Self { + bytecode, + owner, + constructor_args, + nonce, + }) + } +} + +impl ContractCall { + /// Creates a new contract call. + /// + /// # Errors + /// Errors if rkyv serialization fails. + pub fn new( + contract: impl Into, + fn_name: impl Into, + fn_args: &impl Serialize>, + ) -> Result as Fallible>::Error> { + Ok(Self { + contract: contract.into(), + fn_name: fn_name.into(), + fn_args: rkyv::to_bytes::<_, ARGBUF_LEN>(fn_args)?.to_vec(), + }) + } + + /// Serialize a `ContractCall` into a variable length byte buffer. + #[must_use] + pub fn to_var_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + + bytes.extend(self.contract.as_bytes()); + + let fn_name_bytes = self.fn_name.as_bytes(); + bytes.extend((fn_name_bytes.len() as u64).to_bytes()); + bytes.extend(fn_name_bytes); + + bytes.extend((self.fn_args.len() as u64).to_bytes()); + bytes.extend(&self.fn_args); + + bytes + } + + /// Deserialize a `ContractCall` from a byte buffer. + /// + /// # Errors + /// Errors when the bytes are not canonical. + pub fn from_slice(buf: &[u8]) -> Result { + let mut buf = buf; + + let contract = crate::read_arr::<32>(&mut buf)?; + + let fn_name = crate::read_str(&mut buf)?; + + let fn_args = crate::read_vec(&mut buf)?; + + Ok(Self { + contract: contract.into(), + fn_name, + fn_args, + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)] +#[archive_attr(derive(CheckBytes))] +/// Holds bytes of bytecode and its hash. +pub struct ContractBytecode { + /// Hash of the bytecode bytes. + pub hash: [u8; 32], + /// Bytecode bytes. + pub bytes: Vec, +} + +impl ContractBytecode { + /// Provides contribution bytes for an external hash. + #[must_use] + pub fn to_hash_input_bytes(&self) -> Vec { + self.hash.to_vec() + } + + /// Serializes this object into a variable length buffer + #[must_use] + pub fn to_var_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + bytes.extend(self.hash); + bytes.extend((self.bytes.len() as u64).to_bytes()); + bytes.extend(&self.bytes); + bytes + } + + /// Deserialize from a bytes buffer. + /// Resets buffer to a position after the bytes read. + /// + /// # Errors + /// Errors when the bytes are not available. + pub fn from_buf(buf: &mut &[u8]) -> Result { + let hash = crate::read_arr::<32>(buf)?; + let bytes = crate::read_vec(buf)?; + Ok(Self { hash, bytes }) + } +} diff --git a/execution-core/src/transfer/transaction/moonlight.rs b/execution-core/src/transfer/moonlight.rs similarity index 89% rename from execution-core/src/transfer/transaction/moonlight.rs rename to execution-core/src/transfer/moonlight.rs index da2d074369..46992ee422 100644 --- a/execution-core/src/transfer/transaction/moonlight.rs +++ b/execution-core/src/transfer/moonlight.rs @@ -4,6 +4,9 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. +//! Types related to the moonlight transaction model of Dusk's transfer +//! contract. + use alloc::vec::Vec; use bytecheck::CheckBytes; @@ -11,22 +14,37 @@ use dusk_bytes::{DeserializableSlice, Error as BytesError, Serializable}; use rkyv::{Archive, Deserialize, Serialize}; use crate::{ - transfer::{Bytecode, ContractCall, ContractDeploy, ContractExec}, - BlsPublicKey, BlsScalar, BlsSignature, + signatures::bls::{ + PublicKey as AccountPublicKey, Signature as AccountSignature, + }, + transfer::contract_exec::{ + ContractBytecode, ContractCall, ContractDeploy, ContractExec, + }, + BlsScalar, }; +/// A Moonlight account's information. +#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)] +#[archive_attr(derive(CheckBytes))] +pub struct AccountData { + /// Number used for replay protection. + pub nonce: u64, + /// Account balance. + pub balance: u64, +} + /// Moonlight transaction. #[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)] #[archive_attr(derive(CheckBytes))] pub struct Transaction { pub(crate) payload: Payload, - pub(crate) signature: BlsSignature, + pub(crate) signature: AccountSignature, } impl Transaction { /// Create a new transaction. #[must_use] - pub fn new(payload: Payload, signature: BlsSignature) -> Self { + pub fn new(payload: Payload, signature: AccountSignature) -> Self { Self { payload, signature } } @@ -38,7 +56,7 @@ impl Transaction { /// The proof of the transaction. #[must_use] - pub fn signature(&self) -> &BlsSignature { + pub fn signature(&self) -> &AccountSignature { &self.signature } @@ -87,7 +105,7 @@ impl Transaction { exec: Some(ContractExec::Deploy(ContractDeploy { owner: deploy.owner.clone(), constructor_args: deploy.constructor_args.clone(), - bytecode: Bytecode { + bytecode: ContractBytecode { hash: deploy.bytecode.hash, bytes: Vec::new(), }, @@ -130,7 +148,7 @@ impl Transaction { let payload = Payload::from_slice(payload_buf)?; buf = new_buf; - let signature = BlsSignature::from_bytes( + let signature = AccountSignature::from_bytes( buf.try_into().map_err(|_| BytesError::InvalidData)?, ) .map_err(|_| BytesError::InvalidData)?; @@ -168,9 +186,9 @@ impl Transaction { #[archive_attr(derive(CheckBytes))] pub struct Payload { /// Key of the sender of this transaction. - pub from: BlsPublicKey, + pub from: AccountPublicKey, /// Key of the receiver of the funds. - pub to: Option, + pub to: Option, /// Value to be transferred. pub value: u64, /// Deposit for a contract. @@ -237,12 +255,12 @@ impl Payload { pub fn from_slice(buf: &[u8]) -> Result { let mut buf = buf; - let from = BlsPublicKey::from_reader(&mut buf)?; + let from = AccountPublicKey::from_reader(&mut buf)?; // deserialize recipient let to = match u8::from_reader(&mut buf)? { 0 => None, - 1 => Some(BlsPublicKey::from_reader(&mut buf)?), + 1 => Some(AccountPublicKey::from_reader(&mut buf)?), _ => { return Err(BytesError::InvalidData); } @@ -303,7 +321,7 @@ impl Payload { } } Some(ContractExec::Call(c)) => { - bytes.extend(c.contract); + bytes.extend(c.contract.as_bytes()); bytes.extend(c.fn_name.as_bytes()); bytes.extend(&c.fn_args); } diff --git a/execution-core/src/transfer/transaction/phoenix.rs b/execution-core/src/transfer/phoenix.rs similarity index 66% rename from execution-core/src/transfer/transaction/phoenix.rs rename to execution-core/src/transfer/phoenix.rs index 4ea2b238e9..7f5769ba64 100644 --- a/execution-core/src/transfer/transaction/phoenix.rs +++ b/execution-core/src/transfer/phoenix.rs @@ -4,17 +4,197 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. +//! Types related to the phoenix transaction model of Dusk's transfer contract. + use alloc::vec::Vec; +use core::cmp; use bytecheck::CheckBytes; use dusk_bytes::{DeserializableSlice, Error as BytesError, Serializable}; +use dusk_poseidon::{Domain, Hash}; +use ff::Field; +use rand::{CryptoRng, RngCore}; use rkyv::{Archive, Deserialize, Serialize}; use crate::{ - transfer::{Bytecode, ContractCall, ContractDeploy, ContractExec, Fee}, - BlsScalar, JubJubAffine, Sender, TxSkeleton, + transfer::contract_exec::{ + ContractBytecode, ContractCall, ContractDeploy, ContractExec, + }, + BlsScalar, JubJubAffine, JubJubScalar, +}; + +// phoenix types +pub use phoenix_core::{ + value_commitment, Error, Note, PublicKey, SecretKey, Sender, + StealthAddress, TxSkeleton, ViewKey, NOTE_VAL_ENC_SIZE, OUTPUT_NOTES, }; +/// Label used for the ZK transcript initialization. Must be the same for prover +/// and verifier. +pub const TRANSCRIPT_LABEL: &[u8] = b"dusk-network"; + +/// The depth of the transfer tree. +pub const NOTES_TREE_DEPTH: usize = 17; + +/// The Fee structure +#[derive(Debug, Clone, Copy, Archive, Serialize, Deserialize)] +#[archive_attr(derive(CheckBytes))] +pub struct Fee { + /// Gas limit set for a phoenix transaction + pub gas_limit: u64, + /// Gas price set for a phoenix transaction + pub gas_price: u64, + /// Address to send the remainder note + pub stealth_address: StealthAddress, + /// Sender to use for the remainder + pub sender: Sender, +} + +impl PartialEq for Fee { + fn eq(&self, other: &Self) -> bool { + self.sender == other.sender && self.hash() == other.hash() + } +} + +impl Eq for Fee {} + +impl Fee { + /// Create a new Fee with inner randomness + #[must_use] + pub fn new( + rng: &mut R, + pk: &PublicKey, + gas_limit: u64, + gas_price: u64, + ) -> Self { + let r = JubJubScalar::random(&mut *rng); + + let sender_blinder = [ + JubJubScalar::random(&mut *rng), + JubJubScalar::random(&mut *rng), + ]; + + Self::deterministic(&r, pk, gas_limit, gas_price, &sender_blinder) + } + + /// Create a new Fee without inner randomness + #[must_use] + pub fn deterministic( + r: &JubJubScalar, + pk: &PublicKey, + gas_limit: u64, + gas_price: u64, + sender_blinder: &[JubJubScalar; 2], + ) -> Self { + let stealth_address = pk.gen_stealth_address(r); + let sender = + Sender::encrypt(stealth_address.note_pk(), pk, sender_blinder); + + Fee { + gas_limit, + gas_price, + stealth_address, + sender, + } + } + + /// Calculate the max-fee. + #[must_use] + pub fn max_fee(&self) -> u64 { + self.gas_limit * self.gas_price + } + + /// Return a hash represented by `H(gas_limit, gas_price, H([note_pk]))` + #[must_use] + pub fn hash(&self) -> BlsScalar { + let npk = self.stealth_address.note_pk().as_ref().to_hash_inputs(); + + let hash_inputs = [ + BlsScalar::from(self.gas_limit), + BlsScalar::from(self.gas_price), + npk[0], + npk[1], + ]; + Hash::digest(Domain::Other, &hash_inputs)[0] + } + + /// Generates a remainder from the fee and the given gas consumed. + /// + /// If there is a deposit, it means that the deposit hasn't been picked up + /// by the contract. In this case, it is added to the remainder note. + #[must_use] + pub fn gen_remainder_note( + &self, + gas_consumed: u64, + deposit: Option, + ) -> Note { + // Consuming more gas than the limit provided should never occur, and + // it's not the responsibility of the `Fee` to check that. + // Here defensively ensure it's not panicking, capping the gas consumed + // to the gas limit. + let gas_consumed = cmp::min(gas_consumed, self.gas_limit); + let gas_changes = (self.gas_limit - gas_consumed) * self.gas_price; + + Note::transparent_stealth( + self.stealth_address, + gas_changes + deposit.unwrap_or_default(), + self.sender, + ) + } +} + +const SIZE: usize = 2 * u64::SIZE + StealthAddress::SIZE + Sender::SIZE; + +impl Serializable for Fee { + type Error = BytesError; + + /// Converts a Fee into it's byte representation + #[must_use] + fn to_bytes(&self) -> [u8; Self::SIZE] { + let mut buf = [0u8; Self::SIZE]; + + buf[..u64::SIZE].copy_from_slice(&self.gas_limit.to_bytes()); + let mut start = u64::SIZE; + buf[start..start + u64::SIZE] + .copy_from_slice(&self.gas_price.to_bytes()); + start += u64::SIZE; + buf[start..start + StealthAddress::SIZE] + .copy_from_slice(&self.stealth_address.to_bytes()); + start += StealthAddress::SIZE; + buf[start..start + Sender::SIZE] + .copy_from_slice(&self.sender.to_bytes()); + + buf + } + + /// Attempts to convert a byte representation of a fee into a `Fee`, + /// failing if the input is invalid + fn from_bytes(bytes: &[u8; Self::SIZE]) -> Result { + let mut reader = &bytes[..]; + + let gas_limit = u64::from_reader(&mut reader)?; + let gas_price = u64::from_reader(&mut reader)?; + let stealth_address = StealthAddress::from_reader(&mut reader)?; + let sender = Sender::from_reader(&mut reader)?; + + Ok(Fee { + gas_limit, + gas_price, + stealth_address, + sender, + }) + } +} +/// A leaf of the transfer tree. +#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)] +#[archive_attr(derive(CheckBytes))] +pub struct TreeLeaf { + /// The height of the block when the note was inserted in the tree. + pub block_height: u64, + /// The note inserted in the tree. + pub note: Note, +} + /// Phoenix transaction. #[derive(Debug, Clone, Archive, Serialize, Deserialize)] #[archive_attr(derive(CheckBytes))] @@ -93,7 +273,7 @@ impl Transaction { exec: Some(ContractExec::Deploy(ContractDeploy { owner: deploy.owner.clone(), constructor_args: deploy.constructor_args.clone(), - bytecode: Bytecode { + bytecode: ContractBytecode { hash: deploy.bytecode.hash, bytes: Vec::new(), }, @@ -340,7 +520,7 @@ impl Payload { } } Some(ContractExec::Call(c)) => { - bytes.extend(c.contract); + bytes.extend(c.contract.as_bytes()); bytes.extend(c.fn_name.as_bytes()); bytes.extend(&c.fn_args); } diff --git a/execution-core/src/transfer/transaction.rs b/execution-core/src/transfer/transaction.rs deleted file mode 100644 index 07f51aeb85..0000000000 --- a/execution-core/src/transfer/transaction.rs +++ /dev/null @@ -1,258 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. -// -// Copyright (c) DUSK NETWORK. All rights reserved. - -//! Types related to Dusk's transfer contract that are shared across the -//! network. - -use alloc::vec::Vec; - -use bytecheck::CheckBytes; -use dusk_bytes::{DeserializableSlice, Error as BytesError}; -use phoenix_core::{Note, StealthAddress}; -use rkyv::{Archive, Deserialize, Serialize}; - -use crate::{ - transfer::{ContractCall, ContractDeploy}, - BlsPublicKey, BlsScalar, BlsSignature, Sender, -}; - -mod moonlight; -mod phoenix; - -pub use moonlight::{ - Payload as MoonlightPayload, Transaction as MoonlightTransaction, -}; -pub use phoenix::{ - Payload as PhoenixPayload, Transaction as PhoenixTransaction, -}; - -/// The transaction used by the transfer contract. -#[derive(Debug, Clone, Archive, PartialEq, Eq, Serialize, Deserialize)] -#[archive_attr(derive(CheckBytes))] -#[allow(clippy::large_enum_variant)] -pub enum Transaction { - /// A phoenix transaction. - Phoenix(PhoenixTransaction), - /// A moonlight transaction. - Moonlight(MoonlightTransaction), -} - -impl Transaction { - /// Create a new phoenix transaction. - #[must_use] - pub fn phoenix(payload: PhoenixPayload, proof: impl Into>) -> Self { - Self::Phoenix(PhoenixTransaction::new(payload, proof)) - } - - /// Create a new moonlight transaction. - #[must_use] - pub fn moonlight( - payload: MoonlightPayload, - signature: BlsSignature, - ) -> Self { - Self::Moonlight(MoonlightTransaction::new(payload, signature)) - } - - /// Return the sender of the account for Moonlight transactions. - #[must_use] - pub fn from(&self) -> Option<&BlsPublicKey> { - match self { - Self::Phoenix(_) => None, - Self::Moonlight(tx) => Some(&tx.payload.from), - } - } - - /// Return the receiver of the transaction for Moonlight transactions, if it - /// exists. - #[must_use] - pub fn to(&self) -> Option<&BlsPublicKey> { - match self { - Self::Phoenix(_) => None, - Self::Moonlight(tx) => tx.payload.to.as_ref(), - } - } - - /// Return the value transferred in a Moonlight transaction. - #[must_use] - pub fn value(&self) -> Option { - match self { - Self::Phoenix(_) => None, - Self::Moonlight(tx) => Some(tx.payload.value), - } - } - - /// Returns the nullifiers of the transaction, if the transaction is a - /// moonlight transaction, the result will be empty. - #[must_use] - pub fn nullifiers(&self) -> &[BlsScalar] { - match self { - Self::Phoenix(tx) => &tx.payload.tx_skeleton.nullifiers, - Self::Moonlight(_) => &[], - } - } - - /// Return the root of the UTXO tree for Phoenix transactions. - #[must_use] - pub fn root(&self) -> Option<&BlsScalar> { - match self { - Self::Phoenix(tx) => Some(&tx.payload.tx_skeleton.root), - Self::Moonlight(_) => None, - } - } - - /// Return the UTXO outputs of the transaction. - #[must_use] - pub fn outputs(&self) -> &[Note] { - match self { - Self::Phoenix(tx) => &tx.payload.tx_skeleton.outputs, - Self::Moonlight(_) => &[], - } - } - - /// Return the stealth address for returning funds for Phoenix transactions. - #[must_use] - pub fn stealth_address(&self) -> Option<&StealthAddress> { - match self { - Self::Phoenix(tx) => Some(&tx.payload.fee.stealth_address), - Self::Moonlight(_) => None, - } - } - - /// Returns the sender data for Phoenix transactions. - #[must_use] - pub fn sender(&self) -> Option<&Sender> { - match self { - Self::Phoenix(tx) => Some(&tx.payload.fee.sender), - Self::Moonlight(_) => None, - } - } - - /// Returns the deposit of the transaction. - #[must_use] - pub fn deposit(&self) -> u64 { - match self { - Self::Phoenix(tx) => tx.payload.tx_skeleton.deposit, - Self::Moonlight(tx) => tx.payload.deposit, - } - } - - /// Returns the gas limit of the transaction. - #[must_use] - pub fn gas_limit(&self) -> u64 { - match self { - Self::Phoenix(tx) => tx.payload.fee.gas_limit, - Self::Moonlight(tx) => tx.payload.gas_limit, - } - } - - /// Returns the gas price of the transaction. - #[must_use] - pub fn gas_price(&self) -> u64 { - match self { - Self::Phoenix(tx) => tx.payload.fee.gas_price, - Self::Moonlight(tx) => tx.payload.gas_price, - } - } - - /// Return the contract call data, if there is any. - #[must_use] - pub fn call(&self) -> Option<&ContractCall> { - match self { - Self::Phoenix(tx) => tx.call(), - Self::Moonlight(tx) => tx.call(), - } - } - - /// Return the contract deploy data, if there is any. - #[must_use] - pub fn deploy(&self) -> Option<&ContractDeploy> { - match self { - Self::Phoenix(tx) => tx.deploy(), - Self::Moonlight(tx) => tx.deploy(), - } - } - - /// Creates a modified clone of this transaction if it contains data for - /// deployment, clones all fields except for the bytecode' 'bytes' part. - /// Returns none if the transaction is not a deployment transaction. - #[must_use] - pub fn strip_off_bytecode(&self) -> Option { - Some(match self { - Transaction::Phoenix(tx) => { - Transaction::Phoenix(tx.strip_off_bytecode()?) - } - Transaction::Moonlight(tx) => { - Transaction::Moonlight(tx.strip_off_bytecode()?) - } - }) - } - - /// Serialize the transaction into a byte buffer. - #[must_use] - pub fn to_var_bytes(&self) -> Vec { - let mut bytes = Vec::new(); - - match self { - Self::Phoenix(tx) => { - bytes.push(0); - bytes.extend(tx.to_var_bytes()); - } - Self::Moonlight(tx) => { - bytes.push(1); - bytes.extend(tx.to_var_bytes()); - } - } - - bytes - } - - /// Deserialize the transaction from a byte slice. - /// - /// # Errors - /// Errors when the bytes are not canonical. - pub fn from_slice(buf: &[u8]) -> Result { - let mut buf = buf; - - Ok(match u8::from_reader(&mut buf)? { - 0 => Self::Phoenix(PhoenixTransaction::from_slice(buf)?), - 1 => Self::Moonlight(MoonlightTransaction::from_slice(buf)?), - _ => return Err(BytesError::InvalidData), - }) - } - - /// Return input bytes to hash the payload. - /// - /// Note: The result of this function is *only* meant to be used as an input - /// for hashing and *cannot* be used to deserialize the transaction again. - #[must_use] - pub fn to_hash_input_bytes(&self) -> Vec { - match self { - Self::Phoenix(tx) => tx.to_hash_input_bytes(), - Self::Moonlight(tx) => tx.to_hash_input_bytes(), - } - } - - /// Create the unique transaction hash. - #[must_use] - pub fn hash(&self) -> BlsScalar { - match self { - Self::Phoenix(tx) => tx.hash(), - Self::Moonlight(tx) => tx.hash(), - } - } -} - -impl From for Transaction { - fn from(tx: PhoenixTransaction) -> Self { - Self::Phoenix(tx) - } -} - -impl From for Transaction { - fn from(tx: MoonlightTransaction) -> Self { - Self::Moonlight(tx) - } -} diff --git a/execution-core/src/transfer/withdraw.rs b/execution-core/src/transfer/withdraw.rs new file mode 100644 index 0000000000..4b49b220da --- /dev/null +++ b/execution-core/src/transfer/withdraw.rs @@ -0,0 +1,259 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +//! Types related to withdrawing funds into moonlight of phoenix Dusk. + +use alloc::vec::Vec; + +use bytecheck::CheckBytes; +use dusk_bytes::Serializable; +use rand::{CryptoRng, RngCore}; +use rkyv::{Archive, Deserialize, Serialize}; + +use crate::{ + signatures::{ + bls::{ + PublicKey as AccountPublicKey, SecretKey as AccountSecretKey, + Signature as AccountSignature, + }, + schnorr::{SecretKey as NoteSecretKey, Signature as NoteSignature}, + }, + transfer::phoenix::StealthAddress, + BlsScalar, ContractId, +}; + +/// Withdrawal information, proving the intent of a user to withdraw from a +/// contract. +/// +/// This structure is meant to be passed to a contract by a caller. The contract +/// is then responsible for calling `withdraw` in the transfer contract to +/// settle it, if it wants to allow the withdrawal. +/// +/// e.g. the stake contract uses it as a call argument for the `unstake` +/// function +#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize)] +#[archive_attr(derive(CheckBytes))] +pub struct Withdraw { + contract: ContractId, + value: u64, + receiver: WithdrawReceiver, + token: WithdrawReplayToken, + signature: WithdrawSignature, +} + +impl Withdraw { + /// Create a new contract withdrawal. + /// + /// # Panics + /// When the receiver does not match the secret key passed. + #[must_use] + pub fn new<'a, R: RngCore + CryptoRng>( + rng: &mut R, + sk: impl Into>, + contract: ContractId, + value: u64, + receiver: WithdrawReceiver, + token: WithdrawReplayToken, + ) -> Self { + let mut withdraw = Self { + contract, + value, + receiver, + token, + signature: WithdrawSignature::Moonlight(AccountSignature::default()), + }; + + let sk = sk.into(); + + match (&sk, &receiver) { + (WithdrawSecretKey::Phoenix(_), WithdrawReceiver::Moonlight(_)) => { + panic!("Moonlight receiver with phoenix signer"); + } + (WithdrawSecretKey::Moonlight(_), WithdrawReceiver::Phoenix(_)) => { + panic!("Phoenix receiver with moonlight signer"); + } + _ => {} + } + + let msg = withdraw.signature_message(); + + match sk { + WithdrawSecretKey::Phoenix(sk) => { + let digest = BlsScalar::hash_to_scalar(&msg); + let signature = sk.sign(rng, digest); + withdraw.signature = signature.into(); + } + WithdrawSecretKey::Moonlight(sk) => { + let signature = sk.sign(&msg); + withdraw.signature = signature.into(); + } + } + + withdraw + } + + /// The contract to withraw from. + #[must_use] + pub fn contract(&self) -> &ContractId { + &self.contract + } + + /// The amount to withdraw. + #[must_use] + pub fn value(&self) -> u64 { + self.value + } + + /// The receiver of the value. + #[must_use] + pub fn receiver(&self) -> &WithdrawReceiver { + &self.receiver + } + + /// The unique token to prevent replay. + #[must_use] + pub fn token(&self) -> &WithdrawReplayToken { + &self.token + } + + /// Signature of the withdrawal. + #[must_use] + pub fn signature(&self) -> &WithdrawSignature { + &self.signature + } + + /// Return the message that is used as the input to the signature. + /// + /// This message is *not* the one that is meant to be signed on making a + /// withdrawal. Instead it is meant to be used by structures wrapping + /// withdrawals to offer additional functionality. + /// + /// To see the signature used to sign a withdrawal, see + /// [`WithdrawPayload::signature_message`]. + #[must_use] + pub fn signature_message(&self) -> Vec { + let mut bytes = Vec::new(); + + bytes.extend(self.contract.as_bytes()); + bytes.extend(self.value.to_bytes()); + + match self.receiver { + WithdrawReceiver::Phoenix(address) => { + bytes.extend(address.to_bytes()); + } + WithdrawReceiver::Moonlight(account) => { + bytes.extend(account.to_bytes()); + } + } + + match &self.token { + WithdrawReplayToken::Phoenix(nullifiers) => { + for n in nullifiers { + bytes.extend(n.to_bytes()); + } + } + WithdrawReplayToken::Moonlight(nonce) => { + bytes.extend(nonce.to_bytes()); + } + } + + bytes + } + + /// Returns the message that should be "mixed in" as input for a signature + /// of an item that wraps a [`Withdraw`]. + /// + /// One example of this is [`stake::Withdraw`]. + #[must_use] + pub fn wrapped_signature_message(&self) -> Vec { + let mut bytes = self.signature_message(); + bytes.extend(self.signature.to_var_bytes()); + bytes + } +} + +/// The receiver of the [`Withdraw`] value. +#[derive(Debug, Clone, Copy, PartialEq, Archive, Serialize, Deserialize)] +#[archive_attr(derive(CheckBytes))] +pub enum WithdrawReceiver { + /// The stealth address to withdraw to, when the withdrawal is into Phoenix + /// notes. + Phoenix(StealthAddress), + /// The account to withdraw to, when the withdrawal is to a Moonlight + /// account. + Moonlight(AccountPublicKey), +} + +/// The token used for replay protection in a [`Withdraw`]. This is the same as +/// the encapsulating transaction's fields. +#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize)] +#[archive_attr(derive(CheckBytes))] +pub enum WithdrawReplayToken { + /// The nullifiers of the encapsulating Phoenix transaction, when the + /// transaction is paid for using Phoenix notes. + Phoenix(Vec), + /// The nonce of the encapsulating Moonlight transaction, when the + /// transaction is paid for using a Moonlight account. + Moonlight(u64), +} + +/// The secret key used for signing a [`Withdraw`]. +/// +/// When the withdrawal is into Phoenix notes, a [`NoteSecretKey`] should be +/// used. When the withdrawal is into a Moonlight account an +/// [`AccountSecretKey`] should be used. +#[derive(Debug, Clone, PartialEq)] +pub enum WithdrawSecretKey<'a> { + /// The secret key used to sign a withdrawal into Phoenix notes. + Phoenix(&'a NoteSecretKey), + /// The secret key used to sign a withdrawal into a Moonlight account. + Moonlight(&'a AccountSecretKey), +} + +impl<'a> From<&'a NoteSecretKey> for WithdrawSecretKey<'a> { + fn from(sk: &'a NoteSecretKey) -> Self { + Self::Phoenix(sk) + } +} + +impl<'a> From<&'a AccountSecretKey> for WithdrawSecretKey<'a> { + fn from(sk: &'a AccountSecretKey) -> Self { + Self::Moonlight(sk) + } +} + +/// The signature used for a [`Withdraw`]. +#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize)] +#[archive_attr(derive(CheckBytes))] +pub enum WithdrawSignature { + /// A transaction withdrawing to Phoenix must sign using their + /// [`SecretKey`]. + Phoenix(NoteSignature), + /// A transaction withdrawing to Moonlight - must sign using their + /// [`AccountSecretKey`]. + Moonlight(AccountSignature), +} + +impl WithdrawSignature { + fn to_var_bytes(&self) -> Vec { + match self { + WithdrawSignature::Phoenix(sig) => sig.to_bytes().to_vec(), + WithdrawSignature::Moonlight(sig) => sig.to_bytes().to_vec(), + } + } +} + +impl From for WithdrawSignature { + fn from(sig: NoteSignature) -> Self { + Self::Phoenix(sig) + } +} + +impl From for WithdrawSignature { + fn from(sig: AccountSignature) -> Self { + Self::Moonlight(sig) + } +} diff --git a/execution-core/tests/serialization.rs b/execution-core/tests/serialization.rs index 8bd6991274..5e860089be 100644 --- a/execution-core/tests/serialization.rs +++ b/execution-core/tests/serialization.rs @@ -7,15 +7,23 @@ use dusk_bls12_381::BlsScalar; use dusk_bytes::Error; use dusk_jubjub::JubJubScalar; -use execution_core::bytecode::Bytecode; -use execution_core::transfer::{ - ContractCall, ContractDeploy, ContractExec, Fee, Transaction, -}; -use execution_core::transfer::{ - MoonlightPayload, MoonlightTransaction, PhoenixPayload, PhoenixTransaction, -}; use execution_core::{ - BlsPublicKey, BlsSecretKey, Note, PublicKey, SecretKey, TxSkeleton, + signatures::bls::{ + PublicKey as AccountPublicKey, SecretKey as AccountSecretKey, + }, + transfer::{ + contract_exec::{ + ContractBytecode, ContractCall, ContractDeploy, ContractExec, + }, + moonlight::{ + Payload as MoonlightPayload, Transaction as MoonlightTransaction, + }, + phoenix::{ + Fee, Note, Payload as PhoenixPayload, PublicKey, SecretKey, + Transaction as PhoenixTransaction, TxSkeleton, + }, + Transaction, + }, }; use ff::Field; use rand::rngs::StdRng; @@ -87,8 +95,8 @@ fn new_moonlight_tx( rng: &mut R, exec: Option, ) -> Transaction { - let sk = BlsSecretKey::random(rng); - let pk = BlsPublicKey::from(&sk); + let sk = AccountSecretKey::random(rng); + let pk = AccountPublicKey::from(&sk); let payload = MoonlightPayload { from: pk, @@ -102,7 +110,7 @@ fn new_moonlight_tx( }; let msg = payload.to_hash_input_bytes(); - let signature = sk.sign(&pk, &msg); + let signature = sk.sign(&msg); MoonlightTransaction::new(payload, signature).into() } @@ -133,7 +141,7 @@ fn phoenix_with_call() -> Result<(), Error> { rng.fill_bytes(&mut fn_args); let call = ContractCall { - contract, + contract: contract.into(), fn_name: String::from("deposit"), fn_args, }; @@ -157,7 +165,7 @@ fn phoenix_with_deploy() -> Result<(), Error> { rng.fill_bytes(&mut hash); let mut bytes = vec![0; 100]; rng.fill_bytes(&mut bytes); - let bytecode = Bytecode { hash, bytes }; + let bytecode = ContractBytecode { hash, bytes }; let mut owner = [0; 32].to_vec(); rng.fill_bytes(&mut owner); @@ -211,7 +219,7 @@ fn moonlight_with_call() -> Result<(), Error> { rng.fill_bytes(&mut fn_args); let call = ContractCall { - contract, + contract: contract.into(), fn_name: String::from("deposit"), fn_args, }; @@ -235,7 +243,7 @@ fn moonlight_with_deploy() -> Result<(), Error> { rng.fill_bytes(&mut hash); let mut bytes = vec![0; 100]; rng.fill_bytes(&mut bytes); - let bytecode = Bytecode { hash, bytes }; + let bytecode = ContractBytecode { hash, bytes }; let mut owner = [0; 32].to_vec(); rng.fill_bytes(&mut owner); diff --git a/node-data/Cargo.toml b/node-data/Cargo.toml index d47ae7ff3e..ccc4c1c1e3 100644 --- a/node-data/Cargo.toml +++ b/node-data/Cargo.toml @@ -10,7 +10,6 @@ sha2 = "0.10" fake = { version = "2.5", features = ['derive'], optional = true } rand = { version = "0.8", optional = true } hex = { version = "0.4", optional = true } -rusk-abi = { version = "0.13.0-rc", path = "../rusk-abi", default-features = false } execution-core = { version = "0.1.0", path = "../execution-core" } rand_core = { version = "0.6", default-features = false } diff --git a/node-data/src/bls.rs b/node-data/src/bls.rs index a1ca3d80c4..7d59b9a3b1 100644 --- a/node-data/src/bls.rs +++ b/node-data/src/bls.rs @@ -18,7 +18,9 @@ use std::fs; use std::path::PathBuf; use tracing::warn; -use execution_core::{BlsPublicKey, BlsSecretKey}; +use execution_core::signatures::bls::{ + PublicKey as BlsPublicKey, SecretKey as BlsSecretKey, +}; pub const PUBLIC_BLS_SIZE: usize = BlsPublicKey::SIZE; /// Extends BlsPublicKey by implementing a few traits diff --git a/node-data/src/encoding.rs b/node-data/src/encoding.rs index 49773c5d07..0f9cafa486 100644 --- a/node-data/src/encoding.rs +++ b/node-data/src/encoding.rs @@ -6,7 +6,7 @@ use std::io::{self, Read, Write}; -use execution_core::transfer::Transaction as PhoenixTransaction; +use execution_core::transfer::Transaction as ProtocolTransaction; use crate::bls::PublicKeyBytes; use crate::ledger::{ @@ -86,8 +86,8 @@ impl Serializable for Transaction { let version = Self::read_u32_le(r)?; let tx_type = Self::read_u32_le(r)?; - let phoenix_tx = Self::read_var_le_bytes32(r)?; - let inner = PhoenixTransaction::from_slice(&phoenix_tx[..]) + let protocol_tx = Self::read_var_le_bytes32(r)?; + let inner = ProtocolTransaction::from_slice(&protocol_tx[..]) .map_err(|_| io::Error::from(io::ErrorKind::InvalidData))?; Ok(Self { diff --git a/node-data/src/ledger/faults.rs b/node-data/src/ledger/faults.rs index 36e03b096d..b6ed207831 100644 --- a/node-data/src/ledger/faults.rs +++ b/node-data/src/ledger/faults.rs @@ -16,7 +16,12 @@ use crate::{ use dusk_bytes::Serializable as DuskSerializeble; use execution_core::{ - stake::EPOCH, BlsAggPublicKey, BlsScalar, BlsSigError, BlsSignature, + signatures::bls::{ + Error as BlsSigError, MultisigPublicKey as BlsMultisigPublicKey, + MultisigSignature as BlsMultisigSignature, + }, + stake::EPOCH, + BlsScalar, }; use thiserror::Error; use tracing::error; @@ -199,8 +204,8 @@ impl Fault { msg: &[u8], ) -> Result<(), BlsSigError> { let signature = sign_info.signature.inner(); - let sig = BlsSignature::from_bytes(signature)?; - let pk = BlsAggPublicKey::from(sign_info.signer.inner()); + let sig = BlsMultisigSignature::from_bytes(signature)?; + let pk = BlsMultisigPublicKey::aggregate(&[*sign_info.signer.inner()])?; pk.verify(&sig, msg) } } diff --git a/node-data/src/ledger/transaction.rs b/node-data/src/ledger/transaction.rs index 1350a67d55..a28c30f389 100644 --- a/node-data/src/ledger/transaction.rs +++ b/node-data/src/ledger/transaction.rs @@ -94,11 +94,14 @@ pub mod faker { use super::*; use crate::ledger::Dummy; use execution_core::transfer::{ - ContractCall, ContractExec, Fee, PhoenixPayload, - }; - use execution_core::{ - BlsScalar, JubJubScalar, Note, PublicKey, SecretKey, TxSkeleton, + contract_exec::{ContractCall, ContractExec}, + phoenix::{ + Fee, Note, Payload as PhoenixPayload, + PublicKey as PhoenixPublicKey, SecretKey as PhoenixSecretKey, + TxSkeleton, + }, }; + use execution_core::{BlsScalar, JubJubScalar}; use rand::Rng; impl Dummy for Transaction { @@ -122,7 +125,7 @@ pub mod faker { /// Generates a decodable transaction from a fixed blob with a specified /// gas price. pub fn gen_dummy_tx(gas_price: u64) -> Transaction { - let pk = PublicKey::from(&SecretKey::new( + let pk = PhoenixPublicKey::from(&PhoenixSecretKey::new( JubJubScalar::from(42u64), JubJubScalar::from(42u64), )); diff --git a/node-data/src/message.rs b/node-data/src/message.rs index 7618106c65..aa40815f9f 100644 --- a/node-data/src/message.rs +++ b/node-data/src/message.rs @@ -5,8 +5,10 @@ // Copyright (c) DUSK NETWORK. All rights reserved. use dusk_bytes::Serializable as DuskSerializable; -use execution_core::{ - BlsAggPublicKey, BlsPublicKey, BlsSecretKey, BlsSigError, BlsSignature, +use execution_core::signatures::bls::{ + Error as BlsSigError, MultisigPublicKey as BlsMultisigPublicKey, + MultisigSignature as BlsMultisigSignature, PublicKey as BlsPublicKey, + SecretKey as BlsSecretKey, }; use tracing::{error, warn}; @@ -1172,8 +1174,11 @@ pub trait StepMessage { fn verify_signature(&self) -> Result<(), BlsSigError> { let signature = self.sign_info().signature.inner(); - let sig = BlsSignature::from_bytes(signature)?; - let pk = BlsAggPublicKey::from(self.sign_info().signer.inner()); + let sig = BlsMultisigSignature::from_bytes(signature)?; + let pk = BlsMultisigPublicKey::aggregate(&[*self + .sign_info() + .signer + .inner()])?; let msg = self.signable(); pk.verify(&sig, &msg) } @@ -1181,7 +1186,7 @@ pub trait StepMessage { fn sign(&mut self, sk: &BlsSecretKey, pk: &BlsPublicKey) { let msg = self.signable(); let sign_info = self.sign_info_mut(); - let signature = sk.sign(pk, &msg).to_bytes(); + let signature = sk.sign_multisig(pk, &msg).to_bytes(); sign_info.signature = signature.into(); sign_info.signer = PublicKey::new(*pk) } diff --git a/node/Cargo.toml b/node/Cargo.toml index 44be1437c7..2892aaedb3 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -42,7 +42,6 @@ rand = "0.8" rand_core = "0.6" tempdir = "0.3" criterion = { version = "0.5", features = ["async_futures"] } -rusk-abi = { version = "0.13.0-rc", path = "../rusk-abi", default-features = false } [features] with_telemetry = ["dep:console-subscriber"] diff --git a/node/benches/accept.rs b/node/benches/accept.rs index 428c39df1e..c026c41631 100644 --- a/node/benches/accept.rs +++ b/node/benches/accept.rs @@ -21,7 +21,10 @@ use dusk_consensus::user::{ cluster::Cluster, committee::Committee, provisioners::Provisioners, sortition::Config as SortitionConfig, }; -use execution_core::{BlsPublicKey, BlsSecretKey, BlsSignature}; +use execution_core::signatures::bls::{ + MultisigSignature as BlsMultisigSignature, PublicKey as BlsPublicKey, + SecretKey as BlsSecretKey, +}; use node_data::ledger::{Attestation, StepVotes}; use node_data::message::payload::{ QuorumType, RatificationResult, ValidationResult, Vote, @@ -84,7 +87,8 @@ fn create_step_votes( } _ => unreachable!(), }; - signatures.push(BlsSignature::from_bytes(sig.inner()).unwrap()); + signatures + .push(BlsMultisigSignature::from_bytes(sig.inner()).unwrap()); cluster.add(pk, weight); } } diff --git a/node/src/chain/acceptor.rs b/node/src/chain/acceptor.rs index 804f7d0ace..f06c4599e7 100644 --- a/node/src/chain/acceptor.rs +++ b/node/src/chain/acceptor.rs @@ -18,7 +18,7 @@ use node_data::message::AsyncQueue; use node_data::message::Payload; use dusk_consensus::operations::Voter; -use execution_core::stake::Withdraw; +use execution_core::stake::{Withdraw, STAKE_CONTRACT}; use metrics::{counter, gauge, histogram}; use node_data::message::payload::Vote; use node_data::{Serializable, StepName}; @@ -81,12 +81,6 @@ impl Drop const STAKE: &str = "stake"; const UNSTAKE: &str = "unstake"; -const STAKE_CONTRACT: [u8; 32] = stake_contract_id(); -const fn stake_contract_id() -> [u8; 32] { - let mut bytes = [0u8; 32]; - bytes[0] = 2; - bytes -} #[derive(Debug)] enum ProvisionerChange { diff --git a/node/src/chain/consensus.rs b/node/src/chain/consensus.rs index 1656c5acb8..d2838cad01 100644 --- a/node/src/chain/consensus.rs +++ b/node/src/chain/consensus.rs @@ -51,7 +51,10 @@ pub(crate) struct Task { task_id: u64, /// Loaded Consensus keys - pub keys: (execution_core::BlsSecretKey, node_data::bls::PublicKey), + pub keys: ( + execution_core::signatures::bls::SecretKey, + node_data::bls::PublicKey, + ), } impl Task { diff --git a/node/src/chain/header_validation.rs b/node/src/chain/header_validation.rs index afdb22f9e5..a7aa36d1e2 100644 --- a/node/src/chain/header_validation.rs +++ b/node/src/chain/header_validation.rs @@ -154,13 +154,18 @@ impl<'a, DB: database::DB> Validator<'a, DB> { seed: &[u8; 48], pk_bytes: &[u8; 96], ) -> anyhow::Result<()> { - let pk = execution_core::BlsPublicKey::from_bytes(pk_bytes) - .map_err(|err| anyhow!("invalid pk bytes: {:?}", err))?; + let pk = + execution_core::signatures::bls::PublicKey::from_bytes(pk_bytes) + .map_err(|err| anyhow!("invalid pk bytes: {:?}", err))?; - let signature = execution_core::BlsSignature::from_bytes(seed) + let signature = + execution_core::signatures::bls::MultisigSignature::from_bytes( + seed, + ) .map_err(|err| anyhow!("invalid signature bytes: {}", err))?; - execution_core::BlsAggPublicKey::from(&pk) + execution_core::signatures::bls::MultisigPublicKey::aggregate(&[pk]) + .map_err(|err| anyhow!("failed aggregating single key: {}", err))? .verify(&signature, &self.prev_header.seed.inner()[..]) .map_err(|err| anyhow!("invalid seed: {:?}", err))?; diff --git a/node/src/vm.rs b/node/src/vm.rs index 92fa840259..72d2f9361a 100644 --- a/node/src/vm.rs +++ b/node/src/vm.rs @@ -9,7 +9,7 @@ use dusk_consensus::{ operations::{CallParams, VerificationOutput}, user::{provisioners::Provisioners, stake::Stake}, }; -use execution_core::BlsPublicKey; +use execution_core::signatures::bls::PublicKey as BlsPublicKey; use node_data::ledger::{Block, SpentTransaction, Transaction}; #[derive(Default)] diff --git a/rusk-abi/src/abi.rs b/rusk-abi/src/abi.rs index 4afde9a2b5..24aceaaae9 100644 --- a/rusk-abi/src/abi.rs +++ b/rusk-abi/src/abi.rs @@ -8,12 +8,18 @@ use alloc::vec::Vec; use dusk_bytes::Serializable; use execution_core::{ - BlsPublicKey, BlsScalar, BlsSignature, PublicKey, SchnorrPublicKey, - SchnorrSignature, + signatures::{ + bls::{PublicKey as BlsPublicKey, Signature as BlsSignature}, + schnorr::{ + PublicKey as SchnorrPublicKey, Signature as SchnorrSignature, + }, + }, + transfer::phoenix::PublicKey as PhoenixPublicKey, + BlsScalar, ContractId, }; use piecrust_uplink::{host_query, meta_data}; -use crate::{ContractId, Metadata, Query}; +use crate::{Metadata, Query}; /// Compute the blake2b hash of the given bytes, returning the resulting scalar. /// The output of the hasher is truncated (last nibble) to fit onto a scalar. @@ -57,25 +63,27 @@ pub fn block_height() -> u64 { /// Query owner of a given contract. /// Returns none if contract is not found. /// Panics if owner is not a valid public key (should never happen). -pub fn owner(contract: ContractId) -> Option { +pub fn owner(contract: ContractId) -> Option { owner_raw(contract).map(|buf| { - PublicKey::from_bytes(&buf).expect("Owner should deserialize correctly") + PhoenixPublicKey::from_bytes(&buf) + .expect("Owner should deserialize correctly") }) } /// Query self owner of a given contract. /// Panics if owner is not a valid public key (should never happen). -pub fn self_owner() -> PublicKey { +pub fn self_owner() -> PhoenixPublicKey { let buf = self_owner_raw(); - PublicKey::from_bytes(&buf).expect("Owner should deserialize correctly") + PhoenixPublicKey::from_bytes(&buf) + .expect("Owner should deserialize correctly") } /// Query raw "to_bytes" serialization of the owner of a given contract. -pub fn owner_raw(contract: ContractId) -> Option<[u8; PublicKey::SIZE]> { +pub fn owner_raw(contract: ContractId) -> Option<[u8; PhoenixPublicKey::SIZE]> { piecrust_uplink::owner(contract) } /// Query raw "to_bytes" serialization of the self owner. -pub fn self_owner_raw() -> [u8; PublicKey::SIZE] { +pub fn self_owner_raw() -> [u8; PhoenixPublicKey::SIZE] { piecrust_uplink::self_owner() } diff --git a/rusk-abi/src/host.rs b/rusk-abi/src/host.rs index a0830fbf1c..ab37cafe6a 100644 --- a/rusk-abi/src/host.rs +++ b/rusk-abi/src/host.rs @@ -11,8 +11,13 @@ use dusk_bytes::DeserializableSlice; use dusk_plonk::prelude::{Proof, Verifier}; use dusk_poseidon::{Domain, Hash as PoseidonHash}; use execution_core::{ - BlsAggPublicKey, BlsPublicKey, BlsScalar, BlsSignature, SchnorrPublicKey, - SchnorrSignature, + signatures::{ + bls::{PublicKey as BlsPublicKey, Signature as BlsSignature}, + schnorr::{ + PublicKey as SchnorrPublicKey, Signature as SchnorrSignature, + }, + }, + BlsScalar, }; use piecrust::{Error as PiecrustError, Session, SessionData, VM}; use rkyv::ser::serializers::AllocSerializer; @@ -165,6 +170,5 @@ pub fn verify_schnorr( /// Verify a BLS signature is valid for the given public key and message pub fn verify_bls(msg: Vec, pk: BlsPublicKey, sig: BlsSignature) -> bool { - let apk = BlsAggPublicKey::from(&pk); - apk.verify(&sig, &msg).is_ok() + pk.verify(&sig, &msg).is_ok() } diff --git a/rusk-abi/src/lib.rs b/rusk-abi/src/lib.rs index 67753a4523..6724ceb2a6 100644 --- a/rusk-abi/src/lib.rs +++ b/rusk-abi/src/lib.rs @@ -15,18 +15,12 @@ #![cfg_attr(not(feature = "host"), no_std)] #![deny(missing_docs)] #![deny(clippy::all)] -#![feature(const_fn_floating_point_arithmetic)] #[cfg(all(feature = "host", feature = "abi"))] compile_error!("features \"host\" and \"abi\" are mutually exclusive"); extern crate alloc; -pub use piecrust_uplink::{ - ContractError, ContractId, Event, StandardBufSerializer, ARGBUF_LEN, - CONTRACT_ID_BYTES, -}; - #[cfg(feature = "debug")] pub use piecrust_uplink::debug as piecrust_debug; @@ -37,6 +31,7 @@ pub use abi::{ block_height, hash, owner, owner_raw, poseidon_hash, self_owner, self_owner_raw, verify_bls, verify_proof, verify_schnorr, }; + #[cfg(feature = "abi")] pub use piecrust_uplink::{ call, @@ -66,22 +61,6 @@ pub use piecrust::{ PageOpening, Session, VM, }; -pub mod dusk; - -use dusk_bytes::DeserializableSlice; -use execution_core::BlsScalar; - -/// Label used for the ZK transcript initialization. Must be the same for prover -/// and verifier. -pub const TRANSCRIPT_LABEL: &[u8] = b"dusk-network"; - -/// ID of the genesis transfer contract -pub const TRANSFER_CONTRACT: ContractId = reserved(0x1); -/// ID of the genesis stake contract -pub const STAKE_CONTRACT: ContractId = reserved(0x2); -/// ID of the genesis license contract -pub const LICENSE_CONTRACT: ContractId = reserved(0x3); - enum Metadata {} #[allow(dead_code)] @@ -99,25 +78,3 @@ impl Query { pub const VERIFY_SCHNORR: &'static str = "verify_schnorr"; pub const VERIFY_BLS: &'static str = "verify_bls"; } - -#[inline] -const fn reserved(b: u8) -> ContractId { - let mut bytes = [0u8; CONTRACT_ID_BYTES]; - bytes[0] = b; - ContractId::from_bytes(bytes) -} - -/// Generate a [`ContractId`] address from the given slice of bytes, that is -/// also a valid [`BlsScalar`] -pub fn gen_contract_id(bytes: &[u8]) -> ContractId { - ContractId::from_bytes(BlsScalar::hash_to_scalar(bytes).into()) -} - -/// Converts a `ContractId` to a `BlsScalar` -/// -/// This cannot fail since the contract id should be generated always using -/// `rusk_abi::gen_module_id` that ensures the bytes are inside the BLS field. -pub fn contract_to_scalar(module_id: &ContractId) -> BlsScalar { - BlsScalar::from_slice(module_id.as_bytes()) - .expect("Something went REALLY wrong if a contract id is not a scalar") -} diff --git a/rusk-abi/tests/lib.rs b/rusk-abi/tests/lib.rs index bfa457a43f..0a265a2b39 100644 --- a/rusk-abi/tests/lib.rs +++ b/rusk-abi/tests/lib.rs @@ -14,11 +14,19 @@ use rand_core::OsRng; use dusk_bytes::{ParseHexStr, Serializable}; use dusk_plonk::prelude::*; use execution_core::{ - BlsPublicKey, BlsScalar, BlsSecretKey, NotePublicKey, NoteSecretKey, - PublicKey, SecretKey, + signatures::{ + bls::{PublicKey as BlsPublicKey, SecretKey as BlsSecretKey}, + schnorr::{ + PublicKey as SchnorrPublicKey, SecretKey as SchnorrSecretKey, + }, + }, + transfer::phoenix::{ + PublicKey as PhoenixPublicKey, SecretKey as PhoenixSecretKey, + }, + BlsScalar, ContractId, }; use ff::Field; -use rusk_abi::{ContractData, ContractId, Session, VM}; +use rusk_abi::{ContractData, Session, VM}; const POINT_LIMIT: u64 = 0x1000000; @@ -136,19 +144,19 @@ fn schnorr_signature() { rusk_abi::new_ephemeral_vm().expect("Instantiating VM should succeed"); let (mut session, contract_id) = instantiate(&vm, 0); - let note_sk = NoteSecretKey::random(&mut OsRng); + let sk = SchnorrSecretKey::random(&mut OsRng); let message = BlsScalar::random(&mut OsRng); - let note_pk = NotePublicKey::from(¬e_sk); + let pk = SchnorrPublicKey::from(&sk); - let note_sig = note_sk.sign(&mut OsRng, message); + let sig = sk.sign(&mut OsRng, message); - assert!(note_pk.verify(¬e_sig, message).is_ok()); + assert!(pk.verify(&sig, message).is_ok()); let valid: bool = session .call( contract_id, "verify_schnorr", - &(message, note_pk, note_sig), + &(message, pk, sig), POINT_LIMIT, ) .expect("Querying should succeed") @@ -156,14 +164,14 @@ fn schnorr_signature() { assert!(valid, "Signature verification expected to succeed"); - let wrong_sk = NoteSecretKey::random(&mut OsRng); - let note_pk = NotePublicKey::from(&wrong_sk); + let wrong_sk = SchnorrSecretKey::random(&mut OsRng); + let pk = SchnorrPublicKey::from(&wrong_sk); let valid: bool = session .call( contract_id, "verify_schnorr", - &(message, note_pk, note_sig), + &(message, pk, sig), POINT_LIMIT, ) .expect("Querying should succeed") @@ -173,19 +181,19 @@ fn schnorr_signature() { } #[test] -fn stake_signature() { +fn bls_signature() { let vm = rusk_abi::new_ephemeral_vm().expect("Instantiating VM should succeed"); let (mut session, contract_id) = instantiate(&vm, 0); let message = b"some-message".to_vec(); - let stake_sk = BlsSecretKey::random(&mut OsRng); - let stake_pk = BlsPublicKey::from(&stake_sk); + let sk = BlsSecretKey::random(&mut OsRng); + let pk = BlsPublicKey::from(&sk); - let stake_sig = stake_sk.sign(&stake_pk, &message); + let sig = sk.sign(&message); - let arg = (message, stake_pk, stake_sig); + let arg = (message, pk, sig); let valid: bool = session .call(contract_id, "verify_bls", &arg, POINT_LIMIT) .expect("Query should succeed") @@ -341,11 +349,11 @@ fn block_height() { assert_eq!(height, HEIGHT); } -fn get_owner() -> &'static PublicKey { - static OWNER: OnceLock = OnceLock::new(); +fn get_owner() -> &'static PhoenixPublicKey { + static OWNER: OnceLock = OnceLock::new(); OWNER.get_or_init(|| { - let sk = SecretKey::random(&mut OsRng); - PublicKey::from(&sk) + let sk = PhoenixSecretKey::random(&mut OsRng); + PhoenixPublicKey::from(&sk) }) } @@ -369,7 +377,7 @@ fn owner() { rusk_abi::new_ephemeral_vm().expect("Instantiating VM should succeed"); let (mut session, contract_id) = instantiate(&vm, 0); - let owner: PublicKey = session + let owner: PhoenixPublicKey = session .call(contract_id, "contract_owner", get_owner(), POINT_LIMIT) .expect("Query should succeed") .data; diff --git a/rusk-prover/Cargo.toml b/rusk-prover/Cargo.toml index 46506bfc02..9d65a2d6d0 100644 --- a/rusk-prover/Cargo.toml +++ b/rusk-prover/Cargo.toml @@ -13,7 +13,6 @@ rand_core = "0.6" rkyv = { version = "0.7", default-features = false, features = ["size_32"] } bytecheck = { version = "0.6", default-features = false } -rusk-abi = { version = "0.13.0-rc", path = "../rusk-abi", default-features = false } execution-core = { version = "0.1.0", path = "../execution-core" } ## feature local_prover diff --git a/rusk-prover/src/prover/execute.rs b/rusk-prover/src/prover/execute.rs index 9818fee649..c94b28b6d6 100644 --- a/rusk-prover/src/prover/execute.rs +++ b/rusk-prover/src/prover/execute.rs @@ -6,8 +6,9 @@ use super::*; -use execution_core::transfer::TRANSFER_TREE_DEPTH; -use execution_core::{value_commitment, Sender}; +use execution_core::transfer::phoenix::{ + value_commitment, Sender, NOTES_TREE_DEPTH, +}; use phoenix_circuits::transaction::{TxCircuit, TxInputNote, TxOutputNote}; use rand::{CryptoRng, RngCore}; @@ -28,7 +29,7 @@ pub static EXEC_4_2_PROVER: Lazy = fn create_circuit( utx: &UnprovenTransaction, -) -> Result, ProverError> { +) -> Result, ProverError> { // Create the `TxInputNote` let mut tx_input_notes = Vec::with_capacity(utx.inputs().len()); utx.inputs.iter().for_each(|input| { @@ -42,7 +43,7 @@ fn create_circuit( signature: input.sig, }); }); - let tx_input_notes: [TxInputNote; I] = tx_input_notes + let tx_input_notes: [TxInputNote; I] = tx_input_notes .try_into() .expect("the numbers of input-notes should be as expected"); @@ -98,7 +99,7 @@ fn create_circuit( ]; // Build the circuit - let circuit: TxCircuit = TxCircuit::new( + let circuit: TxCircuit = TxCircuit::new( tx_input_notes, tx_output_notes, utx.payload_hash(), diff --git a/rusk-prover/src/tx.rs b/rusk-prover/src/tx.rs index 1e5f5756a0..13e5c721e1 100644 --- a/rusk-prover/src/tx.rs +++ b/rusk-prover/src/tx.rs @@ -12,10 +12,17 @@ use dusk_bytes::{ }; use dusk_plonk::prelude::Proof; use execution_core::{ - transfer::{PhoenixPayload, PhoenixTransaction, TRANSFER_TREE_DEPTH}, - BlsScalar, JubJubAffine, JubJubExtended, JubJubScalar, Note, PublicKey, - SchnorrSignature, SchnorrSignatureDouble, SecretKey, - GENERATOR_NUMS_EXTENDED, OUTPUT_NOTES, + signatures::schnorr::{ + Signature as SchnorrSignature, + SignatureDouble as SchnorrSignatureDouble, + }, + transfer::phoenix::{ + Note, Payload as PhoenixPayload, PublicKey as PhoenixPublicKey, + SecretKey as PhoenixSecretKey, Transaction as PhoenixTransaction, + NOTES_TREE_DEPTH, OUTPUT_NOTES, + }, + BlsScalar, JubJubAffine, JubJubExtended, JubJubScalar, + GENERATOR_NUMS_EXTENDED, }; use poseidon_merkle::Opening as PoseidonOpening; @@ -25,7 +32,7 @@ use rand_core::{CryptoRng, RngCore}; #[derive(PartialEq, Debug, Clone)] pub struct UnprovenTransactionInput { pub nullifier: BlsScalar, - pub opening: PoseidonOpening<(), TRANSFER_TREE_DEPTH>, + pub opening: PoseidonOpening<(), NOTES_TREE_DEPTH>, pub note: Note, pub value: u64, pub value_blinder: JubJubScalar, @@ -36,11 +43,11 @@ pub struct UnprovenTransactionInput { impl UnprovenTransactionInput { pub fn new( rng: &mut Rng, - sender_sk: &SecretKey, + sender_sk: &PhoenixSecretKey, note: Note, value: u64, value_blinder: JubJubScalar, - opening: PoseidonOpening<(), TRANSFER_TREE_DEPTH>, + opening: PoseidonOpening<(), NOTES_TREE_DEPTH>, payload_hash: BlsScalar, ) -> Self { let nullifier = note.gen_nullifier(sender_sk); @@ -124,7 +131,7 @@ impl UnprovenTransactionInput { } /// Returns the opening of the input. - pub fn opening(&self) -> &PoseidonOpening<(), TRANSFER_TREE_DEPTH> { + pub fn opening(&self) -> &PoseidonOpening<(), NOTES_TREE_DEPTH> { &self.opening } @@ -161,7 +168,7 @@ pub struct UnprovenTransaction { pub inputs: Vec, pub outputs: [(Note, u64, JubJubScalar, [JubJubScalar; 2]); OUTPUT_NOTES], pub payload: PhoenixPayload, - pub sender_pk: PublicKey, + pub sender_pk: PhoenixPublicKey, pub signatures: (SchnorrSignature, SchnorrSignature), } @@ -235,7 +242,7 @@ impl UnprovenTransaction { // the payload-hash + BlsScalar::SIZE // the sender-pk - + PublicKey::SIZE + + PhoenixPublicKey::SIZE // the two signatures + 2 * SchnorrSignature::SIZE; @@ -299,7 +306,7 @@ impl UnprovenTransaction { let payload = PhoenixPayload::from_slice(buffer)?; let mut buffer = &buffer[payload_len as usize..]; - let sender_pk = PublicKey::from_reader(&mut buffer)?; + let sender_pk = PhoenixPublicKey::from_reader(&mut buffer)?; let sig_a = SchnorrSignature::from_reader(&mut buffer)?; let sig_b = SchnorrSignature::from_reader(&mut buffer)?; @@ -337,8 +344,11 @@ impl UnprovenTransaction { mod tests { use super::*; use execution_core::{ - transfer::{ContractCall, ContractExec, Fee}, - SchnorrSecretKey, TxSkeleton, + signatures::schnorr::SecretKey as SchnorrSecretKey, + transfer::{ + contract_exec::{ContractCall, ContractExec}, + phoenix::{Fee, TxSkeleton}, + }, }; use poseidon_merkle::{Item, Tree}; use rand::{rngs::StdRng, SeedableRng}; @@ -346,9 +356,10 @@ mod tests { #[test] fn serialize_deserialize() -> Result<(), BytesError> { let mut rng = StdRng::seed_from_u64(0xbeef); - let sender_sk = SecretKey::random(&mut rng); - let sender_pk = PublicKey::from(&sender_sk); - let receiver_pk = PublicKey::from(&SecretKey::random(&mut rng)); + let sender_sk = PhoenixSecretKey::random(&mut rng); + let sender_pk = PhoenixPublicKey::from(&sender_sk); + let receiver_pk = + PhoenixPublicKey::from(&PhoenixSecretKey::random(&mut rng)); let transfer_value = 42; let transfer_value_blinder = JubJubScalar::from(5647890216u64); diff --git a/rusk-recovery/src/keys.rs b/rusk-recovery/src/keys.rs index 7b304a61aa..403c4e9fc5 100644 --- a/rusk-recovery/src/keys.rs +++ b/rusk-recovery/src/keys.rs @@ -6,6 +6,7 @@ use crate::Theme; use dusk_plonk::prelude::{Compiler, PublicParameters}; +use execution_core::transfer::phoenix::TRANSCRIPT_LABEL; use once_cell::sync::Lazy; use rand::rngs::StdRng; use rand::SeedableRng; @@ -17,10 +18,6 @@ use tracing::{info, warn}; mod circuits; -/// Label used for the ZK transcript initialization. Must be the same for prover -/// and verifier. -const TRANSCRIPT_LABEL: &[u8] = b"dusk-network"; - static PUB_PARAMS: Lazy = Lazy::new(|| { let theme = Theme::default(); info!("{} CRS from cache", theme.action("Fetching")); diff --git a/rusk-recovery/src/keys/circuits.rs b/rusk-recovery/src/keys/circuits.rs index db75c0f8b7..0e21dc367c 100644 --- a/rusk-recovery/src/keys/circuits.rs +++ b/rusk-recovery/src/keys/circuits.rs @@ -10,15 +10,15 @@ use cargo_toml::{Dependency, Manifest}; use dusk_plonk::prelude::Circuit; use tracing::info; -use execution_core::transfer::TRANSFER_TREE_DEPTH; +use execution_core::transfer::phoenix::NOTES_TREE_DEPTH; use license_circuits::LicenseCircuit; use phoenix_circuits::transaction::TxCircuit; -type ExecuteCircuitOneTwo = TxCircuit; -type ExecuteCircuitTwoTwo = TxCircuit; -type ExecuteCircuitThreeTwo = TxCircuit; -type ExecuteCircuitFourTwo = TxCircuit; +type ExecuteCircuitOneTwo = TxCircuit; +type ExecuteCircuitTwoTwo = TxCircuit; +type ExecuteCircuitThreeTwo = TxCircuit; +type ExecuteCircuitFourTwo = TxCircuit; use rusk_profile::{Circuit as CircuitProfile, Theme}; diff --git a/rusk-recovery/src/state.rs b/rusk-recovery/src/state.rs index a6efe83049..fa57461e82 100644 --- a/rusk-recovery/src/state.rs +++ b/rusk-recovery/src/state.rs @@ -17,11 +17,16 @@ use tracing::info; use url::Url; use execution_core::{ - stake::{StakeAmount, StakeData}, - BlsPublicKey, JubJubScalar, Note, PublicKey, Sender, + license::LICENSE_CONTRACT, + signatures::bls::PublicKey as AccountPublicKey, + stake::{StakeAmount, StakeData, STAKE_CONTRACT}, + transfer::{ + phoenix::{Note, PublicKey, Sender}, + TRANSFER_CONTRACT, + }, + ContractId, JubJubScalar, }; -use rusk_abi::{ContractData, ContractId, Session, VM}; -use rusk_abi::{LICENSE_CONTRACT, STAKE_CONTRACT, TRANSFER_CONTRACT}; +use rusk_abi::{ContractData, Session, VM}; use crate::Theme; pub use snapshot::{GenesisStake, PhoenixBalance, Snapshot}; @@ -92,7 +97,7 @@ fn generate_transfer_state( info!("{} moonlight account #{idx}", theme.action("Generating")); session - .call::<(BlsPublicKey, u64), ()>( + .call::<(AccountPublicKey, u64), ()>( TRANSFER_CONTRACT, "add_account_balance", &(*account.address(), account.balance), diff --git a/rusk-recovery/src/state/snapshot.rs b/rusk-recovery/src/state/snapshot.rs index a160681fdf..903bf9d136 100644 --- a/rusk-recovery/src/state/snapshot.rs +++ b/rusk-recovery/src/state/snapshot.rs @@ -7,8 +7,10 @@ use std::fmt::Debug; use dusk_bytes::Serializable; -use execution_core::{BlsPublicKey, PublicKey}; -use rusk_abi::dusk::Dusk; +use execution_core::{ + signatures::bls::PublicKey as AccountPublicKey, + transfer::phoenix::PublicKey as PhoenixPublicKey, Dusk, +}; use serde_derive::{Deserialize, Serialize}; mod stake; @@ -20,26 +22,26 @@ use wrapper::Wrapper; #[derive(Serialize, Deserialize, PartialEq, Eq)] pub struct PhoenixBalance { - address: Wrapper, + address: Wrapper, pub seed: Option, #[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")] pub notes: Vec, } impl PhoenixBalance { - pub fn address(&self) -> &PublicKey { + pub fn address(&self) -> &PhoenixPublicKey { &self.address } } #[derive(Serialize, Deserialize, PartialEq, Eq)] pub struct MoonlightAccount { - address: Wrapper, + address: Wrapper, pub balance: Dusk, } impl MoonlightAccount { - pub fn address(&self) -> &BlsPublicKey { + pub fn address(&self) -> &AccountPublicKey { &self.address } } @@ -47,7 +49,7 @@ impl MoonlightAccount { #[derive(Serialize, Deserialize, Default, PartialEq, Eq)] pub struct Snapshot { base_state: Option, - owner: Option>, + owner: Option>, // This "serde skip" workaround seems needed as per https://github.com/toml-rs/toml-rs/issues/384 #[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")] @@ -87,7 +89,7 @@ impl Snapshot { } /// Return the owner of the smart contract. - pub fn owner(&self) -> [u8; PublicKey::SIZE] { + pub fn owner(&self) -> [u8; PhoenixPublicKey::SIZE] { let dusk = Wrapper::from(*state::DUSK_KEY); self.owner.as_ref().unwrap_or(&dusk).to_bytes() } diff --git a/rusk-recovery/src/state/snapshot/stake.rs b/rusk-recovery/src/state/snapshot/stake.rs index 4b5fb8e11b..a31e6ae6a9 100644 --- a/rusk-recovery/src/state/snapshot/stake.rs +++ b/rusk-recovery/src/state/snapshot/stake.rs @@ -7,8 +7,7 @@ use dusk_bytes::Serializable; use serde_derive::{Deserialize, Serialize}; -use execution_core::BlsPublicKey; -use rusk_abi::dusk::Dusk; +use execution_core::{signatures::bls::PublicKey as BlsPublicKey, Dusk}; use super::wrapper::Wrapper; diff --git a/rusk/benches/block_ingestion.rs b/rusk/benches/block_ingestion.rs index eb70cabf57..84962d803c 100644 --- a/rusk/benches/block_ingestion.rs +++ b/rusk/benches/block_ingestion.rs @@ -17,7 +17,8 @@ use criterion::{ criterion_group, criterion_main, BenchmarkGroup, BenchmarkId, Criterion, }; use execution_core::{ - transfer::Transaction as ProtocolTransaction, BlsPublicKey, BlsSecretKey, + signatures::bls::{PublicKey as BlsPublicKey, SecretKey as BlsSecretKey}, + transfer::Transaction as ProtocolTransaction, }; use node_data::ledger::Transaction; use rand::prelude::StdRng; diff --git a/rusk/benches/moonlight-txs b/rusk/benches/moonlight-txs index 2bd54b5f8a..5ee8ecba22 100644 --- a/rusk/benches/moonlight-txs +++ b/rusk/benches/moonlight-txs @@ -1,100 +1,100 @@ -01ea000000000000008d31d41cb190cb54fab965d3edba820a8e8fc5cdf5df2ef0b17ed7b318c290808564ec96651502cb086f4e2317de88441376dca3359fcf084a904343591c9c6d420dea1e576a957120af2ea4c46ef9d3bb1394ad3cbc92413c59b1ce518c085001b6532a7b799b7911b4631bb91c07f2d1b10b0641cdb8fdf53bfc8194c9700e22d2fe0068142786be7a79cce92920d2a518dd851e2d7ee65addb2d79f7a8f8487d6df24627a5bf74428e060be27adca21250cb3382c2a4c8b4a78b699c1d752c940420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000008f9c3d2e55cecd17b9498eb4e72ce0e4c825f1311bfe40fd46c0145c28c1858d34fcc38c9253c01e4fa3b5778d426e67 -01ea00000000000000b6532a7b799b7911b4631bb91c07f2d1b10b0641cdb8fdf53bfc8194c9700e22d2fe0068142786be7a79cce92920d2a518dd851e2d7ee65addb2d79f7a8f8487d6df24627a5bf74428e060be27adca21250cb3382c2a4c8b4a78b699c1d752c90182264d6e2d74b1c837692590461ace1bec066787c45a43ce2069cdfde86ecc9ee39943e18ab0d265426a46656491088701ac039ad5d7c59bb3f0ab7589977cad7eee944fac90d10e847e99cdef22b1f29c139b8ca5ec5706c2ce14e064500eb340420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a505d79d028a3df0eb4d068646818d58502e2a133ca2a2eee785bcf5e2b28a19e20334ac7a82c5f96ef1699bae3afff9 -01ea0000000000000082264d6e2d74b1c837692590461ace1bec066787c45a43ce2069cdfde86ecc9ee39943e18ab0d265426a46656491088701ac039ad5d7c59bb3f0ab7589977cad7eee944fac90d10e847e99cdef22b1f29c139b8ca5ec5706c2ce14e064500eb301ab89ed1e5cf7dfef077864e59ab0f668b9ddcee6037e1367c19f31dd6eb3073d123bee37ca14929dd8a237459ee9b80c032490ccd14acfd81e9d9e99237d6e45a6a0783b4ce9cc3ba2c5943a172cdd3cd0d7362a6a80e72db946d55e6242c39a40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a13bdfb64f15113b80b6df564e32776a046aa7cd6130bb843c6243fb9265eb8778cbb19968fb8ac51d5d914480878676 -01ea00000000000000ab89ed1e5cf7dfef077864e59ab0f668b9ddcee6037e1367c19f31dd6eb3073d123bee37ca14929dd8a237459ee9b80c032490ccd14acfd81e9d9e99237d6e45a6a0783b4ce9cc3ba2c5943a172cdd3cd0d7362a6a80e72db946d55e6242c39a01b36febad973e492620ec23bd0f0546442bc445c97e84ce0217db901b6adefe44b25e860f5c2176f34dac3ae24bf8d02d00895e619029269b46836b49d9f7d3fe647a939add202fed72803881ac065c4ea4bb9b24d7f4179cd93c0eae912a017b40420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000083b1461137ea0c4557340520af335b31c6bb6bb6bf87fc7e9ec122e978ee9c0dac04faaab99ce62cb24558b2d4ba26ab -01ea00000000000000b36febad973e492620ec23bd0f0546442bc445c97e84ce0217db901b6adefe44b25e860f5c2176f34dac3ae24bf8d02d00895e619029269b46836b49d9f7d3fe647a939add202fed72803881ac065c4ea4bb9b24d7f4179cd93c0eae912a017b01944f003e00ee144b44655a21ac808f42d75b4b78095b44e921ef034dcfb31c22ea4119a0c9ecc3178791bc4162427b0813bd445a36851ddf9c7e87c6571fad5fd76f0ee71ccbd00846e893cb29fbb852fe65b2deec192a98766e7deee91d9c0b40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000aa7745c0574a5f189278bc23e6eaef1dfdd1e06bd268aac532f074a329b8029f017552bacf58352356387665b72baf9d -01ea00000000000000944f003e00ee144b44655a21ac808f42d75b4b78095b44e921ef034dcfb31c22ea4119a0c9ecc3178791bc4162427b0813bd445a36851ddf9c7e87c6571fad5fd76f0ee71ccbd00846e893cb29fbb852fe65b2deec192a98766e7deee91d9c0b0198efb3414d4dee951e1391a32f006333295f49383468d5e8c94c7d2ae4f09b54548e24f38672897a1285b56bd97fad5710c0dd9963b805abf6cf69b460c221ae7bb04a23bf79b000e1754e900e7bf2a32065024d009be5c2bc3c18af548e84fb40420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000009026bfd28d99f1944421eba7ac4652b5c6150b110a29af88341dfa3c6f9ee3ad3cfcca8b5d32abec308603ff46b522e3 -01ea0000000000000098efb3414d4dee951e1391a32f006333295f49383468d5e8c94c7d2ae4f09b54548e24f38672897a1285b56bd97fad5710c0dd9963b805abf6cf69b460c221ae7bb04a23bf79b000e1754e900e7bf2a32065024d009be5c2bc3c18af548e84fb01b73b47dcecb50fa831335e8e5bebccce2755b6850fee3418db178cf63eebd1009827147aecd577687c0bd9ca3c3c100b0439e2ff1b26d2ba4d71a2606c3bc5b75b528342e77a19ae1bc43a9e4b6b28fd10aa0247959a8c327d8440948277019040420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b6f49afc43545bf212125aead33cbe043a0db42e83010fefddd44b3df6343469e66f4713687aba1f8320414fa306e729 -01ea00000000000000b73b47dcecb50fa831335e8e5bebccce2755b6850fee3418db178cf63eebd1009827147aecd577687c0bd9ca3c3c100b0439e2ff1b26d2ba4d71a2606c3bc5b75b528342e77a19ae1bc43a9e4b6b28fd10aa0247959a8c327d8440948277019001958f3e0c8c6beb2a34f68ec6db7cd46fc4f7fd5376b5d17e9be5a6357a2618fc97c566f314f407fac76001e761b2123c04d9d1dbb1d66109296f1bb8e6dc86656cef4b0fc85fe3b47aaf087c7c706d9c2abe132701a0506531e385e34887286940420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000008791effb200c13e28ec328f0b753c3b435e9c20cd7fcb40b7a8023b863395012b88503c894528e072ee4c8e22a8697d1 -01ea00000000000000958f3e0c8c6beb2a34f68ec6db7cd46fc4f7fd5376b5d17e9be5a6357a2618fc97c566f314f407fac76001e761b2123c04d9d1dbb1d66109296f1bb8e6dc86656cef4b0fc85fe3b47aaf087c7c706d9c2abe132701a0506531e385e34887286901875b7101d6deed445642a16bf04fe9ec7b9b83f3be3e7fa72825bb92026819adca57099c9d11adcdb830ab881c8226ed00860532520ccb427edd366d0326076be4130ac4936fef9218e99ec1979acff4f0fdfd4ff6181b5945211ac8264da0eb40420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000086a7a2b3dfb93cac701bccfc0793bdde90b728fab346114bc5f5f43caacfdfa22590e34d57a8a979dd4442a012c8c0b3 -01ea00000000000000875b7101d6deed445642a16bf04fe9ec7b9b83f3be3e7fa72825bb92026819adca57099c9d11adcdb830ab881c8226ed00860532520ccb427edd366d0326076be4130ac4936fef9218e99ec1979acff4f0fdfd4ff6181b5945211ac8264da0eb01b7626d33f354c9a5743e31be0d40849b05cdab288bb2993737d444450a3196e8be9785690ec9703c5dad93b9a66593611221e837ef46a58f095fb4e765b3b48a356af904fcf287354c3c32a6514a1c289e647d604b16c374c0ac8c0d1c1fb19440420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000877db225e589fd3c8f6c6474fc182c9ba8d0c62ce17b9e112cadc95145fbd391fb101ee662a7e07382fd7d1af755601f -01ea00000000000000b7626d33f354c9a5743e31be0d40849b05cdab288bb2993737d444450a3196e8be9785690ec9703c5dad93b9a66593611221e837ef46a58f095fb4e765b3b48a356af904fcf287354c3c32a6514a1c289e647d604b16c374c0ac8c0d1c1fb19401a66ca7c857f3297501bc0b65f24284f7921bac9c60bdece5914f02bb7166d16d440af14c25367be48629599c94cf98bd0d518133cc75942aae40d8d5391bad7164e37e966385419e7046186a913a60c37f093e4622fe21cd680d7b0d26d72d9940420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000089241ab32612b8fd563ff1cc470822defd805f4b5af869ef43b6d5f4b3d768460741c63951581d3b33fc97fb63e1c3d1 -01ea00000000000000a66ca7c857f3297501bc0b65f24284f7921bac9c60bdece5914f02bb7166d16d440af14c25367be48629599c94cf98bd0d518133cc75942aae40d8d5391bad7164e37e966385419e7046186a913a60c37f093e4622fe21cd680d7b0d26d72d9901a09d5c38db453aa22044dd9d5e8d38c48b6d5d67382bcb5b92eec17e41167e405f15f9e9ac539d130c2136491243eb860ffabc0e8820c47343b24cd9c98af35f2db95a590510f452686a7b27c6ee7196f60c63bc6208d98b87483e68dbcdc5f640420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b556a498889f6b13f5389a0edd7aa2d65d55b42fe417e3707dc78e5849df1ee921a75aaa0c8982c3f7af4b405f6f1bb2 -01ea00000000000000a09d5c38db453aa22044dd9d5e8d38c48b6d5d67382bcb5b92eec17e41167e405f15f9e9ac539d130c2136491243eb860ffabc0e8820c47343b24cd9c98af35f2db95a590510f452686a7b27c6ee7196f60c63bc6208d98b87483e68dbcdc5f60192e15dd8d5f2940969cf9bd74b67418d43693b23c243a9fbcd582040ccca8abc678f0dd93d304c6230d9f9931402998d0c33f57d638c8ac734d1bd395a09af091677d493c377091353812c7691d4b227c51c00758bb7e882dcd2f9d74dde998540420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000ae75b05adfe268576fb3605bda5254fb46ba6fabe2f3cebcfb3286d2188f75ccd5673ea5510385b65f39096aa3a4f448 -01ea0000000000000092e15dd8d5f2940969cf9bd74b67418d43693b23c243a9fbcd582040ccca8abc678f0dd93d304c6230d9f9931402998d0c33f57d638c8ac734d1bd395a09af091677d493c377091353812c7691d4b227c51c00758bb7e882dcd2f9d74dde99850182b25aa6f8e6ac4650136c92c0ac311aeeb8af274e985c9ec1dafa220a4a7c52eabc06affae35ef56e0825c912a212d717b0e32d09e905f50c0eabfc639b5aa794971a5ffac2a69e948af0af00f6cc58a2a84a9c078a223375904246b9bfba6040420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000088befb746fa998341fa0d0b7e328754168673c34f89541686c758a9f673c0037f64dd67f626d1c2c3adb0070a19c384b -01ea0000000000000082b25aa6f8e6ac4650136c92c0ac311aeeb8af274e985c9ec1dafa220a4a7c52eabc06affae35ef56e0825c912a212d717b0e32d09e905f50c0eabfc639b5aa794971a5ffac2a69e948af0af00f6cc58a2a84a9c078a223375904246b9bfba600195d75b02af80f8f91c610bccf5fbc4f8059f84203d9c6437f5388a7b7a84a70bea35a09529d7a851821aa83555c7d89a02ab451d15bdd94f8796049f1bf6b3f4a0088a0a17cb256b5702fa71876e6feb9863d3b16d5a5d46ba7cc6bb29fc7e0740420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000971f23af93c7a14e7a36d729bac6e9094dedf35ca796057fc33dd8b529019f534bf6f10c9119e903d91080ad9c5bc72f -01ea0000000000000095d75b02af80f8f91c610bccf5fbc4f8059f84203d9c6437f5388a7b7a84a70bea35a09529d7a851821aa83555c7d89a02ab451d15bdd94f8796049f1bf6b3f4a0088a0a17cb256b5702fa71876e6feb9863d3b16d5a5d46ba7cc6bb29fc7e07019952c290953166ec8bbfd259826e910888d31bbdd7112c9ca5a3047d89516205bb75e586461504f5a6d6771a6e9145ea0d1c677b2495a93b25cbe28cccdd993d76a14225572483e966f5d48c4e4eb759570d6a2e3ca2a4a0e580ff394b6137d140420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b4081e2fbfc406bde0fcc4c08f4a9951734eb191e9c8436fcd3714a443cb1f3e2d1d595b289e6c974b01b8bdcd089a15 -01ea000000000000009952c290953166ec8bbfd259826e910888d31bbdd7112c9ca5a3047d89516205bb75e586461504f5a6d6771a6e9145ea0d1c677b2495a93b25cbe28cccdd993d76a14225572483e966f5d48c4e4eb759570d6a2e3ca2a4a0e580ff394b6137d10199201c741f5c7471d4182ebb91cf11c09b47f13e7ef4a303a350c312cf8fab0e54cb6b559bf3968458095a019933f8d90d0373beddb177f1fff22e2cb0a1ff21075e720a696ea4cc84bffad8a5e7a4c1560683cfbadcdfe0551712a285254c3040420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000af6537c5984dd16b56f47b60ccb41cc8a4142c5390cc3c02ccd7c457e9f8265ce962b28070639e6f4fe60ca193947bbe -01ea0000000000000099201c741f5c7471d4182ebb91cf11c09b47f13e7ef4a303a350c312cf8fab0e54cb6b559bf3968458095a019933f8d90d0373beddb177f1fff22e2cb0a1ff21075e720a696ea4cc84bffad8a5e7a4c1560683cfbadcdfe0551712a285254c30018ec01bb93a9049ecef5f667a8b1660e0ec04bc321f0ebe45e4850ed9eadc37db6d8a82138f544108c480d548c299b4b61492608ae8826b3c79fdd162bb6abdc8c4ca456e025d31733a47439bde124fbfad3e73a132e93fde2d1775229079d0e440420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000008b50cb8c276f7382b4e5ed6368e9063831dc637213e9fa5dd9500abcf0c6833c48659fb25374714231b0c832306b832c -01ea000000000000008ec01bb93a9049ecef5f667a8b1660e0ec04bc321f0ebe45e4850ed9eadc37db6d8a82138f544108c480d548c299b4b61492608ae8826b3c79fdd162bb6abdc8c4ca456e025d31733a47439bde124fbfad3e73a132e93fde2d1775229079d0e40184873639c79029c0ab2090a7647dec527426c166fb8befeaea5f3e7833dc97f313a8e9041dad191f350e9daccad80f551726f351f12412825fc5594bae866c074195d9959cfe18d945008867cccec58877509070f9f9a3df1460aa114668d70440420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a81fd6254f536950087bca536e1855d3f27e1a2e16718120fca99f8164d0ed7cdaa09e7148ad4f910a62614659cc4735 -01ea0000000000000084873639c79029c0ab2090a7647dec527426c166fb8befeaea5f3e7833dc97f313a8e9041dad191f350e9daccad80f551726f351f12412825fc5594bae866c074195d9959cfe18d945008867cccec58877509070f9f9a3df1460aa114668d70401ad52a2d92356a7c43c6223b53f391c6bc41c556201c79ad9525db1205715649896a2e4d95b790b8e4eab7e9f6c28a7a0093683d486d81c33173b5c32b54114def41a2c01b39af7f28636d0de9978761ac06fd931a44fa519c4eebda4a744279340420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000959df7c4e58d716308f38eb9340e9436c0a53a7aa3f10c99a4f79096d0eb6aa4126d1dd8aed2bb9d7653c5d0b357f8d2 -01ea00000000000000ad52a2d92356a7c43c6223b53f391c6bc41c556201c79ad9525db1205715649896a2e4d95b790b8e4eab7e9f6c28a7a0093683d486d81c33173b5c32b54114def41a2c01b39af7f28636d0de9978761ac06fd931a44fa519c4eebda4a744279301b0897a885184ac65da3a8144e0605dee5a0835ff801a3f9bed9c9ed433e9c1a63bd9fe0768f615caa7ee79f5e0c3ae79086d922343598a24604c08f1c0da93a6bb890b08ae3dd9fe44efcdfa36f7aae20580386d745085e8fdceedbc05b208ad40420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000008907002b6690e7c5f91a5b2ca018de040932ef1db1624af851299b588e4cc0af27395aacc5d7e480fc14512899087a39 -01ea00000000000000b0897a885184ac65da3a8144e0605dee5a0835ff801a3f9bed9c9ed433e9c1a63bd9fe0768f615caa7ee79f5e0c3ae79086d922343598a24604c08f1c0da93a6bb890b08ae3dd9fe44efcdfa36f7aae20580386d745085e8fdceedbc05b208ad0180217bef9a4a4c9b52179f73b89c8f59bfcd6b96c0a5e50864fe43ddad3491dae4917577e94a705e12116ed1816132a20693efa1d49097329fd91682731e8244cb61e73bf557f5658a6be82af9e49535e95af7c4b099a6bb5c2ef6303e6b2d8b40420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000008352d4a9d617002f527ee50a28ba3899f8f5cd85683bb93e5729b8679619e98e84067467fcf80dc11d434592f0f46baa -01ea0000000000000080217bef9a4a4c9b52179f73b89c8f59bfcd6b96c0a5e50864fe43ddad3491dae4917577e94a705e12116ed1816132a20693efa1d49097329fd91682731e8244cb61e73bf557f5658a6be82af9e49535e95af7c4b099a6bb5c2ef6303e6b2d8b01842d95c2f84cf9739ee10e45888d23cb01e7537ed6c58919db85ae1fae3f22985434a2aa25b279b52f814f05f60f1bdf18b37c2fe00c2f36dd6afb9e9bcbe347c90b475ae48955144c89f109d4e291cd99b18daa59c2f41c753596e6cb2b252a40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a96b3855db7b511c8e116369f93ea09732d2ca150d6efe3a5974dbf8878709224df6a75e67cbba5e3d9b44ddad91884f -01ea00000000000000842d95c2f84cf9739ee10e45888d23cb01e7537ed6c58919db85ae1fae3f22985434a2aa25b279b52f814f05f60f1bdf18b37c2fe00c2f36dd6afb9e9bcbe347c90b475ae48955144c89f109d4e291cd99b18daa59c2f41c753596e6cb2b252a01b77b4ee2fd3554f5bc8f20982b4135f49a7364ecd281575d3e83c360f3a0586e565834a7620b30ec0077281f9e22385a0f4797b6cee99c4860f520e09c2ee34c4f31a2cd17aea9d7cfca96e1e9ab3d46bcc6fa76e56a26db96a06a83f728e71f40420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000008614a2949bb1a946cf9127e0bd128c5b5602703732552b11e0a3dedf52c8720232221e49df82ee0ba84006fcfd008b0b -01ea00000000000000b77b4ee2fd3554f5bc8f20982b4135f49a7364ecd281575d3e83c360f3a0586e565834a7620b30ec0077281f9e22385a0f4797b6cee99c4860f520e09c2ee34c4f31a2cd17aea9d7cfca96e1e9ab3d46bcc6fa76e56a26db96a06a83f728e71f0181831c806b72e16f4c86c1f3c3d21848bff839558a08f64de5ced8b34eb13b2812ae003b9dbf2eabe0e974a209eeb39e0322da840489781a357803b4cf16288adfb060d16bed494c9474d3343aa182a2e51a75d9c8a219171a333e21d02263bb40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a146b7448dd5010ea8f0a4c1379ee9b06c42852fd75979f132bd0592c3dcfe2bfe931472fe682d9dcbb3b0bdce28368f -01ea0000000000000081831c806b72e16f4c86c1f3c3d21848bff839558a08f64de5ced8b34eb13b2812ae003b9dbf2eabe0e974a209eeb39e0322da840489781a357803b4cf16288adfb060d16bed494c9474d3343aa182a2e51a75d9c8a219171a333e21d02263bb01b466bfc5caf876637b3d514dda309cc00877ae33f13c70b23552b3ff0a74539cd3c820e2ccdb2bd7b3c5ad7218e4263618cfe690a73ab6fb1a84c9d0505972938cd5509565235cdad30741a614ee9d6dfcfcd46de4022ca52373443b11c6c6a340420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000098fc0fd8efc68f4c624fbed42acb3d94ed05867d4e073eea4b03a1b49376eb84d1758e67bcbe20ddea5b1baf72a6d854 -01ea00000000000000b466bfc5caf876637b3d514dda309cc00877ae33f13c70b23552b3ff0a74539cd3c820e2ccdb2bd7b3c5ad7218e4263618cfe690a73ab6fb1a84c9d0505972938cd5509565235cdad30741a614ee9d6dfcfcd46de4022ca52373443b11c6c6a301ad8533139e5d66dad1b63e11b548aeb579eb3f485601fc70888287e3105c066d0428627fd44478e89e01defb2abe527b114164d69d2ffcef1563aa61d5e49b1eac3c14fb287aa139a7ce70c748f62815395b2c1d2eb04f384645ff5d839fe57440420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000087c8175820c37e02e99b50a8b61c24ebb6cfc9dbb8bb93b066b099a5f27997d65bfb4a49394ac97fe076fc8cdc345b28 -01ea00000000000000ad8533139e5d66dad1b63e11b548aeb579eb3f485601fc70888287e3105c066d0428627fd44478e89e01defb2abe527b114164d69d2ffcef1563aa61d5e49b1eac3c14fb287aa139a7ce70c748f62815395b2c1d2eb04f384645ff5d839fe57401a8aa762439cfb3ad54177030e59db0874cbc484029fd3c48ea9d36debce777e3bd834c46288b8282843ef40cf7d85ddb0e4d286642cea988804c87e1016a68dd45cde02645e6e98fc0eeadcd68cb156b9d402f5a7fe96041ed0272511091660640420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b6e40c6d4094348f2e7f6f1c641651851ffb01a4778f0a5b5d9a65f5dc3e7f2f50f8e4d211ce4fe3b6f9d224872bdd30 -01ea00000000000000a8aa762439cfb3ad54177030e59db0874cbc484029fd3c48ea9d36debce777e3bd834c46288b8282843ef40cf7d85ddb0e4d286642cea988804c87e1016a68dd45cde02645e6e98fc0eeadcd68cb156b9d402f5a7fe96041ed0272511091660601aa6e391c91601ce3dad444d526def3b1ff1e922d34f0d05c3091ee2af235cc8fc6c031256c0486e32c75f86f45d0f79311e60250b8e905abbfef85394288a6696a4bd38ec75ae9bc98c9ef922f3adb35b580221ad2f538a0d3a705f0f6a1c0c940420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a4a51f774991c5fd984c7a9fadea5967bd923ea56dce70e3baba85e4eeca68c38ddce234460175505267c5c9711ae906 -01ea00000000000000aa6e391c91601ce3dad444d526def3b1ff1e922d34f0d05c3091ee2af235cc8fc6c031256c0486e32c75f86f45d0f79311e60250b8e905abbfef85394288a6696a4bd38ec75ae9bc98c9ef922f3adb35b580221ad2f538a0d3a705f0f6a1c0c901b5b4fa146ce040a4515b233e6d2f433fb07de0792a02d525eaf1bebad079e8215d0ed09214b7bff11971045b9ab32cd4143a4b7c53539bba0a7996de8f4865d4a5df2e63be5fd6258dfa3ce45871178efeec1cad6199a8e015a937b18385d63b40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a66fed8b83f5fb64fa7fbdf4d1153f880030f728aa143fa452796a0cf925bfd0a1f11d6235e25883ca4d4f7ac182d087 -01ea00000000000000b5b4fa146ce040a4515b233e6d2f433fb07de0792a02d525eaf1bebad079e8215d0ed09214b7bff11971045b9ab32cd4143a4b7c53539bba0a7996de8f4865d4a5df2e63be5fd6258dfa3ce45871178efeec1cad6199a8e015a937b18385d63b0196e56fcf360d56b5de10fa6aa6e8008f32aa9b210c85d135723061e21c1ade5317cde980b672cea87733df0e81f704e618e09c8ae089813b68b1855b7e82e249d318d7f959a1ba362738e3de2f47de1ceac99919168ad9ed801d0f5597bd8f5340420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000ade1929d985bced55cde6a0911df0632845564c2df203100949870e8a3bec03bd96c0c75889e280a88d8c2c6e31c1b3c -01ea0000000000000096e56fcf360d56b5de10fa6aa6e8008f32aa9b210c85d135723061e21c1ade5317cde980b672cea87733df0e81f704e618e09c8ae089813b68b1855b7e82e249d318d7f959a1ba362738e3de2f47de1ceac99919168ad9ed801d0f5597bd8f53018eb8f62adbf34801040bd866ae138e3e727a64b901b1195d521c6588880efadb84d131527a9a5efe9a37edffc3f6d98c160b00c7541fe26c27dac4ae4339dae34868ab657d1df65e385deba78aa548a5d4eb165fbdfc0068ab02ed18ba05869940420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000883462cb9779afdd17e2757a95a9ba71adde328ea0caa4444276c5d72d98d061de1bc599c82c8da035a4ce601b65f1bd -01ea000000000000008eb8f62adbf34801040bd866ae138e3e727a64b901b1195d521c6588880efadb84d131527a9a5efe9a37edffc3f6d98c160b00c7541fe26c27dac4ae4339dae34868ab657d1df65e385deba78aa548a5d4eb165fbdfc0068ab02ed18ba05869901b9174a0fa4cfac258271eb5a17ad68801288a69bb26aa75ff62c90b8fbbe2ee7829ee1f41e3863e3cff77e65ae718c03001619e80569ea2ad54e4e424ea387c5ab265b3d2dfa55835c5c21e7c2abcef91e1ecfd325ae36135721aeb0ad9fb59640420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000acedd45a27290816191703daf101212fa111cafd5e381c9430fb9b773065bd1ef8092d7c82122786a0868cd9f7fdf813 -01ea00000000000000b9174a0fa4cfac258271eb5a17ad68801288a69bb26aa75ff62c90b8fbbe2ee7829ee1f41e3863e3cff77e65ae718c03001619e80569ea2ad54e4e424ea387c5ab265b3d2dfa55835c5c21e7c2abcef91e1ecfd325ae36135721aeb0ad9fb5960192082a1f4fdc82eec7070cdc0311e65e5346ca8fe4012ecc935373483ef2c70dda8c2d3803261c095b6085f9bc01c4a30b663c586dfba34c5afc169ce950ec64b462199310b4c9b9b0c383377b5e868afbe5b2a977d5cfe0a706bc0674ec69cc40420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000097f7673e0f9b83c47474e2cefb3d5141d9fb60b7d325ae6713f3a7b931be693f5a903159348879b92b5a23b1ba14a8b6 -01ea0000000000000092082a1f4fdc82eec7070cdc0311e65e5346ca8fe4012ecc935373483ef2c70dda8c2d3803261c095b6085f9bc01c4a30b663c586dfba34c5afc169ce950ec64b462199310b4c9b9b0c383377b5e868afbe5b2a977d5cfe0a706bc0674ec69cc0189c05652e960b4a7525f336a6e1efe40764dfe9a9eb89392bd814c84eca361a9eba3e22c09c721ca306e4ebaffecdd0c0c63846e46440d0161d20b43a75c18e90ba93f4ca0a4485d51ae29cc0a2ea8e660ddbcd1307524b4a286326316e66fd040420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000009850c013de31e36747fc6a18a331d48d3ef1dbfce94b5531cecd9ea2c18daab5dfcbd236ead1cd23fb81b047884ba8b2 -01ea0000000000000089c05652e960b4a7525f336a6e1efe40764dfe9a9eb89392bd814c84eca361a9eba3e22c09c721ca306e4ebaffecdd0c0c63846e46440d0161d20b43a75c18e90ba93f4ca0a4485d51ae29cc0a2ea8e660ddbcd1307524b4a286326316e66fd00192cf863e45a1874de11c3703031d47f3b651a5d0c8d1030338eb65c03843984f0545c13df630cb3235cdb40856aadf5c0a0068a33d06e18147d93ce012a85791f358d410c726131e85bff2cbc393ba5bd563d43a60d796259e528e2a01aedaad40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000ac9ea78593edb04406406a3568b0134969d91e04bf4e42fa2cc58fa3d920b1cd3040501372dd547182ff4683b4e09029 -01ea0000000000000092cf863e45a1874de11c3703031d47f3b651a5d0c8d1030338eb65c03843984f0545c13df630cb3235cdb40856aadf5c0a0068a33d06e18147d93ce012a85791f358d410c726131e85bff2cbc393ba5bd563d43a60d796259e528e2a01aedaad01b788905c2f2de91dcbb558ad5126749c962676f7734fb84b1d0bd4d1d24138513fd7a0ea52215595f54eb03a53276f4609a31140b756959a3ec693bb70199edc976debb75dd6fabd6881297112dbe13bd7fc227f9191c9a9daa309d7fe97186f40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000adbf63e62d1766ac83d6e785c5c1c512a4f2ccc8a91876541a6b9d95a2e445419684e29e963fa8fe7775765f56f4b333 -01ea00000000000000b788905c2f2de91dcbb558ad5126749c962676f7734fb84b1d0bd4d1d24138513fd7a0ea52215595f54eb03a53276f4609a31140b756959a3ec693bb70199edc976debb75dd6fabd6881297112dbe13bd7fc227f9191c9a9daa309d7fe97186f01b37108dc3a0312cb00de57234b987d35f145d4d1ac60dda25a39e1f08d5cf88330be28f07204d9e4a1a79b5a6b59461800657df37e09b0b0882ca7cbd160ada36c1750954f292c1cf218b11416c99c70d6b85ae284fae829cffe9bdecda4560140420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b033d35b827355653805ac27fddc6e7828fdfc8a099e87c1c6814f966bd85517c249a9180825b38b3db173c9bf12acdd -01ea00000000000000b37108dc3a0312cb00de57234b987d35f145d4d1ac60dda25a39e1f08d5cf88330be28f07204d9e4a1a79b5a6b59461800657df37e09b0b0882ca7cbd160ada36c1750954f292c1cf218b11416c99c70d6b85ae284fae829cffe9bdecda4560101b380ee5be0c99e515b6dc0ab74e8ce7ddb1d8c860291f2d868fcb7efdd79bf66038a749860868c80d32b33502808cf7b0f78852892ed5c5f9fa46466e8b76ad52e135d0fe80b377bb4bd1c0d1be33a4669562db4d62a353348aefa6e51b9091040420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b10d23a361fcb13b4d76e57c9b5a185d6dcd76ba0b9ff8fe82da0627dc127c529611e9e1d72049f43dbd091a6c278cbf -01ea00000000000000b380ee5be0c99e515b6dc0ab74e8ce7ddb1d8c860291f2d868fcb7efdd79bf66038a749860868c80d32b33502808cf7b0f78852892ed5c5f9fa46466e8b76ad52e135d0fe80b377bb4bd1c0d1be33a4669562db4d62a353348aefa6e51b90910018ea0c4f75df9f4663ee422e91064c08d3a636833c83aa1bf7d7830641079c2bb66723b135f21447b4d9d8667aa18669a17c01a087075a41b507bbca1de4788cd76395e043658401be699cb9fddaf8dfb93797fbba2cc63eb6320fbe438c9972540420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000912c3b5ee5127302457fc7b483e00ba3ee415fe8cce9b3c9c9b513dc7e86df98f68a9a3d5c148a08f4d554c11f415a02 -01ea000000000000008ea0c4f75df9f4663ee422e91064c08d3a636833c83aa1bf7d7830641079c2bb66723b135f21447b4d9d8667aa18669a17c01a087075a41b507bbca1de4788cd76395e043658401be699cb9fddaf8dfb93797fbba2cc63eb6320fbe438c9972501810071e338fed3022b5aad0eb4daadc215ffd63375cc6f6d7954df38bec79f0ed47ea9f655b5a284cd55cfa213fd93fb0e45b12e53e8298e4c6fedb52cb705cb5a8e019f9bb842581836fbcfe3996834c220f096fd5a916ca6e16b9bf3e1172840420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000aaf1a06a102fba5a3c582e5868902b750bbf99cfe10549bf2b227771d8d29ad989f393a35db727105846789221fb9361 -01ea00000000000000810071e338fed3022b5aad0eb4daadc215ffd63375cc6f6d7954df38bec79f0ed47ea9f655b5a284cd55cfa213fd93fb0e45b12e53e8298e4c6fedb52cb705cb5a8e019f9bb842581836fbcfe3996834c220f096fd5a916ca6e16b9bf3e11728018edb61724d977ddaf3fb94872ca1b7e12ecd7e891d96549cff9f133704df96251b71062614ee3f4a7c81c418e66363530e08fc029a2fd1b465ebdc54ab567eb0615c6e7c79c4dea44aefe1b95bf94e31d265ba2cdf20bdbcae2f5960b094839740420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b79aa8dfce5b3badb5c4bbc147ae4bfa3dbad6c71ff0f88a9b17451461c1f4c6d60f7bea3c95d0215de27ca3e893f661 -01ea000000000000008edb61724d977ddaf3fb94872ca1b7e12ecd7e891d96549cff9f133704df96251b71062614ee3f4a7c81c418e66363530e08fc029a2fd1b465ebdc54ab567eb0615c6e7c79c4dea44aefe1b95bf94e31d265ba2cdf20bdbcae2f5960b09483970187f54651e27d510193f50ebf6f033ce249081c9fd18fc1a05a4f8d1a1654d8c31f2e16bbfd94f89f035e79d387a86a6518801714942af5d5eb1ac130cdca53f115c899b1ab8aca2cf48aa10ce0d437677ac9be86d0cbc5be926c23e9b14eb5cf40420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000009520db92adba69b3ddf46cc334268e97c64f265cd50ecfbae4e8dc466c89cbc763dfdccc19238184d0b4224e1de21e67 -01ea0000000000000087f54651e27d510193f50ebf6f033ce249081c9fd18fc1a05a4f8d1a1654d8c31f2e16bbfd94f89f035e79d387a86a6518801714942af5d5eb1ac130cdca53f115c899b1ab8aca2cf48aa10ce0d437677ac9be86d0cbc5be926c23e9b14eb5cf01aa14153c5ef154e67ae390d79718d245fd9ae631ba3e2a7dd57c3e62e7f8cda529d678212101f18ac06685e04390565a099336f7fae35a8f128c1661f1bdb9ee4ca4b691d1946e963a14d1d95e013d8583a13540cc0123ef16454a1af7fd2c2340420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000008fc5d6951ec32d6e354beab2a52b947117cd9e4d5d68fadc38d403fb5cbadb47dc38647aedec642c7077ca1d8caf164a -01ea00000000000000aa14153c5ef154e67ae390d79718d245fd9ae631ba3e2a7dd57c3e62e7f8cda529d678212101f18ac06685e04390565a099336f7fae35a8f128c1661f1bdb9ee4ca4b691d1946e963a14d1d95e013d8583a13540cc0123ef16454a1af7fd2c2301b5060ee6040cf10d90a7fc7583186fc50306825ac4606ef665e58a9caba05d5622b88f9d606f2afbbbe44dcf1b5822360a063a25daffb180173efa5775e9beee155bb925c0b488f1bec30c8565c026ce61c12f4f8d00b7d9d1712978af47745540420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b8415cd6b5031003a08d020b3972f7ce579441cb3c9be901f66beadd99da4cf68ce17a41b9bab5096d3240dbf7a49bbb -01ea00000000000000b5060ee6040cf10d90a7fc7583186fc50306825ac4606ef665e58a9caba05d5622b88f9d606f2afbbbe44dcf1b5822360a063a25daffb180173efa5775e9beee155bb925c0b488f1bec30c8565c026ce61c12f4f8d00b7d9d1712978af47745501a5d9545148567e0b2b2e86498eaa9a97021203005cc3e2faa17c87331781423f20034fd521ae99de2dee13f02a6f988a16981558d4e36839eb396256f5077de223bba679a493350057f24fa39156c0f6e69b78ea03278017e8185a9cfd8224c540420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000ad5f38f50ca6b5f78fe5fba22fab0fd1dc6ea840b9c98e9ab79c33507ea35ddcec0a5556e84b042809a890c4dba3a12f -01ea00000000000000a5d9545148567e0b2b2e86498eaa9a97021203005cc3e2faa17c87331781423f20034fd521ae99de2dee13f02a6f988a16981558d4e36839eb396256f5077de223bba679a493350057f24fa39156c0f6e69b78ea03278017e8185a9cfd8224c501ab297159791a4221baf2a866327173c95afb8f7e137194ff60e1b00403e2187970703d089a85476f968de7e1c8a59dc90e299d86e342ab4b6e9fb41463608ea03858141ccdc536e9a14cb11754449f7d120205ac3a5175d280a4d16fa4292b3140420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000080e66cc5750a9a68f68d6ea2bc66a04aa354ba7382681ece876c1bc59e42f9a50cfe06b781acf6e04bd59b2e79f0d12c -01ea00000000000000ab297159791a4221baf2a866327173c95afb8f7e137194ff60e1b00403e2187970703d089a85476f968de7e1c8a59dc90e299d86e342ab4b6e9fb41463608ea03858141ccdc536e9a14cb11754449f7d120205ac3a5175d280a4d16fa4292b3101912083dc1244e717312e2e8409066e0b45105066427fe8d71a8668c7b2f54543e0534f70518a86181b14e05aa3bb294c1320b80306b8acfca91c0f13ec371f12129aef9b631f3a063f65784d8e72764db85977a2bad47d1f51780f972cef023840420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b6f581922a4f8a5447b53b85bc4acb9164d8c9654406d8458fc333eff9e88120d5bad5c1c9b468d056d027d06a22a5f4 -01ea00000000000000912083dc1244e717312e2e8409066e0b45105066427fe8d71a8668c7b2f54543e0534f70518a86181b14e05aa3bb294c1320b80306b8acfca91c0f13ec371f12129aef9b631f3a063f65784d8e72764db85977a2bad47d1f51780f972cef023801af5d3782b73bd568841499ebf3fb651b147ae6fe13cc17cb0e6055e909685981c9b3b11adc063c8b817a472d238293c010d8b8d9b8bb3b4222d2ba3d0b8bc14b0a33aefad31671844d506fd21c931b3809c40105c374870e2a631d974f7c3f8440420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000977a7b3bb7411422c344af890c45c80c2c1fb0fc188f79b3e7ee1de9b1f1c918ac9adc6bbbf129b6f9ad41d6334bc1d6 -01ea00000000000000af5d3782b73bd568841499ebf3fb651b147ae6fe13cc17cb0e6055e909685981c9b3b11adc063c8b817a472d238293c010d8b8d9b8bb3b4222d2ba3d0b8bc14b0a33aefad31671844d506fd21c931b3809c40105c374870e2a631d974f7c3f8401b1275a92da6f74da25e08404ac325019af00be8abf5a7e6b8cee9543de3e208b2bbec9dc5d7bad7f6a280660bea799a50396367636b8c17a1b7c3ee4727e68446a45211f4f9899e0f297a5698f98e935d08379567a26b956825962ed04ba97eb40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000929ec88d2f08b6ee04862932effcee13b8f7ae3f8d8212c3ea36a1bc2b31071bbb99b094ab57f382986838bdcab92e58 -01ea00000000000000b1275a92da6f74da25e08404ac325019af00be8abf5a7e6b8cee9543de3e208b2bbec9dc5d7bad7f6a280660bea799a50396367636b8c17a1b7c3ee4727e68446a45211f4f9899e0f297a5698f98e935d08379567a26b956825962ed04ba97eb01a191e04fdd20d0bee8efe762cf13603b78c6e61b90f6c976c4be25348c53ed7cad8b05cc137ab029e605d8ad63e8bb7f001bd9c0a1cab509d0e7780d0493ce68efd02007fab9c1d164b765fc8598b4a1ba1bae694689e5753cbc7c29ae47de5040420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000008465ebcabc2af63839e962e247559fb673556fefef1b963bdb6d8036c5351e9d2a9fcbdb1aa6c1ba33a551c0b7163299 -01ea00000000000000a191e04fdd20d0bee8efe762cf13603b78c6e61b90f6c976c4be25348c53ed7cad8b05cc137ab029e605d8ad63e8bb7f001bd9c0a1cab509d0e7780d0493ce68efd02007fab9c1d164b765fc8598b4a1ba1bae694689e5753cbc7c29ae47de5001b065f8cdc26fad7d6737b11e7b0289a9cce3d13d7b772f6eff441f83744984932b9db6994795f45b36ddaddcf1115ed20f8ddde5c7f258644db26c64b43ab9e4fcd7def04be4357aa037d7754f81862c190c31e0af508dac1187fbe65059fe9a40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a0e8f953326a18448d2846864e6e19e41c4b61e76da4a54dc7fb0ae799df58f4e8611f1a0a74884a0b874f84ab3eedaf -01ea00000000000000b065f8cdc26fad7d6737b11e7b0289a9cce3d13d7b772f6eff441f83744984932b9db6994795f45b36ddaddcf1115ed20f8ddde5c7f258644db26c64b43ab9e4fcd7def04be4357aa037d7754f81862c190c31e0af508dac1187fbe65059fe9a01a4a36d2ebbba0b101b685651dddea0d3bc897b949ddba4ddf6b717939280de64f9b03c3367170d70e2f82cf18882d1b9112166231cae11ef914df20d74197e5f15cc82ee7f100e743e4dba3281f17b32773fd850dde3363f8b268b6583dc6cc840420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000846d775bedb6b7db51528ed84a39e3395e643c7273a0d2556c8d109de1d408c53cb751a972ce1b4589349aa406c574a4 -01ea00000000000000a4a36d2ebbba0b101b685651dddea0d3bc897b949ddba4ddf6b717939280de64f9b03c3367170d70e2f82cf18882d1b9112166231cae11ef914df20d74197e5f15cc82ee7f100e743e4dba3281f17b32773fd850dde3363f8b268b6583dc6cc8019571a5f4a8192b2944a0e1ac9195b2f3f81182fda0c18ff5c7ece42c83f0f83005fc32317021ea36aece781a83240af10d1c53c0d4eeeb3a1af932841c823b6685705f6ad0c6e8f259f09a8845a4a08aaaab9cfc2c54e855204c3a83d8bf8ab140420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000ae440d378a710eb89b3cc78ac75606937e97f42f22a39e94f22e4db1a3d3f4c6b474991d8993e012b485c612fcf30bfc -01ea000000000000009571a5f4a8192b2944a0e1ac9195b2f3f81182fda0c18ff5c7ece42c83f0f83005fc32317021ea36aece781a83240af10d1c53c0d4eeeb3a1af932841c823b6685705f6ad0c6e8f259f09a8845a4a08aaaab9cfc2c54e855204c3a83d8bf8ab1018a2a776459786bd1a382fa9da3b4f3f4fedf9d268b9c069d320f51b2246d1d87596edbf7e910fc4f6610350ab4b6e9dc0d4fcfb475d78de1677466367687f5a21c11ec6757b1e2811317f327b991eaddefac2ebb05582197cf4df13e9f7bede240420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a019b7b5554d665a16740038fd84e3affbf7daeea74c022c98ab8d75f3093adf6373025a55166749c828b406d1ead857 -01ea000000000000008a2a776459786bd1a382fa9da3b4f3f4fedf9d268b9c069d320f51b2246d1d87596edbf7e910fc4f6610350ab4b6e9dc0d4fcfb475d78de1677466367687f5a21c11ec6757b1e2811317f327b991eaddefac2ebb05582197cf4df13e9f7bede2018248093de6a5e86314d66ec0f1cf09822d1b975163fead31e92745e24a1c48192a0cbe2ed7e71793aef1c43a340c0fcb0082f6abf7d0799422adc3c6fa7718c0f84b580a26d9488f35d417b43a62cc79b50d92b275d0413c7a1c00ed90ca379540420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000085db08cd723c734df570e4b9d19424a002597e3f8707215d6517e201144466c28c73bf8117d68e942801b32f696d5d41 -01ea000000000000008248093de6a5e86314d66ec0f1cf09822d1b975163fead31e92745e24a1c48192a0cbe2ed7e71793aef1c43a340c0fcb0082f6abf7d0799422adc3c6fa7718c0f84b580a26d9488f35d417b43a62cc79b50d92b275d0413c7a1c00ed90ca379501b4e8b32b323a77ca7750b37128afd1fe3e4d184c5bedfd6619dfc1f64afbbd864d324423fd005a6dfb93d0e9dd7031540212f96092ada6f1c8f73b69290eb813791fe73efc5009185d5694182e85588b674c5aeed021f9921a6acb323fe6cd7a40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a2a01dd4e010c46bf6c0ad3bed824a0f50aa8ac4fa7636283ae0464494a950414569c8258a6ec516101af36a52524ef7 -01ea00000000000000b4e8b32b323a77ca7750b37128afd1fe3e4d184c5bedfd6619dfc1f64afbbd864d324423fd005a6dfb93d0e9dd7031540212f96092ada6f1c8f73b69290eb813791fe73efc5009185d5694182e85588b674c5aeed021f9921a6acb323fe6cd7a01aa33e2c3123e426f8007fb05cdcee119237dc1eec67e1a53b8e120bf315669673d449ca957c12f98f71a053dccd221a80287945992ec3933e3d5807768f9391cd9fbeda7b133e14453079b85f62298cc6fe92e075db9e90ba8ab07e071f45f1940420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a98deca9f4851b9be4e58d54b87577cc2735dd6b3b8846ca218f099bc8f62a373ef12b85455cddcc94ab8275b631a652 -01ea00000000000000aa33e2c3123e426f8007fb05cdcee119237dc1eec67e1a53b8e120bf315669673d449ca957c12f98f71a053dccd221a80287945992ec3933e3d5807768f9391cd9fbeda7b133e14453079b85f62298cc6fe92e075db9e90ba8ab07e071f45f1901927dc8acdab4a63bbf57514406cba2cfe4b2edc2d14b1fc225017732cc46c9b961877c0fdcc08e97c5b743322c24bd030a98dd7e6ba3c6cead352954d51f9dee62c38f682a872372dab67611b7b481e612f8dae4163206b123fc7e6369e8219640420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000ac8b18e7c590ef310e88e583c06d1acf7de5e0298eed5e30b60bc5c97f9242e105310b3b4bafca3abc276225bd3fa358 -01ea00000000000000927dc8acdab4a63bbf57514406cba2cfe4b2edc2d14b1fc225017732cc46c9b961877c0fdcc08e97c5b743322c24bd030a98dd7e6ba3c6cead352954d51f9dee62c38f682a872372dab67611b7b481e612f8dae4163206b123fc7e6369e82196018b13dec1e65b5cd9d2e62cd0da6ab5641986a8c6116babc3f53d311f8b235928becf0b90af6613bd52f2970511dec81c14c0bc1f8bdfed0399fa4d844d271abded77a05ae57c067b9ee5223053f5bddd5a7f627b3d8a12c5234f1cf4c75d116a40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000964bb4098ec758c77060f8943b3f6f6844f293560b27beb4dc92ca789811c30c92ec4494b086e9acd459d07d5cb94f7f -01ea000000000000008b13dec1e65b5cd9d2e62cd0da6ab5641986a8c6116babc3f53d311f8b235928becf0b90af6613bd52f2970511dec81c14c0bc1f8bdfed0399fa4d844d271abded77a05ae57c067b9ee5223053f5bddd5a7f627b3d8a12c5234f1cf4c75d116a01b3ec482a649fee0eb4af237daded7caf2c782076cd78212bbb832ece6cb29e68b9ffc8741b7ad4dfb32acae3df4c91a90f453134e01ec3524ece4548119362b81a5313bbe0f3ee2938517bf048b53ce62ab413321791d2b1a8ec68e90847697f40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000867f765ac015eae2d748f0be88239771404bfd2c1ae35afc38c3c730095f938f9f05684a61c773b1e9f7638a19962651 -01ea00000000000000b3ec482a649fee0eb4af237daded7caf2c782076cd78212bbb832ece6cb29e68b9ffc8741b7ad4dfb32acae3df4c91a90f453134e01ec3524ece4548119362b81a5313bbe0f3ee2938517bf048b53ce62ab413321791d2b1a8ec68e90847697f01a6d070edf3915837e8c062e1ad2156ad1d194faddcf0bcfc4d257f5b97b26d9f87f90526ccfc3e788db18496e0726eaf0707082f2d3321b7350c18f458c649be00c7393596c9cd6849bcbcfa2280d461c63840c86bc4554024ef9677f312804640420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000912e9ee469c027b525fb76fe3ed4b99716b34cd5621ff61a04bf03a5728d918646d989d9062587b7cf442ce2c34759d1 -01ea00000000000000a6d070edf3915837e8c062e1ad2156ad1d194faddcf0bcfc4d257f5b97b26d9f87f90526ccfc3e788db18496e0726eaf0707082f2d3321b7350c18f458c649be00c7393596c9cd6849bcbcfa2280d461c63840c86bc4554024ef9677f312804601b5737fd0219f73f3bb07849b98c8e4f98943baf42cd80a352efcb00125d8eb3c72a5f5f7ac96ff13bdfb3c8ca858aec7076c8a45f61b7dd2b2645eb9e4683d5058cc8238658fab4f06cc6fa60154acd9a08fe321a172d2ed89b8f47279cf21ac40420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000094c43c9d95ef2c3d8a1511c64092f566c50e32c177044038e7174e6fce93849e488658e95aa561969fce0fda930de8b7 -01ea00000000000000b5737fd0219f73f3bb07849b98c8e4f98943baf42cd80a352efcb00125d8eb3c72a5f5f7ac96ff13bdfb3c8ca858aec7076c8a45f61b7dd2b2645eb9e4683d5058cc8238658fab4f06cc6fa60154acd9a08fe321a172d2ed89b8f47279cf21ac018e00153c3ff0d4acbeeec3b9e5087d77fcb271778f3d1abfe9845f26ec53c7d55228aabe0d33f9de0734b2efb53b5af118a3b99acd4e5b573e9acad1480e5105fe4a66e1760907af3c3a4875f91e63538e8e36413e07fc6e3e42a639b106a6d640420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000089c05f6c92d444da2325411a49f028dc112db2971b89e57f04bbd1a3191c7512ffa199542b994e3c46651fbe03e8debd -01ea000000000000008e00153c3ff0d4acbeeec3b9e5087d77fcb271778f3d1abfe9845f26ec53c7d55228aabe0d33f9de0734b2efb53b5af118a3b99acd4e5b573e9acad1480e5105fe4a66e1760907af3c3a4875f91e63538e8e36413e07fc6e3e42a639b106a6d6018cc68fa0339be31f9a0b0bebeddbe0362a0402d38a11637c07902412fe99df492b8f243b5ae6cb12434b24c05a96ee0005a75854e3a7ea1310464553f867566564587e6d82a910929a43779901894c97e4c32b8e52cd804723afa051a2ac4c1040420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000818648a0435e8c32f730641190bfa8a6d084908701fac0d9a942a320bad40427a9b890c03233de5df217711edac80b17 -01ea000000000000008cc68fa0339be31f9a0b0bebeddbe0362a0402d38a11637c07902412fe99df492b8f243b5ae6cb12434b24c05a96ee0005a75854e3a7ea1310464553f867566564587e6d82a910929a43779901894c97e4c32b8e52cd804723afa051a2ac4c1001963af41e23aa1ac5db53b304b48cfefc08711ec031d7192447a3f6ae66c382775c3b23bf6aae50b9383febbd8a4ca68a0a99b39199f6c5a4e3ac1bba87bac0324866e26f25584f6c3e88c69e8f3a6e1abd27ffa8d0db39bbae16acd1071794ea40420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000089c62991e7384266931a9e6df70767d1621c14eaa8689abb0c0501a8795f5025eaa0a907fd065c81835bb02885ee9122 -01ea00000000000000963af41e23aa1ac5db53b304b48cfefc08711ec031d7192447a3f6ae66c382775c3b23bf6aae50b9383febbd8a4ca68a0a99b39199f6c5a4e3ac1bba87bac0324866e26f25584f6c3e88c69e8f3a6e1abd27ffa8d0db39bbae16acd1071794ea01b7a48e6b8ab8d373570ac7c49bf87f50e8ed96b8f85bfbf3cc876ebd109cec31ca38a5d5b7af312471b74f364565ab0317d33366bac87779a0a580147bd3cf4e655ab115a9ef34708b1dad09dfb3440a359789faffbcd6616a3dad4bd778c2c240420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a67d3a6e9224b8f14e8989a6c557c77b0328b46e830d59d50aece27a7d388779583feade2c2222e09b9f3c51f79c68ec -01ea00000000000000b7a48e6b8ab8d373570ac7c49bf87f50e8ed96b8f85bfbf3cc876ebd109cec31ca38a5d5b7af312471b74f364565ab0317d33366bac87779a0a580147bd3cf4e655ab115a9ef34708b1dad09dfb3440a359789faffbcd6616a3dad4bd778c2c201b6254baa4a16e32e9ebfc5170aca4b30fe320017ed4a59470973ba89b00379e8c004557259ca2e159b4189d610fe7f9409adc8a4339a315d513c18c53734ea2f27e7b70f4ca1abfe97291f0a3ac7042b220dd3ffb2a2ed78fa19ab7b5f83804540420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a734506d697d78ad6f9987edc738dfb49a42ce0873743ed83a6879b1981a6f42e271ad4918261e0d66b083001ea70b2e -01ea00000000000000b6254baa4a16e32e9ebfc5170aca4b30fe320017ed4a59470973ba89b00379e8c004557259ca2e159b4189d610fe7f9409adc8a4339a315d513c18c53734ea2f27e7b70f4ca1abfe97291f0a3ac7042b220dd3ffb2a2ed78fa19ab7b5f83804501803684a80adff86234ae0b5427d228bf46a0eabac52293644343fac42ec82a3f3794c7c59b9043f2625694d3ad3a39df180cc4a79c30a7d81a48ed440db5e577e0f9c0360b9315f947dba078a25590939536edfca7b18e5cbeee9fa508b4bb7240420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000081e80b019ef7ba312f64037abcf2a27b41b2bc209a21cb09113f967f38c7e4b3005c33a157c34a64edc657bcf9396e18 -01ea00000000000000803684a80adff86234ae0b5427d228bf46a0eabac52293644343fac42ec82a3f3794c7c59b9043f2625694d3ad3a39df180cc4a79c30a7d81a48ed440db5e577e0f9c0360b9315f947dba078a25590939536edfca7b18e5cbeee9fa508b4bb720196212323bac6b5e2e6536121eb52c65844bd533b8de6c78059d41cf9fbd414c26ddc6945b1db197e12ba2ce5f9175e7b036669d11da5c228363defdb71a5592e00df7c6c3168b11a385480f74034017ffd640c1a14ad76b62a597dc0b02926dd40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b6ae17086059f273e0c129521e8fc169fec772fa8b7bb4e831bfdffbdc93e6c0f81a7d3f28a223bcac495aa3c3e18c79 -01ea0000000000000096212323bac6b5e2e6536121eb52c65844bd533b8de6c78059d41cf9fbd414c26ddc6945b1db197e12ba2ce5f9175e7b036669d11da5c228363defdb71a5592e00df7c6c3168b11a385480f74034017ffd640c1a14ad76b62a597dc0b02926dd01899e64a0ea0178e09965c763d9231e319a5314b6fa1b7e6ddc69571d4e0017cd592da1a38893b288655b608fd435414d0bb6066ae4bfca448a3653dd9081fa10fe5924c37feb65a9fd40d4537787811e8984500bf992eb0d7d9b156ca3d9542e40420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000081a0fd1fc6e030d992c9d1f7361a748f3d24e27471acb6c04cef6bb2e5e0e4cf72ffdb8d03a1b2e1177724210d0989ad -01ea00000000000000899e64a0ea0178e09965c763d9231e319a5314b6fa1b7e6ddc69571d4e0017cd592da1a38893b288655b608fd435414d0bb6066ae4bfca448a3653dd9081fa10fe5924c37feb65a9fd40d4537787811e8984500bf992eb0d7d9b156ca3d9542e01b09232fac2f4dc06ed35592691aceaebdbe914204d6db4cb7966e8549c16df81639dfa4cca7ad387abf194f2aa9a6a3e0002e1e3d3e9d260bb9de47de04f3c4b64ca8724c6412b195e8a5a2e09cd120d18b14750e0c77701e80702a80d0cf19740420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b3ce9ea13bec073731af6c5b1f4d213828e8e9cca6cc39597a800d1c91aa8bbb2d1ec70f28a4e53ba10c66c17c158088 -01ea00000000000000b09232fac2f4dc06ed35592691aceaebdbe914204d6db4cb7966e8549c16df81639dfa4cca7ad387abf194f2aa9a6a3e0002e1e3d3e9d260bb9de47de04f3c4b64ca8724c6412b195e8a5a2e09cd120d18b14750e0c77701e80702a80d0cf197018c7fd098c59ad95d7ed779570a01e291a1badbe9acde7474a4f0d5696bf8f249c7a9b6b414359088dc504892ea9209f0123d8a95437bd0587c390f2f990b04c272a50d0afe2fbf2c8413d55fda00a566442bf5bf76a669aa97a275985ad0f3f640420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a09b1fee7f05d529b69f3bebe40ea17dd5f9f101a6cf3bd93ee84287dd4f62e1e4006745f1726db3749940eaa3801bce -01ea000000000000008c7fd098c59ad95d7ed779570a01e291a1badbe9acde7474a4f0d5696bf8f249c7a9b6b414359088dc504892ea9209f0123d8a95437bd0587c390f2f990b04c272a50d0afe2fbf2c8413d55fda00a566442bf5bf76a669aa97a275985ad0f3f601b9f213a0089a669561fe3abaaf1c50588632c457ceda29b9595ec545e0f30141dfd50ecda09af8d88593170e6d3995a5112be3e42c7e1e3146f59f9c028a26c97f6e505fe0e296f5e1c45a24eb1d675fca8de617d2701fda1ff020740f8ba79c40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a73b8f43440a5f9d6d674a0a5208565d55f7d025b32e8de1eafe10a93a0de28c19d3c60c6a11622656212749cd44dbd2 -01ea00000000000000b9f213a0089a669561fe3abaaf1c50588632c457ceda29b9595ec545e0f30141dfd50ecda09af8d88593170e6d3995a5112be3e42c7e1e3146f59f9c028a26c97f6e505fe0e296f5e1c45a24eb1d675fca8de617d2701fda1ff020740f8ba79c0183831204787b6cff761e61157b7dfc17deaa91945ed20f21c072e25923459eaeb8a3673ca69069f3cef6f127ef6871d5027481c5bf49db28818717cda24ac42354ed7a2dbfc40d5a599581eab1a195970e6e9f00bb14a7f8cca313ca0fafdec640420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000009935cc1882e967c89eb77a8ac93fa6b4aa0618bf5c76860c662ee7e64bc44c44509e91f6efbc3b9e8e29117c5a7dff56 -01ea0000000000000083831204787b6cff761e61157b7dfc17deaa91945ed20f21c072e25923459eaeb8a3673ca69069f3cef6f127ef6871d5027481c5bf49db28818717cda24ac42354ed7a2dbfc40d5a599581eab1a195970e6e9f00bb14a7f8cca313ca0fafdec601ae0bf8014acf2d6489892c2da83e0196558353f12928cf807f687f60917030aaa066b3ed8faa84297279503a036776c70a6c91b7f3065c0c87d4f26369b5568212bb0e1aad5165fb81536caa9865026694e6890405ed6dcc2a5bee283d8237e540420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b12823e729dd5f298935ad6e168faff41975029e9314c8524dac12dfdcec52061ac06d4e1980d78f5a96365b384dea07 -01ea00000000000000ae0bf8014acf2d6489892c2da83e0196558353f12928cf807f687f60917030aaa066b3ed8faa84297279503a036776c70a6c91b7f3065c0c87d4f26369b5568212bb0e1aad5165fb81536caa9865026694e6890405ed6dcc2a5bee283d8237e50190bded8a6919561a8beb2c4fac3792b0aa6dee67836fc305bba07b836d05e2df582a8d45ed52d03cacdd7a77be3afe8e185f4a7fa6979dbfb9969d2b68d52e7b1cf8d3ad71680f5d8b222fc4f713db2b3a0634378196795d20e293cbc70bb4e940420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a64ed65f2851ecabc2d99c883ba3d6fb43bed25845239bba2bb549a67068423976152ae55e20061b967a1c32b3898c85 -01ea0000000000000090bded8a6919561a8beb2c4fac3792b0aa6dee67836fc305bba07b836d05e2df582a8d45ed52d03cacdd7a77be3afe8e185f4a7fa6979dbfb9969d2b68d52e7b1cf8d3ad71680f5d8b222fc4f713db2b3a0634378196795d20e293cbc70bb4e9018444ef39e0bbd22bc279b94d9b76347d0ac6d5ac456aece2407e0cfbacd6ed0afc0e65d675cb7d63155f04cb27f8ad7f091c5d7f77d4474427469760aafa3ba72a64437ceb74afc289bbcc86dd6db98f972deb2284c74be740938b4af5ded73f40420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000008448bf7b02d7e4d0538b935b083bbb1dbbfa9b30e168f3d3280116828a49b4e361a97289d8b9a7dfa8351c8991cdd436 -01ea000000000000008444ef39e0bbd22bc279b94d9b76347d0ac6d5ac456aece2407e0cfbacd6ed0afc0e65d675cb7d63155f04cb27f8ad7f091c5d7f77d4474427469760aafa3ba72a64437ceb74afc289bbcc86dd6db98f972deb2284c74be740938b4af5ded73f01925cb1dc9b8bb3dba9695204f3a1ea9ddd9ff3742e34b9df47b417ca780f48104c5a576911b19653fe8e09aef49eb1d81393ed25f45c4230836a45995a6c66021c9453f7af2182a83a87635586bd283c925a93228069d7a52a05090b1403fa5a40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000880cfe81ad62c58821fc9bc66c13ffc19dbdbea8a5d778e520a0f8d61aa85005e55fe9d6b225a737d2e740860451abd8 -01ea00000000000000925cb1dc9b8bb3dba9695204f3a1ea9ddd9ff3742e34b9df47b417ca780f48104c5a576911b19653fe8e09aef49eb1d81393ed25f45c4230836a45995a6c66021c9453f7af2182a83a87635586bd283c925a93228069d7a52a05090b1403fa5a019031eb88bab33185963087e1fbbf96ce5cc153e6abcb02314ab4d0bcbc9ed301f6fa075185173d5b485e8ea755383cbd098878afeaf06323ccab122f456161d5d992e81aa8431eceba3840c1e95282a1bba57084b941645540182b0c352fadc940420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000950d7fd9da8919b806723d8a65a5c475a255a3aed746fff6d781163a611e55617708ea3171ebcefc69b4dc3dd20bc76e -01ea000000000000009031eb88bab33185963087e1fbbf96ce5cc153e6abcb02314ab4d0bcbc9ed301f6fa075185173d5b485e8ea755383cbd098878afeaf06323ccab122f456161d5d992e81aa8431eceba3840c1e95282a1bba57084b941645540182b0c352fadc901a990c4c0750588c6922633dad84eaaee6178d7698aeee542bafdbb1ae0b654d4e03d9380fae87596235b181f8b71d51a11b316218b029e40ba3e54a52d075f12b8486a3e3975b916b0e6c1bc848da555275224005892b6f44fd063ae058e164540420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b7807cb8d0d9725d11b8ffb4871c9568b7c60d7a7666fe5a25f98ece9fbff21961df6ab52382f0ca6e4f29ad5031a2c1 -01ea00000000000000a990c4c0750588c6922633dad84eaaee6178d7698aeee542bafdbb1ae0b654d4e03d9380fae87596235b181f8b71d51a11b316218b029e40ba3e54a52d075f12b8486a3e3975b916b0e6c1bc848da555275224005892b6f44fd063ae058e164501b6b6060f242a71b480f2ebf1b702cb2ece4f55ecf8f50a2878816e38041bc4b4ab7e007938a1d8d51dc531eb064e9ff70ef5637b13d48838df32894ce353335af6d3eee6f417ad4804e688c4a81e5df6aad9133885c41ed368c1bd836b1faafc40420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000080a88e7e968a341131504949efdda80cb88ec71b2db2dfb4391d670d2637e904a7fc8c53e7e591af878375068e23c124 -01ea00000000000000b6b6060f242a71b480f2ebf1b702cb2ece4f55ecf8f50a2878816e38041bc4b4ab7e007938a1d8d51dc531eb064e9ff70ef5637b13d48838df32894ce353335af6d3eee6f417ad4804e688c4a81e5df6aad9133885c41ed368c1bd836b1faafc018823a0ef0e5fe0d16677e23854848db2db80cc15750c4bd797a0014841b7fb8e27bbc787ddf7fadbe329f99b8ca70e28012a8fa8a32e07d2688029104ed01dadb684966312c8fd12fde26605522b7080ef52bf848ffd7ba23aa0d058f039c40640420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a62ebccce490534e37cda26b1f13338a335355d3b96724dbe4a1b568d820c61a7bd8b2d484a108cd48eb9f022ad3f2dc -01ea000000000000008823a0ef0e5fe0d16677e23854848db2db80cc15750c4bd797a0014841b7fb8e27bbc787ddf7fadbe329f99b8ca70e28012a8fa8a32e07d2688029104ed01dadb684966312c8fd12fde26605522b7080ef52bf848ffd7ba23aa0d058f039c4060180b3c0fbedea5479e51e6d56d6336c7a65f76311c579d41494da4d1bf1e43270028ff9aa5fa125937beeb9c6a067162b0759e9f26790f7ce57fead375958666da31ec1bb8f0c1bd300731c345ca95e2a05f0429ff6638f2a4c7fe10e48dfad6a40420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000008f1e71540675b6a6ece23e8a20d931dbeba089ca48a509e6c940837b765bd975c3365d908ef9b37b9774247ab2c45979 -01ea0000000000000080b3c0fbedea5479e51e6d56d6336c7a65f76311c579d41494da4d1bf1e43270028ff9aa5fa125937beeb9c6a067162b0759e9f26790f7ce57fead375958666da31ec1bb8f0c1bd300731c345ca95e2a05f0429ff6638f2a4c7fe10e48dfad6a0190ffde44e069f6d4ab591d40dc2a17c7eacebf0eeef6da3019ef4dc958c3dca8cdb5731c5e867ab9efe2c3b54370a551162cbf450dd0192c08f822197223141c6b8acf3aef06fe9ef1fbd977f6a7e79272573f055648cfd1a45b1637d4c812f740420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000ae52830a2eaf215350a5c6bd63ce0801e0c88de15e5177c93797d4fd34fdc21ae1ab5f7710425e582f4adacf593f2dd7 -01ea0000000000000090ffde44e069f6d4ab591d40dc2a17c7eacebf0eeef6da3019ef4dc958c3dca8cdb5731c5e867ab9efe2c3b54370a551162cbf450dd0192c08f822197223141c6b8acf3aef06fe9ef1fbd977f6a7e79272573f055648cfd1a45b1637d4c812f7019420244f16d68896e0dfccc0448ce28e9f7ecd3c217ffbf8045a2c110c5e5af3aff207947a6f553f42c261b31b3ff3760253a01594ea58b354f6d10bfc4c2dd4aee55bdae0fd93a7319aa797164d91564842fd2408f53c361edba48442d10eca40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b0e50b7f073545b0b76a5373028a866bbe72daf2ff8f927df6965f20f185b42052b253141f2eb188f30000142320ba63 -01ea000000000000009420244f16d68896e0dfccc0448ce28e9f7ecd3c217ffbf8045a2c110c5e5af3aff207947a6f553f42c261b31b3ff3760253a01594ea58b354f6d10bfc4c2dd4aee55bdae0fd93a7319aa797164d91564842fd2408f53c361edba48442d10eca018cf23e3cfc2faac0a551fce076313209abae0ea869b708c0771577e3877fac05c1b3ff3bfe8982bef50c9fe74e55a31613f080224b14f0f891430495a6f0c94f2cdcc2bf8127484182fa60745b82c43885873af6739a86c7ac040a15eca2829740420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b18752a4055573f13457a68842b0ce7d3b665c53f17eec227957e2430dba54667a3c34b520a8cd5b374317a59e700543 -01ea000000000000008cf23e3cfc2faac0a551fce076313209abae0ea869b708c0771577e3877fac05c1b3ff3bfe8982bef50c9fe74e55a31613f080224b14f0f891430495a6f0c94f2cdcc2bf8127484182fa60745b82c43885873af6739a86c7ac040a15eca282970192c5f7b905dde2a97e903a7fc3e63f88c3d509f0f0122f789368009f4b0ea02f77a59972b0af830ab54e2209bbff694b0df38fa651d500c610b867706c71228eb9c80bbeb1215a4e00b52c9805a6b622e24bd7e9f3b8c12908d287124a20534640420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a6e8c283f5c65eebe0aadc7142823c65a7857034036566e8d761bc695ee150a3fc9bfbb7be67a3b130c584280afd9b40 -01ea0000000000000092c5f7b905dde2a97e903a7fc3e63f88c3d509f0f0122f789368009f4b0ea02f77a59972b0af830ab54e2209bbff694b0df38fa651d500c610b867706c71228eb9c80bbeb1215a4e00b52c9805a6b622e24bd7e9f3b8c12908d287124a20534601b3df15846333468779e0d30456871392dba2c28eabd0e699a6f358308e89dc9822ca126154cf1c4a7bf853b26456569e15e4be28cd07caf0ba110b66b2a932cc4e95400fbf4a0e812f8bd539785b7fe78d07d437cbe81b6142fc530bbbbc630040420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000008b61e87db0e68fe00762fdd38a6ffd30f6f595756e97419e2d1a6d95492baaa623a4ed18f4bc2c5157243b56bde77eb9 -01ea00000000000000b3df15846333468779e0d30456871392dba2c28eabd0e699a6f358308e89dc9822ca126154cf1c4a7bf853b26456569e15e4be28cd07caf0ba110b66b2a932cc4e95400fbf4a0e812f8bd539785b7fe78d07d437cbe81b6142fc530bbbbc630001ab6d9e7db56766bff16a4511cfad9a6d5e336b3dea04fa00e3e0f36c177bfd9d8a112a837f45a6cc6ddcba1413b69d1112d28f967b9b90a500104e6a730c294637dfd944a83aff22e3bc215669f58513b47763133a4abf41914e163d6c23acc740420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a5e1b41286f1537ce9605e3383017aa83f19ca7821ef1ea786c7926a44cda29fa0d32b88c1aebc710ae1c78c3ec2665f -01ea00000000000000ab6d9e7db56766bff16a4511cfad9a6d5e336b3dea04fa00e3e0f36c177bfd9d8a112a837f45a6cc6ddcba1413b69d1112d28f967b9b90a500104e6a730c294637dfd944a83aff22e3bc215669f58513b47763133a4abf41914e163d6c23acc7018117e7bdc7e84c197c08fa089a3ee85b8bb9e9aac01a81b0365a86a5e00ee7b6229fcbe7302165fd9490adbb5f991fce0bfcd1e4bc43de62b7a70b909dd8baebe151fd36580f5b847ca0ba64a9b03d1fc14ad2b54d0b0ed766fc754d0c3525ea40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a83946bd6708e9b689267aa1e06f9a2223b29a98616d1cb8fe53eb0dd23337477fbfbd281d964ee680ddb5f144934dba -01ea000000000000008117e7bdc7e84c197c08fa089a3ee85b8bb9e9aac01a81b0365a86a5e00ee7b6229fcbe7302165fd9490adbb5f991fce0bfcd1e4bc43de62b7a70b909dd8baebe151fd36580f5b847ca0ba64a9b03d1fc14ad2b54d0b0ed766fc754d0c3525ea0197ac46f79bb62c41852a681a6a21ad8a0bf32522523a73e3c200d62cba80aa6248111c9b7e3931f9dce5876360aab1200e00101e6cbfc352b0b7bfff1837aa31b897cdb6e4e00d5307efa7bbeae78f3a031fd933c662b106cbbf727e90d41d0d40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000959ac38764fbd535a2c5f47af3b7277729ad16039230c81dda5fb4a6491c1bc1d12bd04f88e3b94f4755373da9ace888 -01ea0000000000000097ac46f79bb62c41852a681a6a21ad8a0bf32522523a73e3c200d62cba80aa6248111c9b7e3931f9dce5876360aab1200e00101e6cbfc352b0b7bfff1837aa31b897cdb6e4e00d5307efa7bbeae78f3a031fd933c662b106cbbf727e90d41d0d01a29fef7a4a1174cc7b179f1c5ff5c15fc760f743279241d03da8eae9ac208d6442430b6adadafeaeaa0feb9b0a300b2d00564ab4a656e936ed944aa22580d302f499e67a93065b43f254bc1490678c9a1b0e634d318a60b942e922509b287a4940420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000ab769d67ecfd1c51d514afbc46ab6842fbe5266af3408ffbf1cc23f231ad30405aebe7ef5a95055af263cd007a344efb -01ea00000000000000a29fef7a4a1174cc7b179f1c5ff5c15fc760f743279241d03da8eae9ac208d6442430b6adadafeaeaa0feb9b0a300b2d00564ab4a656e936ed944aa22580d302f499e67a93065b43f254bc1490678c9a1b0e634d318a60b942e922509b287a4901aeb51a3926de36e493243b3bf815c521393047ff6d1535974737a981ef7652fc189d60dd3aa3a6cfd38910d18192118517c4aa1099ee39372a92f2635e05b9218881227c1174a6bad9ddc143ac0f335974505a293f6375d81fc21c74059ec03640420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000008f98b7b5450bbdb497bbfc353f707ba428e8960fa307d3ccf3d0455999846396f01e73d52f9484860046471e710de874 -01ea00000000000000aeb51a3926de36e493243b3bf815c521393047ff6d1535974737a981ef7652fc189d60dd3aa3a6cfd38910d18192118517c4aa1099ee39372a92f2635e05b9218881227c1174a6bad9ddc143ac0f335974505a293f6375d81fc21c74059ec03601acb686f36f2a12ebbb2d2408c3cf75821eca6e917b1eda620bd123432840815cd9610835e90584da1abf5bc6cb26292b09d08188b7a8bc27f8d4a50138888754c9381d3e72c731fb6306e064e62ce647b52f3395b9d95a2cfc77d19084e5fe7940420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000091a3b091ddbea134acd4a71151e8328171c6eba241d649fc774998818f90e169d6ca96c4723d04be800bb29d05625cbd -01ea00000000000000acb686f36f2a12ebbb2d2408c3cf75821eca6e917b1eda620bd123432840815cd9610835e90584da1abf5bc6cb26292b09d08188b7a8bc27f8d4a50138888754c9381d3e72c731fb6306e064e62ce647b52f3395b9d95a2cfc77d19084e5fe790180b4b29b306852bd6accd13dedc4f379b3cad1d44f97798e19466c911842a1a5bc90dd545a36042e4f5cd814d0a4013706de007b9889d577c589057ee8eef811bd31d925d5b1f9564ec217f43ff0286e10bb5ccf7754f42770b236c7ba207ab740420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b504044f5fdc16c784efdc6c8083918c641a58de8c34ae931c83ad70eb42412025c5feb06a5ea3994e51b1cef145e8e4 -01ea0000000000000080b4b29b306852bd6accd13dedc4f379b3cad1d44f97798e19466c911842a1a5bc90dd545a36042e4f5cd814d0a4013706de007b9889d577c589057ee8eef811bd31d925d5b1f9564ec217f43ff0286e10bb5ccf7754f42770b236c7ba207ab7018cd173e54f2d62803916d873bb709dc1a54aa1609ba0bf776891fa2b23c0850ea2042954c63d7d9ba9be4ba5b201cfda0e5cf19f3207fe4496686adf839da36a5b6ef004291c4c00c8173ce428c253833aaaf824b81f2b5de8b733a000362ad740420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000910d88ab38ae0bb8e46cdb2e6aa8ca45163ab56b390f8f5f8f36512ce00859882b9c0e5be4f106adcd92aa86d39a76ce -01ea000000000000008cd173e54f2d62803916d873bb709dc1a54aa1609ba0bf776891fa2b23c0850ea2042954c63d7d9ba9be4ba5b201cfda0e5cf19f3207fe4496686adf839da36a5b6ef004291c4c00c8173ce428c253833aaaf824b81f2b5de8b733a000362ad7018a862e3cf4e4d9a57edb886762fbf0f783821f97a76e15be18f1b38007034fd2f4333c4bb0064d11e736fc9bdc2817220c38a29602c8b20dd91b6d98838dca2db64afd3d93ad93568505a86f22a521418159b1bd1bee70f6fe9494d68da5a5f240420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000095b4c8e97f34c720621e14536e1db021e5a40b44f2671f5942027eccca2c09d6a362cbc407c15c43fcedc4aad746ba9e -01ea000000000000008a862e3cf4e4d9a57edb886762fbf0f783821f97a76e15be18f1b38007034fd2f4333c4bb0064d11e736fc9bdc2817220c38a29602c8b20dd91b6d98838dca2db64afd3d93ad93568505a86f22a521418159b1bd1bee70f6fe9494d68da5a5f201b361321e4403d925349551752acd2528b438111afc767656d85e1af18390257adc4113372c9cf5ca56ac982a7e96cc651012efa63ea74ad9d48b8e0be7e010b18643b68faeec02b5bd210f3559a05cfc61569e26c4cb85006755ee9ea7d1caae40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b357b51560d3715a2330585cc7f37686afc52324b67e37db43149e5e87bac88b90d0db32f34f707ee57a86883d8017a7 -01ea00000000000000b361321e4403d925349551752acd2528b438111afc767656d85e1af18390257adc4113372c9cf5ca56ac982a7e96cc651012efa63ea74ad9d48b8e0be7e010b18643b68faeec02b5bd210f3559a05cfc61569e26c4cb85006755ee9ea7d1caae018d31d41cb190cb54fab965d3edba820a8e8fc5cdf5df2ef0b17ed7b318c290808564ec96651502cb086f4e2317de88441376dca3359fcf084a904343591c9c6d420dea1e576a957120af2ea4c46ef9d3bb1394ad3cbc92413c59b1ce518c085040420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000ab561825fdf924821db847e39ed78ba885ea240a12366a5137b17ecfee657d7d548f419f62576b356ad7bb16d87d1164 +01ea000000000000008d31d41cb190cb54fab965d3edba820a8e8fc5cdf5df2ef0b17ed7b318c290808564ec96651502cb086f4e2317de88441376dca3359fcf084a904343591c9c6d420dea1e576a957120af2ea4c46ef9d3bb1394ad3cbc92413c59b1ce518c085001b6532a7b799b7911b4631bb91c07f2d1b10b0641cdb8fdf53bfc8194c9700e22d2fe0068142786be7a79cce92920d2a518dd851e2d7ee65addb2d79f7a8f8487d6df24627a5bf74428e060be27adca21250cb3382c2a4c8b4a78b699c1d752c940420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000009338f5d83b9af91ad88f82e79f095aeb3c51e86c1eb86debcc07f35508f882c5453d371a04f8e643e3a00ec457cdcf81 +01ea00000000000000b6532a7b799b7911b4631bb91c07f2d1b10b0641cdb8fdf53bfc8194c9700e22d2fe0068142786be7a79cce92920d2a518dd851e2d7ee65addb2d79f7a8f8487d6df24627a5bf74428e060be27adca21250cb3382c2a4c8b4a78b699c1d752c90182264d6e2d74b1c837692590461ace1bec066787c45a43ce2069cdfde86ecc9ee39943e18ab0d265426a46656491088701ac039ad5d7c59bb3f0ab7589977cad7eee944fac90d10e847e99cdef22b1f29c139b8ca5ec5706c2ce14e064500eb340420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000aa633731df87d6c8b6b4222f5865e71c985ac0c44ae259c895ec618c635943d4a0a77eac235289cc9420c3ac073f3256 +01ea0000000000000082264d6e2d74b1c837692590461ace1bec066787c45a43ce2069cdfde86ecc9ee39943e18ab0d265426a46656491088701ac039ad5d7c59bb3f0ab7589977cad7eee944fac90d10e847e99cdef22b1f29c139b8ca5ec5706c2ce14e064500eb301ab89ed1e5cf7dfef077864e59ab0f668b9ddcee6037e1367c19f31dd6eb3073d123bee37ca14929dd8a237459ee9b80c032490ccd14acfd81e9d9e99237d6e45a6a0783b4ce9cc3ba2c5943a172cdd3cd0d7362a6a80e72db946d55e6242c39a40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a97602191430c1877604e206cf4b50ea2a2cf52b680b39c2cd178b9c6e3ad10e84a33998285e06d34b1623d776646d74 +01ea00000000000000ab89ed1e5cf7dfef077864e59ab0f668b9ddcee6037e1367c19f31dd6eb3073d123bee37ca14929dd8a237459ee9b80c032490ccd14acfd81e9d9e99237d6e45a6a0783b4ce9cc3ba2c5943a172cdd3cd0d7362a6a80e72db946d55e6242c39a01b36febad973e492620ec23bd0f0546442bc445c97e84ce0217db901b6adefe44b25e860f5c2176f34dac3ae24bf8d02d00895e619029269b46836b49d9f7d3fe647a939add202fed72803881ac065c4ea4bb9b24d7f4179cd93c0eae912a017b40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a32f671f0a469eb12e2daedc67df7cf12e9965e86a1857efdd1e7342514b34e41a3babb051980943aadbaebd945919e8 +01ea00000000000000b36febad973e492620ec23bd0f0546442bc445c97e84ce0217db901b6adefe44b25e860f5c2176f34dac3ae24bf8d02d00895e619029269b46836b49d9f7d3fe647a939add202fed72803881ac065c4ea4bb9b24d7f4179cd93c0eae912a017b01944f003e00ee144b44655a21ac808f42d75b4b78095b44e921ef034dcfb31c22ea4119a0c9ecc3178791bc4162427b0813bd445a36851ddf9c7e87c6571fad5fd76f0ee71ccbd00846e893cb29fbb852fe65b2deec192a98766e7deee91d9c0b40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b23bd21d9f07b211a973004129c982477795619d79fde42791c7f4a32688cbbcbba02c34910da303fd1e8345408568f7 +01ea00000000000000944f003e00ee144b44655a21ac808f42d75b4b78095b44e921ef034dcfb31c22ea4119a0c9ecc3178791bc4162427b0813bd445a36851ddf9c7e87c6571fad5fd76f0ee71ccbd00846e893cb29fbb852fe65b2deec192a98766e7deee91d9c0b0198efb3414d4dee951e1391a32f006333295f49383468d5e8c94c7d2ae4f09b54548e24f38672897a1285b56bd97fad5710c0dd9963b805abf6cf69b460c221ae7bb04a23bf79b000e1754e900e7bf2a32065024d009be5c2bc3c18af548e84fb40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000ab740d73bf27e7e5e732cf7f32c90a8ae3790c27adb60e8389a7e73f614868418ab0ed5dfd99d9c0e8ee8b44aadd5fa6 +01ea0000000000000098efb3414d4dee951e1391a32f006333295f49383468d5e8c94c7d2ae4f09b54548e24f38672897a1285b56bd97fad5710c0dd9963b805abf6cf69b460c221ae7bb04a23bf79b000e1754e900e7bf2a32065024d009be5c2bc3c18af548e84fb01b73b47dcecb50fa831335e8e5bebccce2755b6850fee3418db178cf63eebd1009827147aecd577687c0bd9ca3c3c100b0439e2ff1b26d2ba4d71a2606c3bc5b75b528342e77a19ae1bc43a9e4b6b28fd10aa0247959a8c327d8440948277019040420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000095f8e5606095b2b0f08e8b605fdf3370fe0b64724f9ee31f3ca752a2fbc042f805d1a1d7bdbb9f9b877511a5306be43d +01ea00000000000000b73b47dcecb50fa831335e8e5bebccce2755b6850fee3418db178cf63eebd1009827147aecd577687c0bd9ca3c3c100b0439e2ff1b26d2ba4d71a2606c3bc5b75b528342e77a19ae1bc43a9e4b6b28fd10aa0247959a8c327d8440948277019001958f3e0c8c6beb2a34f68ec6db7cd46fc4f7fd5376b5d17e9be5a6357a2618fc97c566f314f407fac76001e761b2123c04d9d1dbb1d66109296f1bb8e6dc86656cef4b0fc85fe3b47aaf087c7c706d9c2abe132701a0506531e385e34887286940420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b6eff0a0e7d88812201675bd5ebc97cbd4061af659ea217d061144e95e345a1116ef13b613f99816c96b2ba26eb556de +01ea00000000000000958f3e0c8c6beb2a34f68ec6db7cd46fc4f7fd5376b5d17e9be5a6357a2618fc97c566f314f407fac76001e761b2123c04d9d1dbb1d66109296f1bb8e6dc86656cef4b0fc85fe3b47aaf087c7c706d9c2abe132701a0506531e385e34887286901875b7101d6deed445642a16bf04fe9ec7b9b83f3be3e7fa72825bb92026819adca57099c9d11adcdb830ab881c8226ed00860532520ccb427edd366d0326076be4130ac4936fef9218e99ec1979acff4f0fdfd4ff6181b5945211ac8264da0eb40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000997981b2bc01a70ecded0cee725bd884ef1a73d6cef448761f73502c1db6e4071c12f29580fa1261344bccd59f23b9d9 +01ea00000000000000875b7101d6deed445642a16bf04fe9ec7b9b83f3be3e7fa72825bb92026819adca57099c9d11adcdb830ab881c8226ed00860532520ccb427edd366d0326076be4130ac4936fef9218e99ec1979acff4f0fdfd4ff6181b5945211ac8264da0eb01b7626d33f354c9a5743e31be0d40849b05cdab288bb2993737d444450a3196e8be9785690ec9703c5dad93b9a66593611221e837ef46a58f095fb4e765b3b48a356af904fcf287354c3c32a6514a1c289e647d604b16c374c0ac8c0d1c1fb19440420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000009746757a4fd249f82ae4b970703c341d850765212eba195c7f60d1a8c5e4fa1dd46fb3d94b1b713bbf91d1b3e9e9bec7 +01ea00000000000000b7626d33f354c9a5743e31be0d40849b05cdab288bb2993737d444450a3196e8be9785690ec9703c5dad93b9a66593611221e837ef46a58f095fb4e765b3b48a356af904fcf287354c3c32a6514a1c289e647d604b16c374c0ac8c0d1c1fb19401a66ca7c857f3297501bc0b65f24284f7921bac9c60bdece5914f02bb7166d16d440af14c25367be48629599c94cf98bd0d518133cc75942aae40d8d5391bad7164e37e966385419e7046186a913a60c37f093e4622fe21cd680d7b0d26d72d9940420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000008cc86e2277a2fd16b49fc9fc4ba4010a5484622960cb40be1bd0108ab3b9254d4881f8de330812ac41267368ea5c03fb +01ea00000000000000a66ca7c857f3297501bc0b65f24284f7921bac9c60bdece5914f02bb7166d16d440af14c25367be48629599c94cf98bd0d518133cc75942aae40d8d5391bad7164e37e966385419e7046186a913a60c37f093e4622fe21cd680d7b0d26d72d9901a09d5c38db453aa22044dd9d5e8d38c48b6d5d67382bcb5b92eec17e41167e405f15f9e9ac539d130c2136491243eb860ffabc0e8820c47343b24cd9c98af35f2db95a590510f452686a7b27c6ee7196f60c63bc6208d98b87483e68dbcdc5f640420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b71c907ad470fadc8525c6dff5886aa9d616c6d5fc1921c99134f5fd4e25d9843f18a38a99645dabc44eab5a377e917f +01ea00000000000000a09d5c38db453aa22044dd9d5e8d38c48b6d5d67382bcb5b92eec17e41167e405f15f9e9ac539d130c2136491243eb860ffabc0e8820c47343b24cd9c98af35f2db95a590510f452686a7b27c6ee7196f60c63bc6208d98b87483e68dbcdc5f60192e15dd8d5f2940969cf9bd74b67418d43693b23c243a9fbcd582040ccca8abc678f0dd93d304c6230d9f9931402998d0c33f57d638c8ac734d1bd395a09af091677d493c377091353812c7691d4b227c51c00758bb7e882dcd2f9d74dde998540420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000088f6fe93496a0a2c275033ddf28df7d3b597650dbafd267610e5d46eee1f7275c47432850cb6a26400bbaccd53b1defe +01ea0000000000000092e15dd8d5f2940969cf9bd74b67418d43693b23c243a9fbcd582040ccca8abc678f0dd93d304c6230d9f9931402998d0c33f57d638c8ac734d1bd395a09af091677d493c377091353812c7691d4b227c51c00758bb7e882dcd2f9d74dde99850182b25aa6f8e6ac4650136c92c0ac311aeeb8af274e985c9ec1dafa220a4a7c52eabc06affae35ef56e0825c912a212d717b0e32d09e905f50c0eabfc639b5aa794971a5ffac2a69e948af0af00f6cc58a2a84a9c078a223375904246b9bfba6040420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000008d2588168296fbbe1266f3a0ef9193f5a057f692df0373e85dda32133eafb3c14dc9a4fe7c1c42781692249b9b54bddf +01ea0000000000000082b25aa6f8e6ac4650136c92c0ac311aeeb8af274e985c9ec1dafa220a4a7c52eabc06affae35ef56e0825c912a212d717b0e32d09e905f50c0eabfc639b5aa794971a5ffac2a69e948af0af00f6cc58a2a84a9c078a223375904246b9bfba600195d75b02af80f8f91c610bccf5fbc4f8059f84203d9c6437f5388a7b7a84a70bea35a09529d7a851821aa83555c7d89a02ab451d15bdd94f8796049f1bf6b3f4a0088a0a17cb256b5702fa71876e6feb9863d3b16d5a5d46ba7cc6bb29fc7e0740420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000ad0a2c395a862d0950b783a46dbd07fc91a4b371cb5f9060a908a5c2bd3e025363ffaff1b4a7996b6a7824a41650bc41 +01ea0000000000000095d75b02af80f8f91c610bccf5fbc4f8059f84203d9c6437f5388a7b7a84a70bea35a09529d7a851821aa83555c7d89a02ab451d15bdd94f8796049f1bf6b3f4a0088a0a17cb256b5702fa71876e6feb9863d3b16d5a5d46ba7cc6bb29fc7e07019952c290953166ec8bbfd259826e910888d31bbdd7112c9ca5a3047d89516205bb75e586461504f5a6d6771a6e9145ea0d1c677b2495a93b25cbe28cccdd993d76a14225572483e966f5d48c4e4eb759570d6a2e3ca2a4a0e580ff394b6137d140420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b104a9977a2375e9fd840bccb0d5431f4067db486ebea602147afb98809b92175209c67e9037f8070d0efa48ea9fb34d +01ea000000000000009952c290953166ec8bbfd259826e910888d31bbdd7112c9ca5a3047d89516205bb75e586461504f5a6d6771a6e9145ea0d1c677b2495a93b25cbe28cccdd993d76a14225572483e966f5d48c4e4eb759570d6a2e3ca2a4a0e580ff394b6137d10199201c741f5c7471d4182ebb91cf11c09b47f13e7ef4a303a350c312cf8fab0e54cb6b559bf3968458095a019933f8d90d0373beddb177f1fff22e2cb0a1ff21075e720a696ea4cc84bffad8a5e7a4c1560683cfbadcdfe0551712a285254c3040420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000008e1ba89a68775b42775520640214b07c6822a8987c8dd758affa72070eaf06b51b4752edc25c0f49ead3a276505bb9dd +01ea0000000000000099201c741f5c7471d4182ebb91cf11c09b47f13e7ef4a303a350c312cf8fab0e54cb6b559bf3968458095a019933f8d90d0373beddb177f1fff22e2cb0a1ff21075e720a696ea4cc84bffad8a5e7a4c1560683cfbadcdfe0551712a285254c30018ec01bb93a9049ecef5f667a8b1660e0ec04bc321f0ebe45e4850ed9eadc37db6d8a82138f544108c480d548c299b4b61492608ae8826b3c79fdd162bb6abdc8c4ca456e025d31733a47439bde124fbfad3e73a132e93fde2d1775229079d0e440420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000082dfec3e57d17c9791b173ac1044ddeb92e7e77095abe1b31c8778e940f9b7fb6d9c6920c2654e6c86309f573877dd02 +01ea000000000000008ec01bb93a9049ecef5f667a8b1660e0ec04bc321f0ebe45e4850ed9eadc37db6d8a82138f544108c480d548c299b4b61492608ae8826b3c79fdd162bb6abdc8c4ca456e025d31733a47439bde124fbfad3e73a132e93fde2d1775229079d0e40184873639c79029c0ab2090a7647dec527426c166fb8befeaea5f3e7833dc97f313a8e9041dad191f350e9daccad80f551726f351f12412825fc5594bae866c074195d9959cfe18d945008867cccec58877509070f9f9a3df1460aa114668d70440420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a6491c6e5755ea100370970cb12ab30448c806b0afb94806fb1204c32ea54beda2fb8f608125e318a1a27e54a36d2027 +01ea0000000000000084873639c79029c0ab2090a7647dec527426c166fb8befeaea5f3e7833dc97f313a8e9041dad191f350e9daccad80f551726f351f12412825fc5594bae866c074195d9959cfe18d945008867cccec58877509070f9f9a3df1460aa114668d70401ad52a2d92356a7c43c6223b53f391c6bc41c556201c79ad9525db1205715649896a2e4d95b790b8e4eab7e9f6c28a7a0093683d486d81c33173b5c32b54114def41a2c01b39af7f28636d0de9978761ac06fd931a44fa519c4eebda4a744279340420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000085efd65283fe89628d5894befc71633b52a49e8c0206d204e7becb188c0a4b5f107e269e9c79ddb1302fc7187c389e67 +01ea00000000000000ad52a2d92356a7c43c6223b53f391c6bc41c556201c79ad9525db1205715649896a2e4d95b790b8e4eab7e9f6c28a7a0093683d486d81c33173b5c32b54114def41a2c01b39af7f28636d0de9978761ac06fd931a44fa519c4eebda4a744279301b0897a885184ac65da3a8144e0605dee5a0835ff801a3f9bed9c9ed433e9c1a63bd9fe0768f615caa7ee79f5e0c3ae79086d922343598a24604c08f1c0da93a6bb890b08ae3dd9fe44efcdfa36f7aae20580386d745085e8fdceedbc05b208ad40420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000085fb0325293be10b06962b531cba14360801c382d3592f3340d683b05b86acc940e8db81ad3afc52c428441daf517554 +01ea00000000000000b0897a885184ac65da3a8144e0605dee5a0835ff801a3f9bed9c9ed433e9c1a63bd9fe0768f615caa7ee79f5e0c3ae79086d922343598a24604c08f1c0da93a6bb890b08ae3dd9fe44efcdfa36f7aae20580386d745085e8fdceedbc05b208ad0180217bef9a4a4c9b52179f73b89c8f59bfcd6b96c0a5e50864fe43ddad3491dae4917577e94a705e12116ed1816132a20693efa1d49097329fd91682731e8244cb61e73bf557f5658a6be82af9e49535e95af7c4b099a6bb5c2ef6303e6b2d8b40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000968cb587babaf329a56d8bdcc70079fd1b97a901149156f9d74adebe213801209375c2b8790f06b8bb6969ccb02cc260 +01ea0000000000000080217bef9a4a4c9b52179f73b89c8f59bfcd6b96c0a5e50864fe43ddad3491dae4917577e94a705e12116ed1816132a20693efa1d49097329fd91682731e8244cb61e73bf557f5658a6be82af9e49535e95af7c4b099a6bb5c2ef6303e6b2d8b01842d95c2f84cf9739ee10e45888d23cb01e7537ed6c58919db85ae1fae3f22985434a2aa25b279b52f814f05f60f1bdf18b37c2fe00c2f36dd6afb9e9bcbe347c90b475ae48955144c89f109d4e291cd99b18daa59c2f41c753596e6cb2b252a40420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000008a90ba6fb68a443771e5f2ee34b3a82e32b9a1e42fce96b580d97dc134c1913e26bc7a8679623fa87fdc7db2e278399a +01ea00000000000000842d95c2f84cf9739ee10e45888d23cb01e7537ed6c58919db85ae1fae3f22985434a2aa25b279b52f814f05f60f1bdf18b37c2fe00c2f36dd6afb9e9bcbe347c90b475ae48955144c89f109d4e291cd99b18daa59c2f41c753596e6cb2b252a01b77b4ee2fd3554f5bc8f20982b4135f49a7364ecd281575d3e83c360f3a0586e565834a7620b30ec0077281f9e22385a0f4797b6cee99c4860f520e09c2ee34c4f31a2cd17aea9d7cfca96e1e9ab3d46bcc6fa76e56a26db96a06a83f728e71f40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b1f26e17e2619279ae01ed887dd9e62cb9cc22ceef6859dcaf61a20fb93f201460a24145fd8ad820683bc8dd00278e63 +01ea00000000000000b77b4ee2fd3554f5bc8f20982b4135f49a7364ecd281575d3e83c360f3a0586e565834a7620b30ec0077281f9e22385a0f4797b6cee99c4860f520e09c2ee34c4f31a2cd17aea9d7cfca96e1e9ab3d46bcc6fa76e56a26db96a06a83f728e71f0181831c806b72e16f4c86c1f3c3d21848bff839558a08f64de5ced8b34eb13b2812ae003b9dbf2eabe0e974a209eeb39e0322da840489781a357803b4cf16288adfb060d16bed494c9474d3343aa182a2e51a75d9c8a219171a333e21d02263bb40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b98c96a2dfa1f412d71e81beb76eed94cec11caf80ea1381dbc7337fbb8c9c5ebdd25b99474fed90aab0cf2a654f9217 +01ea0000000000000081831c806b72e16f4c86c1f3c3d21848bff839558a08f64de5ced8b34eb13b2812ae003b9dbf2eabe0e974a209eeb39e0322da840489781a357803b4cf16288adfb060d16bed494c9474d3343aa182a2e51a75d9c8a219171a333e21d02263bb01b466bfc5caf876637b3d514dda309cc00877ae33f13c70b23552b3ff0a74539cd3c820e2ccdb2bd7b3c5ad7218e4263618cfe690a73ab6fb1a84c9d0505972938cd5509565235cdad30741a614ee9d6dfcfcd46de4022ca52373443b11c6c6a340420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a062b51b3d7058ced0923a53569df71697ab552d1c94d0c067b3d8ecaade0ca51a623790579c7cb529499e04ea69eb02 +01ea00000000000000b466bfc5caf876637b3d514dda309cc00877ae33f13c70b23552b3ff0a74539cd3c820e2ccdb2bd7b3c5ad7218e4263618cfe690a73ab6fb1a84c9d0505972938cd5509565235cdad30741a614ee9d6dfcfcd46de4022ca52373443b11c6c6a301ad8533139e5d66dad1b63e11b548aeb579eb3f485601fc70888287e3105c066d0428627fd44478e89e01defb2abe527b114164d69d2ffcef1563aa61d5e49b1eac3c14fb287aa139a7ce70c748f62815395b2c1d2eb04f384645ff5d839fe57440420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000aa2bdb0332b9c2849405f2d9e0c9f58c04a1e4312e960a921a446809e4733c658498c0fa856ea369be69d66c58984fdf +01ea00000000000000ad8533139e5d66dad1b63e11b548aeb579eb3f485601fc70888287e3105c066d0428627fd44478e89e01defb2abe527b114164d69d2ffcef1563aa61d5e49b1eac3c14fb287aa139a7ce70c748f62815395b2c1d2eb04f384645ff5d839fe57401a8aa762439cfb3ad54177030e59db0874cbc484029fd3c48ea9d36debce777e3bd834c46288b8282843ef40cf7d85ddb0e4d286642cea988804c87e1016a68dd45cde02645e6e98fc0eeadcd68cb156b9d402f5a7fe96041ed0272511091660640420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000938ae76d116c896956a420c730f86b4cb080daeb4e69336603968aaef2dd34632214c39d143b18f10a361eafaf6a2b1f +01ea00000000000000a8aa762439cfb3ad54177030e59db0874cbc484029fd3c48ea9d36debce777e3bd834c46288b8282843ef40cf7d85ddb0e4d286642cea988804c87e1016a68dd45cde02645e6e98fc0eeadcd68cb156b9d402f5a7fe96041ed0272511091660601aa6e391c91601ce3dad444d526def3b1ff1e922d34f0d05c3091ee2af235cc8fc6c031256c0486e32c75f86f45d0f79311e60250b8e905abbfef85394288a6696a4bd38ec75ae9bc98c9ef922f3adb35b580221ad2f538a0d3a705f0f6a1c0c940420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000099b2ed6ca317e7a7586affe24038bcf00730b712abca6563d5521f9af7cd72c0b03adcfa4c23231d6937da5a199090e7 +01ea00000000000000aa6e391c91601ce3dad444d526def3b1ff1e922d34f0d05c3091ee2af235cc8fc6c031256c0486e32c75f86f45d0f79311e60250b8e905abbfef85394288a6696a4bd38ec75ae9bc98c9ef922f3adb35b580221ad2f538a0d3a705f0f6a1c0c901b5b4fa146ce040a4515b233e6d2f433fb07de0792a02d525eaf1bebad079e8215d0ed09214b7bff11971045b9ab32cd4143a4b7c53539bba0a7996de8f4865d4a5df2e63be5fd6258dfa3ce45871178efeec1cad6199a8e015a937b18385d63b40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000959ee01e25d3c49fcb12e489d02ab3b7bf50703dd7aad6c6e75ff03ef08f8c1e0b3534ef10d326e462f81c1f83556edc +01ea00000000000000b5b4fa146ce040a4515b233e6d2f433fb07de0792a02d525eaf1bebad079e8215d0ed09214b7bff11971045b9ab32cd4143a4b7c53539bba0a7996de8f4865d4a5df2e63be5fd6258dfa3ce45871178efeec1cad6199a8e015a937b18385d63b0196e56fcf360d56b5de10fa6aa6e8008f32aa9b210c85d135723061e21c1ade5317cde980b672cea87733df0e81f704e618e09c8ae089813b68b1855b7e82e249d318d7f959a1ba362738e3de2f47de1ceac99919168ad9ed801d0f5597bd8f5340420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000984de97180539cce8d661359e93db983cdaa320a15288bd686e53ee904aea1be8305ee0d008bbf52b0d13582ec856ef4 +01ea0000000000000096e56fcf360d56b5de10fa6aa6e8008f32aa9b210c85d135723061e21c1ade5317cde980b672cea87733df0e81f704e618e09c8ae089813b68b1855b7e82e249d318d7f959a1ba362738e3de2f47de1ceac99919168ad9ed801d0f5597bd8f53018eb8f62adbf34801040bd866ae138e3e727a64b901b1195d521c6588880efadb84d131527a9a5efe9a37edffc3f6d98c160b00c7541fe26c27dac4ae4339dae34868ab657d1df65e385deba78aa548a5d4eb165fbdfc0068ab02ed18ba05869940420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000afd2c6f84b15d21f5af365d22ca7c5aa7ceab47a66769a1745fc460c33b20be2423e4eea83ee973b94b85e92041d05cf +01ea000000000000008eb8f62adbf34801040bd866ae138e3e727a64b901b1195d521c6588880efadb84d131527a9a5efe9a37edffc3f6d98c160b00c7541fe26c27dac4ae4339dae34868ab657d1df65e385deba78aa548a5d4eb165fbdfc0068ab02ed18ba05869901b9174a0fa4cfac258271eb5a17ad68801288a69bb26aa75ff62c90b8fbbe2ee7829ee1f41e3863e3cff77e65ae718c03001619e80569ea2ad54e4e424ea387c5ab265b3d2dfa55835c5c21e7c2abcef91e1ecfd325ae36135721aeb0ad9fb59640420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000820438cdda220cb3d5f52edf8fb919e89fda6e0cd9036b56172dc468fbae1799bd1786f47fd29146a7de3e45129e14b2 +01ea00000000000000b9174a0fa4cfac258271eb5a17ad68801288a69bb26aa75ff62c90b8fbbe2ee7829ee1f41e3863e3cff77e65ae718c03001619e80569ea2ad54e4e424ea387c5ab265b3d2dfa55835c5c21e7c2abcef91e1ecfd325ae36135721aeb0ad9fb5960192082a1f4fdc82eec7070cdc0311e65e5346ca8fe4012ecc935373483ef2c70dda8c2d3803261c095b6085f9bc01c4a30b663c586dfba34c5afc169ce950ec64b462199310b4c9b9b0c383377b5e868afbe5b2a977d5cfe0a706bc0674ec69cc40420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000080c186d63ca40a37e1bdd82d08db89118ddcc54939ba4b601636511b93e13d2d5e164ccb2e832815162e1572af33b340 +01ea0000000000000092082a1f4fdc82eec7070cdc0311e65e5346ca8fe4012ecc935373483ef2c70dda8c2d3803261c095b6085f9bc01c4a30b663c586dfba34c5afc169ce950ec64b462199310b4c9b9b0c383377b5e868afbe5b2a977d5cfe0a706bc0674ec69cc0189c05652e960b4a7525f336a6e1efe40764dfe9a9eb89392bd814c84eca361a9eba3e22c09c721ca306e4ebaffecdd0c0c63846e46440d0161d20b43a75c18e90ba93f4ca0a4485d51ae29cc0a2ea8e660ddbcd1307524b4a286326316e66fd040420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000ad8b19212c53465ab3779afeda9c1f1205b7b5be2edf7d6b6351a2d9cfd7b3fd52571fb115a1be666eb30bc085700c03 +01ea0000000000000089c05652e960b4a7525f336a6e1efe40764dfe9a9eb89392bd814c84eca361a9eba3e22c09c721ca306e4ebaffecdd0c0c63846e46440d0161d20b43a75c18e90ba93f4ca0a4485d51ae29cc0a2ea8e660ddbcd1307524b4a286326316e66fd00192cf863e45a1874de11c3703031d47f3b651a5d0c8d1030338eb65c03843984f0545c13df630cb3235cdb40856aadf5c0a0068a33d06e18147d93ce012a85791f358d410c726131e85bff2cbc393ba5bd563d43a60d796259e528e2a01aedaad40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a75e84281d847cf9e07128b4bfb94bf04541a3fda70b66c6fd77fb958ae2e0c0d6bd80c2b1ed488090ce821d44aac7a7 +01ea0000000000000092cf863e45a1874de11c3703031d47f3b651a5d0c8d1030338eb65c03843984f0545c13df630cb3235cdb40856aadf5c0a0068a33d06e18147d93ce012a85791f358d410c726131e85bff2cbc393ba5bd563d43a60d796259e528e2a01aedaad01b788905c2f2de91dcbb558ad5126749c962676f7734fb84b1d0bd4d1d24138513fd7a0ea52215595f54eb03a53276f4609a31140b756959a3ec693bb70199edc976debb75dd6fabd6881297112dbe13bd7fc227f9191c9a9daa309d7fe97186f40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a28a732fbb4df5f196079bf0deb6842947f84ac655de8aa821429836ae28a363dcdaa00f0ab3f394ecaf4570f9db18ab +01ea00000000000000b788905c2f2de91dcbb558ad5126749c962676f7734fb84b1d0bd4d1d24138513fd7a0ea52215595f54eb03a53276f4609a31140b756959a3ec693bb70199edc976debb75dd6fabd6881297112dbe13bd7fc227f9191c9a9daa309d7fe97186f01b37108dc3a0312cb00de57234b987d35f145d4d1ac60dda25a39e1f08d5cf88330be28f07204d9e4a1a79b5a6b59461800657df37e09b0b0882ca7cbd160ada36c1750954f292c1cf218b11416c99c70d6b85ae284fae829cffe9bdecda4560140420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b0d65b5843207f2f62d6535f5e56a2baf107f2ea5a8ad255b83447c7c5a70bf0f8dfc04db4c687b80ef9ceb30e3abed3 +01ea00000000000000b37108dc3a0312cb00de57234b987d35f145d4d1ac60dda25a39e1f08d5cf88330be28f07204d9e4a1a79b5a6b59461800657df37e09b0b0882ca7cbd160ada36c1750954f292c1cf218b11416c99c70d6b85ae284fae829cffe9bdecda4560101b380ee5be0c99e515b6dc0ab74e8ce7ddb1d8c860291f2d868fcb7efdd79bf66038a749860868c80d32b33502808cf7b0f78852892ed5c5f9fa46466e8b76ad52e135d0fe80b377bb4bd1c0d1be33a4669562db4d62a353348aefa6e51b9091040420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b38a1f85bfea2237740ad43154ff66df5402e4b244c30eb6d338c559a0befbba4cb57f7f0d04631389d98c0632e45125 +01ea00000000000000b380ee5be0c99e515b6dc0ab74e8ce7ddb1d8c860291f2d868fcb7efdd79bf66038a749860868c80d32b33502808cf7b0f78852892ed5c5f9fa46466e8b76ad52e135d0fe80b377bb4bd1c0d1be33a4669562db4d62a353348aefa6e51b90910018ea0c4f75df9f4663ee422e91064c08d3a636833c83aa1bf7d7830641079c2bb66723b135f21447b4d9d8667aa18669a17c01a087075a41b507bbca1de4788cd76395e043658401be699cb9fddaf8dfb93797fbba2cc63eb6320fbe438c9972540420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000961b1e0c5d5b1134bca65e8c6f6a41f84b3020bdd33620e922e7cf6680d08a8c943d774e651aabd194fae855d334ec33 +01ea000000000000008ea0c4f75df9f4663ee422e91064c08d3a636833c83aa1bf7d7830641079c2bb66723b135f21447b4d9d8667aa18669a17c01a087075a41b507bbca1de4788cd76395e043658401be699cb9fddaf8dfb93797fbba2cc63eb6320fbe438c9972501810071e338fed3022b5aad0eb4daadc215ffd63375cc6f6d7954df38bec79f0ed47ea9f655b5a284cd55cfa213fd93fb0e45b12e53e8298e4c6fedb52cb705cb5a8e019f9bb842581836fbcfe3996834c220f096fd5a916ca6e16b9bf3e1172840420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000098a2b4f65b2b07b000a983799b38e995b338707ab6ca220926f39c63adf428a58367ab9c9c0c5fb4c4bfef1b9607aefe +01ea00000000000000810071e338fed3022b5aad0eb4daadc215ffd63375cc6f6d7954df38bec79f0ed47ea9f655b5a284cd55cfa213fd93fb0e45b12e53e8298e4c6fedb52cb705cb5a8e019f9bb842581836fbcfe3996834c220f096fd5a916ca6e16b9bf3e11728018edb61724d977ddaf3fb94872ca1b7e12ecd7e891d96549cff9f133704df96251b71062614ee3f4a7c81c418e66363530e08fc029a2fd1b465ebdc54ab567eb0615c6e7c79c4dea44aefe1b95bf94e31d265ba2cdf20bdbcae2f5960b094839740420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000af73936b84d13a9eafb0f12a7014582aa0159b36b312b4ee0b99e699c68626d161177b00b26c80dd613c21bdbe957a0c +01ea000000000000008edb61724d977ddaf3fb94872ca1b7e12ecd7e891d96549cff9f133704df96251b71062614ee3f4a7c81c418e66363530e08fc029a2fd1b465ebdc54ab567eb0615c6e7c79c4dea44aefe1b95bf94e31d265ba2cdf20bdbcae2f5960b09483970187f54651e27d510193f50ebf6f033ce249081c9fd18fc1a05a4f8d1a1654d8c31f2e16bbfd94f89f035e79d387a86a6518801714942af5d5eb1ac130cdca53f115c899b1ab8aca2cf48aa10ce0d437677ac9be86d0cbc5be926c23e9b14eb5cf40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000953883fdbd1a80c584784478a5978fb1061cbbf26f31a35339b388b273db19414dfccc8a9d2fe0ed2c70f9d5c4d39802 +01ea0000000000000087f54651e27d510193f50ebf6f033ce249081c9fd18fc1a05a4f8d1a1654d8c31f2e16bbfd94f89f035e79d387a86a6518801714942af5d5eb1ac130cdca53f115c899b1ab8aca2cf48aa10ce0d437677ac9be86d0cbc5be926c23e9b14eb5cf01aa14153c5ef154e67ae390d79718d245fd9ae631ba3e2a7dd57c3e62e7f8cda529d678212101f18ac06685e04390565a099336f7fae35a8f128c1661f1bdb9ee4ca4b691d1946e963a14d1d95e013d8583a13540cc0123ef16454a1af7fd2c2340420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000854c3c0c27e1c0aa17cfac842bcc41f5a43ddc49abf53bedddeebb3a150d15f96df4892a2f772a283db12f1f6f67773f +01ea00000000000000aa14153c5ef154e67ae390d79718d245fd9ae631ba3e2a7dd57c3e62e7f8cda529d678212101f18ac06685e04390565a099336f7fae35a8f128c1661f1bdb9ee4ca4b691d1946e963a14d1d95e013d8583a13540cc0123ef16454a1af7fd2c2301b5060ee6040cf10d90a7fc7583186fc50306825ac4606ef665e58a9caba05d5622b88f9d606f2afbbbe44dcf1b5822360a063a25daffb180173efa5775e9beee155bb925c0b488f1bec30c8565c026ce61c12f4f8d00b7d9d1712978af47745540420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b944fbc2cd2cea741b6043ce5dc1327272e4a587a2edec693fdefef5b2cf264dedc223fc0517a7f55269b1f0a9b025b6 +01ea00000000000000b5060ee6040cf10d90a7fc7583186fc50306825ac4606ef665e58a9caba05d5622b88f9d606f2afbbbe44dcf1b5822360a063a25daffb180173efa5775e9beee155bb925c0b488f1bec30c8565c026ce61c12f4f8d00b7d9d1712978af47745501a5d9545148567e0b2b2e86498eaa9a97021203005cc3e2faa17c87331781423f20034fd521ae99de2dee13f02a6f988a16981558d4e36839eb396256f5077de223bba679a493350057f24fa39156c0f6e69b78ea03278017e8185a9cfd8224c540420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b22b2d331422a4ac95c7df82c137228d5fead78c2798dfb49ab13c268f77c9128b85e0513b8d9ae517b38c61168b3429 +01ea00000000000000a5d9545148567e0b2b2e86498eaa9a97021203005cc3e2faa17c87331781423f20034fd521ae99de2dee13f02a6f988a16981558d4e36839eb396256f5077de223bba679a493350057f24fa39156c0f6e69b78ea03278017e8185a9cfd8224c501ab297159791a4221baf2a866327173c95afb8f7e137194ff60e1b00403e2187970703d089a85476f968de7e1c8a59dc90e299d86e342ab4b6e9fb41463608ea03858141ccdc536e9a14cb11754449f7d120205ac3a5175d280a4d16fa4292b3140420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b91a089a2cc38a6cac53ccc90fe06a58afb721a5fb0ffdcc4bf4ad3f83fecf2e4fae3ad28ddabe46298a38c76c685d88 +01ea00000000000000ab297159791a4221baf2a866327173c95afb8f7e137194ff60e1b00403e2187970703d089a85476f968de7e1c8a59dc90e299d86e342ab4b6e9fb41463608ea03858141ccdc536e9a14cb11754449f7d120205ac3a5175d280a4d16fa4292b3101912083dc1244e717312e2e8409066e0b45105066427fe8d71a8668c7b2f54543e0534f70518a86181b14e05aa3bb294c1320b80306b8acfca91c0f13ec371f12129aef9b631f3a063f65784d8e72764db85977a2bad47d1f51780f972cef023840420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000092016733e4195d2d86532424ada95ad7dd2fc1b22abcbe4e64a332dacfea96c17aff42b93d4e11a0b303c611a805c094 +01ea00000000000000912083dc1244e717312e2e8409066e0b45105066427fe8d71a8668c7b2f54543e0534f70518a86181b14e05aa3bb294c1320b80306b8acfca91c0f13ec371f12129aef9b631f3a063f65784d8e72764db85977a2bad47d1f51780f972cef023801af5d3782b73bd568841499ebf3fb651b147ae6fe13cc17cb0e6055e909685981c9b3b11adc063c8b817a472d238293c010d8b8d9b8bb3b4222d2ba3d0b8bc14b0a33aefad31671844d506fd21c931b3809c40105c374870e2a631d974f7c3f8440420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000008b74c79bccba1525f1bde12dee78f542f5ad20d93d38dc0e362bc2fd6ec8a9587b60445e21d3a41f053f3ef746301fed +01ea00000000000000af5d3782b73bd568841499ebf3fb651b147ae6fe13cc17cb0e6055e909685981c9b3b11adc063c8b817a472d238293c010d8b8d9b8bb3b4222d2ba3d0b8bc14b0a33aefad31671844d506fd21c931b3809c40105c374870e2a631d974f7c3f8401b1275a92da6f74da25e08404ac325019af00be8abf5a7e6b8cee9543de3e208b2bbec9dc5d7bad7f6a280660bea799a50396367636b8c17a1b7c3ee4727e68446a45211f4f9899e0f297a5698f98e935d08379567a26b956825962ed04ba97eb40420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000008ac54428f0d8e612e642fd773390cd2b822635007432b6fd4bea75dd44f23023b97fe0aeef70193d6c005f59a9e56716 +01ea00000000000000b1275a92da6f74da25e08404ac325019af00be8abf5a7e6b8cee9543de3e208b2bbec9dc5d7bad7f6a280660bea799a50396367636b8c17a1b7c3ee4727e68446a45211f4f9899e0f297a5698f98e935d08379567a26b956825962ed04ba97eb01a191e04fdd20d0bee8efe762cf13603b78c6e61b90f6c976c4be25348c53ed7cad8b05cc137ab029e605d8ad63e8bb7f001bd9c0a1cab509d0e7780d0493ce68efd02007fab9c1d164b765fc8598b4a1ba1bae694689e5753cbc7c29ae47de5040420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000081d8596c7685abc3850b2e2ac2c219c61a1f1964b5be0a0fe63d8e0ea9558bb08a2f0cd060719628f26433d2e9a1f968 +01ea00000000000000a191e04fdd20d0bee8efe762cf13603b78c6e61b90f6c976c4be25348c53ed7cad8b05cc137ab029e605d8ad63e8bb7f001bd9c0a1cab509d0e7780d0493ce68efd02007fab9c1d164b765fc8598b4a1ba1bae694689e5753cbc7c29ae47de5001b065f8cdc26fad7d6737b11e7b0289a9cce3d13d7b772f6eff441f83744984932b9db6994795f45b36ddaddcf1115ed20f8ddde5c7f258644db26c64b43ab9e4fcd7def04be4357aa037d7754f81862c190c31e0af508dac1187fbe65059fe9a40420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000008f3312820d500ff99b2e1913c099de74a3f3de2381e9285d8614d38348adfab017c4e4e3f5f34e9e7b7c101c76bcd644 +01ea00000000000000b065f8cdc26fad7d6737b11e7b0289a9cce3d13d7b772f6eff441f83744984932b9db6994795f45b36ddaddcf1115ed20f8ddde5c7f258644db26c64b43ab9e4fcd7def04be4357aa037d7754f81862c190c31e0af508dac1187fbe65059fe9a01a4a36d2ebbba0b101b685651dddea0d3bc897b949ddba4ddf6b717939280de64f9b03c3367170d70e2f82cf18882d1b9112166231cae11ef914df20d74197e5f15cc82ee7f100e743e4dba3281f17b32773fd850dde3363f8b268b6583dc6cc840420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000908900fa934af1f8782fc94121410d72013193be3e6b9a19851e6b9fdeb781bc8ae0aab59e224004007d57a57fe300f4 +01ea00000000000000a4a36d2ebbba0b101b685651dddea0d3bc897b949ddba4ddf6b717939280de64f9b03c3367170d70e2f82cf18882d1b9112166231cae11ef914df20d74197e5f15cc82ee7f100e743e4dba3281f17b32773fd850dde3363f8b268b6583dc6cc8019571a5f4a8192b2944a0e1ac9195b2f3f81182fda0c18ff5c7ece42c83f0f83005fc32317021ea36aece781a83240af10d1c53c0d4eeeb3a1af932841c823b6685705f6ad0c6e8f259f09a8845a4a08aaaab9cfc2c54e855204c3a83d8bf8ab140420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000096d12f9731b6b3a6ff5b6c70a8a6cacf347ab2608c62056be594987efeaea56cf9f56343bc8834953dd179066691cac7 +01ea000000000000009571a5f4a8192b2944a0e1ac9195b2f3f81182fda0c18ff5c7ece42c83f0f83005fc32317021ea36aece781a83240af10d1c53c0d4eeeb3a1af932841c823b6685705f6ad0c6e8f259f09a8845a4a08aaaab9cfc2c54e855204c3a83d8bf8ab1018a2a776459786bd1a382fa9da3b4f3f4fedf9d268b9c069d320f51b2246d1d87596edbf7e910fc4f6610350ab4b6e9dc0d4fcfb475d78de1677466367687f5a21c11ec6757b1e2811317f327b991eaddefac2ebb05582197cf4df13e9f7bede240420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000903588de6a850c0ae34b351cefcdea5f45ebb0a751a0674cf20c7d335059d160c7af048265608b8de1c5b68276eca8b7 +01ea000000000000008a2a776459786bd1a382fa9da3b4f3f4fedf9d268b9c069d320f51b2246d1d87596edbf7e910fc4f6610350ab4b6e9dc0d4fcfb475d78de1677466367687f5a21c11ec6757b1e2811317f327b991eaddefac2ebb05582197cf4df13e9f7bede2018248093de6a5e86314d66ec0f1cf09822d1b975163fead31e92745e24a1c48192a0cbe2ed7e71793aef1c43a340c0fcb0082f6abf7d0799422adc3c6fa7718c0f84b580a26d9488f35d417b43a62cc79b50d92b275d0413c7a1c00ed90ca379540420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000ac082e8601a41175e003d9fe59c5060f7217dd93221b3ab0c19004bc4830603c990edd9d2cc8b70e06ade54aa933ceef +01ea000000000000008248093de6a5e86314d66ec0f1cf09822d1b975163fead31e92745e24a1c48192a0cbe2ed7e71793aef1c43a340c0fcb0082f6abf7d0799422adc3c6fa7718c0f84b580a26d9488f35d417b43a62cc79b50d92b275d0413c7a1c00ed90ca379501b4e8b32b323a77ca7750b37128afd1fe3e4d184c5bedfd6619dfc1f64afbbd864d324423fd005a6dfb93d0e9dd7031540212f96092ada6f1c8f73b69290eb813791fe73efc5009185d5694182e85588b674c5aeed021f9921a6acb323fe6cd7a40420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000008b572d9102f560a03213078095550ec04a719e39057edce7afb7fabfef79a7d61874afc740664be0e2d587336d7176e1 +01ea00000000000000b4e8b32b323a77ca7750b37128afd1fe3e4d184c5bedfd6619dfc1f64afbbd864d324423fd005a6dfb93d0e9dd7031540212f96092ada6f1c8f73b69290eb813791fe73efc5009185d5694182e85588b674c5aeed021f9921a6acb323fe6cd7a01aa33e2c3123e426f8007fb05cdcee119237dc1eec67e1a53b8e120bf315669673d449ca957c12f98f71a053dccd221a80287945992ec3933e3d5807768f9391cd9fbeda7b133e14453079b85f62298cc6fe92e075db9e90ba8ab07e071f45f1940420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a28eca7c7a884596a0ca0a80e9653d75c269e3f3e0c03d3521d429efb53da6d534b51b56027cfafce595c2f15da9e811 +01ea00000000000000aa33e2c3123e426f8007fb05cdcee119237dc1eec67e1a53b8e120bf315669673d449ca957c12f98f71a053dccd221a80287945992ec3933e3d5807768f9391cd9fbeda7b133e14453079b85f62298cc6fe92e075db9e90ba8ab07e071f45f1901927dc8acdab4a63bbf57514406cba2cfe4b2edc2d14b1fc225017732cc46c9b961877c0fdcc08e97c5b743322c24bd030a98dd7e6ba3c6cead352954d51f9dee62c38f682a872372dab67611b7b481e612f8dae4163206b123fc7e6369e8219640420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b90605e79c1855483da9dd34d23ec69c88b532dc26ccf74e4ec26e6b7c3c4ed6f71501949f98aa024cdf352136838678 +01ea00000000000000927dc8acdab4a63bbf57514406cba2cfe4b2edc2d14b1fc225017732cc46c9b961877c0fdcc08e97c5b743322c24bd030a98dd7e6ba3c6cead352954d51f9dee62c38f682a872372dab67611b7b481e612f8dae4163206b123fc7e6369e82196018b13dec1e65b5cd9d2e62cd0da6ab5641986a8c6116babc3f53d311f8b235928becf0b90af6613bd52f2970511dec81c14c0bc1f8bdfed0399fa4d844d271abded77a05ae57c067b9ee5223053f5bddd5a7f627b3d8a12c5234f1cf4c75d116a40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b4930fa2ca076c04257ac041d070bb3c05a6513a2f323cf74aeae5f898a5ded34cca4abc07c76c51c8d204e4053be39e +01ea000000000000008b13dec1e65b5cd9d2e62cd0da6ab5641986a8c6116babc3f53d311f8b235928becf0b90af6613bd52f2970511dec81c14c0bc1f8bdfed0399fa4d844d271abded77a05ae57c067b9ee5223053f5bddd5a7f627b3d8a12c5234f1cf4c75d116a01b3ec482a649fee0eb4af237daded7caf2c782076cd78212bbb832ece6cb29e68b9ffc8741b7ad4dfb32acae3df4c91a90f453134e01ec3524ece4548119362b81a5313bbe0f3ee2938517bf048b53ce62ab413321791d2b1a8ec68e90847697f40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b90100134382cfbb3ad39c5a1beb199912e91b7de85b5dd1ad9b1dcaeedb6d866ee54f6f1176b0c321389e0bc11396e3 +01ea00000000000000b3ec482a649fee0eb4af237daded7caf2c782076cd78212bbb832ece6cb29e68b9ffc8741b7ad4dfb32acae3df4c91a90f453134e01ec3524ece4548119362b81a5313bbe0f3ee2938517bf048b53ce62ab413321791d2b1a8ec68e90847697f01a6d070edf3915837e8c062e1ad2156ad1d194faddcf0bcfc4d257f5b97b26d9f87f90526ccfc3e788db18496e0726eaf0707082f2d3321b7350c18f458c649be00c7393596c9cd6849bcbcfa2280d461c63840c86bc4554024ef9677f312804640420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000915dac80c0787495559e2efcb9248b7dc481e534cf93a816976036d2a662efe9515d12bc7df04adce722c8415b39adfc +01ea00000000000000a6d070edf3915837e8c062e1ad2156ad1d194faddcf0bcfc4d257f5b97b26d9f87f90526ccfc3e788db18496e0726eaf0707082f2d3321b7350c18f458c649be00c7393596c9cd6849bcbcfa2280d461c63840c86bc4554024ef9677f312804601b5737fd0219f73f3bb07849b98c8e4f98943baf42cd80a352efcb00125d8eb3c72a5f5f7ac96ff13bdfb3c8ca858aec7076c8a45f61b7dd2b2645eb9e4683d5058cc8238658fab4f06cc6fa60154acd9a08fe321a172d2ed89b8f47279cf21ac40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a4f04f79ba5c90ca75793b43f9e67bbf4a4e40c4d8104862568a16df5ac515f6b7296c7b71dc153ed69c036d461247a2 +01ea00000000000000b5737fd0219f73f3bb07849b98c8e4f98943baf42cd80a352efcb00125d8eb3c72a5f5f7ac96ff13bdfb3c8ca858aec7076c8a45f61b7dd2b2645eb9e4683d5058cc8238658fab4f06cc6fa60154acd9a08fe321a172d2ed89b8f47279cf21ac018e00153c3ff0d4acbeeec3b9e5087d77fcb271778f3d1abfe9845f26ec53c7d55228aabe0d33f9de0734b2efb53b5af118a3b99acd4e5b573e9acad1480e5105fe4a66e1760907af3c3a4875f91e63538e8e36413e07fc6e3e42a639b106a6d640420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000008bd762637d5604659cf395c833dbd3ba4f00b1e591134b17603152babff04127d2ab1336125f4e2e86a5fde8dbf20b19 +01ea000000000000008e00153c3ff0d4acbeeec3b9e5087d77fcb271778f3d1abfe9845f26ec53c7d55228aabe0d33f9de0734b2efb53b5af118a3b99acd4e5b573e9acad1480e5105fe4a66e1760907af3c3a4875f91e63538e8e36413e07fc6e3e42a639b106a6d6018cc68fa0339be31f9a0b0bebeddbe0362a0402d38a11637c07902412fe99df492b8f243b5ae6cb12434b24c05a96ee0005a75854e3a7ea1310464553f867566564587e6d82a910929a43779901894c97e4c32b8e52cd804723afa051a2ac4c1040420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a7dea808f4340dd0f5b8aa2dc1a516271ef80885460c957b89f999a40f743146dca533040ea8dc1d6c9d70edc0926b12 +01ea000000000000008cc68fa0339be31f9a0b0bebeddbe0362a0402d38a11637c07902412fe99df492b8f243b5ae6cb12434b24c05a96ee0005a75854e3a7ea1310464553f867566564587e6d82a910929a43779901894c97e4c32b8e52cd804723afa051a2ac4c1001963af41e23aa1ac5db53b304b48cfefc08711ec031d7192447a3f6ae66c382775c3b23bf6aae50b9383febbd8a4ca68a0a99b39199f6c5a4e3ac1bba87bac0324866e26f25584f6c3e88c69e8f3a6e1abd27ffa8d0db39bbae16acd1071794ea40420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000092ab133be7ed6356959bc3242853b365e217abbb32dacc7f51b7bfe6de27eabbe544e271a53bb50a93dc3c391a6bdba3 +01ea00000000000000963af41e23aa1ac5db53b304b48cfefc08711ec031d7192447a3f6ae66c382775c3b23bf6aae50b9383febbd8a4ca68a0a99b39199f6c5a4e3ac1bba87bac0324866e26f25584f6c3e88c69e8f3a6e1abd27ffa8d0db39bbae16acd1071794ea01b7a48e6b8ab8d373570ac7c49bf87f50e8ed96b8f85bfbf3cc876ebd109cec31ca38a5d5b7af312471b74f364565ab0317d33366bac87779a0a580147bd3cf4e655ab115a9ef34708b1dad09dfb3440a359789faffbcd6616a3dad4bd778c2c240420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a1f85b25232901f7576e5da6cc71ccb6cfa1f0971b31b1fdbab0e2e2ae89cc3ec74666375a6ee8a44e6844e6bfb4fe4c +01ea00000000000000b7a48e6b8ab8d373570ac7c49bf87f50e8ed96b8f85bfbf3cc876ebd109cec31ca38a5d5b7af312471b74f364565ab0317d33366bac87779a0a580147bd3cf4e655ab115a9ef34708b1dad09dfb3440a359789faffbcd6616a3dad4bd778c2c201b6254baa4a16e32e9ebfc5170aca4b30fe320017ed4a59470973ba89b00379e8c004557259ca2e159b4189d610fe7f9409adc8a4339a315d513c18c53734ea2f27e7b70f4ca1abfe97291f0a3ac7042b220dd3ffb2a2ed78fa19ab7b5f83804540420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b17a9a212de050ccc4b07e777be0ca882c8a2bab356ab4b39dc63158256fd695216367066180f1eefc4f71883d2aa7e6 +01ea00000000000000b6254baa4a16e32e9ebfc5170aca4b30fe320017ed4a59470973ba89b00379e8c004557259ca2e159b4189d610fe7f9409adc8a4339a315d513c18c53734ea2f27e7b70f4ca1abfe97291f0a3ac7042b220dd3ffb2a2ed78fa19ab7b5f83804501803684a80adff86234ae0b5427d228bf46a0eabac52293644343fac42ec82a3f3794c7c59b9043f2625694d3ad3a39df180cc4a79c30a7d81a48ed440db5e577e0f9c0360b9315f947dba078a25590939536edfca7b18e5cbeee9fa508b4bb7240420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a62e78b5ed5df47c75a63069089d6a467d6213754b13bfaf9d97ce87e9bf236627cb79be705138dfbd167788acf88967 +01ea00000000000000803684a80adff86234ae0b5427d228bf46a0eabac52293644343fac42ec82a3f3794c7c59b9043f2625694d3ad3a39df180cc4a79c30a7d81a48ed440db5e577e0f9c0360b9315f947dba078a25590939536edfca7b18e5cbeee9fa508b4bb720196212323bac6b5e2e6536121eb52c65844bd533b8de6c78059d41cf9fbd414c26ddc6945b1db197e12ba2ce5f9175e7b036669d11da5c228363defdb71a5592e00df7c6c3168b11a385480f74034017ffd640c1a14ad76b62a597dc0b02926dd40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b4b46ebaf331a44ebefbeab2f86b30c1bc6fcac5f1578da101b60ef7c69684f4309fe9facf4f1c46ab47a2cac247b62f +01ea0000000000000096212323bac6b5e2e6536121eb52c65844bd533b8de6c78059d41cf9fbd414c26ddc6945b1db197e12ba2ce5f9175e7b036669d11da5c228363defdb71a5592e00df7c6c3168b11a385480f74034017ffd640c1a14ad76b62a597dc0b02926dd01899e64a0ea0178e09965c763d9231e319a5314b6fa1b7e6ddc69571d4e0017cd592da1a38893b288655b608fd435414d0bb6066ae4bfca448a3653dd9081fa10fe5924c37feb65a9fd40d4537787811e8984500bf992eb0d7d9b156ca3d9542e40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000ac614e09381ce83e54fdad0492a49d4ea5b6b361e3faa98d7440000e07668e5bf5c46754db2d6a843fdd423867d30948 +01ea00000000000000899e64a0ea0178e09965c763d9231e319a5314b6fa1b7e6ddc69571d4e0017cd592da1a38893b288655b608fd435414d0bb6066ae4bfca448a3653dd9081fa10fe5924c37feb65a9fd40d4537787811e8984500bf992eb0d7d9b156ca3d9542e01b09232fac2f4dc06ed35592691aceaebdbe914204d6db4cb7966e8549c16df81639dfa4cca7ad387abf194f2aa9a6a3e0002e1e3d3e9d260bb9de47de04f3c4b64ca8724c6412b195e8a5a2e09cd120d18b14750e0c77701e80702a80d0cf19740420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000088b35978b19b84a6a4724317d5cbbf4fe29b428048156d83377f557904554e2130f0a8d8a518eeff52b1ff8b7584cff7 +01ea00000000000000b09232fac2f4dc06ed35592691aceaebdbe914204d6db4cb7966e8549c16df81639dfa4cca7ad387abf194f2aa9a6a3e0002e1e3d3e9d260bb9de47de04f3c4b64ca8724c6412b195e8a5a2e09cd120d18b14750e0c77701e80702a80d0cf197018c7fd098c59ad95d7ed779570a01e291a1badbe9acde7474a4f0d5696bf8f249c7a9b6b414359088dc504892ea9209f0123d8a95437bd0587c390f2f990b04c272a50d0afe2fbf2c8413d55fda00a566442bf5bf76a669aa97a275985ad0f3f640420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a1e3cd83d66f57c0abfb69162c514cb8bdf1ce2fd0ad27f4e3467da465641182df0bb753c752f4d78eab3960252271c2 +01ea000000000000008c7fd098c59ad95d7ed779570a01e291a1badbe9acde7474a4f0d5696bf8f249c7a9b6b414359088dc504892ea9209f0123d8a95437bd0587c390f2f990b04c272a50d0afe2fbf2c8413d55fda00a566442bf5bf76a669aa97a275985ad0f3f601b9f213a0089a669561fe3abaaf1c50588632c457ceda29b9595ec545e0f30141dfd50ecda09af8d88593170e6d3995a5112be3e42c7e1e3146f59f9c028a26c97f6e505fe0e296f5e1c45a24eb1d675fca8de617d2701fda1ff020740f8ba79c40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b454508ddb23d8fe30f0d916e32dab040b594de958aa6b1c600e2f3cb0cde202cc9c84ddc4cc4d6a9fc1f8f324c7790c +01ea00000000000000b9f213a0089a669561fe3abaaf1c50588632c457ceda29b9595ec545e0f30141dfd50ecda09af8d88593170e6d3995a5112be3e42c7e1e3146f59f9c028a26c97f6e505fe0e296f5e1c45a24eb1d675fca8de617d2701fda1ff020740f8ba79c0183831204787b6cff761e61157b7dfc17deaa91945ed20f21c072e25923459eaeb8a3673ca69069f3cef6f127ef6871d5027481c5bf49db28818717cda24ac42354ed7a2dbfc40d5a599581eab1a195970e6e9f00bb14a7f8cca313ca0fafdec640420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000098107beb1cd2080e5fbf6d626f4b9e3886ffb4de14f9af4555047e2b22b1b37b1a654bc4f95184a069d6f74ee80d82ec +01ea0000000000000083831204787b6cff761e61157b7dfc17deaa91945ed20f21c072e25923459eaeb8a3673ca69069f3cef6f127ef6871d5027481c5bf49db28818717cda24ac42354ed7a2dbfc40d5a599581eab1a195970e6e9f00bb14a7f8cca313ca0fafdec601ae0bf8014acf2d6489892c2da83e0196558353f12928cf807f687f60917030aaa066b3ed8faa84297279503a036776c70a6c91b7f3065c0c87d4f26369b5568212bb0e1aad5165fb81536caa9865026694e6890405ed6dcc2a5bee283d8237e540420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b568645506bbace9173077a8edde83e90b1913c75042e5cfe367beff97dea5fa5efb82acf22cb0514553b9315eb10220 +01ea00000000000000ae0bf8014acf2d6489892c2da83e0196558353f12928cf807f687f60917030aaa066b3ed8faa84297279503a036776c70a6c91b7f3065c0c87d4f26369b5568212bb0e1aad5165fb81536caa9865026694e6890405ed6dcc2a5bee283d8237e50190bded8a6919561a8beb2c4fac3792b0aa6dee67836fc305bba07b836d05e2df582a8d45ed52d03cacdd7a77be3afe8e185f4a7fa6979dbfb9969d2b68d52e7b1cf8d3ad71680f5d8b222fc4f713db2b3a0634378196795d20e293cbc70bb4e940420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a4e18f0483689e7550520f4311817a9dac0c38ecfc28bc62b244c3d4ce356553ab8ff6c17f94f79b00279bfff502bcd1 +01ea0000000000000090bded8a6919561a8beb2c4fac3792b0aa6dee67836fc305bba07b836d05e2df582a8d45ed52d03cacdd7a77be3afe8e185f4a7fa6979dbfb9969d2b68d52e7b1cf8d3ad71680f5d8b222fc4f713db2b3a0634378196795d20e293cbc70bb4e9018444ef39e0bbd22bc279b94d9b76347d0ac6d5ac456aece2407e0cfbacd6ed0afc0e65d675cb7d63155f04cb27f8ad7f091c5d7f77d4474427469760aafa3ba72a64437ceb74afc289bbcc86dd6db98f972deb2284c74be740938b4af5ded73f40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a6a536dd6f0aed5085684e946251e3251f96a2e60b1b6065f3d9e091056875358d9c60311d76c863ed0474f9292e8b62 +01ea000000000000008444ef39e0bbd22bc279b94d9b76347d0ac6d5ac456aece2407e0cfbacd6ed0afc0e65d675cb7d63155f04cb27f8ad7f091c5d7f77d4474427469760aafa3ba72a64437ceb74afc289bbcc86dd6db98f972deb2284c74be740938b4af5ded73f01925cb1dc9b8bb3dba9695204f3a1ea9ddd9ff3742e34b9df47b417ca780f48104c5a576911b19653fe8e09aef49eb1d81393ed25f45c4230836a45995a6c66021c9453f7af2182a83a87635586bd283c925a93228069d7a52a05090b1403fa5a40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b94938a3a0b2964ed2b504d6048c20ab8ff6a144de1431ee31a0825539915494301aaafe8d6680c82062adeff7c33db7 +01ea00000000000000925cb1dc9b8bb3dba9695204f3a1ea9ddd9ff3742e34b9df47b417ca780f48104c5a576911b19653fe8e09aef49eb1d81393ed25f45c4230836a45995a6c66021c9453f7af2182a83a87635586bd283c925a93228069d7a52a05090b1403fa5a019031eb88bab33185963087e1fbbf96ce5cc153e6abcb02314ab4d0bcbc9ed301f6fa075185173d5b485e8ea755383cbd098878afeaf06323ccab122f456161d5d992e81aa8431eceba3840c1e95282a1bba57084b941645540182b0c352fadc940420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000096c7f6e7207565d6dbf2fbb2f79f8a56c72f7bed4e5d1649fae63bb79ded7131433a608c75c654c066a0a5191c3604ff +01ea000000000000009031eb88bab33185963087e1fbbf96ce5cc153e6abcb02314ab4d0bcbc9ed301f6fa075185173d5b485e8ea755383cbd098878afeaf06323ccab122f456161d5d992e81aa8431eceba3840c1e95282a1bba57084b941645540182b0c352fadc901a990c4c0750588c6922633dad84eaaee6178d7698aeee542bafdbb1ae0b654d4e03d9380fae87596235b181f8b71d51a11b316218b029e40ba3e54a52d075f12b8486a3e3975b916b0e6c1bc848da555275224005892b6f44fd063ae058e164540420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000892ee4bec8d4c6ff9c22ceadd8fddc0e5e89de9b626ed7c4f21ac13af25ee224eabeb1a0d71da4707768ee6fce972a72 +01ea00000000000000a990c4c0750588c6922633dad84eaaee6178d7698aeee542bafdbb1ae0b654d4e03d9380fae87596235b181f8b71d51a11b316218b029e40ba3e54a52d075f12b8486a3e3975b916b0e6c1bc848da555275224005892b6f44fd063ae058e164501b6b6060f242a71b480f2ebf1b702cb2ece4f55ecf8f50a2878816e38041bc4b4ab7e007938a1d8d51dc531eb064e9ff70ef5637b13d48838df32894ce353335af6d3eee6f417ad4804e688c4a81e5df6aad9133885c41ed368c1bd836b1faafc40420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000085ca64a1ac8696c2752ffc3bdeb6dfd640b4d3eaf99375ea547e686f3724e1a1b79cc88c4116f176e46182af78e71b45 +01ea00000000000000b6b6060f242a71b480f2ebf1b702cb2ece4f55ecf8f50a2878816e38041bc4b4ab7e007938a1d8d51dc531eb064e9ff70ef5637b13d48838df32894ce353335af6d3eee6f417ad4804e688c4a81e5df6aad9133885c41ed368c1bd836b1faafc018823a0ef0e5fe0d16677e23854848db2db80cc15750c4bd797a0014841b7fb8e27bbc787ddf7fadbe329f99b8ca70e28012a8fa8a32e07d2688029104ed01dadb684966312c8fd12fde26605522b7080ef52bf848ffd7ba23aa0d058f039c40640420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000097dbeb221e17963b45db996ac4c8c1c93f4f0cfa1d280a66dccc9b7cc0753565f1ed741311fcb749e79cdebc7789a754 +01ea000000000000008823a0ef0e5fe0d16677e23854848db2db80cc15750c4bd797a0014841b7fb8e27bbc787ddf7fadbe329f99b8ca70e28012a8fa8a32e07d2688029104ed01dadb684966312c8fd12fde26605522b7080ef52bf848ffd7ba23aa0d058f039c4060180b3c0fbedea5479e51e6d56d6336c7a65f76311c579d41494da4d1bf1e43270028ff9aa5fa125937beeb9c6a067162b0759e9f26790f7ce57fead375958666da31ec1bb8f0c1bd300731c345ca95e2a05f0429ff6638f2a4c7fe10e48dfad6a40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a13e86b56f3d72e9057b5141e3d813427c248e474079da84835061c5e554a9801b5473c3a7536419e8f154c5ae1e9bd0 +01ea0000000000000080b3c0fbedea5479e51e6d56d6336c7a65f76311c579d41494da4d1bf1e43270028ff9aa5fa125937beeb9c6a067162b0759e9f26790f7ce57fead375958666da31ec1bb8f0c1bd300731c345ca95e2a05f0429ff6638f2a4c7fe10e48dfad6a0190ffde44e069f6d4ab591d40dc2a17c7eacebf0eeef6da3019ef4dc958c3dca8cdb5731c5e867ab9efe2c3b54370a551162cbf450dd0192c08f822197223141c6b8acf3aef06fe9ef1fbd977f6a7e79272573f055648cfd1a45b1637d4c812f740420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000008f8f5bdbc01c4e16dc6fa27b2d111af56ddb4ab2f9917ac5fe1b3d9461fac218e87f1612c247e3b1adbe0bd6a330ecf5 +01ea0000000000000090ffde44e069f6d4ab591d40dc2a17c7eacebf0eeef6da3019ef4dc958c3dca8cdb5731c5e867ab9efe2c3b54370a551162cbf450dd0192c08f822197223141c6b8acf3aef06fe9ef1fbd977f6a7e79272573f055648cfd1a45b1637d4c812f7019420244f16d68896e0dfccc0448ce28e9f7ecd3c217ffbf8045a2c110c5e5af3aff207947a6f553f42c261b31b3ff3760253a01594ea58b354f6d10bfc4c2dd4aee55bdae0fd93a7319aa797164d91564842fd2408f53c361edba48442d10eca40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000aaf7f5a12f0e2c87b23176f9a92ab8732694818cf0b1bbc964f59b4d89c95a912b6e8ea2862b4ab32009aaac04c5349c +01ea000000000000009420244f16d68896e0dfccc0448ce28e9f7ecd3c217ffbf8045a2c110c5e5af3aff207947a6f553f42c261b31b3ff3760253a01594ea58b354f6d10bfc4c2dd4aee55bdae0fd93a7319aa797164d91564842fd2408f53c361edba48442d10eca018cf23e3cfc2faac0a551fce076313209abae0ea869b708c0771577e3877fac05c1b3ff3bfe8982bef50c9fe74e55a31613f080224b14f0f891430495a6f0c94f2cdcc2bf8127484182fa60745b82c43885873af6739a86c7ac040a15eca2829740420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000862c843da97ccc1604e2bbb1441d093c1aff89ef9c78af14445312f7bbd5a4f5f7208b02a3752c4e09c7f1ca5510d816 +01ea000000000000008cf23e3cfc2faac0a551fce076313209abae0ea869b708c0771577e3877fac05c1b3ff3bfe8982bef50c9fe74e55a31613f080224b14f0f891430495a6f0c94f2cdcc2bf8127484182fa60745b82c43885873af6739a86c7ac040a15eca282970192c5f7b905dde2a97e903a7fc3e63f88c3d509f0f0122f789368009f4b0ea02f77a59972b0af830ab54e2209bbff694b0df38fa651d500c610b867706c71228eb9c80bbeb1215a4e00b52c9805a6b622e24bd7e9f3b8c12908d287124a20534640420f0000000000000000000000000000e1f5050000000001000000000000000100000000000000008bfb1351edbedf9a71b1329f4f5554e149dcc42b558742f3ba59fce29a8bc3417a340363815e8502b67c1bdad3431faf +01ea0000000000000092c5f7b905dde2a97e903a7fc3e63f88c3d509f0f0122f789368009f4b0ea02f77a59972b0af830ab54e2209bbff694b0df38fa651d500c610b867706c71228eb9c80bbeb1215a4e00b52c9805a6b622e24bd7e9f3b8c12908d287124a20534601b3df15846333468779e0d30456871392dba2c28eabd0e699a6f358308e89dc9822ca126154cf1c4a7bf853b26456569e15e4be28cd07caf0ba110b66b2a932cc4e95400fbf4a0e812f8bd539785b7fe78d07d437cbe81b6142fc530bbbbc630040420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b24aed63567cad525fda1df52414f316311fc25263c9a254be15afbe67fc0d5b73b5401edf05e8ac31d41a99b42035dd +01ea00000000000000b3df15846333468779e0d30456871392dba2c28eabd0e699a6f358308e89dc9822ca126154cf1c4a7bf853b26456569e15e4be28cd07caf0ba110b66b2a932cc4e95400fbf4a0e812f8bd539785b7fe78d07d437cbe81b6142fc530bbbbc630001ab6d9e7db56766bff16a4511cfad9a6d5e336b3dea04fa00e3e0f36c177bfd9d8a112a837f45a6cc6ddcba1413b69d1112d28f967b9b90a500104e6a730c294637dfd944a83aff22e3bc215669f58513b47763133a4abf41914e163d6c23acc740420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000872b233141a5229e507cd5d139e9c0cb30bcdbcfbf7302d80513c73870e297abc0f666fb4ee7808b8ee9491b50f7b52d +01ea00000000000000ab6d9e7db56766bff16a4511cfad9a6d5e336b3dea04fa00e3e0f36c177bfd9d8a112a837f45a6cc6ddcba1413b69d1112d28f967b9b90a500104e6a730c294637dfd944a83aff22e3bc215669f58513b47763133a4abf41914e163d6c23acc7018117e7bdc7e84c197c08fa089a3ee85b8bb9e9aac01a81b0365a86a5e00ee7b6229fcbe7302165fd9490adbb5f991fce0bfcd1e4bc43de62b7a70b909dd8baebe151fd36580f5b847ca0ba64a9b03d1fc14ad2b54d0b0ed766fc754d0c3525ea40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b29902f1fbba01e8d711497d83a2358cdfcdc3217f34c5cc8a65c3cf84b20bbbbec1fcc382a9f767f9b7bb471757636b +01ea000000000000008117e7bdc7e84c197c08fa089a3ee85b8bb9e9aac01a81b0365a86a5e00ee7b6229fcbe7302165fd9490adbb5f991fce0bfcd1e4bc43de62b7a70b909dd8baebe151fd36580f5b847ca0ba64a9b03d1fc14ad2b54d0b0ed766fc754d0c3525ea0197ac46f79bb62c41852a681a6a21ad8a0bf32522523a73e3c200d62cba80aa6248111c9b7e3931f9dce5876360aab1200e00101e6cbfc352b0b7bfff1837aa31b897cdb6e4e00d5307efa7bbeae78f3a031fd933c662b106cbbf727e90d41d0d40420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000aa22e7c00ec00bc695bf99f59d16a5082eadab83453ef305d5810c0af664440941bdecea15ecc64be006289ce722772b +01ea0000000000000097ac46f79bb62c41852a681a6a21ad8a0bf32522523a73e3c200d62cba80aa6248111c9b7e3931f9dce5876360aab1200e00101e6cbfc352b0b7bfff1837aa31b897cdb6e4e00d5307efa7bbeae78f3a031fd933c662b106cbbf727e90d41d0d01a29fef7a4a1174cc7b179f1c5ff5c15fc760f743279241d03da8eae9ac208d6442430b6adadafeaeaa0feb9b0a300b2d00564ab4a656e936ed944aa22580d302f499e67a93065b43f254bc1490678c9a1b0e634d318a60b942e922509b287a4940420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000b683595076cdfa454d1e533562b710119e385efabc3481c5e6584aeb8bbcf1f600fb590c55bf7a087f4bfa344ffe6f6c +01ea00000000000000a29fef7a4a1174cc7b179f1c5ff5c15fc760f743279241d03da8eae9ac208d6442430b6adadafeaeaa0feb9b0a300b2d00564ab4a656e936ed944aa22580d302f499e67a93065b43f254bc1490678c9a1b0e634d318a60b942e922509b287a4901aeb51a3926de36e493243b3bf815c521393047ff6d1535974737a981ef7652fc189d60dd3aa3a6cfd38910d18192118517c4aa1099ee39372a92f2635e05b9218881227c1174a6bad9ddc143ac0f335974505a293f6375d81fc21c74059ec03640420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a0a21eeb4623bdab3b1c20e3d9522d5e4e21cabe30b4efce85d43dcac84e75e03d9fc3be1c08d14e6dd2cbe27ae472a2 +01ea00000000000000aeb51a3926de36e493243b3bf815c521393047ff6d1535974737a981ef7652fc189d60dd3aa3a6cfd38910d18192118517c4aa1099ee39372a92f2635e05b9218881227c1174a6bad9ddc143ac0f335974505a293f6375d81fc21c74059ec03601acb686f36f2a12ebbb2d2408c3cf75821eca6e917b1eda620bd123432840815cd9610835e90584da1abf5bc6cb26292b09d08188b7a8bc27f8d4a50138888754c9381d3e72c731fb6306e064e62ce647b52f3395b9d95a2cfc77d19084e5fe7940420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a7459a5f9192628fe191a2f6795d987cc582eff347c7736e20ee8b7109c5ac97fe1485cc32deb414873bfe8f1c2a9214 +01ea00000000000000acb686f36f2a12ebbb2d2408c3cf75821eca6e917b1eda620bd123432840815cd9610835e90584da1abf5bc6cb26292b09d08188b7a8bc27f8d4a50138888754c9381d3e72c731fb6306e064e62ce647b52f3395b9d95a2cfc77d19084e5fe790180b4b29b306852bd6accd13dedc4f379b3cad1d44f97798e19466c911842a1a5bc90dd545a36042e4f5cd814d0a4013706de007b9889d577c589057ee8eef811bd31d925d5b1f9564ec217f43ff0286e10bb5ccf7754f42770b236c7ba207ab740420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000099221af81a6c39c9d6a06614772745cbd7e968b0950de5ed3a52eabb62859b9b924a9bb26a9653ba6cc36fee360aee6b +01ea0000000000000080b4b29b306852bd6accd13dedc4f379b3cad1d44f97798e19466c911842a1a5bc90dd545a36042e4f5cd814d0a4013706de007b9889d577c589057ee8eef811bd31d925d5b1f9564ec217f43ff0286e10bb5ccf7754f42770b236c7ba207ab7018cd173e54f2d62803916d873bb709dc1a54aa1609ba0bf776891fa2b23c0850ea2042954c63d7d9ba9be4ba5b201cfda0e5cf19f3207fe4496686adf839da36a5b6ef004291c4c00c8173ce428c253833aaaf824b81f2b5de8b733a000362ad740420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000970eb84cca38db815298c367a58334a5579afeedd9a75b9d4d4da56f5ed5ea0354fb6c2742db8c500818c985fe2d91cf +01ea000000000000008cd173e54f2d62803916d873bb709dc1a54aa1609ba0bf776891fa2b23c0850ea2042954c63d7d9ba9be4ba5b201cfda0e5cf19f3207fe4496686adf839da36a5b6ef004291c4c00c8173ce428c253833aaaf824b81f2b5de8b733a000362ad7018a862e3cf4e4d9a57edb886762fbf0f783821f97a76e15be18f1b38007034fd2f4333c4bb0064d11e736fc9bdc2817220c38a29602c8b20dd91b6d98838dca2db64afd3d93ad93568505a86f22a521418159b1bd1bee70f6fe9494d68da5a5f240420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000868867dae11f816510120e12400a0dcc8322f46e4a11d86da685752505b3052484f017e6498f3805fa655e38776621fb +01ea000000000000008a862e3cf4e4d9a57edb886762fbf0f783821f97a76e15be18f1b38007034fd2f4333c4bb0064d11e736fc9bdc2817220c38a29602c8b20dd91b6d98838dca2db64afd3d93ad93568505a86f22a521418159b1bd1bee70f6fe9494d68da5a5f201b361321e4403d925349551752acd2528b438111afc767656d85e1af18390257adc4113372c9cf5ca56ac982a7e96cc651012efa63ea74ad9d48b8e0be7e010b18643b68faeec02b5bd210f3559a05cfc61569e26c4cb85006755ee9ea7d1caae40420f0000000000000000000000000000e1f50500000000010000000000000001000000000000000099f6ff8c1b248ba4ec92c725308e4d0e120ac1cd033e51aad0b7be3c72c2846a2a9c7882b1187fd790656a72c93283b3 +01ea00000000000000b361321e4403d925349551752acd2528b438111afc767656d85e1af18390257adc4113372c9cf5ca56ac982a7e96cc651012efa63ea74ad9d48b8e0be7e010b18643b68faeec02b5bd210f3559a05cfc61569e26c4cb85006755ee9ea7d1caae018d31d41cb190cb54fab965d3edba820a8e8fc5cdf5df2ef0b17ed7b318c290808564ec96651502cb086f4e2317de88441376dca3359fcf084a904343591c9c6d420dea1e576a957120af2ea4c46ef9d3bb1394ad3cbc92413c59b1ce518c085040420f0000000000000000000000000000e1f505000000000100000000000000010000000000000000a50ac11c182f268520cf4dac9334e48ba1fc132fd0af877e5f32f2301554fc65ef971ab4808dab1856f9cd3b51bd66d2 diff --git a/rusk/src/lib/chain.rs b/rusk/src/lib/chain.rs new file mode 100644 index 0000000000..e65ace9dbc --- /dev/null +++ b/rusk/src/lib/chain.rs @@ -0,0 +1,98 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +mod rusk; +mod vm; + +use std::path::PathBuf; +use std::sync::Arc; +use std::time::Duration; + +use parking_lot::RwLock; +use tokio::sync::broadcast; + +use execution_core::{dusk, Dusk}; +use node::database::rocksdb::Backend; +use node::network::Kadcast; +use rusk_abi::VM; + +use crate::http::RuesEvent; + +pub const MINIMUM_STAKE: Dusk = dusk(1000.0); + +#[derive(Debug, Clone, Copy)] +pub struct RuskTip { + pub current: [u8; 32], + pub base: [u8; 32], +} + +#[derive(Clone)] +pub struct Rusk { + pub(crate) tip: Arc>, + pub(crate) vm: Arc, + dir: PathBuf, + pub(crate) generation_timeout: Option, + pub(crate) gas_per_deploy_byte: Option, + pub(crate) feeder_gas_limit: u64, + pub(crate) event_sender: broadcast::Sender, +} + +#[derive(Clone)] +pub struct RuskNode(pub node::Node, Backend, Rusk>); + +impl RuskNode { + pub fn db(&self) -> Arc> { + self.0.database() as Arc> + } + + pub fn network(&self) -> Arc>> { + self.0.network() as Arc>> + } +} + +/// Calculates the value that the coinbase notes should contain. +/// +/// 10% of the reward value goes to the Dusk address (rounded down). +/// 70% of the reward value is considered fixed reward for Block Generator. +/// 10% of the reward value is considered extra reward for Block Generator. +/// 10% of the reward value goes to the all validators/voters of previous block +/// (rounded down). +const fn coinbase_value( + block_height: u64, + dusk_spent: u64, +) -> (Dusk, Dusk, Dusk, Dusk) { + let reward_value = emission_amount(block_height) + dusk_spent; + let one_tenth_reward = reward_value / 10; + + let dusk_value = one_tenth_reward; + let voters_value = one_tenth_reward; + let generator_extra_value = one_tenth_reward; + + let generator_fixed_value = + reward_value - dusk_value - voters_value - generator_extra_value; + + ( + dusk_value, + generator_fixed_value, + generator_extra_value, + voters_value, + ) +} + +/// This implements the emission schedule described in the economic paper. +pub const fn emission_amount(block_height: u64) -> Dusk { + match block_height { + 1..=12_500_000 => dusk(16.0), + 12_500_001..=18_750_000 => dusk(12.8), + 18_750_001..=25_000_000 => dusk(9.6), + 25_000_001..=31_250_000 => dusk(8.0), + 31_250_001..=37_500_000 => dusk(6.4), + 37_500_001..=43_750_000 => dusk(4.8), + 43_750_001..=50_000_000 => dusk(3.2), + 50_000_001..=62_500_000 => dusk(1.6), + _ => dusk(0.0), + } +} diff --git a/rusk/src/lib/error.rs b/rusk/src/lib/error.rs index 98223c791d..57777deda0 100644 --- a/rusk/src/lib/error.rs +++ b/rusk/src/lib/error.rs @@ -7,9 +7,10 @@ use std::{fmt, io}; use dusk_bytes::Serializable; -use execution_core::BlsScalar; -use execution_core::{BlsPublicKey, PhoenixError}; -use rusk_abi::dusk::Dusk; +use execution_core::{ + signatures::bls::PublicKey as BlsPublicKey, + transfer::phoenix::Error as PhoenixError, BlsScalar, Dusk, +}; use rusk_abi::PiecrustError; #[derive(Debug)] diff --git a/rusk/src/lib/gen_id.rs b/rusk/src/lib/gen_id.rs index bb7f2d6389..82c52fa445 100644 --- a/rusk/src/lib/gen_id.rs +++ b/rusk/src/lib/gen_id.rs @@ -4,8 +4,8 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use crate::hash::Hasher; -use rusk_abi::ContractId; +use blake2b_simd::Params; +use execution_core::{ContractId, CONTRACT_ID_BYTES}; /// Generate a [`ContractId`] address from: /// - slice of bytes, @@ -16,11 +16,15 @@ pub fn gen_contract_id( nonce: u64, owner: impl AsRef<[u8]>, ) -> ContractId { - let mut hasher = Hasher::new(); + let mut hasher = Params::new().hash_length(CONTRACT_ID_BYTES).to_state(); hasher.update(bytes.as_ref()); - hasher.update(nonce.to_le_bytes()); + hasher.update(&nonce.to_le_bytes()[..]); hasher.update(owner.as_ref()); - let hash_bytes = hasher.finalize(); + let hash_bytes: [u8; CONTRACT_ID_BYTES] = hasher + .finalize() + .as_bytes() + .try_into() + .expect("the hash result is exactly `CONTRACT_ID_BYTES` long"); ContractId::from_bytes(hash_bytes) } @@ -47,7 +51,7 @@ mod tests { assert_eq!( hex::encode(contract_id.as_bytes()), - "a138d3b9c87235dac6f62d1d30b75cffbb94601d9cbe5bd540b3e1e5842c8a7d" + "2da8b6277789a88c7215789e227ef4dd97486db252e554805c7b874a17e07785" ); } } diff --git a/rusk/src/lib/hash.rs b/rusk/src/lib/hash.rs deleted file mode 100644 index 523baf6e9b..0000000000 --- a/rusk/src/lib/hash.rs +++ /dev/null @@ -1,44 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. -// -// Copyright (c) DUSK NETWORK. All rights reserved. - -use blake2b_simd::{Params, State}; - -/// Hashes scalars and arbitrary slices of bytes using Blake2b, returning an -/// array of 32 bytes. -/// -/// This hash cannot be proven inside a circuit, if that is desired, use -/// `poseidon_hash` instead. -pub struct Hasher { - state: State, -} - -impl Default for Hasher { - fn default() -> Self { - Hasher { - state: Params::new().hash_length(64).to_state(), - } - } -} - -impl Hasher { - /// Create new hasher instance. - pub fn new() -> Self { - Self::default() - } - - /// Process data, updating the internal state. - pub fn update(&mut self, data: impl AsRef<[u8]>) { - self.state.update(data.as_ref()); - } - - /// Retrieve result and consume hasher instance. - pub fn finalize(self) -> [u8; 32] { - let hash = self.state.finalize(); - let mut a = [0u8; 32]; - a.clone_from_slice(&hash.as_array()[..32]); - a - } -} diff --git a/rusk/src/lib/http.rs b/rusk/src/lib/http.rs index fa00bdef9e..01995cb343 100644 --- a/rusk/src/lib/http.rs +++ b/rusk/src/lib/http.rs @@ -20,7 +20,7 @@ pub(crate) use event::{ RequestData, Target, }; -use rusk_abi::Event; +use execution_core::Event; use tracing::{info, warn}; use std::borrow::Cow; @@ -861,7 +861,7 @@ mod tests { use event::Event as EventRequest; use crate::http::event::WrappedContractId; - use rusk_abi::ContractId; + use execution_core::ContractId; use std::net::TcpStream; use tungstenite::client; diff --git a/rusk/src/lib/http/chain/graphql.rs b/rusk/src/lib/http/chain/graphql.rs index 600ff0a0f2..ed664150de 100644 --- a/rusk/src/lib/http/chain/graphql.rs +++ b/rusk/src/lib/http/chain/graphql.rs @@ -13,6 +13,7 @@ use data::*; use tx::*; use async_graphql::{Context, FieldError, FieldResult, Object}; +use execution_core::{transfer::TRANSFER_CONTRACT, ContractId}; use node::database::rocksdb::Backend; use node::database::{Ledger, DB}; @@ -66,7 +67,11 @@ impl Query { let blocks = self.blocks(ctx, last, range).await?; let contract = match contract { - Some(contract) => Some(hex::decode(contract)?), + Some(contract) => { + let mut decoded = [0u8; 32]; + decoded.copy_from_slice(&hex::decode(contract)?[..]); + Some(ContractId::from(decoded)) + } _ => None, }; @@ -84,11 +89,9 @@ impl Query { .inner .call() .map(|c| c.contract) - .unwrap_or( - rusk_abi::TRANSFER_CONTRACT.to_bytes(), - ); + .unwrap_or(TRANSFER_CONTRACT); - tx_contract == contract[..] + tx_contract == *contract }) .collect(); txs.append(&mut txs_to_add); diff --git a/rusk/src/lib/http/event.rs b/rusk/src/lib/http/event.rs index 7d1a01eeb2..298e4ce631 100644 --- a/rusk/src/lib/http/event.rs +++ b/rusk/src/lib/http/event.rs @@ -8,6 +8,7 @@ use super::RUSK_VERSION_HEADER; use base64::engine::{general_purpose::STANDARD as BASE64, Engine}; use bytecheck::CheckBytes; +use execution_core::ContractId; use futures_util::stream::Iter as StreamIter; use futures_util::{stream, Stream, StreamExt}; use http_body_util::{BodyExt, Either, Full, StreamBody}; @@ -21,7 +22,6 @@ use pin_project::pin_project; use rand::distributions::{Distribution, Standard}; use rand::Rng; use rkyv::Archive; -use rusk_abi::ContractId; use semver::{Version, VersionReq}; use serde::de::{Error, MapAccess, Unexpected, Visitor}; use serde::ser::SerializeMap; @@ -747,8 +747,8 @@ pub struct ContractEvent { pub data: Vec, } -impl From for ContractEvent { - fn from(event: rusk_abi::Event) -> Self { +impl From for ContractEvent { + fn from(event: execution_core::Event) -> Self { Self { target: WrappedContractId(event.source), topic: event.topic, @@ -757,7 +757,7 @@ impl From for ContractEvent { } } -impl From for rusk_abi::Event { +impl From for execution_core::Event { fn from(event: ContractEvent) -> Self { Self { source: event.target.0, @@ -828,8 +828,8 @@ impl From for RuesEvent { } } -impl From for RuesEvent { - fn from(event: rusk_abi::Event) -> Self { +impl From for RuesEvent { + fn from(event: execution_core::Event) -> Self { Self::from(ContractEvent::from(event)) } } diff --git a/rusk/src/lib/http/rusk.rs b/rusk/src/lib/http/rusk.rs index adc3570b1e..665443816e 100644 --- a/rusk/src/lib/http/rusk.rs +++ b/rusk/src/lib/http/rusk.rs @@ -15,7 +15,7 @@ use std::sync::{mpsc, Arc}; use std::thread; use tokio::task; -use rusk_abi::ContractId; +use execution_core::ContractId; use crate::node::Rusk; diff --git a/rusk/src/lib/lib.rs b/rusk/src/lib/lib.rs index 910f8ed45e..9726c74f61 100644 --- a/rusk/src/lib/lib.rs +++ b/rusk/src/lib/lib.rs @@ -8,7 +8,6 @@ mod error; pub mod gen_id; -mod hash; pub mod http; #[cfg(feature = "node")] pub mod node; diff --git a/rusk/src/lib/node.rs b/rusk/src/lib/node.rs index 1f2f5ecac8..5b93cb0486 100644 --- a/rusk/src/lib/node.rs +++ b/rusk/src/lib/node.rs @@ -11,7 +11,7 @@ use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; -use crate::http::{HandleRequest, RuesEvent}; +use execution_core::{dusk, Dusk}; use kadcast::config::Config as KadcastConfig; use node::chain::ChainSrv; use node::database::rocksdb::{self, Backend}; @@ -25,7 +25,6 @@ use node::network::Kadcast; use node::telemetry::TelemetrySrv; use node::{LongLivedService, Node}; use parking_lot::RwLock; -use rusk_abi::dusk::{dusk, Dusk}; use rusk_abi::VM; use tokio::sync::broadcast; diff --git a/rusk/src/lib/node/rusk.rs b/rusk/src/lib/node/rusk.rs index 440736bad9..32cfcc2de3 100644 --- a/rusk/src/lib/node/rusk.rs +++ b/rusk/src/lib/node/rusk.rs @@ -21,20 +21,18 @@ use dusk_consensus::config::{ RATIFICATION_COMMITTEE_CREDITS, VALIDATION_COMMITTEE_CREDITS, }; use dusk_consensus::operations::{CallParams, VerificationOutput, Voter}; -use execution_core::bytecode::Bytecode; -use execution_core::transfer::ContractDeploy; use execution_core::{ - stake::StakeData, - transfer::{AccountData, Transaction as ProtocolTransaction}, - BlsPublicKey, BlsScalar, + signatures::bls::PublicKey as BlsPublicKey, + stake::{StakeData, STAKE_CONTRACT}, + transfer::{ + contract_exec::{ContractBytecode, ContractDeploy}, + moonlight::AccountData, + Transaction as ProtocolTransaction, TRANSFER_CONTRACT, + }, + BlsScalar, ContractError, Dusk, Event, }; use node_data::ledger::{Slash, SpentTransaction, Transaction}; -use rusk_abi::dusk::Dusk; -use rusk_abi::ContractError::{OutOfGas, Panic}; -use rusk_abi::{ - CallReceipt, ContractError, Event, PiecrustError, Session, STAKE_CONTRACT, - TRANSFER_CONTRACT, VM, -}; +use rusk_abi::{CallReceipt, PiecrustError, Session, VM}; use rusk_profile::to_rusk_state_id_path; use tokio::sync::broadcast; @@ -507,7 +505,7 @@ fn accept( // Returns gas charge for bytecode deployment. fn bytecode_charge( - bytecode: &Bytecode, + bytecode: &ContractBytecode, gas_per_deploy_byte: &Option, ) -> u64 { bytecode.bytes.len() as u64 @@ -534,9 +532,10 @@ fn contract_deploy( let min_gas_limit = receipt.gas_spent + deploy_charge; let hash = blake3::hash(deploy.bytecode.bytes.as_slice()); if gas_limit < min_gas_limit { - receipt.data = Err(OutOfGas); + receipt.data = Err(ContractError::OutOfGas); } else if hash != deploy.bytecode.hash { - receipt.data = Err(Panic("failed bytecode hash check".into())) + receipt.data = + Err(ContractError::Panic("failed bytecode hash check".into())) } else { let result = session.deploy_raw( Some(gen_contract_id( @@ -553,7 +552,8 @@ fn contract_deploy( Ok(_) => receipt.gas_spent += deploy_charge, Err(err) => { info!("Tx caused deployment error {err:?}"); - receipt.data = Err(Panic("failed deployment".into())) + receipt.data = + Err(ContractError::Panic("failed deployment".into())) } } } diff --git a/rusk/src/lib/node/vm.rs b/rusk/src/lib/node/vm.rs index 9714ca0cdf..d0f8e8139a 100644 --- a/rusk/src/lib/node/vm.rs +++ b/rusk/src/lib/node/vm.rs @@ -13,8 +13,8 @@ use dusk_consensus::operations::{CallParams, VerificationOutput, Voter}; use dusk_consensus::user::provisioners::Provisioners; use dusk_consensus::user::stake::Stake; use execution_core::{ - stake::StakeData, transfer::Transaction as ProtocolTransaction, - BlsPublicKey, + signatures::bls::PublicKey as BlsPublicKey, stake::StakeData, + transfer::Transaction as ProtocolTransaction, }; use node::vm::VMExecution; use node_data::ledger::{Block, Slash, SpentTransaction, Transaction}; diff --git a/rusk/src/lib/node/vm/query.rs b/rusk/src/lib/node/vm/query.rs index a6dc9f0de1..f15d52ab21 100644 --- a/rusk/src/lib/node/vm/query.rs +++ b/rusk/src/lib/node/vm/query.rs @@ -10,9 +10,9 @@ use crate::Result; use std::sync::mpsc; use bytecheck::CheckBytes; +use execution_core::{ContractId, StandardBufSerializer}; use rkyv::validation::validators::DefaultValidator; use rkyv::{Archive, Deserialize, Infallible, Serialize}; -use rusk_abi::{ContractId, StandardBufSerializer}; impl Rusk { pub fn query_raw( diff --git a/rusk/src/lib/test_utils.rs b/rusk/src/lib/test_utils.rs index 21876c3ac7..37e797afa1 100644 --- a/rusk/src/lib/test_utils.rs +++ b/rusk/src/lib/test_utils.rs @@ -16,12 +16,18 @@ use futures::Stream; use tokio::spawn; use tracing::{error, info}; -use execution_core::stake::StakeData; -use execution_core::transfer::{TreeLeaf, TRANSFER_TREE_DEPTH}; -use execution_core::{BlsPublicKey, BlsScalar, Note, ViewKey}; +use execution_core::{ + signatures::bls::PublicKey as BlsPublicKey, + stake::{StakeData, STAKE_CONTRACT}, + transfer::{ + phoenix::{Note, TreeLeaf, ViewKey, NOTES_TREE_DEPTH}, + TRANSFER_CONTRACT, + }, + BlsScalar, ContractId, +}; use parking_lot::RwLockWriteGuard; use poseidon_merkle::Opening as PoseidonOpening; -use rusk_abi::{ContractId, STAKE_CONTRACT, TRANSFER_CONTRACT, VM}; +use rusk_abi::VM; pub type StoredNote = (Note, u64); @@ -59,7 +65,7 @@ impl Rusk { pub fn tree_opening( &self, pos: u64, - ) -> Result>> { + ) -> Result>> { self.query(TRANSFER_CONTRACT, "opening", &pos) } diff --git a/rusk/src/lib/verifier.rs b/rusk/src/lib/verifier.rs index 107a57ce36..accca6f4a9 100644 --- a/rusk/src/lib/verifier.rs +++ b/rusk/src/lib/verifier.rs @@ -9,7 +9,10 @@ use crate::error::Error; use crate::Result; -use execution_core::transfer::{MoonlightTransaction, PhoenixTransaction}; +use execution_core::transfer::{ + moonlight::Transaction as MoonlightTransaction, + phoenix::Transaction as PhoenixTransaction, +}; use rusk_profile::Circuit as CircuitProfile; use std::sync::LazyLock; diff --git a/rusk/tests/common/keys.rs b/rusk/tests/common/keys.rs index 73c92e9756..7815d79885 100644 --- a/rusk/tests/common/keys.rs +++ b/rusk/tests/common/keys.rs @@ -10,7 +10,7 @@ use rand::prelude::*; use rand::rngs::StdRng; use tracing::info; -use execution_core::BlsSecretKey; +use execution_core::signatures::bls::SecretKey as BlsSecretKey; #[allow(dead_code)] pub static STAKE_SK: LazyLock = LazyLock::new(|| { diff --git a/rusk/tests/common/state.rs b/rusk/tests/common/state.rs index ed4b4ef1b7..e4c3965c2a 100644 --- a/rusk/tests/common/state.rs +++ b/rusk/tests/common/state.rs @@ -13,7 +13,9 @@ use rusk::{Result, Rusk}; use rusk_recovery_tools::state::{self, Snapshot}; use dusk_consensus::operations::CallParams; -use execution_core::{transfer::Transaction, BlsPublicKey}; +use execution_core::{ + signatures::bls::PublicKey as BlsPublicKey, transfer::Transaction, +}; use node_data::{ bls::PublicKeyBytes, ledger::{ diff --git a/rusk/tests/common/wallet.rs b/rusk/tests/common/wallet.rs index f5b74c80f5..bd7ad2b9fd 100644 --- a/rusk/tests/common/wallet.rs +++ b/rusk/tests/common/wallet.rs @@ -13,9 +13,14 @@ use crate::common::block::Block as BlockAwait; use dusk_bytes::{DeserializableSlice, Serializable}; use dusk_plonk::prelude::Proof; use execution_core::{ + signatures::bls::PublicKey as BlsPublicKey, stake::StakeData, - transfer::{AccountData, Transaction, TRANSFER_TREE_DEPTH}, - BlsPublicKey, BlsScalar, Note, ViewKey, + transfer::{ + moonlight::AccountData, + phoenix::{Note, ViewKey, NOTES_TREE_DEPTH}, + Transaction, + }, + BlsScalar, }; use futures::StreamExt; use poseidon_merkle::Opening as PoseidonOpening; @@ -101,7 +106,7 @@ impl wallet::StateClient for TestStateClient { fn fetch_opening( &self, note: &Note, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { self.rusk .tree_opening(*note.pos())? .ok_or(Error::OpeningPositionNotFound(*note.pos())) diff --git a/rusk/tests/rusk-state.rs b/rusk/tests/rusk-state.rs index 315a298134..5656a46224 100644 --- a/rusk/tests/rusk-state.rs +++ b/rusk/tests/rusk-state.rs @@ -13,7 +13,14 @@ use std::path::Path; use std::sync::{mpsc, Arc}; use execution_core::{ - transfer::TreeLeaf, JubJubScalar, Note, PublicKey, SecretKey, + transfer::{ + phoenix::{ + Note, PublicKey as PhoenixPublicKey, SecretKey as PhoenixSecretKey, + TreeLeaf, + }, + TRANSFER_CONTRACT, + }, + JubJubScalar, LUX, }; use ff::Field; use parking_lot::RwLockWriteGuard; @@ -21,7 +28,7 @@ use rand::prelude::*; use rand::rngs::StdRng; use rusk::node::{Rusk, RuskTip}; use rusk::Result; -use rusk_abi::{TRANSFER_CONTRACT, VM}; +use rusk_abi::VM; use tempfile::tempdir; use tracing::info; @@ -54,9 +61,10 @@ where info!("Generating a note"); let mut rng = StdRng::seed_from_u64(0xdead); - let sender_sk = SecretKey::random(&mut rng); - let sender_pk = PublicKey::from(&sender_sk); - let receiver_pk = PublicKey::from(&SecretKey::random(&mut rng)); + let sender_sk = PhoenixSecretKey::random(&mut rng); + let sender_pk = PhoenixPublicKey::from(&sender_sk); + let receiver_pk = + PhoenixPublicKey::from(&PhoenixSecretKey::random(&mut rng)); let sender_blinder = [ JubJubScalar::random(&mut rng), @@ -210,7 +218,7 @@ async fn generate_phoenix_txs() -> Result<(), Box> { &receiver, TRANSFER_VALUE, GAS_LIMIT, - rusk_abi::dusk::LUX, + LUX, ) .expect("Making a transfer TX should succeed") }); @@ -273,7 +281,7 @@ async fn generate_moonlight_txs() -> Result<(), Box> { receiver, TRANSFER_VALUE, GAS_LIMIT, - rusk_abi::dusk::LUX, + LUX, ) .expect("Making a transfer TX should succeed") }); diff --git a/rusk/tests/services/contract_deployment.rs b/rusk/tests/services/contract_deployment.rs index ef79774b71..4cbf3b6035 100644 --- a/rusk/tests/services/contract_deployment.rs +++ b/rusk/tests/services/contract_deployment.rs @@ -8,13 +8,15 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::sync::{Arc, RwLock}; -use execution_core::bytecode::Bytecode; -use execution_core::transfer::{ContractDeploy, ContractExec}; +use execution_core::{ + transfer::contract_exec::{ContractBytecode, ContractDeploy, ContractExec}, + ContractId, +}; use rand::prelude::*; use rand::rngs::StdRng; use rusk::gen_id::gen_contract_id; use rusk::{Result, Rusk}; -use rusk_abi::{ContractData, ContractId, PiecrustError}; +use rusk_abi::{ContractData, PiecrustError}; use rusk_recovery_tools::state; use tempfile::tempdir; use test_wallet::{self as wallet, Wallet}; @@ -86,7 +88,7 @@ fn initial_state>(dir: P, deploy_bob: bool) -> Result { )), POINT_LIMIT, ) - .expect("Deploying the alice contract should succeed"); + .expect("Deploying the bob contract should succeed"); } }) .expect("Deploying initial state should succeed"); @@ -121,7 +123,7 @@ fn make_and_execute_transaction_deploy( .phoenix_execute( &mut rng, ContractExec::Deploy(ContractDeploy { - bytecode: Bytecode { + bytecode: ContractBytecode { hash, bytes: bytecode.as_ref().to_vec(), }, diff --git a/rusk/tests/services/gas_behavior.rs b/rusk/tests/services/gas_behavior.rs index 0d01ddc8a8..31a0e3678d 100644 --- a/rusk/tests/services/gas_behavior.rs +++ b/rusk/tests/services/gas_behavior.rs @@ -8,11 +8,13 @@ use std::collections::HashMap; use std::path::Path; use std::sync::{Arc, RwLock}; -use execution_core::transfer::{ContractCall, ContractExec}; +use execution_core::transfer::{ + contract_exec::{ContractCall, ContractExec}, + TRANSFER_CONTRACT, +}; use rand::prelude::*; use rand::rngs::StdRng; use rusk::{Result, Rusk}; -use rusk_abi::TRANSFER_CONTRACT; use tempfile::tempdir; use test_wallet::{self as wallet}; use tracing::info; @@ -68,7 +70,7 @@ fn make_transactions( // contract, querying for the root of the tree. This will be given too // little gas to execute correctly and error, consuming all gas provided. let contract_call = ContractCall { - contract: TRANSFER_CONTRACT.to_bytes(), + contract: TRANSFER_CONTRACT, fn_name: String::from("root"), fn_args: Vec::new(), }; diff --git a/rusk/tests/services/stake.rs b/rusk/tests/services/stake.rs index 0a149ac12b..0648822309 100644 --- a/rusk/tests/services/stake.rs +++ b/rusk/tests/services/stake.rs @@ -8,14 +8,15 @@ use std::path::Path; use std::sync::{Arc, RwLock}; use execution_core::{ - stake::StakeAmount, transfer::ContractCall, BlsPublicKey, + dusk, + signatures::bls::PublicKey as BlsPublicKey, + stake::{StakeAmount, STAKE_CONTRACT}, + transfer::contract_exec::ContractCall, }; use rand::prelude::*; use rand::rngs::StdRng; use rusk::node::MINIMUM_STAKE; use rusk::{Result, Rusk}; -use rusk_abi::dusk::dusk; -use rusk_abi::STAKE_CONTRACT; use std::collections::HashMap; use tempfile::tempdir; use test_wallet::{self as wallet, Store}; diff --git a/rusk/tests/services/unspendable.rs b/rusk/tests/services/unspendable.rs index af2c2d0c5b..8ffeebe6b6 100644 --- a/rusk/tests/services/unspendable.rs +++ b/rusk/tests/services/unspendable.rs @@ -8,11 +8,13 @@ use std::collections::HashMap; use std::path::Path; use std::sync::{Arc, RwLock}; -use execution_core::transfer::{ContractCall, ContractExec}; +use execution_core::transfer::{ + contract_exec::{ContractCall, ContractExec}, + TRANSFER_CONTRACT, +}; use rand::prelude::*; use rand::rngs::StdRng; use rusk::{Result, Rusk}; -use rusk_abi::TRANSFER_CONTRACT; use tempfile::tempdir; use test_wallet::{self as wallet}; use tracing::info; @@ -80,7 +82,7 @@ fn make_transactions( // contract, querying for the root of the tree. This will be given too // little gas to execute correctly and error, consuming all gas provided. let contract_call = ContractCall { - contract: TRANSFER_CONTRACT.to_bytes(), + contract: TRANSFER_CONTRACT, fn_name: String::from("root"), fn_args: Vec::new(), }; diff --git a/test-wallet/Cargo.toml b/test-wallet/Cargo.toml index a47a449c6b..3220d0cc56 100644 --- a/test-wallet/Cargo.toml +++ b/test-wallet/Cargo.toml @@ -18,7 +18,6 @@ rusk-prover = { version = "0.3.0", path = "../rusk-prover", default-features = f ff = { version = "0.13", default-features = false } # rusk dependencies -rusk-abi = { version = "0.13.0-rc", path = "../rusk-abi", default-features = false } execution-core = { version = "0.1.0", path = "../execution-core" } [dev-dependencies] diff --git a/test-wallet/src/imp.rs b/test-wallet/src/imp.rs index a1ea4f32bf..28212e6fc6 100644 --- a/test-wallet/src/imp.rs +++ b/test-wallet/src/imp.rs @@ -13,14 +13,21 @@ use alloc::vec::Vec; use dusk_bytes::Error as BytesError; use execution_core::{ - stake::{Stake, StakeData, Withdraw as StakeWithdraw}, + signatures::{ + bls::PublicKey as BlsPublicKey, schnorr::SecretKey as SchnorrSecretKey, + }, + stake::{Stake, StakeData, Withdraw as StakeWithdraw, STAKE_CONTRACT}, transfer::{ - AccountData, ContractCall, ContractDeploy, ContractExec, Fee, - MoonlightPayload, PhoenixPayload, Transaction, Withdraw, - WithdrawReceiver, WithdrawReplayToken, + contract_exec::{ContractCall, ContractDeploy, ContractExec}, + moonlight::{AccountData, Payload as MoonlightPayload}, + phoenix::{ + Error as PhoenixError, Fee, Note, Payload as PhoenixPayload, + PublicKey, SecretKey, TxSkeleton, ViewKey, OUTPUT_NOTES, + }, + withdraw::{Withdraw, WithdrawReceiver, WithdrawReplayToken}, + Transaction, }, - BlsPublicKey, BlsScalar, JubJubScalar, Note, PhoenixError, PublicKey, - SchnorrSecretKey, SecretKey, TxSkeleton, ViewKey, OUTPUT_NOTES, + BlsScalar, JubJubScalar, }; use ff::Field; use rand_core::{CryptoRng, Error as RngError, RngCore}; @@ -486,7 +493,7 @@ where .to_vec(); let contract_call = ContractCall { - contract: rusk_abi::STAKE_CONTRACT.to_bytes(), + contract: STAKE_CONTRACT, fn_name: String::from(TX_STAKE), fn_args: stake_bytes, }; @@ -555,7 +562,7 @@ where let withdraw = Withdraw::new( rng, ¬e_sk, - rusk_abi::STAKE_CONTRACT.to_bytes(), + STAKE_CONTRACT, amount.value, WithdrawReceiver::Phoenix(address), WithdrawReplayToken::Phoenix(nullifiers), @@ -569,7 +576,7 @@ where .to_vec(); ContractCall { - contract: rusk_abi::STAKE_CONTRACT.to_bytes(), + contract: STAKE_CONTRACT, fn_name: String::from(TX_UNSTAKE), fn_args: withdraw_bytes, } @@ -632,7 +639,7 @@ where let withdraw = Withdraw::new( rng, ¬e_sk, - rusk_abi::STAKE_CONTRACT.to_bytes(), + STAKE_CONTRACT, stake.reward, WithdrawReceiver::Phoenix(address), WithdrawReplayToken::Phoenix(nullifiers), @@ -645,7 +652,7 @@ where .to_vec(); ContractCall { - contract: rusk_abi::STAKE_CONTRACT.to_bytes(), + contract: STAKE_CONTRACT, fn_name: String::from(TX_WITHDRAW), fn_args: unstake_bytes, } @@ -717,7 +724,7 @@ where }; let digest = payload.to_hash_input_bytes(); - let signature = from_sk.sign(&from, &digest); + let signature = from_sk.sign(&digest); Ok(Transaction::moonlight(payload, signature)) } diff --git a/test-wallet/src/lib.rs b/test-wallet/src/lib.rs index 06e25b647f..5db7beab69 100644 --- a/test-wallet/src/lib.rs +++ b/test-wallet/src/lib.rs @@ -17,9 +17,16 @@ mod imp; use alloc::vec::Vec; use dusk_bytes::{DeserializableSlice, Serializable, Write}; use execution_core::{ + signatures::bls::{PublicKey as BlsPublicKey, SecretKey as BlsSecretKey}, stake::StakeData, - transfer::{AccountData, Transaction, TRANSFER_TREE_DEPTH}, - BlsPublicKey, BlsScalar, BlsSecretKey, Note, SecretKey, ViewKey, + transfer::{ + moonlight::AccountData, + phoenix::{ + Note, SecretKey as PhoenixSecretKey, ViewKey, NOTES_TREE_DEPTH, + }, + Transaction, + }, + BlsScalar, }; use poseidon_merkle::Opening as PoseidonOpening; use rand_chacha::ChaCha12Rng; @@ -30,7 +37,7 @@ pub use imp::*; pub use rusk_prover::UnprovenTransaction; /// The maximum size of call data. -pub const MAX_CALL_SIZE: usize = rusk_abi::ARGBUF_LEN; +pub const MAX_CALL_SIZE: usize = execution_core::ARGBUF_LEN; /// Stores the cryptographic material necessary to derive cryptographic keys. pub trait Store { @@ -46,7 +53,10 @@ pub trait Store { /// every time with [`generate_sk`]. It may be reimplemented to /// provide a cache for keys, or implement a different key generation /// algorithm. - fn fetch_secret_key(&self, index: u64) -> Result { + fn fetch_secret_key( + &self, + index: u64, + ) -> Result { let seed = self.get_seed()?; Ok(derive_sk(&seed, index)) } @@ -72,7 +82,7 @@ pub trait Store { /// `index` are passed through SHA-256. A constant is then mixed in and the /// resulting hash is then used to seed a `ChaCha12` CSPRNG, which is /// subsequently used to generate the key. -pub fn derive_sk(seed: &[u8; 64], index: u64) -> SecretKey { +pub fn derive_sk(seed: &[u8; 64], index: u64) -> PhoenixSecretKey { let mut hash = Sha256::new(); hash.update(seed); @@ -82,7 +92,7 @@ pub fn derive_sk(seed: &[u8; 64], index: u64) -> SecretKey { let hash = hash.finalize().into(); let mut rng = ChaCha12Rng::from_seed(hash); - SecretKey::random(&mut rng) + PhoenixSecretKey::random(&mut rng) } /// Generates a secret key from its seed and index. @@ -147,7 +157,7 @@ pub trait StateClient { fn fetch_opening( &self, note: &Note, - ) -> Result, Self::Error>; + ) -> Result, Self::Error>; /// Queries the node for the stake of a key. If the key has no stake, a /// `Default` stake info should be returned.