From 37b865dd0c336ff5cc06edc115f508ede5b51817 Mon Sep 17 00:00:00 2001 From: mrnaveira <47919901+mrnaveira@users.noreply.github.com> Date: Mon, 4 Jul 2022 16:27:25 +0100 Subject: [PATCH 01/13] validate acceptance signature --- .../dan_validators/acceptance_validator.rs | 134 +++++++++++++++--- .../src/validation/dan_validators/error.rs | 2 + .../validation/dan_validators/test_helpers.rs | 47 +++++- 3 files changed, 156 insertions(+), 27 deletions(-) diff --git a/base_layer/core/src/validation/dan_validators/acceptance_validator.rs b/base_layer/core/src/validation/dan_validators/acceptance_validator.rs index 3100758009..55575abd16 100644 --- a/base_layer/core/src/validation/dan_validators/acceptance_validator.rs +++ b/base_layer/core/src/validation/dan_validators/acceptance_validator.rs @@ -20,8 +20,9 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use tari_common_types::types::{FixedHash, PublicKey}; -use tari_utilities::hex::Hex; +use blake2::Digest; +use tari_common_types::types::{Challenge, Commitment, FixedHash, PublicKey, Signature}; +use tari_utilities::{hex::Hex, ByteArray}; use super::helpers::{ fetch_contract_constitution, @@ -54,14 +55,15 @@ pub fn validate_acceptance( let acceptance_features = get_contract_acceptance(sidechain_features)?; let validator_node_public_key = &acceptance_features.validator_node_public_key; + let signature = &acceptance_features.signature; let constitution = fetch_contract_constitution(db, contract_id)?; validate_uniqueness(db, contract_id, validator_node_public_key)?; validate_public_key(&constitution, validator_node_public_key)?; validate_acceptance_window(db, contract_id, &constitution)?; + validate_signature(db, signature, contract_id, validator_node_public_key)?; - // TODO: check that the signature of the transaction is valid // TODO: check that the stake of the transaction is at least the minimum specified in the constitution Ok(()) @@ -150,22 +152,69 @@ pub fn fetch_constitution_height( } } +pub fn validate_signature( + db: &BlockchainDatabase, + signature: &Signature, + contract_id: FixedHash, + validator_node_public_key: &PublicKey, +) -> Result<(), ValidationError> { + let commitment = fetch_constitution_commitment(db, contract_id)?; + + let challenge = Challenge::new() + .chain(validator_node_public_key.as_bytes()) + .chain(commitment.as_bytes()) + .chain(contract_id) + .finalize(); + + let is_valid_signature = signature.verify_challenge(validator_node_public_key, &challenge); + + if !is_valid_signature { + return Err(ValidationError::DanLayerError( + DanLayerValidationError::InvalidAcceptanceSignature, + )); + } + + Ok(()) +} + +pub fn fetch_constitution_commitment( + db: &BlockchainDatabase, + contract_id: FixedHash, +) -> Result { + let outputs: Vec = fetch_contract_utxos(db, contract_id, OutputType::ContractConstitution)? + .into_iter() + .filter_map(|utxo| utxo.output.into_unpruned_output()) + .collect(); + + // Only one constitution should be stored for a particular contract_id + if outputs.is_empty() { + return Err(ValidationError::DanLayerError( + DanLayerValidationError::ContractConstitutionNotFound { contract_id }, + )); + } + + Ok(outputs[0].commitment().clone()) +} + #[cfg(test)] mod test { use std::convert::TryInto; - use tari_common_types::types::PublicKey; - use tari_utilities::hex::Hex; + use tari_common_types::types::{Commitment, PublicKey}; + use super::fetch_constitution_commitment; use crate::{ txn_schema, validation::dan_validators::test_helpers::{ assert_dan_validator_fail, assert_dan_validator_success, + create_acceptance_signature, create_block, create_contract_acceptance_schema, + create_contract_acceptance_schema_with_signature, create_contract_constitution, create_contract_constitution_schema, + create_random_key_pair, init_test_blockchain, publish_constitution, publish_definition, @@ -182,14 +231,17 @@ mod test { let contract_id = publish_definition(&mut blockchain, change[0].clone()); // publish the contract constitution into a block - let validator_node_public_key = PublicKey::default(); + let (private_key, public_key) = create_random_key_pair(); let mut constitution = create_contract_constitution(); - constitution.validator_committee = vec![validator_node_public_key.clone()].try_into().unwrap(); + constitution.validator_committee = vec![public_key.clone()].try_into().unwrap(); publish_constitution(&mut blockchain, change[1].clone(), contract_id, constitution); // create a valid contract acceptance transaction - let schema = create_contract_acceptance_schema(contract_id, change[2].clone(), validator_node_public_key); + let commitment = fetch_constitution_commitment(blockchain.db(), contract_id).unwrap(); + let schema = + create_contract_acceptance_schema(contract_id, commitment, change[2].clone(), private_key, public_key); let (tx, _) = schema_to_transaction(&schema); + assert_dan_validator_success(&blockchain, &tx); } @@ -204,8 +256,10 @@ mod test { // skip the contract constitution publication // create a contract acceptance transaction - let validator_node_public_key = PublicKey::default(); - let schema = create_contract_acceptance_schema(contract_id, change[1].clone(), validator_node_public_key); + let (private_key, public_key) = create_random_key_pair(); + let commitment = Commitment::default(); + let schema = + create_contract_acceptance_schema(contract_id, commitment, change[1].clone(), private_key, public_key); let (tx, _) = schema_to_transaction(&schema); // try to validate the acceptance transaction and check that we get the error @@ -221,18 +275,25 @@ mod test { let contract_id = publish_definition(&mut blockchain, change[0].clone()); // publish the contract constitution into a block - let validator_node_public_key = PublicKey::default(); + let (private_key, public_key) = create_random_key_pair(); let mut constitution = create_contract_constitution(); - constitution.validator_committee = vec![validator_node_public_key.clone()].try_into().unwrap(); + constitution.validator_committee = vec![public_key.clone()].try_into().unwrap(); publish_constitution(&mut blockchain, change[1].clone(), contract_id, constitution); // publish a contract acceptance into a block - let schema = - create_contract_acceptance_schema(contract_id, change[2].clone(), validator_node_public_key.clone()); + let commitment = fetch_constitution_commitment(blockchain.db(), contract_id).unwrap(); + let schema = create_contract_acceptance_schema( + contract_id, + commitment.clone(), + change[2].clone(), + private_key.clone(), + public_key.clone(), + ); create_block(&mut blockchain, "acceptance", schema); // create a (duplicated) contract acceptance transaction - let schema = create_contract_acceptance_schema(contract_id, change[3].clone(), validator_node_public_key); + let schema = + create_contract_acceptance_schema(contract_id, commitment, change[3].clone(), private_key, public_key); let (tx, _) = schema_to_transaction(&schema); // try to validate the duplicated acceptance transaction and check that we get the error @@ -240,7 +301,7 @@ mod test { } #[test] - fn it_rejects_contract_acceptances_of_non_committee_members() { + fn it_rejects_acceptances_of_non_committee_members() { // initialise a blockchain with enough funds to spend at contract transactions let (mut blockchain, change) = init_test_blockchain(); @@ -257,9 +318,10 @@ mod test { // create a contract acceptance transaction // we use a public key that is not included in the constitution committee, to trigger the error - let validator_node_public_key = - PublicKey::from_hex("70350e09c474809209824c6e6888707b7dd09959aa227343b5106382b856f73a").unwrap(); - let schema = create_contract_acceptance_schema(contract_id, change[2].clone(), validator_node_public_key); + let (private_key, public_key) = create_random_key_pair(); + let commitment = fetch_constitution_commitment(blockchain.db(), contract_id).unwrap(); + let schema = + create_contract_acceptance_schema(contract_id, commitment, change[2].clone(), private_key, public_key); let (tx, _) = schema_to_transaction(&schema); // try to validate the acceptance transaction and check that we get the committee error @@ -275,8 +337,8 @@ mod test { let contract_id = publish_definition(&mut blockchain, change[0].clone()); // publish the contract constitution into a block, with a very short (1 block) expiration time - let validator_node_public_key = PublicKey::default(); - let committee = vec![validator_node_public_key.clone()]; + let (private_key, public_key) = create_random_key_pair(); + let committee = vec![public_key.clone()]; let mut constitution = create_contract_constitution(); constitution.validator_committee = committee.try_into().unwrap(); constitution.acceptance_requirements.acceptance_period_expiry = 1; @@ -289,10 +351,38 @@ mod test { create_block(&mut blockchain, "filler2", schema); // create a contract acceptance after the expiration block height - let schema = create_contract_acceptance_schema(contract_id, change[4].clone(), validator_node_public_key); + let commitment = fetch_constitution_commitment(blockchain.db(), contract_id).unwrap(); + let schema = + create_contract_acceptance_schema(contract_id, commitment, change[4].clone(), private_key, public_key); let (tx, _) = schema_to_transaction(&schema); // try to validate the acceptance transaction and check that we get the expiration error assert_dan_validator_fail(&blockchain, &tx, "Acceptance window has expired"); } + + #[test] + fn it_rejects_acceptances_with_invalid_signatures() { + // initialise a blockchain with enough funds to spend at contract transactions + let (mut blockchain, change) = init_test_blockchain(); + + // publish the contract definition into a block + let contract_id = publish_definition(&mut blockchain, change[0].clone()); + + // publish the contract constitution into a block + let (_, public_key) = create_random_key_pair(); + let mut constitution = create_contract_constitution(); + constitution.validator_committee = vec![public_key.clone()].try_into().unwrap(); + publish_constitution(&mut blockchain, change[1].clone(), contract_id, constitution); + + // create a valid contract acceptance transaction, but with a signature done by a different private key + let (altered_private_key, _) = create_random_key_pair(); + let commitment = fetch_constitution_commitment(blockchain.db(), contract_id).unwrap(); + let signature = create_acceptance_signature(contract_id, commitment, altered_private_key, public_key.clone()); + let schema = + create_contract_acceptance_schema_with_signature(contract_id, change[2].clone(), public_key, signature); + let (tx, _) = schema_to_transaction(&schema); + + // try to validate the acceptance transaction and check that we get the error + assert_dan_validator_fail(&blockchain, &tx, "Invalid acceptance signature"); + } } diff --git a/base_layer/core/src/validation/dan_validators/error.rs b/base_layer/core/src/validation/dan_validators/error.rs index cea5ab7d82..bd8822ca68 100644 --- a/base_layer/core/src/validation/dan_validators/error.rs +++ b/base_layer/core/src/validation/dan_validators/error.rs @@ -59,6 +59,8 @@ pub enum DanLayerValidationError { UpdatedConstitutionAmendmentMismatch, #[error("Acceptance window has expired for contract_id ({contract_id})")] AcceptanceWindowHasExpired { contract_id: FixedHash }, + #[error("Invalid acceptance signature")] + InvalidAcceptanceSignature, #[error("Proposal acceptance window has expired for contract_id ({contract_id}) and proposal_id ({proposal_id})")] ProposalAcceptanceWindowHasExpired { contract_id: FixedHash, proposal_id: u64 }, #[error("Checkpoint has non-sequential number. Got: {got}, expected: {expected}")] diff --git a/base_layer/core/src/validation/dan_validators/test_helpers.rs b/base_layer/core/src/validation/dan_validators/test_helpers.rs index 2a0465bfaf..19091cd367 100644 --- a/base_layer/core/src/validation/dan_validators/test_helpers.rs +++ b/base_layer/core/src/validation/dan_validators/test_helpers.rs @@ -22,8 +22,11 @@ use std::convert::TryInto; -use tari_common_types::types::{FixedHash, PublicKey, Signature}; +use blake2::Digest; +use tari_common_types::types::{Challenge, Commitment, FixedHash, PrivateKey, PublicKey, Signature}; +use tari_crypto::ristretto::{RistrettoPublicKey, RistrettoSecretKey}; use tari_p2p::Network; +use tari_utilities::ByteArray; use super::TxDanLayerValidator; use crate::{ @@ -217,17 +220,51 @@ pub fn create_contract_checkpoint_schema( pub fn create_contract_acceptance_schema( contract_id: FixedHash, + commitment: Commitment, input: UnblindedOutput, - validator_node_public_key: PublicKey, + private_key: RistrettoSecretKey, + public_key: RistrettoPublicKey, ) -> TransactionSchema { - let signature = Signature::default(); + let signature = create_acceptance_signature(contract_id, commitment, private_key, public_key.clone()); - let acceptance_features = - OutputFeatures::for_contract_acceptance(contract_id, validator_node_public_key, signature); + let acceptance_features = OutputFeatures::for_contract_acceptance(contract_id, public_key, signature); + + txn_schema!(from: vec![input], to: vec![0.into()], fee: 5.into(), lock: 0, features: acceptance_features) +} + +pub fn create_contract_acceptance_schema_with_signature( + contract_id: FixedHash, + input: UnblindedOutput, + public_key: RistrettoPublicKey, + signature: Signature, +) -> TransactionSchema { + let acceptance_features = OutputFeatures::for_contract_acceptance(contract_id, public_key, signature); txn_schema!(from: vec![input], to: vec![0.into()], fee: 5.into(), lock: 0, features: acceptance_features) } +pub fn create_acceptance_signature( + contract_id: FixedHash, + commitment: Commitment, + private_key: RistrettoSecretKey, + public_key: RistrettoPublicKey, +) -> Signature { + let challenge = Challenge::new() + .chain(public_key.as_bytes()) + .chain(commitment.as_bytes()) + .chain(contract_id) + .finalize(); + + let nonce = PrivateKey::default(); + + Signature::sign(private_key, nonce, &challenge).unwrap() +} + +pub fn create_random_key_pair() -> (RistrettoSecretKey, RistrettoPublicKey) { + let mut rng = rand::thread_rng(); + ::random_keypair(&mut rng) +} + pub fn create_contract_proposal_schema( contract_id: FixedHash, input: UnblindedOutput, From 2d30d05997b52fc8edeffd666511b8d22952ccec Mon Sep 17 00:00:00 2001 From: mrnaveira <47919901+mrnaveira@users.noreply.github.com> Date: Tue, 5 Jul 2022 08:45:19 +0100 Subject: [PATCH 02/13] add public_nonce to the challenge --- .../validation/dan_validators/acceptance_validator.rs | 6 ++++-- .../core/src/validation/dan_validators/test_helpers.rs | 10 ++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/base_layer/core/src/validation/dan_validators/acceptance_validator.rs b/base_layer/core/src/validation/dan_validators/acceptance_validator.rs index 55575abd16..67ce5ee1d5 100644 --- a/base_layer/core/src/validation/dan_validators/acceptance_validator.rs +++ b/base_layer/core/src/validation/dan_validators/acceptance_validator.rs @@ -21,7 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use blake2::Digest; -use tari_common_types::types::{Challenge, Commitment, FixedHash, PublicKey, Signature}; +use tari_common_types::types::{Commitment, FixedHash, HashDigest, PublicKey, Signature}; use tari_utilities::{hex::Hex, ByteArray}; use super::helpers::{ @@ -160,8 +160,10 @@ pub fn validate_signature( ) -> Result<(), ValidationError> { let commitment = fetch_constitution_commitment(db, contract_id)?; - let challenge = Challenge::new() + // TODO: Use domain-seperated hasher from tari_crypto + let challenge = HashDigest::new() .chain(validator_node_public_key.as_bytes()) + .chain(signature.get_public_nonce().as_bytes()) .chain(commitment.as_bytes()) .chain(contract_id) .finalize(); diff --git a/base_layer/core/src/validation/dan_validators/test_helpers.rs b/base_layer/core/src/validation/dan_validators/test_helpers.rs index 19091cd367..3c7ba8ebe5 100644 --- a/base_layer/core/src/validation/dan_validators/test_helpers.rs +++ b/base_layer/core/src/validation/dan_validators/test_helpers.rs @@ -23,7 +23,7 @@ use std::convert::TryInto; use blake2::Digest; -use tari_common_types::types::{Challenge, Commitment, FixedHash, PrivateKey, PublicKey, Signature}; +use tari_common_types::types::{Commitment, FixedHash, HashDigest, PublicKey, Signature}; use tari_crypto::ristretto::{RistrettoPublicKey, RistrettoSecretKey}; use tari_p2p::Network; use tari_utilities::ByteArray; @@ -249,14 +249,16 @@ pub fn create_acceptance_signature( private_key: RistrettoSecretKey, public_key: RistrettoPublicKey, ) -> Signature { - let challenge = Challenge::new() + let (nonce, public_nonce) = create_random_key_pair(); + + // TODO: Use domain-seperated hasher from tari_crypto + let challenge = HashDigest::new() .chain(public_key.as_bytes()) + .chain(public_nonce.as_bytes()) .chain(commitment.as_bytes()) .chain(contract_id) .finalize(); - let nonce = PrivateKey::default(); - Signature::sign(private_key, nonce, &challenge).unwrap() } From 1c234a76f59c01e4bb95f4671b5e3820dde622bb Mon Sep 17 00:00:00 2001 From: mrnaveira <47919901+mrnaveira@users.noreply.github.com> Date: Tue, 5 Jul 2022 12:47:47 +0100 Subject: [PATCH 03/13] refactor common integration test step --- integration_tests/features/ValidatorNode.feature | 2 +- .../features/support/validator_node_steps.js | 16 +--------------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/integration_tests/features/ValidatorNode.feature b/integration_tests/features/ValidatorNode.feature index ad5f813b28..ba44484a78 100644 --- a/integration_tests/features/ValidatorNode.feature +++ b/integration_tests/features/ValidatorNode.feature @@ -33,7 +33,7 @@ Feature: Validator Node And I add VN1 to the validator committee on COM1 And I publish the contract constitution COM1 on wallet WALLET1 via command line And I mine 9 blocks using wallet WALLET1 on NODE1 - Then wallet WALLET1 will have a successfully mined constitution acceptance transaction for contract DEF1 + Then wallet WALLET1 will have a successfully mined contract acceptance transaction for contract DEF1 @critical Scenario: Publish contract update proposal acceptance diff --git a/integration_tests/features/support/validator_node_steps.js b/integration_tests/features/support/validator_node_steps.js index c8da062b8b..1182c117eb 100644 --- a/integration_tests/features/support/validator_node_steps.js +++ b/integration_tests/features/support/validator_node_steps.js @@ -79,19 +79,6 @@ Then( } ); -Then( - "wallet {word} will have a successfully mined constitution acceptance transaction for contract {word}", - { timeout: 40 * 1000 }, - async function (wallet_name, contract_name) { - let wallet = await this.getWallet(wallet_name); - let contract_id = await this.fetchContract(contract_name); - let message = `Contract acceptance for contract with id=${contract_id}`; - - let utxos = await findUtxoWithOutputMessage(wallet, message); - expect(utxos.length).to.equal(1); - } -); - Then( "wallet {word} will have a successfully mined contract acceptance transaction for contract {word}", { timeout: 40 * 1000 }, @@ -101,8 +88,7 @@ Then( let message = `Contract acceptance for contract with id=${contract_id}`; let utxos = await findUtxoWithOutputMessage(wallet, message); - // FIXME: it seems that the validator node publishes acceptances for both definitions and constitutions - expect(utxos.length).to.be.gte(1); + expect(utxos.length).to.equal(1); } ); From 009e37fa69583cb674854ee3670d90d89357d4fc Mon Sep 17 00:00:00 2001 From: mrnaveira <47919901+mrnaveira@users.noreply.github.com> Date: Tue, 5 Jul 2022 18:46:11 +0100 Subject: [PATCH 04/13] create acceptance manager in the validator node --- .../src/default_service_specification.rs | 2 + .../src/grpc/validator_node_grpc_server.rs | 13 +-- applications/tari_validator_node/src/main.rs | 10 +- .../transaction_components/side_chain/mod.rs | 3 + .../side_chain/signer_signature.rs | 101 ++++++++++++++++++ .../core/src/models/acceptance_challenge.rs | 46 ++++++++ dan_layer/core/src/models/mod.rs | 2 + .../core/src/services/acceptance_manager.rs | 71 ++++++++++++ dan_layer/core/src/services/mocks/mod.rs | 1 + dan_layer/core/src/services/mod.rs | 3 +- .../src/services/service_specification.rs | 2 + 11 files changed, 246 insertions(+), 8 deletions(-) create mode 100644 base_layer/core/src/transactions/transaction_components/side_chain/signer_signature.rs create mode 100644 dan_layer/core/src/models/acceptance_challenge.rs create mode 100644 dan_layer/core/src/services/acceptance_manager.rs diff --git a/applications/tari_validator_node/src/default_service_specification.rs b/applications/tari_validator_node/src/default_service_specification.rs index 98573e6d0e..803da52eb5 100644 --- a/applications/tari_validator_node/src/default_service_specification.rs +++ b/applications/tari_validator_node/src/default_service_specification.rs @@ -24,6 +24,7 @@ use tari_common_types::types::PublicKey; use tari_dan_core::{ models::{domain_events::ConsensusWorkerDomainEvent, TariDanPayload}, services::{ + ConcreteAcceptanceManager, ConcreteAssetProcessor, ConcreteAssetProxy, ConcreteCheckpointManager, @@ -57,6 +58,7 @@ use crate::{ pub struct DefaultServiceSpecification; impl ServiceSpecification for DefaultServiceSpecification { + type AcceptanceManager = ConcreteAcceptanceManager; type Addr = PublicKey; type AssetProcessor = ConcreteAssetProcessor; type AssetProxy = ConcreteAssetProxy; diff --git a/applications/tari_validator_node/src/grpc/validator_node_grpc_server.rs b/applications/tari_validator_node/src/grpc/validator_node_grpc_server.rs index bf4c9eb7dc..ce97c0847f 100644 --- a/applications/tari_validator_node/src/grpc/validator_node_grpc_server.rs +++ b/applications/tari_validator_node/src/grpc/validator_node_grpc_server.rs @@ -31,7 +31,7 @@ use tari_comms::NodeIdentity; use tari_crypto::tari_utilities::ByteArray; use tari_dan_core::{ models::Instruction, - services::{AssetProcessor, AssetProxy, ServiceSpecification, WalletClient}, + services::{AcceptanceManager, AssetProcessor, AssetProxy, ServiceSpecification, WalletClient}, storage::DbFactory, }; use tokio::{task, time}; @@ -45,6 +45,7 @@ pub struct ValidatorNodeGrpcServer asset_processor: TServiceSpecification::AssetProcessor, asset_proxy: TServiceSpecification::AssetProxy, wallet_client: TServiceSpecification::WalletClient, + acceptance_manager: TServiceSpecification::AcceptanceManager, } impl ValidatorNodeGrpcServer { @@ -54,6 +55,7 @@ impl ValidatorNodeGrpcServer Self { Self { node_identity, @@ -61,6 +63,7 @@ impl ValidatorNodeGrpcServer rpc::validator_node_ &self, request: tonic::Request, ) -> Result, tonic::Status> { - let mut wallet_client = self.wallet_client.clone(); + let mut acceptance_manager = self.acceptance_manager.clone(); let request = request.into_inner(); let contract_id = FixedHash::try_from(request.contract_id).map_err(|err| tonic::Status::invalid_argument(err.to_string()))?; - let validator_node_public_key = self.node_identity.public_key(); - let signature = Signature::default(); - match wallet_client - .submit_contract_acceptance(&contract_id, validator_node_public_key, &signature) + match acceptance_manager + .publish_acceptance(&self.node_identity, &contract_id) .await { Ok(tx_id) => Ok(Response::new(rpc::PublishContractAcceptanceResponse { diff --git a/applications/tari_validator_node/src/main.rs b/applications/tari_validator_node/src/main.rs index 13ab37b864..fbe9b43f2a 100644 --- a/applications/tari_validator_node/src/main.rs +++ b/applications/tari_validator_node/src/main.rs @@ -51,7 +51,13 @@ use tari_comms::{ }; use tari_comms_dht::Dht; use tari_dan_core::{ - services::{ConcreteAssetProcessor, ConcreteAssetProxy, MempoolServiceHandle, ServiceSpecification}, + services::{ + ConcreteAcceptanceManager, + ConcreteAssetProcessor, + ConcreteAssetProxy, + MempoolServiceHandle, + ServiceSpecification, + }, storage::{global::GlobalDb, DbFactory}, }; use tari_dan_storage_sqlite::{global::SqliteGlobalDbBackendAdapter, SqliteDbFactory}; @@ -149,12 +155,14 @@ async fn run_node(config: &ApplicationConfig) -> Result<(), ExitError> { db_factory.clone(), ); let wallet_client = GrpcWalletClient::new(config.validator_node.wallet_grpc_address); + let acceptance_manager = ConcreteAcceptanceManager::new(wallet_client.clone()); let grpc_server: ValidatorNodeGrpcServer = ValidatorNodeGrpcServer::new( node_identity.as_ref().clone(), db_factory.clone(), asset_processor, asset_proxy, wallet_client, + acceptance_manager, ); if let Some(address) = config.validator_node.grpc_address.clone() { diff --git a/base_layer/core/src/transactions/transaction_components/side_chain/mod.rs b/base_layer/core/src/transactions/transaction_components/side_chain/mod.rs index 448449c8e2..866d4d578b 100644 --- a/base_layer/core/src/transactions/transaction_components/side_chain/mod.rs +++ b/base_layer/core/src/transactions/transaction_components/side_chain/mod.rs @@ -58,6 +58,9 @@ pub use committee_members::CommitteeMembers; mod committee_signatures; pub use committee_signatures::CommitteeSignatures; +mod signer_signature; +pub use signer_signature::SignerSignature; + mod sidechain_features; pub use sidechain_features::{SideChainFeatures, SideChainFeaturesBuilder}; diff --git a/base_layer/core/src/transactions/transaction_components/side_chain/signer_signature.rs b/base_layer/core/src/transactions/transaction_components/side_chain/signer_signature.rs new file mode 100644 index 0000000000..95a4dbadab --- /dev/null +++ b/base_layer/core/src/transactions/transaction_components/side_chain/signer_signature.rs @@ -0,0 +1,101 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::io; + +use digest::Digest; +use rand::rngs::OsRng; +use serde::{Deserialize, Serialize}; +use tari_common_types::types::{HashDigest, PrivateKey, PublicKey, Signature}; +use tari_crypto::keys::PublicKey as PublicKeyT; +use tari_utilities::ByteArray; + +use crate::consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized}; + +#[derive(Debug, Clone, Hash, PartialEq, Deserialize, Serialize, Eq, Default)] +pub struct SignerSignature { + pub signer: PublicKey, + pub signature: Signature, +} + +impl SignerSignature { + pub fn new(signer: PublicKey, signature: Signature) -> Self { + Self { signer, signature } + } + + pub fn sign>(signer_secret: &PrivateKey, challenge: C) -> Self { + let signer = PublicKey::from_secret_key(signer_secret); + let (nonce, public_nonce) = PublicKey::random_keypair(&mut OsRng); + // TODO: Use domain-seperated hasher from tari_crypto + let final_challenge = HashDigest::new() + .chain(signer.as_bytes()) + .chain(public_nonce.as_bytes()) + .chain(challenge) + .finalize(); + let signature = + Signature::sign(signer_secret.clone(), nonce, &*final_challenge).expect("challenge is the correct length"); + Self { signer, signature } + } + + pub fn signer(&self) -> &PublicKey { + &self.signer + } + + pub fn signature(&self) -> &Signature { + &self.signature + } +} + +impl ConsensusEncoding for SignerSignature { + fn consensus_encode(&self, writer: &mut W) -> Result<(), io::Error> { + self.signer.consensus_encode(writer)?; + self.signature.consensus_encode(writer)?; + Ok(()) + } +} + +impl ConsensusEncodingSized for SignerSignature { + fn consensus_encode_exact_size(&self) -> usize { + 32 + 64 + } +} + +impl ConsensusDecoding for SignerSignature { + fn consensus_decode(reader: &mut R) -> Result { + Ok(Self { + signer: PublicKey::consensus_decode(reader)?, + signature: Signature::consensus_decode(reader)?, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::consensus::check_consensus_encoding_correctness; + + #[test] + fn it_encodes_and_decodes_correctly() { + let subject = SignerSignature::default(); + check_consensus_encoding_correctness(subject).unwrap(); + } +} diff --git a/dan_layer/core/src/models/acceptance_challenge.rs b/dan_layer/core/src/models/acceptance_challenge.rs new file mode 100644 index 0000000000..4fd46a28f7 --- /dev/null +++ b/dan_layer/core/src/models/acceptance_challenge.rs @@ -0,0 +1,46 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use digest::Digest; +use tari_common_types::types::{Commitment, FixedHash, HashDigest}; +use tari_utilities::ByteArray; + +#[derive(Debug, Clone, Copy)] +pub struct AcceptanceChallenge(FixedHash); + +impl AcceptanceChallenge { + pub fn new(constiution_commitment: &Commitment, contract_id: &FixedHash) -> Self { + // TODO: Use new tari_crypto domain-separated hashing + let hash = HashDigest::new() + .chain(constiution_commitment.as_bytes()) + .chain(contract_id.as_slice()) + .finalize() + .into(); + Self(hash) + } +} + +impl AsRef<[u8]> for AcceptanceChallenge { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} diff --git a/dan_layer/core/src/models/mod.rs b/dan_layer/core/src/models/mod.rs index 2349e3c465..06cf9affbc 100644 --- a/dan_layer/core/src/models/mod.rs +++ b/dan_layer/core/src/models/mod.rs @@ -27,6 +27,7 @@ use std::{ str::FromStr, }; +mod acceptance_challenge; mod asset_definition; mod base_layer_metadata; mod base_layer_output; @@ -49,6 +50,7 @@ mod tree_node_hash; mod view; mod view_id; +pub use acceptance_challenge::AcceptanceChallenge; pub use asset_definition::{AssetDefinition, InitialState, KeyValue, SchemaState}; pub use base_layer_metadata::BaseLayerMetadata; pub use base_layer_output::{BaseLayerOutput, CheckpointOutput, CommitteeOutput}; diff --git a/dan_layer/core/src/services/acceptance_manager.rs b/dan_layer/core/src/services/acceptance_manager.rs new file mode 100644 index 0000000000..28441dbc7d --- /dev/null +++ b/dan_layer/core/src/services/acceptance_manager.rs @@ -0,0 +1,71 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use async_trait::async_trait; +use tari_common_types::types::{Commitment, FixedHash}; +use tari_comms::NodeIdentity; +use tari_core::transactions::transaction_components::SignerSignature; + +use crate::{models::AcceptanceChallenge, services::wallet_client::WalletClient, DigitalAssetError}; + +#[async_trait] +pub trait AcceptanceManager: Send + Sync { + async fn publish_acceptance( + &mut self, + node_identity: &NodeIdentity, + contract_id: &FixedHash, + ) -> Result; +} + +#[derive(Clone)] +pub struct ConcreteAcceptanceManager { + wallet: TWallet, +} + +impl ConcreteAcceptanceManager { + pub fn new(wallet: TWallet) -> Self { + Self { wallet } + } +} + +#[async_trait] +impl AcceptanceManager for ConcreteAcceptanceManager { + async fn publish_acceptance( + &mut self, + node_identity: &NodeIdentity, + contract_id: &FixedHash, + ) -> Result { + // TODO: fetch the real contract constitution commitment from the base_node_client + let constitution_commitment = Commitment::default(); + let public_key = node_identity.public_key(); + + // build the acceptance signature + let secret_key = node_identity.secret_key(); + let challenge = AcceptanceChallenge::new(&constitution_commitment, contract_id); + let signature = SignerSignature::sign(secret_key, challenge).signature; + + // publish the acceptance + self.wallet + .submit_contract_acceptance(contract_id, public_key, &signature) + .await + } +} diff --git a/dan_layer/core/src/services/mocks/mod.rs b/dan_layer/core/src/services/mocks/mod.rs index 3b76fd50bb..a7d84845bd 100644 --- a/dan_layer/core/src/services/mocks/mod.rs +++ b/dan_layer/core/src/services/mocks/mod.rs @@ -470,6 +470,7 @@ pub struct MockServiceSpecification; #[cfg(test)] impl ServiceSpecification for MockServiceSpecification { + type AcceptanceManager = super::ConcreteAcceptanceManager; type Addr = RistrettoPublicKey; type AssetProcessor = MockAssetProcessor; type AssetProxy = ConcreteAssetProxy; diff --git a/dan_layer/core/src/services/mod.rs b/dan_layer/core/src/services/mod.rs index 34a809df4a..d239a6567e 100644 --- a/dan_layer/core/src/services/mod.rs +++ b/dan_layer/core/src/services/mod.rs @@ -20,6 +20,7 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +mod acceptance_manager; mod asset_processor; mod base_node_client; mod committee_manager; @@ -30,6 +31,7 @@ mod payload_processor; mod payload_provider; mod signing_service; +pub use acceptance_manager::{AcceptanceManager, ConcreteAcceptanceManager}; pub use asset_processor::{AssetProcessor, ConcreteAssetProcessor, MemoryInstructionLog}; pub use asset_proxy::{AssetProxy, ConcreteAssetProxy}; pub use base_node_client::BaseNodeClient; @@ -39,7 +41,6 @@ pub use mempool_service::{ConcreteMempoolService, MempoolService, MempoolService pub use payload_processor::{PayloadProcessor, TariDanPayloadProcessor}; pub use payload_provider::{PayloadProvider, TariDanPayloadProvider}; pub use signing_service::{NodeIdentitySigningService, SigningService}; - mod asset_proxy; mod checkpoint_manager; pub mod mocks; diff --git a/dan_layer/core/src/services/service_specification.rs b/dan_layer/core/src/services/service_specification.rs index 04d8dad095..f81203c402 100644 --- a/dan_layer/core/src/services/service_specification.rs +++ b/dan_layer/core/src/services/service_specification.rs @@ -20,6 +20,7 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use super::acceptance_manager::AcceptanceManager; use crate::{ models::{domain_events::ConsensusWorkerDomainEvent, Payload}, services::{ @@ -50,6 +51,7 @@ use crate::{ /// simply reference types. /// This trait is intended to only include `types` and no methods. pub trait ServiceSpecification: Default + Clone { + type AcceptanceManager: AcceptanceManager + Clone; type Addr: NodeAddressable; type AssetProcessor: AssetProcessor + Clone; type AssetProxy: AssetProxy + Clone; From bc83f1f8280a6fd9657e4bf3ef4701f478866ec0 Mon Sep 17 00:00:00 2001 From: mrnaveira <47919901+mrnaveira@users.noreply.github.com> Date: Tue, 5 Jul 2022 19:45:20 +0100 Subject: [PATCH 05/13] use the acceptance_manager in the auto accept --- .../src/contract_worker_manager.rs | 34 ++++++------------- .../tari_validator_node/src/dan_node.rs | 8 +++-- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/applications/tari_validator_node/src/contract_worker_manager.rs b/applications/tari_validator_node/src/contract_worker_manager.rs index 8b7f3bbcec..271cd5d453 100644 --- a/applications/tari_validator_node/src/contract_worker_manager.rs +++ b/applications/tari_validator_node/src/contract_worker_manager.rs @@ -28,19 +28,17 @@ use std::{ }; use log::*; -use rand::rngs::OsRng; -use tari_common_types::types::{FixedHash, FixedHashSizeError, HashDigest, PrivateKey, Signature}; +use tari_common_types::types::{FixedHash, FixedHashSizeError}; use tari_comms::{types::CommsPublicKey, NodeIdentity}; use tari_comms_dht::Dht; -use tari_core::{consensus::ConsensusHashWriter, transactions::transaction_components::ContractConstitution}; -use tari_crypto::{ - keys::SecretKey, - tari_utilities::{hex::Hex, message_format::MessageFormat, ByteArray}, -}; +use tari_core::transactions::transaction_components::ContractConstitution; +use tari_crypto::tari_utilities::{hex::Hex, message_format::MessageFormat, ByteArray}; use tari_dan_core::{ models::{AssetDefinition, BaseLayerMetadata, Committee}, services::{ + AcceptanceManager, BaseNodeClient, + ConcreteAcceptanceManager, ConcreteAssetProcessor, ConcreteCheckpointManager, ConcreteCommitteeManager, @@ -49,7 +47,6 @@ use tari_dan_core::{ NodeIdentitySigningService, TariDanPayloadProcessor, TariDanPayloadProvider, - WalletClient, }, storage::{ global::{ContractState, GlobalDb, GlobalDbMetadataKey}, @@ -88,7 +85,7 @@ pub struct ContractWorkerManager { last_scanned_height: u64, last_scanned_hash: Option, base_node_client: GrpcBaseNodeClient, - wallet_client: GrpcWalletClient, + acceptance_manager: ConcreteAcceptanceManager, identity: Arc, active_workers: HashMap>, mempool: MempoolServiceHandle, @@ -113,7 +110,7 @@ impl ContractWorkerManager { identity: Arc, global_db: GlobalDb, base_node_client: GrpcBaseNodeClient, - wallet_client: GrpcWalletClient, + acceptance_manager: ConcreteAcceptanceManager, mempool: MempoolServiceHandle, handles: ServiceHandles, subscription_factory: SubscriptionFactory, @@ -126,7 +123,7 @@ impl ContractWorkerManager { last_scanned_height: 0, last_scanned_hash: None, base_node_client, - wallet_client, + acceptance_manager, identity, mempool, handles, @@ -448,13 +445,10 @@ impl ContractWorkerManager { } async fn post_contract_acceptance(&mut self, contract: &ActiveContract) -> Result<(), WorkerManagerError> { - let nonce = PrivateKey::random(&mut OsRng); - let challenge = generate_constitution_challenge(&contract.constitution); - let signature = Signature::sign(self.identity.secret_key().clone(), nonce, challenge.as_slice()).unwrap(); + let mut acceptance_manager = self.acceptance_manager.clone(); - let tx_id = self - .wallet_client - .submit_contract_acceptance(&contract.contract_id, self.identity.public_key(), &signature) + let tx_id = acceptance_manager + .publish_acceptance(&self.identity, &contract.contract_id) .await?; info!( "Contract {} acceptance submitted with id={}", @@ -478,12 +472,6 @@ impl ContractWorkerManager { } } -fn generate_constitution_challenge(constitution: &ContractConstitution) -> [u8; 32] { - ConsensusHashWriter::new(HashDigest::with_params(&[], &[], b"tari/vn/constsig")) - .chain(constitution) - .finalize() -} - #[derive(Debug, thiserror::Error)] pub enum WorkerManagerError { #[error(transparent)] diff --git a/applications/tari_validator_node/src/dan_node.rs b/applications/tari_validator_node/src/dan_node.rs index 1c880958b7..bfa51f0986 100644 --- a/applications/tari_validator_node/src/dan_node.rs +++ b/applications/tari_validator_node/src/dan_node.rs @@ -24,7 +24,10 @@ use std::sync::Arc; use tari_common::exit_codes::{ExitCode, ExitError}; use tari_comms::NodeIdentity; -use tari_dan_core::{services::MempoolServiceHandle, storage::global::GlobalDb}; +use tari_dan_core::{ + services::{ConcreteAcceptanceManager, MempoolServiceHandle}, + storage::global::GlobalDb, +}; use tari_dan_storage_sqlite::{global::SqliteGlobalDbBackendAdapter, SqliteDbFactory}; use tari_p2p::comms_connector::SubscriptionFactory; use tari_service_framework::ServiceHandles; @@ -68,12 +71,13 @@ impl DanNode { ) -> Result<(), ExitError> { let base_node_client = GrpcBaseNodeClient::new(self.config.base_node_grpc_address); let wallet_client = GrpcWalletClient::new(self.config.wallet_grpc_address); + let acceptance_manager = ConcreteAcceptanceManager::new(wallet_client); let workers = ContractWorkerManager::new( self.config.clone(), self.identity.clone(), self.global_db.clone(), base_node_client, - wallet_client, + acceptance_manager, mempool_service, handles, subscription_factory, From 3178cadd9e334f463ea49a7fd0bedc04dc6d37cd Mon Sep 17 00:00:00 2001 From: mrnaveira <47919901+mrnaveira@users.noreply.github.com> Date: Wed, 6 Jul 2022 11:09:52 +0100 Subject: [PATCH 06/13] vn uses constitution commitment on acceptances --- .../src/contract_worker_manager.rs | 4 +-- .../tari_validator_node/src/dan_node.rs | 2 +- .../src/default_service_specification.rs | 2 +- applications/tari_validator_node/src/main.rs | 5 ++-- .../dan_validators/acceptance_validator.rs | 12 ++++---- .../validation/dan_validators/test_helpers.rs | 13 +++----- .../core/src/services/acceptance_manager.rs | 30 ++++++++++++------- dan_layer/core/src/services/mocks/mod.rs | 2 +- 8 files changed, 39 insertions(+), 31 deletions(-) diff --git a/applications/tari_validator_node/src/contract_worker_manager.rs b/applications/tari_validator_node/src/contract_worker_manager.rs index 271cd5d453..3f4abca1f9 100644 --- a/applications/tari_validator_node/src/contract_worker_manager.rs +++ b/applications/tari_validator_node/src/contract_worker_manager.rs @@ -85,7 +85,7 @@ pub struct ContractWorkerManager { last_scanned_height: u64, last_scanned_hash: Option, base_node_client: GrpcBaseNodeClient, - acceptance_manager: ConcreteAcceptanceManager, + acceptance_manager: ConcreteAcceptanceManager, identity: Arc, active_workers: HashMap>, mempool: MempoolServiceHandle, @@ -110,7 +110,7 @@ impl ContractWorkerManager { identity: Arc, global_db: GlobalDb, base_node_client: GrpcBaseNodeClient, - acceptance_manager: ConcreteAcceptanceManager, + acceptance_manager: ConcreteAcceptanceManager, mempool: MempoolServiceHandle, handles: ServiceHandles, subscription_factory: SubscriptionFactory, diff --git a/applications/tari_validator_node/src/dan_node.rs b/applications/tari_validator_node/src/dan_node.rs index bfa51f0986..ed156da799 100644 --- a/applications/tari_validator_node/src/dan_node.rs +++ b/applications/tari_validator_node/src/dan_node.rs @@ -71,7 +71,7 @@ impl DanNode { ) -> Result<(), ExitError> { let base_node_client = GrpcBaseNodeClient::new(self.config.base_node_grpc_address); let wallet_client = GrpcWalletClient::new(self.config.wallet_grpc_address); - let acceptance_manager = ConcreteAcceptanceManager::new(wallet_client); + let acceptance_manager = ConcreteAcceptanceManager::new(wallet_client, base_node_client.clone()); let workers = ContractWorkerManager::new( self.config.clone(), self.identity.clone(), diff --git a/applications/tari_validator_node/src/default_service_specification.rs b/applications/tari_validator_node/src/default_service_specification.rs index 803da52eb5..76caf5f94e 100644 --- a/applications/tari_validator_node/src/default_service_specification.rs +++ b/applications/tari_validator_node/src/default_service_specification.rs @@ -58,7 +58,7 @@ use crate::{ pub struct DefaultServiceSpecification; impl ServiceSpecification for DefaultServiceSpecification { - type AcceptanceManager = ConcreteAcceptanceManager; + type AcceptanceManager = ConcreteAcceptanceManager; type Addr = PublicKey; type AssetProcessor = ConcreteAssetProcessor; type AssetProxy = ConcreteAssetProxy; diff --git a/applications/tari_validator_node/src/main.rs b/applications/tari_validator_node/src/main.rs index fbe9b43f2a..977c8b14b0 100644 --- a/applications/tari_validator_node/src/main.rs +++ b/applications/tari_validator_node/src/main.rs @@ -147,15 +147,16 @@ async fn run_node(config: &ApplicationConfig) -> Result<(), ExitError> { let asset_processor = ConcreteAssetProcessor::default(); let validator_node_client_factory = TariCommsValidatorNodeClientFactory::new(handles.expect_handle::().dht_requester()); + let base_node_client = GrpcBaseNodeClient::new(config.validator_node.base_node_grpc_address); let asset_proxy: ConcreteAssetProxy = ConcreteAssetProxy::new( - GrpcBaseNodeClient::new(config.validator_node.base_node_grpc_address), + base_node_client.clone(), validator_node_client_factory, 5, mempool_service.clone(), db_factory.clone(), ); let wallet_client = GrpcWalletClient::new(config.validator_node.wallet_grpc_address); - let acceptance_manager = ConcreteAcceptanceManager::new(wallet_client.clone()); + let acceptance_manager = ConcreteAcceptanceManager::new(wallet_client.clone(), base_node_client); let grpc_server: ValidatorNodeGrpcServer = ValidatorNodeGrpcServer::new( node_identity.as_ref().clone(), db_factory.clone(), diff --git a/base_layer/core/src/validation/dan_validators/acceptance_validator.rs b/base_layer/core/src/validation/dan_validators/acceptance_validator.rs index 67ce5ee1d5..1e50eb9be1 100644 --- a/base_layer/core/src/validation/dan_validators/acceptance_validator.rs +++ b/base_layer/core/src/validation/dan_validators/acceptance_validator.rs @@ -162,14 +162,16 @@ pub fn validate_signature( // TODO: Use domain-seperated hasher from tari_crypto let challenge = HashDigest::new() + .chain(commitment.as_bytes()) + .chain(contract_id.as_slice()) + .finalize(); + let final_challenge = HashDigest::new() .chain(validator_node_public_key.as_bytes()) .chain(signature.get_public_nonce().as_bytes()) - .chain(commitment.as_bytes()) - .chain(contract_id) + .chain(challenge) .finalize(); - let is_valid_signature = signature.verify_challenge(validator_node_public_key, &challenge); - + let is_valid_signature = signature.verify_challenge(validator_node_public_key, &final_challenge); if !is_valid_signature { return Err(ValidationError::DanLayerError( DanLayerValidationError::InvalidAcceptanceSignature, @@ -379,7 +381,7 @@ mod test { // create a valid contract acceptance transaction, but with a signature done by a different private key let (altered_private_key, _) = create_random_key_pair(); let commitment = fetch_constitution_commitment(blockchain.db(), contract_id).unwrap(); - let signature = create_acceptance_signature(contract_id, commitment, altered_private_key, public_key.clone()); + let signature = create_acceptance_signature(contract_id, commitment, altered_private_key); let schema = create_contract_acceptance_schema_with_signature(contract_id, change[2].clone(), public_key, signature); let (tx, _) = schema_to_transaction(&schema); diff --git a/base_layer/core/src/validation/dan_validators/test_helpers.rs b/base_layer/core/src/validation/dan_validators/test_helpers.rs index 3c7ba8ebe5..51b131f384 100644 --- a/base_layer/core/src/validation/dan_validators/test_helpers.rs +++ b/base_layer/core/src/validation/dan_validators/test_helpers.rs @@ -52,6 +52,7 @@ use crate::{ OutputFeatures, RequirementsForConstitutionChange, SideChainConsensus, + SignerSignature, Transaction, UnblindedOutput, }, @@ -225,7 +226,7 @@ pub fn create_contract_acceptance_schema( private_key: RistrettoSecretKey, public_key: RistrettoPublicKey, ) -> TransactionSchema { - let signature = create_acceptance_signature(contract_id, commitment, private_key, public_key.clone()); + let signature = create_acceptance_signature(contract_id, commitment, private_key); let acceptance_features = OutputFeatures::for_contract_acceptance(contract_id, public_key, signature); @@ -247,19 +248,13 @@ pub fn create_acceptance_signature( contract_id: FixedHash, commitment: Commitment, private_key: RistrettoSecretKey, - public_key: RistrettoPublicKey, ) -> Signature { - let (nonce, public_nonce) = create_random_key_pair(); - - // TODO: Use domain-seperated hasher from tari_crypto let challenge = HashDigest::new() - .chain(public_key.as_bytes()) - .chain(public_nonce.as_bytes()) .chain(commitment.as_bytes()) - .chain(contract_id) + .chain(contract_id.as_slice()) .finalize(); - Signature::sign(private_key, nonce, &challenge).unwrap() + SignerSignature::sign(&private_key, &challenge).signature } pub fn create_random_key_pair() -> (RistrettoSecretKey, RistrettoPublicKey) { diff --git a/dan_layer/core/src/services/acceptance_manager.rs b/dan_layer/core/src/services/acceptance_manager.rs index 28441dbc7d..6e267d610c 100644 --- a/dan_layer/core/src/services/acceptance_manager.rs +++ b/dan_layer/core/src/services/acceptance_manager.rs @@ -21,10 +21,11 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use async_trait::async_trait; -use tari_common_types::types::{Commitment, FixedHash}; +use tari_common_types::types::FixedHash; use tari_comms::NodeIdentity; -use tari_core::transactions::transaction_components::SignerSignature; +use tari_core::transactions::transaction_components::{SignerSignature, TransactionOutput}; +use super::BaseNodeClient; use crate::{models::AcceptanceChallenge, services::wallet_client::WalletClient, DigitalAssetError}; #[async_trait] @@ -37,30 +38,39 @@ pub trait AcceptanceManager: Send + Sync { } #[derive(Clone)] -pub struct ConcreteAcceptanceManager { +pub struct ConcreteAcceptanceManager { wallet: TWallet, + base_node: TBaseNode, } -impl ConcreteAcceptanceManager { - pub fn new(wallet: TWallet) -> Self { - Self { wallet } +impl ConcreteAcceptanceManager { + pub fn new(wallet: TWallet, base_node: TBaseNode) -> Self { + Self { wallet, base_node } } } #[async_trait] -impl AcceptanceManager for ConcreteAcceptanceManager { +impl AcceptanceManager + for ConcreteAcceptanceManager +{ async fn publish_acceptance( &mut self, node_identity: &NodeIdentity, contract_id: &FixedHash, ) -> Result { - // TODO: fetch the real contract constitution commitment from the base_node_client - let constitution_commitment = Commitment::default(); let public_key = node_identity.public_key(); + // FIXME: this is not the proper way to get the constitution commitment, we need a new method in the base node + let outputs = self.base_node.get_constitutions(None, public_key).await?; + let constitution_outputs: Vec = outputs + .into_iter() + .filter_map(|utxo| utxo.output.into_unpruned_output()) + .collect(); + let constitution_commitment = constitution_outputs.first().unwrap().commitment(); + // build the acceptance signature let secret_key = node_identity.secret_key(); - let challenge = AcceptanceChallenge::new(&constitution_commitment, contract_id); + let challenge = AcceptanceChallenge::new(constitution_commitment, contract_id); let signature = SignerSignature::sign(secret_key, challenge).signature; // publish the acceptance diff --git a/dan_layer/core/src/services/mocks/mod.rs b/dan_layer/core/src/services/mocks/mod.rs index a7d84845bd..6f6ff3de21 100644 --- a/dan_layer/core/src/services/mocks/mod.rs +++ b/dan_layer/core/src/services/mocks/mod.rs @@ -470,7 +470,7 @@ pub struct MockServiceSpecification; #[cfg(test)] impl ServiceSpecification for MockServiceSpecification { - type AcceptanceManager = super::ConcreteAcceptanceManager; + type AcceptanceManager = super::ConcreteAcceptanceManager; type Addr = RistrettoPublicKey; type AssetProcessor = MockAssetProcessor; type AssetProxy = ConcreteAssetProxy; From 82178a80972f8bf4b31faf0e7db85deca6fbd227 Mon Sep 17 00:00:00 2001 From: mrnaveira <47919901+mrnaveira@users.noreply.github.com> Date: Wed, 6 Jul 2022 14:17:07 +0100 Subject: [PATCH 07/13] use a better function to get the commitment --- .../src/grpc/services/base_node_client.rs | 31 ++++++++++++ .../core/src/services/acceptance_manager.rs | 47 ++++++++++++++----- .../core/src/services/base_node_client.rs | 6 +++ dan_layer/core/src/services/mocks/mod.rs | 8 ++++ 4 files changed, 81 insertions(+), 11 deletions(-) diff --git a/applications/tari_validator_node/src/grpc/services/base_node_client.rs b/applications/tari_validator_node/src/grpc/services/base_node_client.rs index 57a6eea358..6d70fddf3e 100644 --- a/applications/tari_validator_node/src/grpc/services/base_node_client.rs +++ b/applications/tari_validator_node/src/grpc/services/base_node_client.rs @@ -124,6 +124,37 @@ impl BaseNodeClient for GrpcBaseNodeClient { .collect() } + async fn get_contract_utxos( + &mut self, + contract_id: FixedHash, + output_type: OutputType, + ) -> Result, DigitalAssetError> { + let conn = self.connection().await?; + let request = grpc::GetCurrentContractOutputsRequest { + contract_id: contract_id.to_vec(), + output_type: u32::from(output_type.as_byte()), + }; + let result = conn.get_current_contract_outputs(request).await?.into_inner(); + + let mut outputs = vec![]; + for mined_info in result.outputs { + let output = mined_info + .output + .map(TryInto::try_into) + .transpose() + .map_err(DigitalAssetError::ConversionError)? + .ok_or_else(|| DigitalAssetError::InvalidPeerMessage("Mined info contained no output".to_string()))?; + + outputs.push(UtxoMinedInfo { + output: PrunedOutput::NotPruned { output }, + mmr_position: mined_info.mmr_position, + mined_height: mined_info.mined_height, + header_hash: mined_info.header_hash, + }); + } + Ok(outputs) + } + async fn get_constitutions( &mut self, start_block_hash: Option, diff --git a/dan_layer/core/src/services/acceptance_manager.rs b/dan_layer/core/src/services/acceptance_manager.rs index 6e267d610c..f51fac9009 100644 --- a/dan_layer/core/src/services/acceptance_manager.rs +++ b/dan_layer/core/src/services/acceptance_manager.rs @@ -21,9 +21,13 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use async_trait::async_trait; -use tari_common_types::types::FixedHash; +use tari_common_types::types::{Commitment, FixedHash}; use tari_comms::NodeIdentity; -use tari_core::transactions::transaction_components::{SignerSignature, TransactionOutput}; +use tari_core::{ + chain_storage::UtxoMinedInfo, + transactions::transaction_components::{OutputType, SignerSignature, TransactionOutput}, +}; +use tari_utilities::hex::Hex; use super::BaseNodeClient; use crate::{models::AcceptanceChallenge, services::wallet_client::WalletClient, DigitalAssetError}; @@ -60,17 +64,10 @@ impl Result { let public_key = node_identity.public_key(); - // FIXME: this is not the proper way to get the constitution commitment, we need a new method in the base node - let outputs = self.base_node.get_constitutions(None, public_key).await?; - let constitution_outputs: Vec = outputs - .into_iter() - .filter_map(|utxo| utxo.output.into_unpruned_output()) - .collect(); - let constitution_commitment = constitution_outputs.first().unwrap().commitment(); - // build the acceptance signature let secret_key = node_identity.secret_key(); - let challenge = AcceptanceChallenge::new(constitution_commitment, contract_id); + let constitution_commitment = self.fetch_constitution_commitment(contract_id).await?; + let challenge = AcceptanceChallenge::new(&constitution_commitment, contract_id); let signature = SignerSignature::sign(secret_key, challenge).signature; // publish the acceptance @@ -79,3 +76,31 @@ impl + ConcreteAcceptanceManager +{ + async fn fetch_constitution_commitment( + &mut self, + contract_id: &FixedHash, + ) -> Result { + let outputs: Vec = self + .base_node + .get_contract_utxos(*contract_id, OutputType::ContractConstitution) + .await?; + let transaction_outputs: Vec = outputs + .into_iter() + .filter_map(|utxo| utxo.output.into_unpruned_output()) + .collect(); + + if transaction_outputs.is_empty() { + return Err(DigitalAssetError::NotFound { + entity: "constitution", + id: contract_id.to_hex(), + }); + } + let constitution_commitment = transaction_outputs[0].commitment(); + + Ok(constitution_commitment.clone()) + } +} diff --git a/dan_layer/core/src/services/base_node_client.rs b/dan_layer/core/src/services/base_node_client.rs index b76fe2b3ab..1bc75733fe 100644 --- a/dan_layer/core/src/services/base_node_client.rs +++ b/dan_layer/core/src/services/base_node_client.rs @@ -46,6 +46,12 @@ pub trait BaseNodeClient: Send + Sync { dan_node_public_key: &PublicKey, ) -> Result, DigitalAssetError>; + async fn get_contract_utxos( + &mut self, + contract_id: FixedHash, + output_type: OutputType, + ) -> Result, DigitalAssetError>; + async fn check_if_in_committee( &mut self, asset_public_key: PublicKey, diff --git a/dan_layer/core/src/services/mocks/mod.rs b/dan_layer/core/src/services/mocks/mod.rs index 6f6ff3de21..d0a994128e 100644 --- a/dan_layer/core/src/services/mocks/mod.rs +++ b/dan_layer/core/src/services/mocks/mod.rs @@ -228,6 +228,14 @@ impl BaseNodeClient for MockBaseNodeClient { todo!() } + async fn get_contract_utxos( + &mut self, + _contract_id: FixedHash, + _output_type: OutputType, + ) -> Result, DigitalAssetError> { + todo!() + } + async fn check_if_in_committee( &mut self, _asset_public_key: PublicKey, From 07faafcdbaf722d61add588d6e1bccce3ac6c418 Mon Sep 17 00:00:00 2001 From: mrnaveira <47919901+mrnaveira@users.noreply.github.com> Date: Wed, 6 Jul 2022 16:14:56 +0100 Subject: [PATCH 08/13] avoid duplicated build of acceptance challenges --- .../side_chain/contract_acceptance_challenge.rs | 6 +++--- .../transaction_components/side_chain/mod.rs | 3 +++ .../validation/dan_validators/acceptance_validator.rs | 8 ++------ dan_layer/core/src/models/mod.rs | 2 -- dan_layer/core/src/services/acceptance_manager.rs | 11 ++++++++--- 5 files changed, 16 insertions(+), 14 deletions(-) rename dan_layer/core/src/models/acceptance_challenge.rs => base_layer/core/src/transactions/transaction_components/side_chain/contract_acceptance_challenge.rs (93%) diff --git a/dan_layer/core/src/models/acceptance_challenge.rs b/base_layer/core/src/transactions/transaction_components/side_chain/contract_acceptance_challenge.rs similarity index 93% rename from dan_layer/core/src/models/acceptance_challenge.rs rename to base_layer/core/src/transactions/transaction_components/side_chain/contract_acceptance_challenge.rs index 4fd46a28f7..10888dca39 100644 --- a/dan_layer/core/src/models/acceptance_challenge.rs +++ b/base_layer/core/src/transactions/transaction_components/side_chain/contract_acceptance_challenge.rs @@ -25,9 +25,9 @@ use tari_common_types::types::{Commitment, FixedHash, HashDigest}; use tari_utilities::ByteArray; #[derive(Debug, Clone, Copy)] -pub struct AcceptanceChallenge(FixedHash); +pub struct ContractAcceptanceChallenge(FixedHash); -impl AcceptanceChallenge { +impl ContractAcceptanceChallenge { pub fn new(constiution_commitment: &Commitment, contract_id: &FixedHash) -> Self { // TODO: Use new tari_crypto domain-separated hashing let hash = HashDigest::new() @@ -39,7 +39,7 @@ impl AcceptanceChallenge { } } -impl AsRef<[u8]> for AcceptanceChallenge { +impl AsRef<[u8]> for ContractAcceptanceChallenge { fn as_ref(&self) -> &[u8] { self.0.as_ref() } diff --git a/base_layer/core/src/transactions/transaction_components/side_chain/mod.rs b/base_layer/core/src/transactions/transaction_components/side_chain/mod.rs index 3a2012e99d..4774ad2cab 100644 --- a/base_layer/core/src/transactions/transaction_components/side_chain/mod.rs +++ b/base_layer/core/src/transactions/transaction_components/side_chain/mod.rs @@ -23,6 +23,9 @@ mod contract_acceptance; pub use contract_acceptance::ContractAcceptance; +mod contract_acceptance_challenge; +pub use contract_acceptance_challenge::ContractAcceptanceChallenge; + mod contract_constitution; pub use contract_constitution::{ CheckpointParameters, diff --git a/base_layer/core/src/validation/dan_validators/acceptance_validator.rs b/base_layer/core/src/validation/dan_validators/acceptance_validator.rs index 1e50eb9be1..5f43ee1a00 100644 --- a/base_layer/core/src/validation/dan_validators/acceptance_validator.rs +++ b/base_layer/core/src/validation/dan_validators/acceptance_validator.rs @@ -35,6 +35,7 @@ use crate::{ chain_storage::{BlockchainBackend, BlockchainDatabase}, transactions::transaction_components::{ ContractAcceptance, + ContractAcceptanceChallenge, ContractConstitution, OutputType, SideChainFeatures, @@ -159,12 +160,7 @@ pub fn validate_signature( validator_node_public_key: &PublicKey, ) -> Result<(), ValidationError> { let commitment = fetch_constitution_commitment(db, contract_id)?; - - // TODO: Use domain-seperated hasher from tari_crypto - let challenge = HashDigest::new() - .chain(commitment.as_bytes()) - .chain(contract_id.as_slice()) - .finalize(); + let challenge = ContractAcceptanceChallenge::new(&commitment, &contract_id); let final_challenge = HashDigest::new() .chain(validator_node_public_key.as_bytes()) .chain(signature.get_public_nonce().as_bytes()) diff --git a/dan_layer/core/src/models/mod.rs b/dan_layer/core/src/models/mod.rs index 6149a9b8e2..c202a79ea6 100644 --- a/dan_layer/core/src/models/mod.rs +++ b/dan_layer/core/src/models/mod.rs @@ -22,7 +22,6 @@ use std::{convert::TryFrom, fmt::Debug, hash::Hash}; -mod acceptance_challenge; mod asset_definition; mod base_layer_metadata; mod base_layer_output; @@ -43,7 +42,6 @@ mod tree_node_hash; mod view; mod view_id; -pub use acceptance_challenge::AcceptanceChallenge; pub use asset_definition::{AssetDefinition, InitialState}; pub use base_layer_metadata::BaseLayerMetadata; pub use base_layer_output::{BaseLayerOutput, CheckpointOutput, CommitteeOutput}; diff --git a/dan_layer/core/src/services/acceptance_manager.rs b/dan_layer/core/src/services/acceptance_manager.rs index f51fac9009..a8925f0615 100644 --- a/dan_layer/core/src/services/acceptance_manager.rs +++ b/dan_layer/core/src/services/acceptance_manager.rs @@ -25,12 +25,17 @@ use tari_common_types::types::{Commitment, FixedHash}; use tari_comms::NodeIdentity; use tari_core::{ chain_storage::UtxoMinedInfo, - transactions::transaction_components::{OutputType, SignerSignature, TransactionOutput}, + transactions::transaction_components::{ + ContractAcceptanceChallenge, + OutputType, + SignerSignature, + TransactionOutput, + }, }; use tari_utilities::hex::Hex; use super::BaseNodeClient; -use crate::{models::AcceptanceChallenge, services::wallet_client::WalletClient, DigitalAssetError}; +use crate::{services::wallet_client::WalletClient, DigitalAssetError}; #[async_trait] pub trait AcceptanceManager: Send + Sync { @@ -67,7 +72,7 @@ impl Date: Wed, 6 Jul 2022 16:18:48 +0100 Subject: [PATCH 09/13] reuse the acceptance challenge in unit tests --- .../core/src/validation/dan_validators/test_helpers.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/base_layer/core/src/validation/dan_validators/test_helpers.rs b/base_layer/core/src/validation/dan_validators/test_helpers.rs index ac715e8c46..2fd6592494 100644 --- a/base_layer/core/src/validation/dan_validators/test_helpers.rs +++ b/base_layer/core/src/validation/dan_validators/test_helpers.rs @@ -22,11 +22,9 @@ use std::convert::TryInto; -use blake2::Digest; -use tari_common_types::types::{Commitment, FixedHash, HashDigest, PublicKey, Signature}; +use tari_common_types::types::{Commitment, FixedHash, PublicKey, Signature}; use tari_crypto::ristretto::{RistrettoPublicKey, RistrettoSecretKey}; use tari_p2p::Network; -use tari_utilities::ByteArray; use super::TxDanLayerValidator; use crate::{ @@ -42,6 +40,7 @@ use crate::{ CommitteeSignatures, ConstitutionChangeFlags, ConstitutionChangeRules, + ContractAcceptanceChallenge, ContractAcceptanceRequirements, ContractAmendment, ContractCheckpoint, @@ -249,10 +248,7 @@ pub fn create_acceptance_signature( commitment: Commitment, private_key: RistrettoSecretKey, ) -> Signature { - let challenge = HashDigest::new() - .chain(commitment.as_bytes()) - .chain(contract_id.as_slice()) - .finalize(); + let challenge = ContractAcceptanceChallenge::new(&commitment, &contract_id); SignerSignature::sign(&private_key, &challenge).signature } From 6fecc993e1e0e8e381f3e8f32cac5eeefb21158e Mon Sep 17 00:00:00 2001 From: mrnaveira <47919901+mrnaveira@users.noreply.github.com> Date: Wed, 6 Jul 2022 17:19:30 +0100 Subject: [PATCH 10/13] create signer_signature verify function --- .../side_chain/signer_signature.rs | 27 ++++++++++++++----- .../dan_validators/acceptance_validator.rs | 13 +++------ 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/base_layer/core/src/transactions/transaction_components/side_chain/signer_signature.rs b/base_layer/core/src/transactions/transaction_components/side_chain/signer_signature.rs index 95a4dbadab..8c510f73e7 100644 --- a/base_layer/core/src/transactions/transaction_components/side_chain/signer_signature.rs +++ b/base_layer/core/src/transactions/transaction_components/side_chain/signer_signature.rs @@ -22,7 +22,7 @@ use std::io; -use digest::Digest; +use digest::{Digest, Output}; use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; use tari_common_types::types::{HashDigest, PrivateKey, PublicKey, Signature}; @@ -45,15 +45,30 @@ impl SignerSignature { pub fn sign>(signer_secret: &PrivateKey, challenge: C) -> Self { let signer = PublicKey::from_secret_key(signer_secret); let (nonce, public_nonce) = PublicKey::random_keypair(&mut OsRng); + + let final_challenge = Self::build_final_challenge(&signer, challenge, &public_nonce); + let signature = + Signature::sign(signer_secret.clone(), nonce, &*final_challenge).expect("challenge is the correct length"); + Self { signer, signature } + } + + pub fn verify>(signature: &Signature, signer: &PublicKey, challenge: C) -> bool { + let public_nonce = signature.get_public_nonce(); + let final_challenge = Self::build_final_challenge(signer, challenge, public_nonce); + signature.verify_challenge(signer, &final_challenge) + } + + fn build_final_challenge>( + signer: &PublicKey, + challenge: C, + public_nonce: &PublicKey, + ) -> Output { // TODO: Use domain-seperated hasher from tari_crypto - let final_challenge = HashDigest::new() + HashDigest::new() .chain(signer.as_bytes()) .chain(public_nonce.as_bytes()) .chain(challenge) - .finalize(); - let signature = - Signature::sign(signer_secret.clone(), nonce, &*final_challenge).expect("challenge is the correct length"); - Self { signer, signature } + .finalize() } pub fn signer(&self) -> &PublicKey { diff --git a/base_layer/core/src/validation/dan_validators/acceptance_validator.rs b/base_layer/core/src/validation/dan_validators/acceptance_validator.rs index 5f43ee1a00..88c04652b7 100644 --- a/base_layer/core/src/validation/dan_validators/acceptance_validator.rs +++ b/base_layer/core/src/validation/dan_validators/acceptance_validator.rs @@ -20,9 +20,8 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use blake2::Digest; -use tari_common_types::types::{Commitment, FixedHash, HashDigest, PublicKey, Signature}; -use tari_utilities::{hex::Hex, ByteArray}; +use tari_common_types::types::{Commitment, FixedHash, PublicKey, Signature}; +use tari_utilities::hex::Hex; use super::helpers::{ fetch_contract_constitution, @@ -39,6 +38,7 @@ use crate::{ ContractConstitution, OutputType, SideChainFeatures, + SignerSignature, TransactionOutput, }, validation::{dan_validators::DanLayerValidationError, ValidationError}, @@ -161,13 +161,8 @@ pub fn validate_signature( ) -> Result<(), ValidationError> { let commitment = fetch_constitution_commitment(db, contract_id)?; let challenge = ContractAcceptanceChallenge::new(&commitment, &contract_id); - let final_challenge = HashDigest::new() - .chain(validator_node_public_key.as_bytes()) - .chain(signature.get_public_nonce().as_bytes()) - .chain(challenge) - .finalize(); - let is_valid_signature = signature.verify_challenge(validator_node_public_key, &final_challenge); + let is_valid_signature = SignerSignature::verify(signature, validator_node_public_key, challenge); if !is_valid_signature { return Err(ValidationError::DanLayerError( DanLayerValidationError::InvalidAcceptanceSignature, From 1f223de4b7c252477f437a15ad9fe104984c49eb Mon Sep 17 00:00:00 2001 From: mrnaveira <47919901+mrnaveira@users.noreply.github.com> Date: Thu, 7 Jul 2022 09:35:58 +0100 Subject: [PATCH 11/13] fix multiple contract integration tests --- .../features/ValidatorNode.feature | 4 +++ .../features/support/validator_node_steps.js | 2 +- .../fixtures/contract_amendment.json | 27 +++---------------- .../fixtures/contract_update_proposal.json | 5 ++-- 4 files changed, 11 insertions(+), 27 deletions(-) diff --git a/integration_tests/features/ValidatorNode.feature b/integration_tests/features/ValidatorNode.feature index ba44484a78..28776a5860 100644 --- a/integration_tests/features/ValidatorNode.feature +++ b/integration_tests/features/ValidatorNode.feature @@ -43,6 +43,10 @@ Feature: Validator Node And I wait for wallet WALLET1 to have at least 1000000 uT And I publish a contract definition DEF1 from file "fixtures/contract_definition.json" on wallet WALLET1 via command line And I mine 4 blocks using wallet WALLET1 on NODE1 + And I publish a contract constitution from file "fixtures/contract_constitution.json" on wallet WALLET1 via command line + And I mine 4 blocks using wallet WALLET1 on NODE1 + And I publish a contract update proposal from file "fixtures/contract_update_proposal.json" on wallet WALLET1 via command line + And I mine 4 blocks using wallet WALLET1 on NODE1 And I have a validator node VN1 connected to base node NODE1 and wallet WALLET1 When I publish a contract update proposal acceptance transaction for the validator node VN1 And I mine 9 blocks using wallet WALLET1 on NODE1 diff --git a/integration_tests/features/support/validator_node_steps.js b/integration_tests/features/support/validator_node_steps.js index 1182c117eb..6082dfc787 100644 --- a/integration_tests/features/support/validator_node_steps.js +++ b/integration_tests/features/support/validator_node_steps.js @@ -71,7 +71,7 @@ Then( let dan_node = this.getNode(vn_name); let grpc_dan_node = await dan_node.createGrpcClient(); let response = await grpc_dan_node.publishContractUpdateProposalAcceptance( - "90b1da4524ea0e9479040d906db9194d8af90f28d05ff2d64c0a82eb93125177", // contract_id + "a58fb2adefcc40242f20f2d896e14451549dd60839fee78a7bd40ba2cc0a0e91", // contract_id 0 // proposal_id ); expect(response.status).to.be.equal("Accepted"); diff --git a/integration_tests/fixtures/contract_amendment.json b/integration_tests/fixtures/contract_amendment.json index 7fd9a90ddf..3bbbc3f592 100644 --- a/integration_tests/fixtures/contract_amendment.json +++ b/integration_tests/fixtures/contract_amendment.json @@ -1,38 +1,19 @@ { "proposal_id": 1, "validator_committee": [ - "ccac168b8edd67b10d152d1ed2337efc65da9fc0b6256dd49b3c559032553d44", - "ccac168b8edd67b10d152d1ed2337efc65da9fc0b6256dd49b3c559032553d44", "ccac168b8edd67b10d152d1ed2337efc65da9fc0b6256dd49b3c559032553d44" ], "validator_signatures": [ { "signer": "ccac168b8edd67b10d152d1ed2337efc65da9fc0b6256dd49b3c559032553d44", - "signature": { - "public_nonce": "3431860a4f70ddd6748d759cf66179321809e1c120a97cbdbbf2c01af5c8802f", - "signature": "be1b1e7cd18210bfced717d39bebc2534b31274976fb141856d9ee2bfe571900" - } - }, - { - "signer": "ccac168b8edd67b10d152d1ed2337efc65da9fc0b6256dd49b3c559032553d44", - "signature": { - "public_nonce": "3431860a4f70ddd6748d759cf66179321809e1c120a97cbdbbf2c01af5c8802f", - "signature": "be1b1e7cd18210bfced717d39bebc2534b31274976fb141856d9ee2bfe571900" - } - }, - { - "signer": "ccac168b8edd67b10d152d1ed2337efc65da9fc0b6256dd49b3c559032553d44", - "signature": { - "public_nonce": "3431860a4f70ddd6748d759cf66179321809e1c120a97cbdbbf2c01af5c8802f", - "signature": "be1b1e7cd18210bfced717d39bebc2534b31274976fb141856d9ee2bfe571900" - } + "public_nonce": "3431860a4f70ddd6748d759cf66179321809e1c120a97cbdbbf2c01af5c8802f", + "signature": "be1b1e7cd18210bfced717d39bebc2534b31274976fb141856d9ee2bfe571900" } ], "updated_constitution": { - "contract_id": "90b1da4524ea0e9479040d906db9194d8af90f28d05ff2d64c0a82eb93125177", + "contract_id": "a58fb2adefcc40242f20f2d896e14451549dd60839fee78a7bd40ba2cc0a0e91", "validator_committee": [ - "ccac168b8edd67b10d152d1ed2337efc65da9fc0b6256dd49b3c559032553d44", - "ccac168b8edd67b10d152d1ed2337efc65da9fc0b6256dd49b3c559032553d44", + "608001dffed28d058591cd65eaca11c465165592baf872cf1d984e26fb12b472", "ccac168b8edd67b10d152d1ed2337efc65da9fc0b6256dd49b3c559032553d44" ], "acceptance_parameters": { diff --git a/integration_tests/fixtures/contract_update_proposal.json b/integration_tests/fixtures/contract_update_proposal.json index fa0819d8a4..c990e487a8 100644 --- a/integration_tests/fixtures/contract_update_proposal.json +++ b/integration_tests/fixtures/contract_update_proposal.json @@ -6,10 +6,9 @@ "signature": "be1b1e7cd18210bfced717d39bebc2534b31274976fb141856d9ee2bfe571900" }, "updated_constitution": { - "contract_id": "90b1da4524ea0e9479040d906db9194d8af90f28d05ff2d64c0a82eb93125177", + "contract_id": "a58fb2adefcc40242f20f2d896e14451549dd60839fee78a7bd40ba2cc0a0e91", "validator_committee": [ - "ccac168b8edd67b10d152d1ed2337efc65da9fc0b6256dd49b3c559032553d44", - "ccac168b8edd67b10d152d1ed2337efc65da9fc0b6256dd49b3c559032553d44", + "608001dffed28d058591cd65eaca11c465165592baf872cf1d984e26fb12b472", "ccac168b8edd67b10d152d1ed2337efc65da9fc0b6256dd49b3c559032553d44" ], "acceptance_parameters": { From 06b43b0e77697598ef97cfe16706f7a4abc302ee Mon Sep 17 00:00:00 2001 From: mrnaveira <47919901+mrnaveira@users.noreply.github.com> Date: Thu, 7 Jul 2022 11:02:50 +0100 Subject: [PATCH 12/13] refactor base_node_client contract function --- .../src/grpc/services/base_node_client.rs | 35 ++----------------- .../core/src/models/base_layer_output.rs | 28 +++++++++++++-- .../core/src/services/acceptance_manager.rs | 2 +- dan_layer/core/src/services/asset_proxy.rs | 9 +++-- .../core/src/services/base_node_client.rs | 8 +---- dan_layer/core/src/services/mocks/mod.rs | 10 +----- dan_layer/core/src/workers/states/starting.rs | 8 ++--- .../core/src/workers/states/synchronizing.rs | 9 +++-- 8 files changed, 47 insertions(+), 62 deletions(-) diff --git a/applications/tari_validator_node/src/grpc/services/base_node_client.rs b/applications/tari_validator_node/src/grpc/services/base_node_client.rs index 6d70fddf3e..84f603b2a7 100644 --- a/applications/tari_validator_node/src/grpc/services/base_node_client.rs +++ b/applications/tari_validator_node/src/grpc/services/base_node_client.rs @@ -85,7 +85,7 @@ impl BaseNodeClient for GrpcBaseNodeClient { _height: u64, contract_id: FixedHash, output_type: OutputType, - ) -> Result, DigitalAssetError> { + ) -> Result, DigitalAssetError> { let inner = self.connection().await?; let request = grpc::GetCurrentContractOutputsRequest { contract_id: contract_id.to_vec(), @@ -105,39 +105,8 @@ impl BaseNodeClient for GrpcBaseNodeClient { Err(err) => return Err(err.into()), }; - resp.outputs - .into_iter() - .map(|output| { - let mined_height = output.mined_height; - let features = output.output.and_then(|o| o.features).ok_or_else(|| { - DigitalAssetError::ConversionError("Output was none/pruned or did not contain features".to_string()) - })?; - - match features.try_into() { - Ok(features) => Ok(BaseLayerOutput { - features, - height: mined_height, - }), - Err(e) => Err(DigitalAssetError::ConversionError(e)), - } - }) - .collect() - } - - async fn get_contract_utxos( - &mut self, - contract_id: FixedHash, - output_type: OutputType, - ) -> Result, DigitalAssetError> { - let conn = self.connection().await?; - let request = grpc::GetCurrentContractOutputsRequest { - contract_id: contract_id.to_vec(), - output_type: u32::from(output_type.as_byte()), - }; - let result = conn.get_current_contract_outputs(request).await?.into_inner(); - let mut outputs = vec![]; - for mined_info in result.outputs { + for mined_info in resp.outputs { let output = mined_info .output .map(TryInto::try_into) diff --git a/dan_layer/core/src/models/base_layer_output.rs b/dan_layer/core/src/models/base_layer_output.rs index 3bae121e89..d01f18d255 100644 --- a/dan_layer/core/src/models/base_layer_output.rs +++ b/dan_layer/core/src/models/base_layer_output.rs @@ -24,9 +24,12 @@ use std::convert::TryFrom; use tari_common_types::types::{FixedHash, PublicKey}; -use tari_core::transactions::transaction_components::{OutputFeatures, OutputType}; +use tari_core::{ + chain_storage::UtxoMinedInfo, + transactions::transaction_components::{OutputFeatures, OutputType}, +}; -use crate::models::ModelError; +use crate::{models::ModelError, DigitalAssetError}; #[derive(Debug)] pub struct BaseLayerOutput { @@ -87,6 +90,27 @@ impl TryFrom for CheckpointOutput { } } +impl TryFrom for BaseLayerOutput { + type Error = DigitalAssetError; + + fn try_from(utxo: UtxoMinedInfo) -> Result { + let mined_height = utxo.mined_height; + let features = match utxo.output.as_transaction_output() { + Some(o) => o.features.clone(), + None => { + return Err(DigitalAssetError::ConversionError( + "Output was none/pruned or did not contain features".to_string(), + )) + }, + }; + + Ok(BaseLayerOutput { + features, + height: mined_height, + }) + } +} + #[derive(Debug, Clone)] pub struct CommitteeOutput { pub flags: OutputType, diff --git a/dan_layer/core/src/services/acceptance_manager.rs b/dan_layer/core/src/services/acceptance_manager.rs index a8925f0615..20f9716f53 100644 --- a/dan_layer/core/src/services/acceptance_manager.rs +++ b/dan_layer/core/src/services/acceptance_manager.rs @@ -91,7 +91,7 @@ impl Result { let outputs: Vec = self .base_node - .get_contract_utxos(*contract_id, OutputType::ContractConstitution) + .get_current_contract_outputs(0, *contract_id, OutputType::ContractConstitution) .await?; let transaction_outputs: Vec = outputs .into_iter() diff --git a/dan_layer/core/src/services/asset_proxy.rs b/dan_layer/core/src/services/asset_proxy.rs index 0c0babd1e1..3065ff3b5c 100644 --- a/dan_layer/core/src/services/asset_proxy.rs +++ b/dan_layer/core/src/services/asset_proxy.rs @@ -20,6 +20,8 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::convert::TryFrom; + use async_trait::async_trait; use futures::stream::FuturesUnordered; use log::*; @@ -31,6 +33,7 @@ use tari_utilities::hex::Hex; use tokio_stream::StreamExt; use crate::{ + models::BaseLayerOutput, services::{ validator_node_rpc_client::ValidatorNodeRpcClient, BaseNodeClient, @@ -141,7 +144,7 @@ impl> ConcreteAsse ) -> Result>, DigitalAssetError> { let mut base_node_client = self.base_node_client.clone(); let tip = base_node_client.get_tip_info().await?; - let mut constitution = base_node_client + let mut outputs = base_node_client .get_current_contract_outputs( tip.height_of_longest_chain, contract_id, @@ -149,8 +152,8 @@ impl> ConcreteAsse ) .await?; - let constitution = match constitution.pop() { - Some(chk) => chk, + let constitution = match outputs.pop() { + Some(chk) => BaseLayerOutput::try_from(chk)?, None => { return Err(DigitalAssetError::NotFound { entity: "checkpoint", diff --git a/dan_layer/core/src/services/base_node_client.rs b/dan_layer/core/src/services/base_node_client.rs index 1bc75733fe..0b6caeb92d 100644 --- a/dan_layer/core/src/services/base_node_client.rs +++ b/dan_layer/core/src/services/base_node_client.rs @@ -38,7 +38,7 @@ pub trait BaseNodeClient: Send + Sync { height: u64, contract_id: FixedHash, output_type: OutputType, - ) -> Result, DigitalAssetError>; + ) -> Result, DigitalAssetError>; async fn get_constitutions( &mut self, @@ -46,12 +46,6 @@ pub trait BaseNodeClient: Send + Sync { dan_node_public_key: &PublicKey, ) -> Result, DigitalAssetError>; - async fn get_contract_utxos( - &mut self, - contract_id: FixedHash, - output_type: OutputType, - ) -> Result, DigitalAssetError>; - async fn check_if_in_committee( &mut self, asset_public_key: PublicKey, diff --git a/dan_layer/core/src/services/mocks/mod.rs b/dan_layer/core/src/services/mocks/mod.rs index 1ade4f183d..2a7966aa1b 100644 --- a/dan_layer/core/src/services/mocks/mod.rs +++ b/dan_layer/core/src/services/mocks/mod.rs @@ -232,14 +232,6 @@ impl BaseNodeClient for MockBaseNodeClient { todo!() } - async fn get_contract_utxos( - &mut self, - _contract_id: FixedHash, - _output_type: OutputType, - ) -> Result, DigitalAssetError> { - todo!() - } - async fn check_if_in_committee( &mut self, _asset_public_key: PublicKey, @@ -267,7 +259,7 @@ impl BaseNodeClient for MockBaseNodeClient { _height: u64, _contract_id: FixedHash, _output_type: OutputType, - ) -> Result, DigitalAssetError> { + ) -> Result, DigitalAssetError> { todo!() } } diff --git a/dan_layer/core/src/workers/states/starting.rs b/dan_layer/core/src/workers/states/starting.rs index 75f39bac69..360aba2947 100644 --- a/dan_layer/core/src/workers/states/starting.rs +++ b/dan_layer/core/src/workers/states/starting.rs @@ -20,7 +20,7 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::marker::PhantomData; +use std::{convert::TryInto, marker::PhantomData}; use log::*; use tari_core::transactions::transaction_components::OutputType; @@ -62,7 +62,7 @@ impl Starting { ); let tip = base_node_client.get_tip_info().await?; // get latest checkpoint on the base layer - let mut constitution = base_node_client + let mut outputs = base_node_client .get_current_contract_outputs( tip.height_of_longest_chain - asset_definition.base_layer_confirmation_time, asset_definition.contract_id, @@ -70,8 +70,8 @@ impl Starting { ) .await?; - let output = match constitution.pop() { - Some(chk) => chk, + let output = match outputs.pop() { + Some(chk) => chk.try_into()?, None => return Ok(ConsensusWorkerStateEvent::BaseLayerCheckopintNotFound), }; diff --git a/dan_layer/core/src/workers/states/synchronizing.rs b/dan_layer/core/src/workers/states/synchronizing.rs index d94f12553a..d937ea8425 100644 --- a/dan_layer/core/src/workers/states/synchronizing.rs +++ b/dan_layer/core/src/workers/states/synchronizing.rs @@ -28,7 +28,7 @@ use tari_core::transactions::transaction_components::OutputType; use tari_dan_engine::state::StateDbUnitOfWorkReader; use crate::{ - models::{AssetDefinition, CheckpointOutput}, + models::{AssetDefinition, BaseLayerOutput, CheckpointOutput}, services::{BaseNodeClient, ServiceSpecification}, storage::DbFactory, workers::{state_sync::StateSynchronizer, states::ConsensusWorkerStateEvent}, @@ -70,7 +70,10 @@ impl> Synchronizing< .await?; let last_checkpoint = match last_checkpoint.pop() { - Some(o) => CheckpointOutput::try_from(o)?, + Some(utxo) => { + let output = BaseLayerOutput::try_from(utxo)?; + CheckpointOutput::try_from(output)? + }, None => return Ok(ConsensusWorkerStateEvent::BaseLayerCheckpointNotFound), }; @@ -83,7 +86,7 @@ impl> Synchronizing< .await?; let current_constitution = match constitution.pop() { - Some(o) => o, + Some(o) => BaseLayerOutput::try_from(o)?, None => return Ok(ConsensusWorkerStateEvent::BaseLayerCheckopintNotFound), }; From 5318a9ec1f212f7aa371ef9e514fe42578dd86ed Mon Sep 17 00:00:00 2001 From: mrnaveira <47919901+mrnaveira@users.noreply.github.com> Date: Thu, 7 Jul 2022 11:09:37 +0100 Subject: [PATCH 13/13] remove redundant trait bound --- dan_layer/core/src/services/acceptance_manager.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dan_layer/core/src/services/acceptance_manager.rs b/dan_layer/core/src/services/acceptance_manager.rs index 20f9716f53..e7a4e03ece 100644 --- a/dan_layer/core/src/services/acceptance_manager.rs +++ b/dan_layer/core/src/services/acceptance_manager.rs @@ -47,7 +47,7 @@ pub trait AcceptanceManager: Send + Sync { } #[derive(Clone)] -pub struct ConcreteAcceptanceManager { +pub struct ConcreteAcceptanceManager { wallet: TWallet, base_node: TBaseNode, }