diff --git a/Cargo.lock b/Cargo.lock index 28a3e37705..6168ec28df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -683,16 +683,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "console_error_panic_hook" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" -dependencies = [ - "cfg-if", - "wasm-bindgen", -] - [[package]] name = "constant_time_eq" version = "0.2.5" @@ -955,7 +945,7 @@ dependencies = [ [[package]] name = "dashcore-rpc" version = "0.15.0" -source = "git+https://github.com/dashpay/rust-dashcore-rpc?branch=feat/quorumListImprovement#4362e2f90677fac3a53cb5b5a9f3e52a9b1665b2" +source = "git+https://github.com/dashpay/rust-dashcore-rpc?branch=feat/quorumListImprovement#03b1c43b33670644971e1557ce97dc17bf55e868" dependencies = [ "dashcore-rpc-json", "env_logger 0.10.0", @@ -968,7 +958,7 @@ dependencies = [ [[package]] name = "dashcore-rpc-json" version = "0.15.0" -source = "git+https://github.com/dashpay/rust-dashcore-rpc?branch=feat/quorumListImprovement#4362e2f90677fac3a53cb5b5a9f3e52a9b1665b2" +source = "git+https://github.com/dashpay/rust-dashcore-rpc?branch=feat/quorumListImprovement#03b1c43b33670644971e1557ce97dc17bf55e868" dependencies = [ "dashcore", "hex", @@ -1877,6 +1867,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "lhash" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6df04c84bd3f83849dd23c51be4cbb22a02d80ebdea22741558fe30127d837ae" + [[package]] name = "libc" version = "0.2.141" @@ -3120,16 +3116,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde-wasm-bindgen" -version = "0.5.0" -source = "git+https://github.com/QuantumExplorer/serde-wasm-bindgen?branch=feat/not_human_readable#121d1f7fbf62cb97f74b91626a1b23851098cc82" -dependencies = [ - "js-sys", - "serde", - "wasm-bindgen", -] - [[package]] name = "serde_bytes" version = "0.11.9" @@ -3435,7 +3421,7 @@ dependencies = [ [[package]] name = "tenderdash-abci" version = "0.12.0-dev.1" -source = "git+https://github.com/dashpay/rs-tenderdash-abci?branch=master#d61717a0c6ae129f6f64da6189d1e59e176ed42c" +source = "git+https://github.com/dashpay/rs-tenderdash-abci#e66a7d6683c0843f11e980f8f057ea78feccef49" dependencies = [ "bytes", "prost", @@ -3450,12 +3436,13 @@ dependencies = [ [[package]] name = "tenderdash-proto" version = "0.12.0-dev.1" -source = "git+https://github.com/dashpay/rs-tenderdash-abci?branch=master#d61717a0c6ae129f6f64da6189d1e59e176ed42c" +source = "git+https://github.com/dashpay/rs-tenderdash-abci#e66a7d6683c0843f11e980f8f057ea78feccef49" dependencies = [ "bytes", "chrono", "derive_more", "flex-error", + "lhash", "num-derive", "num-traits", "prost", @@ -3468,7 +3455,7 @@ dependencies = [ [[package]] name = "tenderdash-proto-compiler" version = "0.1.0" -source = "git+https://github.com/dashpay/rs-tenderdash-abci?branch=master#d61717a0c6ae129f6f64da6189d1e59e176ed42c" +source = "git+https://github.com/dashpay/rs-tenderdash-abci#e66a7d6683c0843f11e980f8f057ea78feccef49" dependencies = [ "fs_extra", "prost-build", @@ -3925,18 +3912,6 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "wasm-bindgen-macro" version = "0.2.84" @@ -3966,25 +3941,6 @@ version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" -[[package]] -name = "wasm-dpp" -version = "0.1.0" -dependencies = [ - "anyhow", - "async-trait", - "console_error_panic_hook", - "dpp", - "itertools", - "js-sys", - "serde", - "serde-wasm-bindgen", - "serde_json", - "thiserror", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "web-sys" version = "0.3.61" diff --git a/Cargo.toml b/Cargo.toml index 0af5b8688a..1e84b12d92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,5 +12,5 @@ members = [ "packages/masternode-reward-shares-contract", "packages/feature-flags-contract", "packages/dpns-contract", - "packages/data-contracts" + "packages/data-contracts", ] diff --git a/packages/rs-dpp/Cargo.toml b/packages/rs-dpp/Cargo.toml index 70d9b7e790..2cee32bdc7 100644 --- a/packages/rs-dpp/Cargo.toml +++ b/packages/rs-dpp/Cargo.toml @@ -5,46 +5,57 @@ edition = "2018" authors = ["Anton Suprunchuk "] [dependencies] -anyhow = { version = "1.0.70"} -async-trait = { version = "0.1"} +anyhow = { version = "1.0.70" } +async-trait = { version = "0.1" } base64 = "0.20.0" bls-signatures = { git = "https://github.com/dashpay/bls-signatures", branch = "feat/threshold_bindings" } bs58 = "0.4.0" -byteorder = { version="1.4"} -chrono = { version="0.4.20", default-features=false, features=["wasmbind", "clock"]} -ciborium = { git="https://github.com/qrayven/ciborium", branch="feat-ser-null-as-undefined"} -dashcore = { git="https://github.com/dashpay/rust-dashcore", features=["std", "secp-recovery", "rand", "signer", "use-serde"], default-features = false, branch = "feat/addons" } -env_logger = { version="0.9"} -futures = { version ="0.3"} -getrandom= { version="0.2", features=["js"]} -hex = { version = "0.4"} -integer-encoding = { version="3.0.4"} -itertools = { version ="0.10"} +byteorder = { version = "1.4" } +chrono = { version = "0.4.20", default-features = false, features = [ + "wasmbind", + "clock", +] } +ciborium = { git = "https://github.com/qrayven/ciborium", branch = "feat-ser-null-as-undefined" } +dashcore = { git = "https://github.com/dashpay/rust-dashcore", features = [ + "std", + "secp-recovery", + "rand", + "signer", + "use-serde", +], default-features = false, branch = "feat/addons" } +env_logger = { version = "0.9" } +futures = { version = "0.3" } +getrandom = { version = "0.2", features = ["js"] } +hex = { version = "0.4" } +integer-encoding = { version = "3.0.4" } +itertools = { version = "0.10" } json-patch = "0.2.6" jsonptr = "0.1.5" -jsonschema = { git="https://github.com/fominok/jsonschema-rs", branch="feat-unevaluated-properties", default-features=false, features=["draft202012"] } -lazy_static = { version ="1.4"} -log = { version="0.4"} +jsonschema = { git = "https://github.com/fominok/jsonschema-rs", branch = "feat-unevaluated-properties", default-features = false, features = [ + "draft202012", +] } +lazy_static = { version = "1.4" } +log = { version = "0.4" } num_enum = "0.5.7" -bincode = { version="2.0.0-rc.3", features=["serde"] } +bincode = { version = "2.0.0-rc.3", features = ["serde"] } rand = { version = "0.8.4", features = ["small_rng"] } -regex = { version="1.5"} -serde = { version="1.0.152", features=["derive"]} +regex = { version = "1.5" } +serde = { version = "1.0.152", features = ["derive"] } serde-big-array = "0.4.1" serde_cbor = "0.11.2" -serde_json = { version="1.0", features=["preserve_order"]} +serde_json = { version = "1.0", features = ["preserve_order"] } serde_repr = { version = "0.1.7" } -sha2 = { version="0.10"} -thiserror = { version = "1.0"} -mockall = { version="0.11.3", optional=true} +sha2 = { version = "0.10" } +thiserror = { version = "1.0" } +mockall = { version = "0.11.3", optional = true } data-contracts = { path = "../data-contracts" } platform-value = { path = "../rs-platform-value" } derive_more = "0.99.17" [dev-dependencies] -test-case = { version ="2.0"} -tokio = { version ="1.17", features=["full"]} -pretty_assertions = { version="1.3.0"} +test-case = { version = "2.0" } +tokio = { version = "1.17", features = ["full"] } +pretty_assertions = { version = "1.3.0" } [features] default = ["fixtures-and-mocks"] diff --git a/packages/rs-drive-abci/.env.example b/packages/rs-drive-abci/.env.example index 29c9705c2e..265175ac1d 100644 --- a/packages/rs-drive-abci/.env.example +++ b/packages/rs-drive-abci/.env.example @@ -62,3 +62,5 @@ TENDERDASH_P2P_PORT=26656 QUORUM_SIZE=5 QUORUM_TYPE=llmq_25_67 +CHAIN_ID=devnet +BLOCK_SPACING_MS=3000 diff --git a/packages/rs-drive-abci/Cargo.toml b/packages/rs-drive-abci/Cargo.toml index 16fc0d310f..2229d7a9d9 100644 --- a/packages/rs-drive-abci/Cargo.toml +++ b/packages/rs-drive-abci/Cargo.toml @@ -50,15 +50,24 @@ tracing-subscriber = { version = "0.3.16", default-features = false, features = "ansi", ], optional = true } atty = { version = "0.2.14", optional = true } -tenderdash-abci = { git = "https://github.com/dashpay/rs-tenderdash-abci", branch = "master", optional = true } -# tenderdash-abci = { path = "/home/lklimek/git/lklimek/tenderdash-abci-rs/abci", optional = true } +tenderdash-abci = { git = "https://github.com/dashpay/rs-tenderdash-abci", optional = true } +# tenderdash-abci = { path = "../../../rs-tenderdash-abci/abci", optional = true } anyhow = { version = "1.0.70" } lazy_static = "1.4.0" itertools = { version = "0.10.5" } +[dev-dependencies] + [features] default = ["server"] -server = ["tenderdash-abci", "clap", "dotenvy", "tracing-subscriber", "atty", "mockall"] +server = [ + "tenderdash-abci", + "clap", + "dotenvy", + "tracing-subscriber", + "atty", + "mockall", +] [[bin]] name = "drive-abci" diff --git a/packages/rs-drive-abci/src/abci/commit.rs b/packages/rs-drive-abci/src/abci/commit.rs new file mode 100644 index 0000000000..2393f73bb1 --- /dev/null +++ b/packages/rs-drive-abci/src/abci/commit.rs @@ -0,0 +1,174 @@ +//! Processing of commits generated by Tenderdash +use crate::execution::finalize_block_cleaned_request::{CleanedBlockId, CleanedCommitInfo}; +use dashcore_rpc::dashcore_rpc_json::QuorumType; +use tenderdash_abci::proto::{self, signatures::SignDigest}; + +use super::AbciError; + +/// Represents block commit +pub struct Commit { + inner: proto::types::Commit, + chain_id: String, + quorum_type: QuorumType, +} + +impl Commit { + /// Create new Commit struct based on commit info and block id received from Tenderdash + pub fn new( + ci: CleanedCommitInfo, + block_id: CleanedBlockId, + height: u64, + quorum_type: QuorumType, + chain_id: &str, + ) -> Self { + Self { + chain_id: String::from(chain_id), + quorum_type: quorum_type, + + inner: proto::types::Commit { + block_id: Some(block_id.try_into().expect("cannot convert block id")), + height: height as i64, + round: ci.round as i32, + quorum_hash: ci.quorum_hash.to_vec(), + threshold_block_signature: ci.block_signature.to_vec(), + threshold_vote_extensions: ci.threshold_vote_extensions.to_vec(), + }, + } + } + + /// Verify all signatures using provided public key. + /// + /// ## Return value + /// + /// * Ok(true) when all signatures are correct + /// * Ok(false) when at least one signature is invalid + /// * Err(e) on error + pub fn verify_signature( + &self, + signature: &Vec, + public_key: &bls_signatures::PublicKey, + ) -> Result { + // We could have received a fake commit, so signature validation needs to be returned if error as a simple validation result + let signature = + bls_signatures::Signature::from_bytes(&signature).map_err(AbciError::from)?; + + let hash = self + .inner + .sign_digest( + &self.chain_id, + self.quorum_type as u8, + &self.inner.quorum_hash, + self.inner.height, + self.inner.round, + ) + .map_err(AbciError::TenderdashProto)?; + + Ok(public_key.verify(&signature, &hash)) + } +} + +#[cfg(test)] +mod test { + use crate::execution::finalize_block_cleaned_request::CleanedCommitInfo; + + use super::Commit; + use bls_signatures::PublicKey; + use dashcore_rpc::{ + dashcore::hashes::sha256, dashcore::hashes::Hash, dashcore_rpc_json::QuorumType, + }; + use tenderdash_abci::proto::{ + signatures::{SignBytes, SignDigest}, + types::{BlockId, PartSetHeader, StateId}, + }; + + /// Given a commit info and a signature, check that the signature is verified correctly + #[test] + fn test_commit_verify() { + const HEIGHT: i64 = 12345; + const ROUND: u32 = 2; + const CHAIN_ID: &str = "test_chain_id"; + + const QUORUM_HASH: [u8; 32] = [0u8; 32]; + + let ci = CleanedCommitInfo { + round: ROUND, + quorum_hash: QUORUM_HASH, + block_signature: [0u8; 96], + threshold_vote_extensions: Vec::new(), + }; + let app_hash = [1u8, 2, 3, 4].repeat(8); + + let state_id = StateId { + height: HEIGHT as u64, + app_hash, + app_version: 1, + core_chain_locked_height: 3, + time: Some(tenderdash_abci::proto::google::protobuf::Timestamp { + seconds: 0, + nanos: 0, + }), + }; + + let block_id = BlockId { + hash: sha256::Hash::hash("blockID_hash".as_bytes()).to_vec(), + part_set_header: Some(PartSetHeader { + total: 1000000, + hash: sha256::Hash::hash("blockID_part_set_header_hash".as_bytes()).to_vec(), + }), + state_id: state_id + .sha256(CHAIN_ID, HEIGHT as i64, ROUND as i32) + .unwrap(), + }; + let pubkey = hex::decode( + "b7b76cbef11f48952b4c9778b0cd1e27948c6438c0480e69ce78\ + dc4748611f4463389450a6898f91b08f1de666934324", + ) + .unwrap(); + + let pubkey = PublicKey::from_bytes(pubkey.as_slice()).unwrap(); + let signature = hex::decode("95e4a532ccb549cd4feca372b61dd2a5dedea2bb5c33ac22d70e310f\ + 7e38126b21029c29e6af6d00462b7c6f5e47047414dbfb2e1008fa0969a246bc38b61e96edddea9c35a01670b0ae45f0\ + 8a2626b251bb2a8e937547e65994f2c72d2e8f4e").unwrap(); + + let commit = Commit::new( + ci, + block_id.try_into().unwrap(), + HEIGHT as u64, + QuorumType::LlmqTest, + CHAIN_ID, + ); + + let expect_sign_bytes = hex::decode("0200000039300000000000000200000000000000\ + 35117edfe49351da1e81d1b0f2edfa0b984a7508958870337126efb352f1210711ae5fef92053e8998c37cb4\ + 915968cadfbd2af4fa176b77ade0dadc74028fc5746573745f636861696e5f6964").unwrap(); + let expect_sign_id = + hex::decode("6f3cb0168cfaf3d9806be8a9eaa85d6ac10e2d32ce02e6a965a66f6c598b06cf") + .unwrap(); + assert_eq!( + expect_sign_bytes, + commit + .inner + .sign_bytes(CHAIN_ID, HEIGHT, ROUND as i32) + .unwrap() + ); + assert_eq!( + expect_sign_id, + commit + .inner + .sign_digest( + CHAIN_ID, + QuorumType::LlmqTest as u8, + &QUORUM_HASH, + HEIGHT, + ROUND as i32 + ) + .unwrap() + ); + assert!(commit.verify_signature(&signature, &pubkey).unwrap()); + + // mutate data and ensure it is invalid + let mut commit = commit; + commit.chain_id = "invalid".to_string(); + assert!(!commit.verify_signature(&signature, &pubkey).unwrap()); + } +} diff --git a/packages/rs-drive-abci/src/abci/config.rs b/packages/rs-drive-abci/src/abci/config.rs index 00ec128a8d..5e348d4359 100644 --- a/packages/rs-drive-abci/src/abci/config.rs +++ b/packages/rs-drive-abci/src/abci/config.rs @@ -33,6 +33,10 @@ pub struct AbciConfig { /// Height of core at genesis #[serde(default = "AbciConfig::default_genesis_core_height")] pub genesis_core_height: u32, + + /// Chain ID of the network to use + #[serde(default)] + pub chain_id: String, } impl AbciConfig { diff --git a/packages/rs-drive-abci/src/abci/error.rs b/packages/rs-drive-abci/src/abci/error.rs index 2689d9bf9c..0a249a6df0 100644 --- a/packages/rs-drive-abci/src/abci/error.rs +++ b/packages/rs-drive-abci/src/abci/error.rs @@ -39,6 +39,10 @@ pub enum AbciError { #[error("tenderdash: {0}")] Tenderdash(#[from] tenderdash_abci::Error), + /// Error occurred during protobuf data manipulation + #[error("tenderdash data: {0}")] + TenderdashProto(tenderdash_abci::proto::Error), + /// Error occurred during signature verification or deserializing a BLS primitive #[error("bls error: {0}")] BlsError(#[from] BlsError), diff --git a/packages/rs-drive-abci/src/abci/handlers.rs b/packages/rs-drive-abci/src/abci/handlers.rs index 861780b67f..da6e37881e 100644 --- a/packages/rs-drive-abci/src/abci/handlers.rs +++ b/packages/rs-drive-abci/src/abci/handlers.rs @@ -231,7 +231,7 @@ where if !block_state_info.matches_current_block(height as u64, round as u32, block_hash)? { return Err(Error::from(AbciError::RequestForWrongBlockReceived(format!( - "received request for height: {} rount: {}, expected height: {} round: {}", + "received request for height: {} round: {}, expected height: {} round: {}", height, round, block_state_info.height, block_state_info.round ))) .into()); @@ -276,7 +276,7 @@ where if !block_state_info.matches_current_block(height as u64, round as u32, block_hash)? { return Err(Error::from(AbciError::RequestForWrongBlockReceived(format!( - "received request for height: {} rount: {}, expected height: {} round: {}", + "received request for height: {} round: {}, expected height: {} round: {}", height, round, block_state_info.height, block_state_info.round ))) .into()); @@ -311,7 +311,14 @@ where // }); // }; - let validation_result = self.platform.check_withdrawals(&got, &expected, None); + let validation_result = self.platform.check_withdrawals( + &got, + &expected, + height as u64, + round as u32, + None, + None, + ); if validation_result.is_valid() { Ok(proto::ResponseVerifyVoteExtension { diff --git a/packages/rs-drive-abci/src/abci/mimic.rs b/packages/rs-drive-abci/src/abci/mimic.rs index 8ca6777aa9..804dd799c2 100644 --- a/packages/rs-drive-abci/src/abci/mimic.rs +++ b/packages/rs-drive-abci/src/abci/mimic.rs @@ -34,7 +34,7 @@ impl<'a, C: CoreRPCLike> AbciApplication<'a, C> { current_quorum: &Quorum, next_quorum: &Quorum, proposed_version: ProtocolVersion, - total_hpmns: u32, + _total_hpmns: u32, block_info: BlockInfo, expect_validation_errors: bool, state_transitions: Vec, @@ -48,7 +48,7 @@ impl<'a, C: CoreRPCLike> AbciApplication<'a, C> { time_ms, height, core_height, - epoch, + epoch: _, } = block_info; let request_prepare_proposal = RequestPrepareProposal { @@ -82,9 +82,9 @@ impl<'a, C: CoreRPCLike> AbciApplication<'a, C> { tx_records, app_hash, tx_results, - consensus_param_updates, - core_chain_lock_update, - validator_set_update, + consensus_param_updates: _, + core_chain_lock_update: _, + validator_set_update: _, } = response_prepare_proposal; if expect_validation_errors == false { diff --git a/packages/rs-drive-abci/src/abci/mod.rs b/packages/rs-drive-abci/src/abci/mod.rs index 7b6e8e98dc..35705362f5 100644 --- a/packages/rs-drive-abci/src/abci/mod.rs +++ b/packages/rs-drive-abci/src/abci/mod.rs @@ -15,7 +15,7 @@ pub mod mimic; #[cfg(any(feature = "server", test))] mod server; -pub mod signature_verifier; +pub mod commit; pub mod withdrawal; pub use error::AbciError; diff --git a/packages/rs-drive-abci/src/abci/signature_verifier.rs b/packages/rs-drive-abci/src/abci/signature_verifier.rs deleted file mode 100644 index 743ec73c81..0000000000 --- a/packages/rs-drive-abci/src/abci/signature_verifier.rs +++ /dev/null @@ -1,43 +0,0 @@ -//! Signature verification - -use bls_signatures::{BlsError, PublicKey as BlsPublicKey, Signature as BlsSignature}; -use dpp::validation::ValidationResult; -use tenderdash_abci::proto::types::VoteExtension; - -/// SignatureVerifier can be used to verify a BLS signature. -pub trait SignatureVerifier { - /// Verify all signatures using provided public key. - /// - /// ## Return value - /// - /// * Ok(true) when all signatures are correct - /// * Ok(false) when at least one signature is invalid - /// * Err(e) on error - fn verify_signature(&self, public_key: &BlsPublicKey) -> ValidationResult; -} - -impl SignatureVerifier for Vec { - fn verify_signature(&self, public_key: &BlsPublicKey) -> ValidationResult { - for tx in self { - match tx.verify_signature(public_key) { - ValidationResult { .. } => {} - } - } - - ValidationResult::new_with_data(true) - } -} - -impl SignatureVerifier for VoteExtension { - fn verify_signature(&self, public_key: &BlsPublicKey) -> ValidationResult { - // We could have received a fake commit, so signature validation needs to be returned if error as a simple validation result - let _signature = match BlsSignature::from_bytes(self.signature.as_slice()) { - Ok(signature) => signature, - Err(e) => return ValidationResult::new_with_error(e), - }; - // TODO: implement correct signature verification for VoteExtension. It uses CanonicalVoteExtension. - // For now, we just return `true` - // Ok(quorum_public_key.verify(signature, &self.extension)) - ValidationResult::new_with_data(true) - } -} diff --git a/packages/rs-drive-abci/src/abci/withdrawal.rs b/packages/rs-drive-abci/src/abci/withdrawal.rs index 1be8a4b9d5..e4f4c3d01e 100644 --- a/packages/rs-drive-abci/src/abci/withdrawal.rs +++ b/packages/rs-drive-abci/src/abci/withdrawal.rs @@ -1,7 +1,8 @@ //! Withdrawal transactions definitions and processing -use bls_signatures::{self, BlsError}; -use dpp::validation::ValidationResult; +use bls_signatures; +use dashcore_rpc::dashcore_rpc_json::QuorumType; +use dpp::validation::SimpleValidationResult; use drive::{ drive::{batch::DriveOperation, block_info::BlockInfo, Drive}, fee::result::FeeResult, @@ -10,10 +11,11 @@ use drive::{ use std::fmt::Display; use tenderdash_abci::proto::{ abci::ExtendVoteExtension, + signatures::SignDigest, types::{VoteExtension, VoteExtensionType}, }; -use super::{signature_verifier::SignatureVerifier, AbciError}; +use super::AbciError; const MAX_WITHDRAWAL_TXS: u16 = 16; @@ -100,6 +102,57 @@ impl<'a> WithdrawalTxs<'a> { }) .collect::>() } + + /// Verify signatures of all withdrawal TXs + /// + /// ## Return value + /// + /// There are the following types of errors during verification: + /// + /// 1. The signature was invalid, most likely due to change in the data; in this case, + /// [AbciError::VoteExtensionsSignatureInvalid] is returned. + /// 2. Signature or public key is malformed - in this case, [AbciError::BlsError] is returned + /// 3. Provided data is invalid - [AbciError::TenderdashProto] is returned + /// + /// As all these conditions, in normal circumstances, should cause processing to be terminated, they are all + /// treated as errors. + pub fn verify_signatures( + &self, + chain_id: &str, + quorum_type: QuorumType, + quorum_hash: &[u8], + height: u64, + round: u32, + public_key: &bls_signatures::PublicKey, + ) -> SimpleValidationResult { + for s in &self.inner { + let hash = match s.sign_digest( + chain_id, + quorum_type as u8, + quorum_hash, + height as i64, + round as i32, + ) { + Ok(h) => h, + Err(e) => { + return SimpleValidationResult::new_with_error(AbciError::TenderdashProto(e)) + } + }; + + let signature = match bls_signatures::Signature::from_bytes(&s.signature) { + Ok(s) => s, + Err(e) => return SimpleValidationResult::new_with_error(AbciError::BlsError(e)), + }; + + if !public_key.verify(&signature, &hash) { + return SimpleValidationResult::new_with_error( + AbciError::VoteExtensionsSignatureInvalid, + ); + } + } + + SimpleValidationResult::default() + } } impl<'a> Display for WithdrawalTxs<'a> { @@ -143,17 +196,86 @@ impl<'a> From<&Vec> for WithdrawalTxs<'a> { impl<'a> PartialEq for WithdrawalTxs<'a> { /// Two sets of withdrawal transactions are equal if all their inner raw transactions are equal. - /// Note we don't compare `drive_operations`. + /// + /// ## Notes + /// + /// 1. We don't compare `drive_operations`, as this is internal utility fields + /// 2. For a transaction, we don't compare signatures if at least one of them is empty fn eq(&self, other: &Self) -> bool { - self.inner.eq(&other.inner) + if self.inner.len() != other.inner.len() { + return false; + } + + std::iter::zip(&self.inner, &other.inner).all(|(left, right)| { + left.r#type == right.r#type + && left.extension == right.extension + && (left.signature.len() == 0 + || right.signature.len() == 0 + || left.signature == right.signature) + }) } } +#[cfg(test)] +mod test { + use dashcore_rpc::dashcore_rpc_json::QuorumType; + use tenderdash_abci::proto::types::{VoteExtension, VoteExtensionType}; -impl<'a> SignatureVerifier for WithdrawalTxs<'a> { - fn verify_signature( - &self, - public_key: &bls_signatures::PublicKey, - ) -> ValidationResult { - self.inner.verify_signature(public_key) + #[test] + fn verify_signature() { + const HEIGHT: u64 = 100; + const ROUND: u32 = 0; + const CHAIN_ID: &str = "test-chain"; + + let quorum_hash = + hex::decode("D6711FA18C7DA6D3FF8615D3CD3C14500EE91DA5FA942425B8E2B79A30FD8E6C") + .unwrap(); + + let mut wt = super::WithdrawalTxs { + inner: Vec::new(), + drive_operations: Vec::new(), + }; + let pubkey = hex::decode("8280cb6694f181db486c59dfa0c6d12d1c4ca26789340aebad0540ffe2edeac387aceec979454c2cfbe75fd8cf04d56d").unwrap(); + let pubkey = bls_signatures::PublicKey::from_bytes(&pubkey).unwrap(); + + let signature = hex::decode("A1022D9503CCAFC94FF76FA2E58E10A0474E6EB46305009274FAFCE57E28C7DE57602277777D07855567FAEF6A2F27590258858A875707F4DA32936DDD556BA28455AB04D9301E5F6F0762AC5B9FC036A302EE26116B1F89B74E1457C2D7383A").unwrap(); + // check if signature is correct + bls_signatures::Signature::from_bytes(&signature).unwrap(); + wt.inner.push(VoteExtension { + extension: [ + 82u8, 79, 29, 3, 209, 216, 30, 148, 160, 153, 4, 39, 54, 212, 11, 217, 104, 27, + 134, 115, 33, 68, 63, 245, 138, 69, 104, 226, 116, 219, 216, 59, + ] + .into(), + signature, + r#type: VoteExtensionType::ThresholdRecover.into(), + }); + + assert_eq!( + true, + wt.verify_signatures( + CHAIN_ID, + QuorumType::LlmqTest, + &quorum_hash, + HEIGHT, + ROUND, + &pubkey + ) + .is_valid() + ); + + // Now break the data + wt.inner[0].extension[3] = 0; + assert_eq!( + false, + wt.verify_signatures( + CHAIN_ID, + QuorumType::LlmqTest, + &quorum_hash, + HEIGHT, + ROUND, + &pubkey + ) + .is_valid() + ); } } diff --git a/packages/rs-drive-abci/src/asset_lock/fetch_tx_out.rs b/packages/rs-drive-abci/src/asset_lock/fetch_tx_out.rs index 1da24471f9..5400ece500 100644 --- a/packages/rs-drive-abci/src/asset_lock/fetch_tx_out.rs +++ b/packages/rs-drive-abci/src/asset_lock/fetch_tx_out.rs @@ -1,14 +1,14 @@ use crate::error::execution::ExecutionError; use crate::error::Error; use crate::rpc::core::CoreRPCLike; -use dashcore::hashes::Hash; -use dashcore::{OutPoint, TxOut}; use dpp::consensus::basic::identity::{ IdentityAssetLockTransactionIsNotFoundError, IdentityAssetLockTransactionOutputNotFoundError, InvalidAssetLockProofCoreChainHeightError, InvalidIdentityAssetLockProofChainLockValidationError, }; use dpp::consensus::ConsensusError; +use dpp::dashcore::hashes::Hash; +use dpp::dashcore::{OutPoint, TxOut}; use dpp::identity::state_transition::asset_lock_proof::AssetLockProof; use dpp::validation::ValidationResult; @@ -47,7 +47,7 @@ impl FetchAssetLockProofTxOut for AssetLockProof { let transaction_data = match core.get_transaction_extended_info(&transaction_hash) { Ok(transaction) => transaction, - Err(e) => { + Err(_e) => { //todo: deal with IO errors return Ok(ValidationResult::new_with_error( ConsensusError::IdentityAssetLockTransactionIsNotFoundError( @@ -80,7 +80,7 @@ impl FetchAssetLockProofTxOut for AssetLockProof { let transaction_data = match core.get_transaction_extended_info(&transaction_hash) { Ok(transaction) => transaction, - Err(e) => { + Err(_e) => { return Ok(ValidationResult::new_with_error( ConsensusError::IdentityAssetLockTransactionIsNotFoundError( IdentityAssetLockTransactionIsNotFoundError::new( diff --git a/packages/rs-drive-abci/src/config.rs b/packages/rs-drive-abci/src/config.rs index daa31bc471..2369b0ea7e 100644 --- a/packages/rs-drive-abci/src/config.rs +++ b/packages/rs-drive-abci/src/config.rs @@ -150,7 +150,7 @@ pub struct PlatformConfig { pub verify_sum_trees: bool, /// The default quorum type - pub quorum_type: QuorumType, + pub quorum_type: String, /// The default quorum size pub quorum_size: u16, @@ -171,6 +171,21 @@ impl PlatformConfig { fn default_verify_sum_trees() -> bool { true } + + /// Return type of quorum + pub fn quorum_type(&self) -> QuorumType { + let found = if let Ok(t) = self.quorum_type.trim().parse::() { + QuorumType::from(t) + } else { + QuorumType::from(self.quorum_type.as_str()) + }; + + if found == QuorumType::UNKNOWN { + panic!("config: unsupported QUORUM_TYPE: {}", self.quorum_type); + } + + found + } } /// create new object using values from environment variables pub trait FromEnv { @@ -189,7 +204,7 @@ impl Default for PlatformConfig { fn default() -> Self { Self { verify_sum_trees: true, - quorum_type: QuorumType::Llmq100_67, + quorum_type: "llmq_100_67".to_string(), quorum_size: 100, block_spacing_ms: 5000, validator_set_quorum_rotation_block_count: 15, @@ -199,6 +214,7 @@ impl Default for PlatformConfig { keys: Keys::new_random_keys_with_seed(18012014), //Dash genesis day genesis_height: 1, genesis_core_height: 0, + chain_id: "chain_id".to_string(), }, core: Default::default(), db_path: PathBuf::from("/var/lib/dash-platform/data"), @@ -208,11 +224,9 @@ impl Default for PlatformConfig { #[cfg(test)] mod tests { - use std::env; - - use dashcore_rpc::dashcore_rpc_json::QuorumType; - use super::FromEnv; + use dashcore_rpc::dashcore_rpc_json::QuorumType; + use std::env; #[test] fn test_config_from_env() { @@ -223,6 +237,6 @@ mod tests { let config = super::PlatformConfig::from_env().unwrap(); assert_eq!(config.verify_sum_trees, true); - assert_ne!(config.quorum_type, QuorumType::UNKNOWN); + assert_ne!(config.quorum_type(), QuorumType::UNKNOWN); } } diff --git a/packages/rs-drive-abci/src/execution/block_proposal.rs b/packages/rs-drive-abci/src/execution/block_proposal.rs index 2085fb7792..0f216f81ac 100644 --- a/packages/rs-drive-abci/src/execution/block_proposal.rs +++ b/packages/rs-drive-abci/src/execution/block_proposal.rs @@ -34,13 +34,13 @@ impl<'a> TryFrom<&'a RequestPrepareProposal> for BlockProposal<'a> { fn try_from(value: &'a RequestPrepareProposal) -> Result { let RequestPrepareProposal { - max_tx_bytes, + max_tx_bytes: _, txs, - local_last_commit, - misbehavior, + local_last_commit: _, + misbehavior: _, height, time, - next_validators_hash, + next_validators_hash: _, round, core_chain_locked_height, proposer_pro_tx_hash, @@ -108,15 +108,15 @@ impl<'a> TryFrom<&'a RequestProcessProposal> for BlockProposal<'a> { fn try_from(value: &'a RequestProcessProposal) -> Result { let RequestProcessProposal { txs, - proposed_last_commit, - misbehavior, + proposed_last_commit: _, + misbehavior: _, hash, height, round, time, - next_validators_hash, + next_validators_hash: _, core_chain_locked_height, - core_chain_lock_update, + core_chain_lock_update: _, proposer_pro_tx_hash, proposed_app_version, version, diff --git a/packages/rs-drive-abci/src/execution/commit_validation.rs b/packages/rs-drive-abci/src/execution/commit_validation.rs deleted file mode 100644 index 0309f02cee..0000000000 --- a/packages/rs-drive-abci/src/execution/commit_validation.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::abci::AbciError; -use crate::error::Error; -use crate::platform::Platform; -use crate::rpc::core::CoreRPCLike; -use bls_signatures; -use dpp::validation::SimpleValidationResult; -use tenderdash_abci::proto::abci::CommitInfo; -use tenderdash_abci::proto::types::BlockId; - -impl Platform -where - C: CoreRPCLike, -{ - /// Validates a commit received in finalize block - /// An explanation can be found here - /// https://github.com/dashpay/tenderdash/blob/v0.12-dev/spec/consensus/signing.md#block-signature-verification-on-light-client - /// - /// Verification algorithm can be described as follows: - /// - /// Build StateID message and encode it using Protobuf encoding. - /// Calculate checksum (SHA256) of encoded StateID. - /// Retrieve or calculate SHA256 checksum of CanonicalBlockID - /// Build CanonicalVote message and encode it using Protobuf. - /// Calculate SHA256 checksum of encoded CanonicalVote. - /// Verify that block signature matches calculated checksum. - pub(crate) fn validate_commit( - &self, - commit: CommitInfo, - block_id: BlockId, - quorum_public_key: bls_signatures::PublicKey, - ) -> Result, Error> { - let signature = commit.block_signature; - // We could have received a fake commit, so signature validation needs to be returned if error as a simple validation result - let signature = match bls_signatures::Signature::from_bytes(signature.as_slice()) { - Ok(signature) => signature, - Err(e) => return Ok(SimpleValidationResult::new_with_error(e.into())), - }; - // todo: public_key.verify(signature, ) - - Ok(SimpleValidationResult::default()) - } -} diff --git a/packages/rs-drive-abci/src/execution/engine.rs b/packages/rs-drive-abci/src/execution/engine.rs index cb2c3ad3b2..e65032f28f 100644 --- a/packages/rs-drive-abci/src/execution/engine.rs +++ b/packages/rs-drive-abci/src/execution/engine.rs @@ -16,7 +16,7 @@ use std::collections::BTreeMap; use tenderdash_abci::proto::abci::ExecTxResult; use tenderdash_abci::proto::serializers::timestamp::ToMilis; -use crate::abci::signature_verifier::SignatureVerifier; +use crate::abci::commit::Commit; use crate::abci::withdrawal::WithdrawalTxs; use crate::abci::AbciError; use crate::block::{BlockExecutionContext, BlockStateInfo}; @@ -396,7 +396,10 @@ where &self, received_withdrawals: &WithdrawalTxs, our_withdrawals: &WithdrawalTxs, + height: u64, + round: u32, verify_with_validator_public_key: Option<&bls_signatures::PublicKey>, + quorum_hash: Option<&[u8]>, ) -> SimpleValidationResult { if received_withdrawals.ne(&our_withdrawals) { return SimpleValidationResult::new_with_error( @@ -409,23 +412,18 @@ where // we only verify if verify_with_validator_public_key exists if let Some(validator_public_key) = verify_with_validator_public_key { - let validation_result = received_withdrawals.verify_signature(validator_public_key); - - // There are two types of errors, - // The first is that the signature was invalid and that is shown with the result bool as false - // The second is that the signature is malformed, and that gives a BLSError - - // However for this case we want to treat both as errors + let quorum_hash = quorum_hash.expect("quorum hash is required to verify signature"); + let validation_result = received_withdrawals.verify_signatures( + &self.config.abci.chain_id, + self.config.quorum_type(), + quorum_hash, + height, + round, + validator_public_key, + ); if validation_result.is_valid() { - let value = validation_result.into_data().expect("expected data"); - if value == true { - SimpleValidationResult::default() - } else { - SimpleValidationResult::new_with_error( - AbciError::VoteExtensionsSignatureInvalid, - ) - } + SimpleValidationResult::default() } else { SimpleValidationResult::new_with_error( validation_result @@ -446,7 +444,7 @@ where let public_key = self .core_rpc .get_quorum_info( - self.config.quorum_type, + self.config.quorum_type(), &QuorumHash::from_inner(quorum_hash), Some(false), )? @@ -483,7 +481,7 @@ where // Let's decompose the request let FinalizeBlockCleanedRequest { - commit, + commit: commit_info, misbehavior, hash, height, @@ -520,28 +518,28 @@ where } let mut state = self.state.write().unwrap(); - if state.current_validator_set_quorum_hash.as_inner() != &commit.quorum_hash { + if state.current_validator_set_quorum_hash.as_inner() != &commit_info.quorum_hash { validation_result.add_error(AbciError::WrongFinalizeBlockReceived(format!( "received a block for h: {} r: {} with validator set quorum hash {} expected current validator set quorum hash is {}", - height, round, hex::encode(commit.quorum_hash), hex::encode(state.current_validator_set_quorum_hash) + height, round, hex::encode(commit_info.quorum_hash), hex::encode(state.current_validator_set_quorum_hash) ))); } - let quorum_public_key = self.get_quorum_key(commit.quorum_hash)?; + let quorum_public_key = self.get_quorum_key(commit_info.quorum_hash)?; - //todo: verify commit - // if let Some(block_id) = block_id { - // let result = self.validate_commit(commit.clone(), block_id, quorum_public_key)?; - // if !result.is_valid() { - // return Ok(validation_result.into()); - // } - // } else { - // validation_result.add_error(AbciError::WrongFinalizeBlockReceived(format!( - // "received a block for h: {} r: {} without a block id", - // height, round - // ))); - // return Ok(validation_result.into()); - // } + // Verify commit + + let quorum_type = self.config.quorum_type(); + let commit = Commit::new( + commit_info.clone(), + block_id.clone(), + height, + quorum_type, + &block_header.chain_id, + ); + commit + .verify_signature(&commit_info.block_signature.to_vec(), &quorum_public_key) + .map_err(AbciError::from)?; // Verify vote extensions // let received_withdrawals = WithdrawalTxs::from(&commit.threshold_vote_extensions); diff --git a/packages/rs-drive-abci/src/execution/finalize_block_cleaned_request.rs b/packages/rs-drive-abci/src/execution/finalize_block_cleaned_request.rs index 215c24134c..ee2bc9ce0a 100644 --- a/packages/rs-drive-abci/src/execution/finalize_block_cleaned_request.rs +++ b/packages/rs-drive-abci/src/execution/finalize_block_cleaned_request.rs @@ -299,6 +299,16 @@ impl TryFrom for CleanedBlockId { } } +impl From for BlockId { + fn from(value: CleanedBlockId) -> Self { + Self { + hash: value.hash.to_vec(), + part_set_header: Some(value.part_set_header), + state_id: value.state_id.to_vec(), + } + } +} + /// The `CleanedBlock` struct represents a block that has been properly formatted. /// It stores essential data required to finalize a block in a simplified format. /// diff --git a/packages/rs-drive-abci/src/execution/helpers.rs b/packages/rs-drive-abci/src/execution/helpers.rs index 9750b27547..75a264873f 100644 --- a/packages/rs-drive-abci/src/execution/helpers.rs +++ b/packages/rs-drive-abci/src/execution/helpers.rs @@ -74,7 +74,7 @@ where .get_quorum_listextended(Some(core_block_height))?; let quorum_info = quorum_list .quorums_by_type - .get(&self.config.quorum_type) + .get(&self.config.quorum_type()) .ok_or(Error::Execution(ExecutionError::DashCoreBadResponseError( format!( "expected quorums of type {}, but did not receive any from Dash Core", @@ -93,7 +93,7 @@ where .map(|(key, _)| { let quorum_info_result = self.core_rpc - .get_quorum_info(self.config.quorum_type, key, None)?; + .get_quorum_info(self.config.quorum_type(), key, None)?; let quorum: Quorum = quorum_info_result.try_into()?; Ok((key.clone(), quorum)) }) diff --git a/packages/rs-drive-abci/src/execution/mod.rs b/packages/rs-drive-abci/src/execution/mod.rs index eb35951d3a..e5a6393775 100644 --- a/packages/rs-drive-abci/src/execution/mod.rs +++ b/packages/rs-drive-abci/src/execution/mod.rs @@ -1,7 +1,5 @@ /// The block proposal pub mod block_proposal; -/// Commit validation -pub mod commit_validation; /// Data triggers pub mod data_trigger; /// Engine module diff --git a/packages/rs-drive-abci/src/platform/mod.rs b/packages/rs-drive-abci/src/platform/mod.rs index e890947d03..9889fcc2b0 100644 --- a/packages/rs-drive-abci/src/platform/mod.rs +++ b/packages/rs-drive-abci/src/platform/mod.rs @@ -43,8 +43,8 @@ use std::path::Path; use std::sync::RwLock; use crate::rpc::core::MockCoreRPCLike; -use dashcore::hashes::hex::FromHex; -use dashcore::BlockHash; +use dpp::dashcore::hashes::hex::FromHex; +use dpp::dashcore::BlockHash; use serde_json::json; mod state_repository;