From dba167beab6e92071d51a2415b865417b39a3d5a Mon Sep 17 00:00:00 2001 From: Hansie Odendaal <39146854+hansieodendaal@users.noreply.github.com> Date: Fri, 22 Jul 2022 14:45:49 +0200 Subject: [PATCH 1/3] feat: add tari_crypto hashing api support (#4328) Description --- Added a hashing domain API for general use based on the new domain separated hashing API from `tari_crypto`; this is a proposal to implement the hashing domain API system wide. A new struct `HashingDomain` was added to provide a standardized way of implementing the new tari_crypto hashing api, which provides standardized domain separated hashing, with two tari project specific hash domain structs, `DefaultHashDomain` and `MacHashDomain`. USe of `HashingDomain` allows differentiated domain declarations per "hash originating generation" crate level (_see **Suggested hashing domains** below_). The domain labels were chosen to complement the tari project specific hash domain label prefixes provided by `DefaultHashDomain` and `MacHashDomain` as `"com.tari.tari_project.hash_domain.v1"` and `"com.tari.tari_project.mac_domain.v1"`. As an example, resulting domain labels for `COMMON_HASH_DOMAIN` that specifies its own label `"common"` will then be `"com.tari.tari_project.hash_domain.v1.common"` and `"com.tari.tari_project.mac_domain.v1.common"`. An example use case is: ``` rust let mut hasher = common_hash_domain().hasher::(); hasher.update(b"my 1st secret"); hasher.update(b"my 2nd secret"); let hash = hasher.finalize(); ``` The general idea is to use the lowest level hashing domain for the crate where an original hash needs to be generated, and where no higher level hash domain is defined for a specific crate, `common_hash_domain()` needs to be used. Code that replays a specific hash needs to use the same hash domain where the hash originated, for example the mempool. This standardized domain declarations should make code maintenance where domain hashing is used simpler and more efficient. The next PR will demonstrate use of `tari_script_hash_domain()` and `mmr_hash_domain()`. **Suggested hashing domains** (_This can easily be modified as needed._) ``` rust pub fn common_hash_domain() -> HashingDomain {HashingDomain::new("common")} pub fn comms_core_hash_domain() -> HashingDomain {HashingDomain::new("comms.core")} pub fn comms_dht_hash_domain() -> HashingDomain {HashingDomain::new("comms.dht")} pub fn core_hash_domain() -> HashingDomain {HashingDomain::new("base_layer.core")} pub fn core_blocks_hash_domain() -> HashingDomain {HashingDomain::new("base_layer.core.blocks")} pub fn core_consensus_hash_domain() -> HashingDomain {HashingDomain::new("base_layer.core.consensus")} pub fn core_covenants_hash_domain() -> HashingDomain {HashingDomain::new("base_layer.core.covenants")} pub fn core_proof_of_work_hash_domain() -> HashingDomain {HashingDomain::new("base_layer.core.proof_of_work")} pub fn core_transactions_hash_domain() -> HashingDomain {HashingDomain::new("base_layer.core.transactions")} pub fn dan_layer_core_hash_domain() -> HashingDomain {HashingDomain::new("dan_layer.core")} pub fn dan_layer_engine_hash_domain() -> HashingDomain {HashingDomain::new("dan_layer.engine")} pub fn dan_layer_hash_domain() -> HashingDomain {HashingDomain::new("dan_layer")} pub fn key_manager_hash_domain() -> HashingDomain {HashingDomain::new("base_layer.key_manager")} pub fn mmr_hash_domain() -> HashingDomain {HashingDomain::new("base_layer.mmr")} pub fn p2p_hash_domain() -> HashingDomain {HashingDomain::new("base_layer.p2p")} pub fn tari_script_hash_domain() -> HashingDomain {HashingDomain::new("infrastructure.tari_script")} ``` Motivation and Context --- Needed to implement the `tari_crypto` standardized domain separated hashing. How Has This Been Tested? --- Unit tests (_no system level impact with this PR yet_). --- Cargo.lock | 6 + .../src/types/default_hash_domain.rs | 36 ++++ .../common_types/src/types/mac_hash_domain.rs | 36 ++++ base_layer/common_types/src/types/mod.rs | 11 ++ base_layer/core/src/blocks/mod.rs | 9 + base_layer/core/src/consensus/mod.rs | 9 + base_layer/core/src/covenants/mod.rs | 9 + base_layer/core/src/lib.rs | 9 + base_layer/core/src/proof_of_work/mod.rs | 10 + base_layer/core/src/transactions/mod.rs | 9 + .../unblinded_output.rs | 1 + base_layer/key_manager/Cargo.toml | 1 + base_layer/key_manager/src/lib.rs | 10 + base_layer/mmr/Cargo.toml | 1 + base_layer/mmr/src/lib.rs | 9 + base_layer/p2p/src/lib.rs | 9 + common/Cargo.toml | 2 + common/src/hashing_domain.rs | 176 ++++++++++++++++++ common/src/lib.rs | 12 ++ comms/core/Cargo.toml | 1 + comms/core/src/lib.rs | 9 + comms/dht/src/lib.rs | 9 + dan_layer/common_types/Cargo.toml | 1 + dan_layer/common_types/src/lib.rs | 10 + dan_layer/core/src/lib.rs | 10 + dan_layer/engine/Cargo.toml | 1 + dan_layer/engine/src/lib.rs | 10 + infrastructure/tari_script/Cargo.toml | 1 + infrastructure/tari_script/src/lib.rs | 9 + 29 files changed, 426 insertions(+) create mode 100644 base_layer/common_types/src/types/default_hash_domain.rs create mode 100644 base_layer/common_types/src/types/mac_hash_domain.rs create mode 100644 common/src/hashing_domain.rs diff --git a/Cargo.lock b/Cargo.lock index 012509b6e9..13ffab44c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6849,6 +6849,8 @@ dependencies = [ "serde_json", "sha2 0.9.9", "structopt", + "tari_common_types", + "tari_crypto", "tari_test_utils 0.34.0", "tempfile", "thiserror", @@ -7186,6 +7188,7 @@ dependencies = [ "log", "serde", "serde_json", + "tari_common", "tari_common_types", "tari_crypto", "tari_dan_common_types", @@ -7236,6 +7239,7 @@ dependencies = [ "sha2 0.9.9", "strum 0.22.0", "strum_macros 0.22.0", + "tari_common", "tari_common_types", "tari_crypto", "tari_utilities", @@ -7404,6 +7408,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde_json", + "tari_common", "tari_crypto", "tari_utilities", "thiserror", @@ -7461,6 +7466,7 @@ dependencies = [ "serde", "sha2 0.9.9", "sha3", + "tari_common", "tari_common_types", "tari_crypto", "tari_utilities", diff --git a/base_layer/common_types/src/types/default_hash_domain.rs b/base_layer/common_types/src/types/default_hash_domain.rs new file mode 100644 index 0000000000..f84ea56867 --- /dev/null +++ b/base_layer/common_types/src/types/default_hash_domain.rs @@ -0,0 +1,36 @@ +// 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 tari_crypto::hashing::DomainSeparation; + +/// The default domain separation marker for use in the tari project. +pub struct DefaultHashDomain; + +impl DomainSeparation for DefaultHashDomain { + fn version() -> u8 { + 1 + } + + fn domain() -> &'static str { + "com.tari.tari_project.hash_domain" + } +} diff --git a/base_layer/common_types/src/types/mac_hash_domain.rs b/base_layer/common_types/src/types/mac_hash_domain.rs new file mode 100644 index 0000000000..25509c1c48 --- /dev/null +++ b/base_layer/common_types/src/types/mac_hash_domain.rs @@ -0,0 +1,36 @@ +// 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 tari_crypto::hashing::DomainSeparation; + +/// A domain separation marker for use in MAC derivation algorithms. +pub struct MacHashDomain; + +impl DomainSeparation for MacHashDomain { + fn version() -> u8 { + 1 + } + + fn domain() -> &'static str { + "com.tari.tari_project.mac_domain" + } +} diff --git a/base_layer/common_types/src/types/mod.rs b/base_layer/common_types/src/types/mod.rs index 9230f31f76..9d496350da 100644 --- a/base_layer/common_types/src/types/mod.rs +++ b/base_layer/common_types/src/types/mod.rs @@ -21,11 +21,14 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. mod bullet_rangeproofs; +mod default_hash_domain; mod fixed_hash; +mod mac_hash_domain; pub use bullet_rangeproofs::BulletRangeProof; use tari_crypto::{ hash::blake2::Blake256, + hashing::DomainSeparatedHasher, ristretto::{ bulletproofs_plus::BulletproofsPlusService, pedersen::{extended_commitment_factory::ExtendedPedersenCommitmentFactory, PedersenCommitment}, @@ -41,6 +44,8 @@ pub type BlockHash = Vec; pub use fixed_hash::{FixedHash, FixedHashSizeError}; +use crate::types::{default_hash_domain::DefaultHashDomain, mac_hash_domain::MacHashDomain}; + /// Define the explicit Signature implementation for the Tari base layer. A different signature scheme can be /// employed by redefining this type. pub type Signature = RistrettoSchnorr; @@ -81,3 +86,9 @@ pub type RangeProofService = BulletproofsPlusService; /// Specify the range proof pub type RangeProof = BulletRangeProof; + +/// Generic domain separated hasher +pub type DefaultDomainHasher = DomainSeparatedHasher; + +/// MAC domain separated hasher +pub type MacDomainHasher = DomainSeparatedHasher; diff --git a/base_layer/core/src/blocks/mod.rs b/base_layer/core/src/blocks/mod.rs index 3b7eb851f1..70ab78af8f 100644 --- a/base_layer/core/src/blocks/mod.rs +++ b/base_layer/core/src/blocks/mod.rs @@ -61,3 +61,12 @@ pub use new_block_template::NewBlockTemplate; mod new_blockheader_template; #[cfg(feature = "base_node")] pub use new_blockheader_template::NewBlockHeaderTemplate; +use tari_common::hashing_domain::HashingDomain; + +/// The base layer core blocks domain separated hashing domain +/// Usage: +/// let hash = core_blocks_hash_domain().digest::(b"my secret"); +/// etc. +pub fn core_blocks_hash_domain() -> HashingDomain { + HashingDomain::new("base_layer.core.blocks") +} diff --git a/base_layer/core/src/consensus/mod.rs b/base_layer/core/src/consensus/mod.rs index 7c6f783f18..d4343131d0 100644 --- a/base_layer/core/src/consensus/mod.rs +++ b/base_layer/core/src/consensus/mod.rs @@ -44,5 +44,14 @@ pub use consensus_encoding::{ mod network; pub use network::NetworkConsensus; +use tari_common::hashing_domain::HashingDomain; pub mod emission; + +/// The base layer core consensus domain separated hashing domain +/// Usage: +/// let hash = core_consensus_hash_domain().digest::(b"my secret"); +/// etc. +pub fn core_consensus_hash_domain() -> HashingDomain { + HashingDomain::new("base_layer.core.consensus") +} diff --git a/base_layer/core/src/covenants/mod.rs b/base_layer/core/src/covenants/mod.rs index 74cf99b0af..c0a3e712b7 100644 --- a/base_layer/core/src/covenants/mod.rs +++ b/base_layer/core/src/covenants/mod.rs @@ -44,6 +44,7 @@ pub use error::CovenantError; // Used in macro #[allow(unused_imports)] pub(crate) use fields::OutputField; +use tari_common::hashing_domain::HashingDomain; pub use token::CovenantToken; #[macro_use] @@ -51,3 +52,11 @@ mod macros; #[cfg(test)] mod test; + +/// The base layer core covenants domain separated hashing domain +/// Usage: +/// let hash = core_covenants_hash_domain().digest::(b"my secret"); +/// etc. +pub fn core_covenants_hash_domain() -> HashingDomain { + HashingDomain::new("base_layer.core.covenants") +} diff --git a/base_layer/core/src/lib.rs b/base_layer/core/src/lib.rs index 85f7afadad..b1498d1a5b 100644 --- a/base_layer/core/src/lib.rs +++ b/base_layer/core/src/lib.rs @@ -67,3 +67,12 @@ pub mod large_ints { } } pub use large_ints::{U256, U512}; +use tari_common::hashing_domain::HashingDomain; + +/// The base layer core domain separated hashing domain +/// Usage: +/// let hash = core_hash_domain().digest::(b"my secret"); +/// etc. +pub fn core_hash_domain() -> HashingDomain { + HashingDomain::new("base_layer.core") +} diff --git a/base_layer/core/src/proof_of_work/mod.rs b/base_layer/core/src/proof_of_work/mod.rs index 7caaa8bc1a..1083a114f6 100644 --- a/base_layer/core/src/proof_of_work/mod.rs +++ b/base_layer/core/src/proof_of_work/mod.rs @@ -65,3 +65,13 @@ pub mod lwma_diff; #[cfg(feature = "base_node")] pub mod randomx_factory; + +use tari_common::hashing_domain::HashingDomain; + +/// The base layer core proof-of-work domain separated hashing domain +/// Usage: +/// let hash = core_proof_of_work_hash_domain().digest::(b"my secret"); +/// etc. +pub fn core_proof_of_work_hash_domain() -> HashingDomain { + HashingDomain::new("base_layer.core.proof_of_work") +} diff --git a/base_layer/core/src/transactions/mod.rs b/base_layer/core/src/transactions/mod.rs index 770608f33c..5bf2a08452 100644 --- a/base_layer/core/src/transactions/mod.rs +++ b/base_layer/core/src/transactions/mod.rs @@ -15,6 +15,7 @@ pub mod transaction_components; mod format_currency; pub use format_currency::format_currency; +use tari_common::hashing_domain::HashingDomain; pub mod transaction_protocol; pub use transaction_protocol::{recipient::ReceiverTransactionProtocol, sender::SenderTransactionProtocol}; @@ -24,3 +25,11 @@ pub mod weight; #[macro_use] pub mod test_helpers; + +/// The base layer core transactions domain separated hashing domain +/// Usage: +/// let hash = core_transactions_hash_domain().digest::(b"my secret"); +/// etc. +pub fn core_transactions_hash_domain() -> HashingDomain { + HashingDomain::new("base_layer.core.transactions") +} diff --git a/base_layer/core/src/transactions/transaction_components/unblinded_output.rs b/base_layer/core/src/transactions/transaction_components/unblinded_output.rs index 5dd7f38907..0d55686c04 100644 --- a/base_layer/core/src/transactions/transaction_components/unblinded_output.rs +++ b/base_layer/core/src/transactions/transaction_components/unblinded_output.rs @@ -95,6 +95,7 @@ pub struct UnblindedOutput { impl UnblindedOutput { /// Creates a new un-blinded output + #[allow(clippy::too_many_arguments)] pub fn new( version: TransactionOutputVersion, value: MicroTari, diff --git a/base_layer/key_manager/Cargo.toml b/base_layer/key_manager/Cargo.toml index 83cfad77a7..d40d183b52 100644 --- a/base_layer/key_manager/Cargo.toml +++ b/base_layer/key_manager/Cargo.toml @@ -12,6 +12,7 @@ crate-type = ["lib", "cdylib"] [dependencies] tari_common_types = { version = "^0.34", path = "../../base_layer/common_types" } +tari_common = {path = "../../common"} tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", tag = "v0.15.0" } tari_utilities = { git = "https://github.com/tari-project/tari_utilities.git", tag = "v0.4.4" } diff --git a/base_layer/key_manager/src/lib.rs b/base_layer/key_manager/src/lib.rs index 8222d0ecf1..1457fd3da8 100644 --- a/base_layer/key_manager/src/lib.rs +++ b/base_layer/key_manager/src/lib.rs @@ -1,6 +1,8 @@ // Copyright 2022 The Tari Project // SPDX-License-Identifier: BSD-3-Clause +use tari_common::hashing_domain::HashingDomain; + pub mod cipher_seed; pub mod diacritics; pub mod error; @@ -11,3 +13,11 @@ pub mod mnemonic_wordlists; #[allow(clippy::unused_unit)] #[cfg(feature = "wasm")] pub mod wasm; + +/// The base layer key manager domain separated hashing domain +/// Usage: +/// let hash = key_manager_hash_domain().digest::(b"my secret"); +/// etc. +pub fn key_manager_hash_domain() -> HashingDomain { + HashingDomain::new("base_layer.key_manager") +} diff --git a/base_layer/mmr/Cargo.toml b/base_layer/mmr/Cargo.toml index 83da0b8fc2..80887d4158 100644 --- a/base_layer/mmr/Cargo.toml +++ b/base_layer/mmr/Cargo.toml @@ -14,6 +14,7 @@ benches = ["criterion"] [dependencies] tari_utilities = { git = "https://github.com/tari-project/tari_utilities.git", tag = "v0.4.4" } +tari_common = {path = "../../common"} thiserror = "1.0.26" digest = "0.9.0" log = "0.4" diff --git a/base_layer/mmr/src/lib.rs b/base_layer/mmr/src/lib.rs index 687fca5977..aca3888374 100644 --- a/base_layer/mmr/src/lib.rs +++ b/base_layer/mmr/src/lib.rs @@ -156,6 +156,15 @@ pub use mem_backend_vec::MemBackendVec; pub use merkle_mountain_range::MerkleMountainRange; /// A data structure for proving a hash inclusion in an MMR pub use merkle_proof::{MerkleProof, MerkleProofError}; +use tari_common::hashing_domain::HashingDomain; + +/// The base layer MMR domain separated hashing domain +/// Usage: +/// let hash = mmr_hash_domain().digest::(b"my secret"); +/// etc. +pub fn mmr_hash_domain() -> HashingDomain { + HashingDomain::new("base_layer.mmr") +} macro_rules! if_native_bitmap { ($($item:item)*) => { diff --git a/base_layer/p2p/src/lib.rs b/base_layer/p2p/src/lib.rs index 5763213f5e..c1b715500e 100644 --- a/base_layer/p2p/src/lib.rs +++ b/base_layer/p2p/src/lib.rs @@ -44,6 +44,7 @@ mod dns; // Re-export pub use socks_authentication::SocksAuthentication; pub use tari_common::configuration::Network; +use tari_common::hashing_domain::HashingDomain; pub use tor_authentication::TorControlAuthentication; pub use transport::{Socks5TransportConfig, TcpTransportConfig, TorTransportConfig, TransportConfig, TransportType}; @@ -57,3 +58,11 @@ pub const MAJOR_NETWORK_VERSION: u8 = 0; /// Minor network version. This should change with each time the network protocol has changed in a backward-compatible /// way. pub const MINOR_NETWORK_VERSION: u8 = 0; + +/// The base layer p2p domain separated hashing domain +/// Usage: +/// let hash = p2p_hash_domain(.digest::(b"my secret"); +/// etc. +pub fn p2p_hash_domain() -> HashingDomain { + HashingDomain::new("base_layer.p2p") +} diff --git a/common/Cargo.toml b/common/Cargo.toml index 064c12b36c..e7374e77d9 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -14,6 +14,8 @@ build = ["toml", "prost-build"] static-application-info = ["git2"] [dependencies] +tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", tag = "v0.15.0" } +tari_common_types = { path = "../base_layer/common_types" } anyhow = "1.0.53" config = { version = "0.13.0", default_features = false, features = ["toml"] } derivative = "2.2.0" diff --git a/common/src/hashing_domain.rs b/common/src/hashing_domain.rs new file mode 100644 index 0000000000..d3c5899da6 --- /dev/null +++ b/common/src/hashing_domain.rs @@ -0,0 +1,176 @@ +// Copyright 2019. 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 sha2::Digest; +use tari_common_types::types::{DefaultDomainHasher, MacDomainHasher}; +use tari_crypto::hashing::{DomainSeparatedHash, LengthExtensionAttackResistant, Mac}; +use thiserror::Error; + +pub struct HashingDomain { + domain_label: &'static str, +} + +/// Error type for the pipeline. +#[derive(Debug, Error)] +pub enum HashingDomainError { + #[error("Two slices have different lengths")] + CopyFromSlice, +} + +impl HashingDomain { + /// A new constant hashing domain with domain label + pub const fn new(domain_label: &'static str) -> Self { + Self { domain_label } + } + + /// A new generic domain separated hasher for the chosen hashing domain + pub fn hasher(&self) -> DefaultDomainHasher { + DefaultDomainHasher::new(self.domain_label) + } + + /// Convenience function to compute hash of the data. It will handle hasher creation, data feeding and finalization. + pub fn digest(&self, data: &[u8]) -> DomainSeparatedHash { + self.hasher::().chain(data).finalize() + } + + /// A new MAC domain separated hasher for the chosen hashing domain - can be used for custom MAC hashing + pub fn mac_hasher(&self) -> MacDomainHasher { + MacDomainHasher::new(self.domain_label) + } + + /// Convenience function to compute hash of the data. It will handle hasher creation, data feeding and finalization. + pub fn mac_digest(&self, data: &[u8]) -> DomainSeparatedHash { + self.mac_hasher::().chain(data).finalize() + } + + /// Generate a finalized domain separated Hash-based Message Authentication Code (HMAC) for the key and message + pub fn generate_hmac(&self, key: &[u8], msg: &[u8]) -> Mac { + Mac::generate::(key, msg, self.domain_label) + } +} + +pub trait HashToBytes: AsRef<[u8]> { + fn hash_to_bytes(&self) -> Result<[u8; I], HashingDomainError> { + let hash_vec = self.as_ref(); + if hash_vec.is_empty() || hash_vec.len() < I { + return Err(HashingDomainError::CopyFromSlice); + } + let mut buffer: [u8; I] = [0; I]; + buffer.copy_from_slice(&hash_vec[..I]); + Ok(buffer) + } +} + +impl HashToBytes for DomainSeparatedHash {} + +#[cfg(test)] +mod test { + use tari_crypto::{hash::blake2::Blake256, tari_utilities::hex::Hex}; + + use crate::{common_hash_domain, hashing_domain::HashToBytes}; + + #[test] + fn test_generic_domain_hasher() { + let mut hasher = common_hash_domain().hasher::(); + hasher.update(b"my 1st secret"); + hasher.update(b"my 2nd secret"); + let hash = hasher.finalize(); + + let hash_to_bytes_7: [u8; 7] = hash.hash_to_bytes().unwrap(); + assert_eq!(hash_to_bytes_7, hash.hash_to_bytes().unwrap()); + let hash_to_bytes_23: [u8; 23] = hash.hash_to_bytes().unwrap(); + assert_eq!(hash_to_bytes_23, hash.hash_to_bytes().unwrap()); + let hash_to_bytes_32: [u8; 32] = hash.hash_to_bytes().unwrap(); + assert_eq!(hash_to_bytes_32, hash.hash_to_bytes().unwrap()); + + let mut hasher = common_hash_domain().hasher::(); + hasher.update(b"my 3rd secret"); + let hash_1 = hasher.finalize(); + let hash_2 = common_hash_domain().digest::(b"my 3rd secret"); + assert_eq!(hash_1.as_ref(), hash_2.as_ref()); + assert_eq!(hash_1.domain_separation_tag(), hash_2.domain_separation_tag()); + assert_eq!(hash_1.domain_separation_tag(), hash.domain_separation_tag()); + } + + #[test] + fn test_mac_domain_hasher() { + // The compiler won't even let you write these tests :), so they're commented out. + // let mut hasher = COMMON_HASH_DOMAIN.mac_hasher::(); + // + // error[E0277]: the trait bound `Sha256: LengthExtensionAttackResistant` is not satisfied + // --> common\src\hashing_domain.rs:121:41 + // | + // 121 | let mut hasher = common_hash_domain().mac_hasher::(); + // | ^^^^^^^^^^ the trait `LengthExtensionAttackResistant` is not + // | implemented for `Sha256` + // | + // = help: the following other types implement trait `LengthExtensionAttackResistant`: + // Blake256 + // blake2::blake2b::VarBlake2b + // sha3::Sha3_256 + // note: required by a bound in `HashingDomain::mac_hasher` + + let mut hasher = common_hash_domain().mac_hasher::(); + hasher.update(b"my 1st secret"); + hasher.update(b"my 2nd secret"); + let hash = hasher.finalize(); + + let hash_to_bytes_7: [u8; 7] = hash.hash_to_bytes().unwrap(); + assert_eq!(hash_to_bytes_7, hash.hash_to_bytes().unwrap()); + let hash_to_bytes_23: [u8; 23] = hash.hash_to_bytes().unwrap(); + assert_eq!(hash_to_bytes_23, hash.hash_to_bytes().unwrap()); + let hash_to_bytes_32: [u8; 32] = hash.hash_to_bytes().unwrap(); + assert_eq!(hash_to_bytes_32, hash.hash_to_bytes().unwrap()); + + let mut hasher = common_hash_domain().mac_hasher::(); + hasher.update(b"my 3rd secret"); + let hash_1 = hasher.finalize(); + let hash_2 = common_hash_domain().mac_digest::(b"my 3rd secret"); + assert_eq!(hash_1.as_ref(), hash_2.as_ref()); + assert_eq!(hash_1.domain_separation_tag(), hash_2.domain_separation_tag()); + assert_eq!(hash_1.domain_separation_tag(), hash.domain_separation_tag()); + + let hmac = common_hash_domain().generate_hmac::(b"my secret key", b"my message"); + assert_ne!(hmac.domain_separation_tag(), hash_1.domain_separation_tag()); + assert_eq!( + hmac.into_vec().to_hex(), + "412767200f4b3bcfbf02bdd556d6fad33be176b06bdcbb00963bd3cb51b5dc79" + ); + } + + #[test] + fn test_domain_separation() { + let secret = b"my secret"; + let hash_generic = common_hash_domain().digest::(secret); + let hash_mac = common_hash_domain().mac_digest::(secret); + assert_ne!(hash_generic.as_ref(), hash_mac.as_ref()); + assert_ne!(hash_generic.domain_separation_tag(), hash_mac.domain_separation_tag()); + assert_eq!( + hash_generic.domain_separation_tag(), + "com.tari.tari_project.hash_domain.v1.common" + ); + assert_eq!( + hash_mac.domain_separation_tag(), + "com.tari.tari_project.mac_domain.v1.common" + ); + } +} diff --git a/common/src/lib.rs b/common/src/lib.rs index 2ea19329e3..cc55f136f9 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -65,7 +65,11 @@ pub use configuration::{ }; pub mod dir_utils; pub use logging::initialize_logging; + +use crate::hashing_domain::HashingDomain; + pub mod file_lock; +pub mod hashing_domain; pub const DEFAULT_CONFIG: &str = "config/config.toml"; pub const DEFAULT_BASE_NODE_LOG_CONFIG: &str = "config/log4rs_base_node.yml"; @@ -76,3 +80,11 @@ pub const DEFAULT_MINER_LOG_CONFIG: &str = "config/log4rs_miner.yml"; pub const DEFAULT_COLLECTIBLES_LOG_CONFIG: &str = "config/log4rs_collectibles.yml"; pub(crate) const LOG_TARGET: &str = "common::config"; + +/// The MMR domain separated hashing domain +/// Usage: +/// let hash = common_hash_domain().digest::(b"my secret"); +/// etc. +pub fn common_hash_domain() -> HashingDomain { + HashingDomain::new("common") +} diff --git a/comms/core/Cargo.toml b/comms/core/Cargo.toml index 9a24a6c67d..c3c4626cf2 100644 --- a/comms/core/Cargo.toml +++ b/comms/core/Cargo.toml @@ -11,6 +11,7 @@ edition = "2018" [dependencies] tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", tag = "v0.15.0" } +tari_common = {path = "../../common"} tari_metrics = { path = "../../infrastructure/metrics" } tari_storage = { version = "^0.34", path = "../../infrastructure/storage" } tari_shutdown = { version = "^0.34", path = "../../infrastructure/shutdown" } diff --git a/comms/core/src/lib.rs b/comms/core/src/lib.rs index d72b52585a..cf178384f3 100644 --- a/comms/core/src/lib.rs +++ b/comms/core/src/lib.rs @@ -67,5 +67,14 @@ pub mod multiaddr { pub use async_trait::async_trait; pub use bytes::{Bytes, BytesMut}; +use tari_common::hashing_domain::HashingDomain; #[cfg(feature = "rpc")] pub use tower::make::MakeService; + +/// The comms core domain separated hashing domain +/// Usage: +/// let hash = comms_core_hash_domain().digest::(b"my secret"); +/// etc. +pub fn comms_core_hash_domain() -> HashingDomain { + HashingDomain::new("comms.core") +} diff --git a/comms/dht/src/lib.rs b/comms/dht/src/lib.rs index 69b4b31c9b..3ced6dac94 100644 --- a/comms/dht/src/lib.rs +++ b/comms/dht/src/lib.rs @@ -99,6 +99,7 @@ pub use storage::DbConnectionUrl; mod dedup; pub use dedup::DedupLayer; +use tari_common::hashing_domain::HashingDomain; mod filter; mod logging_middleware; @@ -118,3 +119,11 @@ pub mod event; pub mod inbound; pub mod outbound; pub mod store_forward; + +/// The comms DHT domain separated hashing domain +/// Usage: +/// let hash = comms_dht_hash_domain().digest::(b"my secret"); +/// etc. +pub fn comms_dht_hash_domain() -> HashingDomain { + HashingDomain::new("comms.dht") +} diff --git a/dan_layer/common_types/Cargo.toml b/dan_layer/common_types/Cargo.toml index 524fc93f5c..bf822c0abc 100644 --- a/dan_layer/common_types/Cargo.toml +++ b/dan_layer/common_types/Cargo.toml @@ -9,6 +9,7 @@ license = "BSD-3-Clause" [dependencies] prost = "0.9" prost-types = "0.9" +tari_common = { path = "../../common", features = ["build"] } [build-dependencies] tari_common = { path = "../../common", features = ["build"] } diff --git a/dan_layer/common_types/src/lib.rs b/dan_layer/common_types/src/lib.rs index 7772dd98c9..6a2bf5022a 100644 --- a/dan_layer/common_types/src/lib.rs +++ b/dan_layer/common_types/src/lib.rs @@ -5,4 +5,14 @@ pub mod proto; pub mod storage; mod template_id; + +use tari_common::hashing_domain::HashingDomain; pub use template_id::TemplateId; + +/// The DAN layer domain separated hashing domain +/// Usage: +/// let hash = dan_layer_hash_domain().digest::(b"my secret"); +/// etc. +pub fn dan_layer_hash_domain() -> HashingDomain { + HashingDomain::new("dan_layer") +} diff --git a/dan_layer/core/src/lib.rs b/dan_layer/core/src/lib.rs index 7437bb9c1c..2cc45a9808 100644 --- a/dan_layer/core/src/lib.rs +++ b/dan_layer/core/src/lib.rs @@ -22,6 +22,8 @@ #![allow(clippy::too_many_arguments)] mod digital_assets_error; pub use digital_assets_error::DigitalAssetError; +use tari_common::hashing_domain::HashingDomain; + mod helpers; pub mod models; pub mod services; @@ -30,3 +32,11 @@ pub mod template_command; pub mod templates; pub mod types; pub mod workers; + +/// The DAN layer core domain separated hashing domain +/// Usage: +/// let hash = dan_layer_core_hash_domain().digest::(b"my secret"); +/// etc. +pub fn dan_layer_core_hash_domain() -> HashingDomain { + HashingDomain::new("dan_layer.core") +} diff --git a/dan_layer/engine/Cargo.toml b/dan_layer/engine/Cargo.toml index 8f262b695f..17c536932a 100644 --- a/dan_layer/engine/Cargo.toml +++ b/dan_layer/engine/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" [dependencies] tari_common_types = {path = "../../base_layer/common_types"} +tari_common = { path = "../../common" } tari_dan_common_types = {path = "../common_types"} tari_mmr = { path = "../../base_layer/mmr" } tari_utilities = { git = "https://github.com/tari-project/tari_utilities.git", tag = "v0.4.4" } diff --git a/dan_layer/engine/src/lib.rs b/dan_layer/engine/src/lib.rs index a543eb9be5..6f4307c4a4 100644 --- a/dan_layer/engine/src/lib.rs +++ b/dan_layer/engine/src/lib.rs @@ -1,9 +1,19 @@ // Copyright 2022 The Tari Project // SPDX-License-Identifier: BSD-3-Clause +use tari_common::hashing_domain::HashingDomain; + pub mod flow; pub mod function_definitions; pub mod instructions; pub mod models; pub mod state; pub mod wasm; + +/// The DAN layer engine domain separated hashing domain +/// Usage: +/// let hash = dan_layer_engine_hash_domain().digest::(b"my secret"); +/// etc. +pub fn dan_layer_engine_hash_domain() -> HashingDomain { + HashingDomain::new("dan_layer.engine") +} diff --git a/infrastructure/tari_script/Cargo.toml b/infrastructure/tari_script/Cargo.toml index 69bcf71ba8..c5c8868b96 100644 --- a/infrastructure/tari_script/Cargo.toml +++ b/infrastructure/tari_script/Cargo.toml @@ -12,6 +12,7 @@ license = "BSD-3-Clause" [dependencies] tari_common_types = { path = "../../base_layer/common_types" } +tari_common = {path = "../../common"} tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", tag = "v0.15.0" } tari_utilities = { git = "https://github.com/tari-project/tari_utilities.git", tag = "v0.4.4" } diff --git a/infrastructure/tari_script/src/lib.rs b/infrastructure/tari_script/src/lib.rs index e796c55a4d..deb8706666 100644 --- a/infrastructure/tari_script/src/lib.rs +++ b/infrastructure/tari_script/src/lib.rs @@ -29,6 +29,15 @@ pub use script::TariScript; pub use script_commitment::{ScriptCommitment, ScriptCommitmentError, ScriptCommitmentFactory}; pub use script_context::ScriptContext; pub use stack::{ExecutionStack, StackItem}; +use tari_common::hashing_domain::HashingDomain; + +/// The TariScript domain separated hashing domain +/// Usage: +/// let hash = tari_script_hash_domain().digest::(b"my secret"); +/// etc. +pub fn tari_script_hash_domain() -> HashingDomain { + HashingDomain::new("infrastructure.tari_script") +} // As hex: c5a1ea6d3e0a6a0d650c99489bcd563e37a06221fd04b8f3a842a982b2813907 pub const DEFAULT_SCRIPT_HASH: HashValue = [ From 5e3797f310562369601846f04d347003badf4e1d Mon Sep 17 00:00:00 2001 From: Stan Bondi Date: Fri, 22 Jul 2022 15:33:20 +0200 Subject: [PATCH 2/3] fix: remove tari_common dep from keymanager (#4335) Description --- - removes tari_common dependency from the key manager - remove (unused) `key_manager_hash_domain` function Motivation and Context --- Quick fix to fix CI WASM tests as `fs2` does not support the WASM target. Suggest moving hashing domain into its own crate. A hardcoded domain for all keys within key manager is likely not correct and should be specified by the caller e.g. independent KeyManager domain for wallet etc. --- Cargo.lock | 1 - base_layer/key_manager/Cargo.toml | 1 - base_layer/key_manager/src/lib.rs | 10 ---------- 3 files changed, 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 13ffab44c1..dd1106d2ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7239,7 +7239,6 @@ dependencies = [ "sha2 0.9.9", "strum 0.22.0", "strum_macros 0.22.0", - "tari_common", "tari_common_types", "tari_crypto", "tari_utilities", diff --git a/base_layer/key_manager/Cargo.toml b/base_layer/key_manager/Cargo.toml index d40d183b52..83cfad77a7 100644 --- a/base_layer/key_manager/Cargo.toml +++ b/base_layer/key_manager/Cargo.toml @@ -12,7 +12,6 @@ crate-type = ["lib", "cdylib"] [dependencies] tari_common_types = { version = "^0.34", path = "../../base_layer/common_types" } -tari_common = {path = "../../common"} tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", tag = "v0.15.0" } tari_utilities = { git = "https://github.com/tari-project/tari_utilities.git", tag = "v0.4.4" } diff --git a/base_layer/key_manager/src/lib.rs b/base_layer/key_manager/src/lib.rs index 1457fd3da8..8222d0ecf1 100644 --- a/base_layer/key_manager/src/lib.rs +++ b/base_layer/key_manager/src/lib.rs @@ -1,8 +1,6 @@ // Copyright 2022 The Tari Project // SPDX-License-Identifier: BSD-3-Clause -use tari_common::hashing_domain::HashingDomain; - pub mod cipher_seed; pub mod diacritics; pub mod error; @@ -13,11 +11,3 @@ pub mod mnemonic_wordlists; #[allow(clippy::unused_unit)] #[cfg(feature = "wasm")] pub mod wasm; - -/// The base layer key manager domain separated hashing domain -/// Usage: -/// let hash = key_manager_hash_domain().digest::(b"my secret"); -/// etc. -pub fn key_manager_hash_domain() -> HashingDomain { - HashingDomain::new("base_layer.key_manager") -} From d265c6f61c4f68098129b72f5907108e527868a3 Mon Sep 17 00:00:00 2001 From: Stan Bondi Date: Fri, 22 Jul 2022 16:01:12 +0200 Subject: [PATCH 3/3] feat(dan): add WASM template invocation from user instruction (#4331) Description --- Adds minimum required code to call a WASM template from a user instruction. Motivation and Context --- This PR adds and stubs out the required code to call WASM templates from a user instruction. This PR makes use of radix's excellent sbor encoding library, but this is not required and could be switched out. It's quite messy and incomplete in places. How Has This Been Tested? --- Adds an integration test that compiles and calls the stubbed-out template code. This is bordering on POC code, so no other tests are written - tests should be implemented after some feedback/iterations from this PR. --- Cargo.lock | 73 +++++- Cargo.toml | 1 + dan_layer/engine/Cargo.toml | 18 +- dan_layer/engine/src/compile.rs | 69 ++++++ dan_layer/engine/src/crypto.rs | 49 ++++ dan_layer/engine/src/env.rs | 41 ++++ dan_layer/engine/src/instruction/builder.rs | 60 +++++ dan_layer/engine/src/instruction/error.rs | 33 +++ dan_layer/engine/src/instruction/mod.rs | 49 ++++ dan_layer/engine/src/instruction/processor.rs | 78 +++++++ dan_layer/engine/src/instruction/signature.rs | 37 +++ dan_layer/engine/src/lib.rs | 7 + dan_layer/engine/src/package.rs | 216 ++++++++++++++++++ dan_layer/engine/src/traits.rs | 29 +++ dan_layer/engine/src/wasm/error.rs | 19 ++ dan_layer/engine/src/wasm/mod.rs | 11 +- dan_layer/engine/src/wasm/module.rs | 47 ++++ dan_layer/engine/src/wasm/process.rs | 108 +++++++++ dan_layer/engine/src/wasm/vm.rs | 153 +++++++++++++ dan_layer/engine/tests/test.rs | 56 +++++ .../engine/tests/test_template/Cargo.lock | 182 +++++++++++++++ .../engine/tests/test_template/Cargo.toml | 20 ++ .../engine/tests/test_template/src/lib.rs | 91 ++++++++ dan_layer/template_abi/Cargo.toml | 9 + dan_layer/template_abi/src/encoding.rs | 47 ++++ dan_layer/template_abi/src/lib.rs | 68 ++++++ 26 files changed, 1559 insertions(+), 12 deletions(-) create mode 100644 dan_layer/engine/src/compile.rs create mode 100644 dan_layer/engine/src/crypto.rs create mode 100644 dan_layer/engine/src/env.rs create mode 100644 dan_layer/engine/src/instruction/builder.rs create mode 100644 dan_layer/engine/src/instruction/error.rs create mode 100644 dan_layer/engine/src/instruction/mod.rs create mode 100644 dan_layer/engine/src/instruction/processor.rs create mode 100644 dan_layer/engine/src/instruction/signature.rs create mode 100644 dan_layer/engine/src/package.rs create mode 100644 dan_layer/engine/src/traits.rs create mode 100644 dan_layer/engine/src/wasm/module.rs create mode 100644 dan_layer/engine/src/wasm/process.rs create mode 100644 dan_layer/engine/src/wasm/vm.rs create mode 100644 dan_layer/engine/tests/test.rs create mode 100644 dan_layer/engine/tests/test_template/Cargo.lock create mode 100644 dan_layer/engine/tests/test_template/Cargo.toml create mode 100644 dan_layer/engine/tests/test_template/src/lib.rs create mode 100644 dan_layer/template_abi/Cargo.toml create mode 100644 dan_layer/template_abi/src/encoding.rs create mode 100644 dan_layer/template_abi/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index dd1106d2ce..86786a4888 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -559,6 +559,51 @@ dependencies = [ "serde_with", ] +[[package]] +name = "borsh" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa" +dependencies = [ + "borsh-derive", + "hashbrown 0.11.2", +] + +[[package]] +name = "borsh-derive" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775" +dependencies = [ + "borsh-derive-internal", + "borsh-schema-derive-internal", + "proc-macro-crate 0.1.5", + "proc-macro2", + "syn", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "brotli" version = "3.3.4" @@ -2758,7 +2803,7 @@ checksum = "25a68131a662b04931e71891fb14aaf65ee4b44d08e8abc10f49e77418c86c64" dependencies = [ "anyhow", "heck 0.4.0", - "proc-macro-crate", + "proc-macro-crate 1.1.3", "proc-macro-error", "proc-macro2", "quote", @@ -2853,7 +2898,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24f518afe90c23fba585b2d7697856f9e6a7bbc62f65588035e66f6afb01a2e9" dependencies = [ "anyhow", - "proc-macro-crate", + "proc-macro-crate 1.1.3", "proc-macro-error", "proc-macro2", "quote", @@ -3999,7 +4044,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc076939022111618a5026d3be019fd8b366e76314538ff9a1b59ffbcbf98bcd" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 1.1.3", "proc-macro-error", "proc-macro2", "quote", @@ -4284,7 +4329,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn", @@ -5118,6 +5163,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + [[package]] name = "proc-macro-crate" version = "1.1.3" @@ -7183,9 +7237,12 @@ name = "tari_dan_engine" version = "0.1.0" dependencies = [ "anyhow", + "borsh", + "cargo_toml", "d3ne", "digest 0.9.0", "log", + "rand 0.8.5", "serde", "serde_json", "tari_common", @@ -7193,6 +7250,7 @@ dependencies = [ "tari_crypto", "tari_dan_common_types", "tari_mmr", + "tari_template_abi", "tari_utilities", "thiserror", "wasmer", @@ -7511,6 +7569,13 @@ dependencies = [ "thiserror", ] +[[package]] +name = "tari_template_abi" +version = "0.1.0" +dependencies = [ + "borsh", +] + [[package]] name = "tari_test_utils" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index 6e3e7eb64b..fccac6403b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ "comms/dht", "comms/rpc_macros", "dan_layer/core", + "dan_layer/template_abi", "dan_layer/storage_sqlite", "common_sqlite", "infrastructure/libtor", diff --git a/dan_layer/engine/Cargo.toml b/dan_layer/engine/Cargo.toml index 17c536932a..3e244cfc3e 100644 --- a/dan_layer/engine/Cargo.toml +++ b/dan_layer/engine/Cargo.toml @@ -6,18 +6,22 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -tari_common_types = {path = "../../base_layer/common_types"} +tari_common_types = { path = "../../base_layer/common_types" } tari_common = { path = "../../common" } -tari_dan_common_types = {path = "../common_types"} +tari_dan_common_types = { path = "../common_types" } tari_mmr = { path = "../../base_layer/mmr" } tari_utilities = { git = "https://github.com/tari-project/tari_utilities.git", tag = "v0.4.4" } tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", tag = "v0.15.0" } +tari_template_abi = { path = "../template_abi" } -wasmer = "2.2.1" -digest = "0.9.0" -d3ne = { git="https://github.com/stringhandler/d3ne-rs.git", branch="st-fixes2"} anyhow = "1.0.53" -serde = "1.0.126" +borsh = "0.9.3" +cargo_toml = "0.11.5" +d3ne = { git = "https://github.com/stringhandler/d3ne-rs.git", branch = "st-fixes2" } +digest = "0.9.0" log = { version = "0.4.8", features = ["std"] } -serde_json ="1.0.81" +rand = "0.8.1" +serde = "1.0.126" +serde_json = "1.0.81" thiserror = "^1.0.20" +wasmer = "2.2.1" diff --git a/dan_layer/engine/src/compile.rs b/dan_layer/engine/src/compile.rs new file mode 100644 index 0000000000..a22dc6f3d6 --- /dev/null +++ b/dan_layer/engine/src/compile.rs @@ -0,0 +1,69 @@ +// 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::{fs, io, io::ErrorKind, path::Path, process::Command}; + +use cargo_toml::{Manifest, Product}; + +pub fn compile_template>(package_dir: P) -> io::Result> { + let status = Command::new("cargo") + .current_dir(package_dir.as_ref()) + .args(["build", "--target", "wasm32-unknown-unknown", "--release"]) + .status()?; + if !status.success() { + return Err(io::Error::new( + ErrorKind::Other, + format!("Failed to compile package: {:?}", package_dir.as_ref()), + )); + } + + // resolve wasm name + let manifest = Manifest::from_path(&package_dir.as_ref().join("Cargo.toml")).unwrap(); + let wasm_name = if let Some(Product { name: Some(name), .. }) = manifest.lib { + // lib name + name + } else if let Some(pkg) = manifest.package { + // package name + pkg.name.replace('-', "_") + } else { + // file name + package_dir + .as_ref() + .file_name() + .unwrap() + .to_str() + .unwrap() + .to_owned() + .replace('-', "_") + }; + + // path of the wasm executable + let mut path = package_dir.as_ref().to_path_buf(); + path.push("target"); + path.push("wasm32-unknown-unknown"); + path.push("release"); + path.push(wasm_name); + path.set_extension("wasm"); + + // return + fs::read(path) +} diff --git a/dan_layer/engine/src/crypto.rs b/dan_layer/engine/src/crypto.rs new file mode 100644 index 0000000000..44b30183d9 --- /dev/null +++ b/dan_layer/engine/src/crypto.rs @@ -0,0 +1,49 @@ +// 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 rand::rngs::OsRng; +use tari_common_types::types::{PrivateKey, PublicKey}; +use tari_crypto::{ + hash::blake2::Blake256, + hashing::{DomainSeparatedHasher, DomainSeparation}, + keys::PublicKey as PublicKeyT, +}; + +pub fn create_key_pair() -> (PrivateKey, PublicKey) { + PublicKey::random_keypair(&mut OsRng) +} + +pub struct TariEngineDomainSeparation; + +impl DomainSeparation for TariEngineDomainSeparation { + fn version() -> u8 { + 0 + } + + fn domain() -> &'static str { + "tari.dan.engine" + } +} + +pub fn domain_separated_hasher(label: &str) -> DomainSeparatedHasher { + DomainSeparatedHasher::new(label) +} diff --git a/dan_layer/engine/src/env.rs b/dan_layer/engine/src/env.rs new file mode 100644 index 0000000000..09d6be4509 --- /dev/null +++ b/dan_layer/engine/src/env.rs @@ -0,0 +1,41 @@ +// 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::sync::{Arc, Mutex}; + +pub fn tari_engine(env: &EngineEnvironment, op: i32, _args_ptr: i32, args_len: i32) -> i32 { + println!("tari_engine CALLED: op: {}, args: {}", op, args_len); + // TODO: + env.inc_counter(); + 0 +} + +#[derive(wasmer::WasmerEnv, Clone, Default)] +pub struct EngineEnvironment { + counter: Arc>, +} + +impl EngineEnvironment { + pub fn inc_counter(&self) { + *self.counter.lock().unwrap() += 1; + } +} diff --git a/dan_layer/engine/src/instruction/builder.rs b/dan_layer/engine/src/instruction/builder.rs new file mode 100644 index 0000000000..a532175348 --- /dev/null +++ b/dan_layer/engine/src/instruction/builder.rs @@ -0,0 +1,60 @@ +// 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 tari_common_types::types::PrivateKey; + +use super::{Instruction, InstructionSet}; +use crate::instruction::signature::InstructionSignature; + +#[derive(Debug, Clone, Default)] +pub struct InstructionBuilder { + instructions: Vec, + signature: Option, +} + +impl InstructionBuilder { + pub fn new() -> Self { + Self { + instructions: Vec::new(), + signature: None, + } + } + + pub fn add_instruction(&mut self, instruction: Instruction) -> &mut Self { + self.instructions.push(instruction); + // Reset the signature as it is no longer valid + self.signature = None; + self + } + + pub fn sign(&mut self, secret_key: &PrivateKey) -> &mut Self { + self.signature = Some(InstructionSignature::sign(secret_key, &self.instructions)); + self + } + + pub fn build(&mut self) -> InstructionSet { + InstructionSet { + instructions: self.instructions.drain(..).collect(), + signature: self.signature.take().expect("not signed"), + } + } +} diff --git a/dan_layer/engine/src/instruction/error.rs b/dan_layer/engine/src/instruction/error.rs new file mode 100644 index 0000000000..6f1cf4eed4 --- /dev/null +++ b/dan_layer/engine/src/instruction/error.rs @@ -0,0 +1,33 @@ +// 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 crate::{package::PackageId, wasm::WasmExecutionError}; + +#[derive(Debug, thiserror::Error)] +pub enum InstructionError { + #[error(transparent)] + WasmExecutionError(#[from] WasmExecutionError), + #[error("Package {package_id} not found")] + PackageNotFound { package_id: PackageId }, + #[error("Invalid template")] + TemplateNameNotFound { name: String }, +} diff --git a/dan_layer/engine/src/instruction/mod.rs b/dan_layer/engine/src/instruction/mod.rs new file mode 100644 index 0000000000..0d76d569f6 --- /dev/null +++ b/dan_layer/engine/src/instruction/mod.rs @@ -0,0 +1,49 @@ +// 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. + +mod builder; +pub use builder::InstructionBuilder; + +mod error; + +mod processor; +pub use processor::InstructionProcessor; + +mod signature; + +use crate::{instruction::signature::InstructionSignature, package::PackageId}; + +#[derive(Debug, Clone)] +pub enum Instruction { + CallFunction { + package_id: PackageId, + template: String, + function: String, + args: Vec>, + }, +} + +#[derive(Debug, Clone)] +pub struct InstructionSet { + pub instructions: Vec, + pub signature: InstructionSignature, +} diff --git a/dan_layer/engine/src/instruction/processor.rs b/dan_layer/engine/src/instruction/processor.rs new file mode 100644 index 0000000000..e2a71db139 --- /dev/null +++ b/dan_layer/engine/src/instruction/processor.rs @@ -0,0 +1,78 @@ +// 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::collections::HashMap; + +use crate::{ + instruction::{error::InstructionError, Instruction, InstructionSet}, + package::{Package, PackageId}, + traits::Invokable, + wasm::{ExecutionResult, Process, VmInstance}, +}; + +#[derive(Debug, Clone, Default)] +pub struct InstructionProcessor { + packages: HashMap, +} + +impl InstructionProcessor { + pub fn new() -> Self { + Self { + packages: HashMap::new(), + } + } + + pub fn load(&mut self, package: Package) -> &mut Self { + self.packages.insert(package.id(), package); + self + } + + pub fn execute(&self, instruction_set: InstructionSet) -> Result, InstructionError> { + let mut results = vec![]; + for instruction in instruction_set.instructions { + match instruction { + Instruction::CallFunction { + package_id, + template, + function, + args, + } => { + let package = self + .packages + .get(&package_id) + .ok_or(InstructionError::PackageNotFound { package_id })?; + let module = package + .get_module_by_name(&template) + .ok_or(InstructionError::TemplateNameNotFound { name: template })?; + + // TODO: implement intelligent instance caching + let vm = VmInstance::instantiate(module.wasm_module())?; + let process = Process::new(module.clone(), vm); + let result = process.invoke_by_name(&function, args)?; + results.push(result); + }, + } + } + + Ok(results) + } +} diff --git a/dan_layer/engine/src/instruction/signature.rs b/dan_layer/engine/src/instruction/signature.rs new file mode 100644 index 0000000000..e6a4cbd6be --- /dev/null +++ b/dan_layer/engine/src/instruction/signature.rs @@ -0,0 +1,37 @@ +// 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 tari_common_types::types::{PrivateKey, Signature}; + +use crate::{crypto::create_key_pair, instruction::Instruction}; + +#[derive(Debug, Clone)] +pub struct InstructionSignature(Signature); + +impl InstructionSignature { + pub fn sign(secret_key: &PrivateKey, _instructions: &[Instruction]) -> Self { + let (nonce, _) = create_key_pair(); + // TODO: create proper challenge + let challenge = [0u8; 32]; + Self(Signature::sign(secret_key.clone(), nonce, &challenge).unwrap()) + } +} diff --git a/dan_layer/engine/src/lib.rs b/dan_layer/engine/src/lib.rs index 6f4307c4a4..1d4ec36f67 100644 --- a/dan_layer/engine/src/lib.rs +++ b/dan_layer/engine/src/lib.rs @@ -10,6 +10,13 @@ pub mod models; pub mod state; pub mod wasm; +pub mod compile; +pub mod crypto; +pub mod env; +pub mod instruction; +pub mod package; +pub mod traits; + /// The DAN layer engine domain separated hashing domain /// Usage: /// let hash = dan_layer_engine_hash_domain().digest::(b"my secret"); diff --git a/dan_layer/engine/src/package.rs b/dan_layer/engine/src/package.rs new file mode 100644 index 0000000000..f6227088fa --- /dev/null +++ b/dan_layer/engine/src/package.rs @@ -0,0 +1,216 @@ +// 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::{cell::Cell, collections::HashMap, convert::TryInto}; + +use rand::{rngs::OsRng, RngCore}; +use tari_common_types::types::FixedHash; +use tari_template_abi::TemplateDef; +use wasmer::{imports, Extern, Function, Instance, Memory, Module, Store, Val}; + +use crate::{crypto, wasm::LoadedWasmModule}; + +#[derive(Debug, Clone, Default)] +pub struct PackageBuilder { + wasm_code: Vec>, +} + +impl PackageBuilder { + pub fn new() -> Self { + Self { wasm_code: Vec::new() } + } + + pub fn add_wasm_template(&mut self, wasm_code: Vec) -> &mut Self { + self.wasm_code.push(wasm_code); + self + } + + pub fn build(&self) -> Result { + let mut wasm_modules = HashMap::with_capacity(self.wasm_code.len()); + let store = Store::default(); + let id = new_package_id(); + for code in &self.wasm_code { + let module = load_wasm_module(&store, code)?; + wasm_modules.insert(module.template_name().to_string(), module); + } + + Ok(Package { + id, + wasm_modules, + _store: store, + }) + } +} + +fn new_package_id() -> PackageId { + let v = OsRng.next_u32(); + crypto::domain_separated_hasher("package") + // TODO: Proper package id + .chain(&v.to_le_bytes()) + .finalize() + .as_ref() + .try_into() + .unwrap() +} + +fn load_wasm_module(store: &Store, code: &[u8]) -> Result { + let module = Module::new(store, code)?; + + fn stub(_op: i32, _args_ptr: i32, _args_len: i32) -> i32 { + 0 + } + + let imports = imports! { + "env" => { + "tari_engine" => Function::new_native(store, stub), + } + }; + let instance = Instance::new(&module, &imports)?; + validate_instance(&instance)?; + + let template = initialize_and_load_template_abi(&instance)?; + Ok(LoadedWasmModule::new(template, module)) +} + +fn initialize_and_load_template_abi(instance: &Instance) -> Result { + let abi_func = instance + .exports + .iter() + .find_map(|(name, export)| match export { + Extern::Function(f) if name.ends_with("_abi") => Some(f), + _ => None, + }) + .ok_or(PackageError::NoAbiDefinition)?; + + // Initialize ABI memory + let ret = abi_func.call(&[])?; + let ptr = match ret.get(0) { + Some(Val::I32(ptr)) => *ptr as u32, + Some(_) | None => return Err(PackageError::InvalidReturnTypeFromAbiFunc), + }; + + // Load ABI from memory + let memory = instance.exports.get_memory("memory")?; + let data = copy_abi_data_from_memory_checked(memory, ptr)?; + let decoded = tari_template_abi::decode(&data).map_err(|_| PackageError::AbiDecodeError)?; + Ok(decoded) +} + +fn copy_abi_data_from_memory_checked(memory: &Memory, ptr: u32) -> Result, PackageError> { + // Check memory bounds + if memory.data_size() < u64::from(ptr) { + return Err(PackageError::AbiPointerOutOfBounds); + } + + let view = memory.uint8view().subarray(ptr, memory.data_size() as u32 - 1); + let data = &*view; + if data.len() < 4 { + return Err(PackageError::MemoryUnderflow { + required: 4, + remaining: data.len(), + }); + } + + fn copy_from_cell_slice(src: &[Cell], dest: &mut [u8], len: usize) { + for i in 0..len { + dest[i] = src[i].get(); + } + } + + let mut buf = [0u8; 4]; + copy_from_cell_slice(data, &mut buf, 4); + let len = u32::from_le_bytes(buf) as usize; + const MAX_ABI_DATA_LEN: usize = 1024 * 1024; + if len > MAX_ABI_DATA_LEN { + return Err(PackageError::AbiDataTooLarge { + max: MAX_ABI_DATA_LEN, + size: len, + }); + } + if data.len() < 4 + len { + return Err(PackageError::MemoryUnderflow { + required: 4 + len, + remaining: data.len(), + }); + } + + let mut data = vec![0u8; len]; + let src = view.subarray(4, 4 + len as u32); + copy_from_cell_slice(&*src, &mut data, len); + Ok(data) +} + +pub fn validate_instance(instance: &Instance) -> Result<(), PackageError> { + if let Ok(mem) = instance.exports.get_memory("memory") { + if mem.size().bytes().0 > 2 * 1024 * 1024 { + return Err(PackageError::MaxMemorySizeExceeded); + } + } + // TODO other package validations + + Ok(()) +} + +pub type PackageId = FixedHash; + +#[derive(Debug, Clone)] +pub struct Package { + id: PackageId, + wasm_modules: HashMap, + _store: Store, +} + +impl Package { + pub fn get_module_by_name(&self, name: &str) -> Option<&LoadedWasmModule> { + self.wasm_modules.get(name) + } + + pub fn id(&self) -> PackageId { + self.id + } +} + +#[derive(Debug, thiserror::Error)] +pub enum PackageError { + #[error(transparent)] + CompileError(#[from] wasmer::CompileError), + #[error(transparent)] + InstantiationError(#[from] wasmer::InstantiationError), + #[error(transparent)] + RuntimeError(#[from] wasmer::RuntimeError), + #[error(transparent)] + ExportError(#[from] wasmer::ExportError), + #[error("Failed to decode ABI")] + AbiDecodeError, + #[error("maximum module memory size exceeded")] + MaxMemorySizeExceeded, + #[error("package did not contain an ABI definition")] + NoAbiDefinition, + #[error("package ABI function returned an invalid type")] + InvalidReturnTypeFromAbiFunc, + #[error("package ABI function returned an out of bounds pointer")] + AbiPointerOutOfBounds, + #[error("memory underflow: {required} bytes required but {remaining} remaining")] + MemoryUnderflow { required: usize, remaining: usize }, + #[error("ABI data is too large: a maximum of {max} bytes allowed but size is {size}")] + AbiDataTooLarge { max: usize, size: usize }, +} diff --git a/dan_layer/engine/src/traits.rs b/dan_layer/engine/src/traits.rs new file mode 100644 index 0000000000..062c1452ed --- /dev/null +++ b/dan_layer/engine/src/traits.rs @@ -0,0 +1,29 @@ +// 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 crate::wasm::ExecutionResult; + +pub trait Invokable { + type Error; + + fn invoke_by_name(&self, name: &str, args: Vec>) -> Result; +} diff --git a/dan_layer/engine/src/wasm/error.rs b/dan_layer/engine/src/wasm/error.rs index b7f3cd3cb7..9e2ba062bc 100644 --- a/dan_layer/engine/src/wasm/error.rs +++ b/dan_layer/engine/src/wasm/error.rs @@ -2,9 +2,28 @@ // SPDX-License-Identifier: BSD-3-Clause use thiserror::Error; +use wasmer::{ExportError, InstantiationError, RuntimeError}; #[derive(Debug, Error)] pub enum WasmError { #[error("Missing argument at position {position} (name: {argument_name}")] MissingArgument { argument_name: String, position: usize }, } + +#[derive(Debug, thiserror::Error)] +pub enum WasmExecutionError { + #[error("Function {name} not found")] + FunctionNotFound { name: String }, + #[error("Expected function {function} to return a pointer")] + ExpectedPointerReturn { function: String }, + #[error("Attempted to write {requested} bytes but pointer allocated {allocated}")] + InvalidWriteLength { allocated: u32, requested: u32 }, + #[error("memory underflow: {required} bytes required but {remaining} remaining")] + MemoryUnderflow { required: usize, remaining: usize }, + #[error(transparent)] + InstantiationError(#[from] InstantiationError), + #[error(transparent)] + ExportError(#[from] ExportError), + #[error(transparent)] + RuntimeError(#[from] RuntimeError), +} diff --git a/dan_layer/engine/src/wasm/mod.rs b/dan_layer/engine/src/wasm/mod.rs index d884d88abc..0612136de0 100644 --- a/dan_layer/engine/src/wasm/mod.rs +++ b/dan_layer/engine/src/wasm/mod.rs @@ -5,6 +5,15 @@ mod error; mod wasm_module_definition; mod wasm_module_factory; -pub use error::WasmError; +pub use error::{WasmError, WasmExecutionError}; pub use wasm_module_definition::WasmModuleDefinition; pub use wasm_module_factory::WasmModuleFactory; + +mod module; +pub use module::LoadedWasmModule; + +mod process; +pub use process::{ExecutionResult, Process}; + +mod vm; +pub use vm::VmInstance; diff --git a/dan_layer/engine/src/wasm/module.rs b/dan_layer/engine/src/wasm/module.rs new file mode 100644 index 0000000000..44ec5deee1 --- /dev/null +++ b/dan_layer/engine/src/wasm/module.rs @@ -0,0 +1,47 @@ +// 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 tari_template_abi::{FunctionDef, TemplateDef}; + +#[derive(Debug, Clone)] +pub struct LoadedWasmModule { + template: TemplateDef, + module: wasmer::Module, +} + +impl LoadedWasmModule { + pub fn new(template: TemplateDef, module: wasmer::Module) -> Self { + Self { template, module } + } + + pub fn wasm_module(&self) -> &wasmer::Module { + &self.module + } + + pub fn template_name(&self) -> &str { + &self.template.template_name + } + + pub fn find_func_by_name(&self, function_name: &str) -> Option<&FunctionDef> { + self.template.functions.iter().find(|f| f.name == *function_name) + } +} diff --git a/dan_layer/engine/src/wasm/process.rs b/dan_layer/engine/src/wasm/process.rs new file mode 100644 index 0000000000..1fc2524053 --- /dev/null +++ b/dan_layer/engine/src/wasm/process.rs @@ -0,0 +1,108 @@ +// 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 borsh::{BorshDeserialize, BorshSerialize}; +use tari_template_abi::{encode_into, CallInfo}; +use wasmer::{Module, Val}; + +use crate::{ + traits::Invokable, + wasm::{ + error::WasmExecutionError, + vm::{AllocPtr, VmInstance}, + LoadedWasmModule, + }, +}; + +#[derive(Debug)] +pub struct Process { + module: LoadedWasmModule, + vm: VmInstance, +} + +pub struct ExecutionResult { + pub value: wasmer::Value, + pub raw: Vec, +} + +impl ExecutionResult { + pub fn decode(&self) -> io::Result { + tari_template_abi::decode(&self.raw) + } +} + +impl Process { + pub fn new(module: LoadedWasmModule, vm: VmInstance) -> Self { + Self { module, vm } + } + + fn alloc_and_write(&self, val: &T) -> Result { + let mut buf = Vec::with_capacity(512); + encode_into(val, &mut buf).unwrap(); + let ptr = self.vm.alloc(buf.len() as u32)?; + self.vm.write_to_memory(&ptr, &buf)?; + + Ok(ptr) + } + + pub fn wasm_module(&self) -> &Module { + self.module.wasm_module() + } +} + +impl Invokable for Process { + type Error = WasmExecutionError; + + fn invoke_by_name(&self, name: &str, args: Vec>) -> Result { + let func_def = self + .module + .find_func_by_name(name) + .ok_or_else(|| WasmExecutionError::FunctionNotFound { name: name.into() })?; + + let call_info = CallInfo { + func_name: func_def.name.clone(), + args, + }; + + let main_name = format!("{}_main", self.module.template_name()); + let func = self.vm.get_function(&main_name)?; + + let call_info_ptr = self.alloc_and_write(&call_info)?; + let res = func.call(&[call_info_ptr.as_val_i32(), Val::I32(call_info_ptr.len() as i32)])?; + self.vm.free(call_info_ptr)?; + let ptr = res + .get(0) + .and_then(|v| v.i32()) + .ok_or(WasmExecutionError::ExpectedPointerReturn { function: main_name })?; + + // Read response from memory + let raw = self.vm.read_from_memory(ptr as u32)?; + + // TODO: decode raw as per function def + Ok(ExecutionResult { + value: wasmer::Value::I32(ptr), + raw, + }) + } +} diff --git a/dan_layer/engine/src/wasm/vm.rs b/dan_layer/engine/src/wasm/vm.rs new file mode 100644 index 0000000000..dfbc94d353 --- /dev/null +++ b/dan_layer/engine/src/wasm/vm.rs @@ -0,0 +1,153 @@ +// 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::cell::Cell; + +use wasmer::{imports, Function, Instance, Memory, Module, Store, Val}; + +use crate::{ + env::{tari_engine, EngineEnvironment}, + wasm::error::WasmExecutionError, +}; + +#[derive(Debug)] +pub struct VmInstance { + memory: Memory, + instance: Instance, + _store: Store, +} + +impl VmInstance { + pub fn instantiate(module: &Module) -> Result { + let store = Store::default(); + // TODO: proper environment + let env = EngineEnvironment::default(); + let imports = imports! { + "env" => { + "tari_engine" => Function::new_native_with_env(&store, env, tari_engine), + } + }; + let instance = Instance::new(module, &imports)?; + let memory = instance.exports.get_memory("memory")?; + Ok(Self { + memory: memory.clone(), + _store: store, + instance, + }) + } + + pub(super) fn alloc(&self, len: u32) -> Result { + let alloc = self.instance.exports.get_function("tari_alloc")?; + let ret = alloc.call(&[Val::I32(len as i32)])?; + match ret.get(0) { + Some(Val::I32(ptr)) => Ok(AllocPtr(*ptr as u32, len)), + _ => Err(WasmExecutionError::ExpectedPointerReturn { + function: "tari_alloc".into(), + }), + } + } + + pub(super) fn free(&self, ptr: AllocPtr) -> Result<(), WasmExecutionError> { + let alloc = self.instance.exports.get_function("tari_free")?; + alloc.call(&[ptr.as_val_i32()])?; + Ok(()) + } + + pub(super) fn write_to_memory(&self, ptr: &AllocPtr, data: &[u8]) -> Result<(), WasmExecutionError> { + if data.len() != ptr.len() as usize { + return Err(WasmExecutionError::InvalidWriteLength { + allocated: ptr.len(), + requested: data.len() as u32, + }); + } + // SAFETY: The VM owns the only memory instance, and the pointer has been allocated by alloc above so data races + // are not possible. + unsafe { + self.memory.uint8view().subarray(ptr.get(), ptr.end()).copy_from(data); + } + Ok(()) + } + + pub(super) fn read_from_memory(&self, ptr: u32) -> Result, WasmExecutionError> { + // TODO: DRY this up + let view = self + .memory + .uint8view() + .subarray(ptr, self.memory.data_size() as u32 - 1); + let view_bytes = &*view; + if view_bytes.len() < 4 { + return Err(WasmExecutionError::MemoryUnderflow { + required: 4, + remaining: view_bytes.len(), + }); + } + + fn copy_from_cell_slice(src: &[Cell], dest: &mut [u8], len: usize) { + // TODO: Is there a more efficient way to do this? + for i in 0..len { + dest[i] = src[i].get(); + } + } + + let mut buf = [0u8; 4]; + copy_from_cell_slice(view_bytes, &mut buf, 4); + let len = u32::from_le_bytes(buf) as usize; + if view_bytes.len() < 4 + len { + return Err(WasmExecutionError::MemoryUnderflow { + required: 4 + len, + remaining: view_bytes.len(), + }); + } + + let mut data = vec![0u8; len]; + let src = view.subarray(4, 4 + len as u32); + copy_from_cell_slice(&*src, &mut data, len); + Ok(data) + } + + pub fn get_function(&self, name: &str) -> Result<&Function, WasmExecutionError> { + let func = self.instance.exports.get_function(name)?; + Ok(func) + } +} + +#[derive(Debug)] +pub struct AllocPtr(u32, u32); + +impl AllocPtr { + pub fn get(&self) -> u32 { + self.0 + } + + pub fn len(&self) -> u32 { + self.1 + } + + pub fn end(&self) -> u32 { + self.get() + self.len() + } + + pub fn as_val_i32(&self) -> Val { + // We want the 'u32 as i32' conversion to wrap + Val::I32(self.get() as i32) + } +} diff --git a/dan_layer/engine/tests/test.rs b/dan_layer/engine/tests/test.rs new file mode 100644 index 0000000000..08630e1924 --- /dev/null +++ b/dan_layer/engine/tests/test.rs @@ -0,0 +1,56 @@ +// 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 tari_dan_engine::{ + compile::compile_template, + crypto::create_key_pair, + instruction::{Instruction, InstructionBuilder, InstructionProcessor}, + package::PackageBuilder, +}; + +#[test] +fn test_metro() { + let mut processor = InstructionProcessor::new(); + let (sk, _pk) = create_key_pair(); + + let wasm = compile_template("tests/test_template").unwrap(); + let package = PackageBuilder::new().add_wasm_template(wasm).build().unwrap(); + let package_id = package.id(); + processor.load(package); + + let instruction = InstructionBuilder::new() + .add_instruction(Instruction::CallFunction { + package_id, + template: "TestTemplate".to_string(), + function: "initialize".to_string(), + args: vec![], + }) + .sign(&sk) + .build(); + + let result = processor.execute(instruction).unwrap(); + let result = result[0].decode::().unwrap(); + assert_eq!(result, "'initialize' was called"); + + // let instruction = InstructionBuilder::new().method("hello_world").sign(sk).build(); + // let result = processor.execute(instruction).unwrap(); +} diff --git a/dan_layer/engine/tests/test_template/Cargo.lock b/dan_layer/engine/tests/test_template/Cargo.lock new file mode 100644 index 0000000000..6c644b143f --- /dev/null +++ b/dan_layer/engine/tests/test_template/Cargo.lock @@ -0,0 +1,182 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "borsh" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa" +dependencies = [ + "borsh-derive", + "hashbrown", +] + +[[package]] +name = "borsh-derive" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775" +dependencies = [ + "borsh-derive-internal", + "borsh-schema-derive-internal", + "proc-macro-crate", + "proc-macro2", + "syn", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "once_cell" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0171ebb889e45aa68b44aee0859b3eede84c6f5f5c228e6f140c0b2a0a46cad6" + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tari_template_abi" +version = "0.1.0" +dependencies = [ + "borsh", +] + +[[package]] +name = "test_template" +version = "0.1.0" +dependencies = [ + "tari_template_abi", +] + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + +[[package]] +name = "unicode-ident" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/dan_layer/engine/tests/test_template/Cargo.toml b/dan_layer/engine/tests/test_template/Cargo.toml new file mode 100644 index 0000000000..6b3e099768 --- /dev/null +++ b/dan_layer/engine/tests/test_template/Cargo.toml @@ -0,0 +1,20 @@ +[workspace] +[package] +name = "test_template" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tari_template_abi = { path = "../../../template_abi" } + +[profile.release] +opt-level = 's' # Optimize for size. +lto = true # Enable Link Time Optimization. +codegen-units = 1 # Reduce number of codegen units to increase optimizations. +panic = 'abort' # Abort on panic. +strip = "debuginfo" # Strip debug info. + +[lib] +crate-type = ["cdylib", "lib"] \ No newline at end of file diff --git a/dan_layer/engine/tests/test_template/src/lib.rs b/dan_layer/engine/tests/test_template/src/lib.rs new file mode 100644 index 0000000000..704b8ae46d --- /dev/null +++ b/dan_layer/engine/tests/test_template/src/lib.rs @@ -0,0 +1,91 @@ +// 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. + +// TODO: we should only use stdlib if the template dev needs to include it e.g. use core::mem when stdlib is not +// available +use std::{mem, ptr::copy, vec::Vec}; + +// TODO: Macro generated code +#[no_mangle] +extern "C" fn TestTemplate_abi() -> *mut u8 { + use tari_template_abi::{encode_with_len, FunctionDef, TemplateDef, Type}; + + let template = TemplateDef { + template_name: "TestTemplate".to_string(), + functions: vec![FunctionDef { + name: "initialize".to_string(), + arguments: vec![], + output: Type::Unit, + }], + }; + + let buf = encode_with_len(&template); + wrap_ptr(buf) +} + +#[no_mangle] +extern "C" fn TestTemplate_main(call_info: *mut u8, call_info_len: usize) -> *mut u8 { + use tari_template_abi::{decode, encode_with_len, CallInfo}; + if call_info.is_null() { + panic!("call_info is null"); + } + + let call_data = unsafe { Vec::from_raw_parts(call_info, call_info_len, call_info_len) }; + let call_info: CallInfo = decode(&call_data).unwrap(); + + // Call engine for fun + unsafe { tari_engine(123, std::ptr::null(), 0) }; + + let msg = format!("'{}' was called", call_info.func_name); + let v = encode_with_len(&msg); + wrap_ptr(v) +} + +// TODO: ------ Everything below here should be in a common wasm lib ------ +fn wrap_ptr(mut v: Vec) -> *mut u8 { + let ptr = v.as_mut_ptr(); + mem::forget(v); + ptr +} + +extern "C" { + pub fn tari_engine(op: u32, input_ptr: *const u8, input_len: usize) -> *mut u8; +} + +#[no_mangle] +unsafe extern "C" fn tari_alloc(len: u32) -> *mut u8 { + let cap = (len + 4) as usize; + let mut buf = Vec::::with_capacity(cap); + let ptr = buf.as_mut_ptr(); + mem::forget(buf); + copy(len.to_le_bytes().as_ptr(), ptr, 4); + ptr +} + +#[no_mangle] +unsafe extern "C" fn tari_free(ptr: *mut u8) { + let mut len = [0u8; 4]; + copy(ptr, len.as_mut_ptr(), 4); + + let cap = (u32::from_le_bytes(len) + 4) as usize; + let _ = Vec::::from_raw_parts(ptr, cap, cap); +} diff --git a/dan_layer/template_abi/Cargo.toml b/dan_layer/template_abi/Cargo.toml new file mode 100644 index 0000000000..b2d11f346b --- /dev/null +++ b/dan_layer/template_abi/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "tari_template_abi" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +borsh = "0.9.3" diff --git a/dan_layer/template_abi/src/encoding.rs b/dan_layer/template_abi/src/encoding.rs new file mode 100644 index 0000000000..b64ce07917 --- /dev/null +++ b/dan_layer/template_abi/src/encoding.rs @@ -0,0 +1,47 @@ +// 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. + +// TODO: Move to tari template lib crate + +use std::io; + +use borsh::{BorshDeserialize, BorshSerialize}; + +pub fn encode_with_len(val: &T) -> Vec { + let mut buf = Vec::with_capacity(512); + buf.extend([0u8; 4]); + + encode_into(val, &mut buf).expect("Vec Write impl is infallible"); + + let len = ((buf.len() - 4) as u32).to_le_bytes(); + buf[..4].copy_from_slice(&len); + + buf +} + +pub fn encode_into(val: &T, buf: &mut Vec) -> io::Result<()> { + val.serialize(buf) +} + +pub fn decode(mut input: &[u8]) -> io::Result { + T::deserialize(&mut input) +} diff --git a/dan_layer/template_abi/src/lib.rs b/dan_layer/template_abi/src/lib.rs new file mode 100644 index 0000000000..db7b1d48f5 --- /dev/null +++ b/dan_layer/template_abi/src/lib.rs @@ -0,0 +1,68 @@ +// 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. + +mod encoding; + +use borsh::{BorshDeserialize, BorshSerialize}; +pub use encoding::{decode, encode_into, encode_with_len}; + +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +pub struct TemplateDef { + pub template_name: String, + pub functions: Vec, +} + +impl TemplateDef { + pub fn get_function(&self, name: &str) -> Option<&FunctionDef> { + self.functions.iter().find(|f| f.name.as_str() == name) + } +} + +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +pub struct FunctionDef { + pub name: String, + pub arguments: Vec, + pub output: Type, +} + +#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +pub enum Type { + Unit, + Bool, + I8, + I16, + I32, + I64, + I128, + U8, + U16, + U32, + U64, + U128, + String, +} + +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +pub struct CallInfo { + pub func_name: String, + pub args: Vec>, +}