diff --git a/rusk/CHANGELOG.md b/rusk/CHANGELOG.md index f5f6568727..9f199491be 100644 --- a/rusk/CHANGELOG.md +++ b/rusk/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Add `gen_contract_id` and 32-byte hash for contract deployment [#1884] - Add execution of contract deployment [#1882] - Add first version of RUES, allowing websocket clients to subscribe for events emitted by block transitions [#931] @@ -210,6 +211,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add linking between Rusk and Protobuff structs - Add build system that generates keys for circuits and caches them. +[#1884]: https://github.com/dusk-network/rusk/issues/1884 [#1882]: https://github.com/dusk-network/rusk/issues/1882 [#1675]: https://github.com/dusk-network/rusk/issues/1675 [#1640]: https://github.com/dusk-network/rusk/issues/1640 diff --git a/rusk/Cargo.toml b/rusk/Cargo.toml index 05d0275e52..d2d5b6b83e 100644 --- a/rusk/Cargo.toml +++ b/rusk/Cargo.toml @@ -39,6 +39,7 @@ rkyv = { version = "0.7", default-features = false, features = ["size_32"] } bytecheck = { version = "0.6", default-features = false } dirs = "4" blake3 = "1" +blake2b_simd = { version = "1", default-features = false } poseidon-merkle = { version = "0.6", features = ["rkyv-impl", "size_32"] } sha3 = "0.10" diff --git a/rusk/src/lib/chain/rusk.rs b/rusk/src/lib/chain/rusk.rs index 6ec6fea890..bf6a7b68da 100644 --- a/rusk/src/lib/chain/rusk.rs +++ b/rusk/src/lib/chain/rusk.rs @@ -41,6 +41,7 @@ use rusk_profile::to_rusk_state_id_path; use tokio::sync::broadcast; use super::{coinbase_value, Rusk, RuskTip}; +use crate::gen_id::gen_contract_id; use crate::http::RuesEvent; use crate::Error::InvalidCreditsCount; use crate::{Error, Result}; @@ -540,7 +541,11 @@ fn contract_deploy( receipt.data = Err(Panic("failed bytecode hash check".into())) } else { let result = session.deploy_raw( - None, + Some(gen_contract_id( + &deploy.bytecode.bytes, + deploy.nonce, + &deploy.owner, + )), deploy.bytecode.bytes.as_slice(), deploy.constructor_args.clone(), deploy.owner.clone(), diff --git a/rusk/src/lib/gen_id.rs b/rusk/src/lib/gen_id.rs new file mode 100644 index 0000000000..bb7f2d6389 --- /dev/null +++ b/rusk/src/lib/gen_id.rs @@ -0,0 +1,53 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use crate::hash::Hasher; +use rusk_abi::ContractId; + +/// Generate a [`ContractId`] address from: +/// - slice of bytes, +/// - nonce +/// - owner +pub fn gen_contract_id( + bytes: impl AsRef<[u8]>, + nonce: u64, + owner: impl AsRef<[u8]>, +) -> ContractId { + let mut hasher = Hasher::new(); + hasher.update(bytes.as_ref()); + hasher.update(nonce.to_le_bytes()); + hasher.update(owner.as_ref()); + let hash_bytes = hasher.finalize(); + ContractId::from_bytes(hash_bytes) +} + +#[cfg(test)] +mod tests { + use super::*; + use rand::rngs::StdRng; + use rand::{RngCore, SeedableRng}; + + #[test] + fn test_gen_contract_id() { + let mut rng = StdRng::seed_from_u64(42); + + let mut bytes = vec![0; 1000]; + rng.fill_bytes(&mut bytes); + + let nonce = rng.next_u64(); + + let mut owner = vec![0, 100]; + rng.fill_bytes(&mut owner); + + let contract_id = + gen_contract_id(bytes.as_slice(), nonce, owner.as_slice()); + + assert_eq!( + hex::encode(contract_id.as_bytes()), + "a138d3b9c87235dac6f62d1d30b75cffbb94601d9cbe5bd540b3e1e5842c8a7d" + ); + } +} diff --git a/rusk/src/lib/hash.rs b/rusk/src/lib/hash.rs new file mode 100644 index 0000000000..523baf6e9b --- /dev/null +++ b/rusk/src/lib/hash.rs @@ -0,0 +1,44 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use blake2b_simd::{Params, State}; + +/// Hashes scalars and arbitrary slices of bytes using Blake2b, returning an +/// array of 32 bytes. +/// +/// This hash cannot be proven inside a circuit, if that is desired, use +/// `poseidon_hash` instead. +pub struct Hasher { + state: State, +} + +impl Default for Hasher { + fn default() -> Self { + Hasher { + state: Params::new().hash_length(64).to_state(), + } + } +} + +impl Hasher { + /// Create new hasher instance. + pub fn new() -> Self { + Self::default() + } + + /// Process data, updating the internal state. + pub fn update(&mut self, data: impl AsRef<[u8]>) { + self.state.update(data.as_ref()); + } + + /// Retrieve result and consume hasher instance. + pub fn finalize(self) -> [u8; 32] { + let hash = self.state.finalize(); + let mut a = [0u8; 32]; + a.clone_from_slice(&hash.as_array()[..32]); + a + } +} diff --git a/rusk/src/lib/lib.rs b/rusk/src/lib/lib.rs index a3f4edec4c..a9cdd23505 100644 --- a/rusk/src/lib/lib.rs +++ b/rusk/src/lib/lib.rs @@ -9,6 +9,8 @@ #[cfg(feature = "node")] pub mod chain; mod error; +pub mod gen_id; +mod hash; pub mod http; pub mod verifier; mod version; diff --git a/rusk/tests/services/contract_deployment.rs b/rusk/tests/services/contract_deployment.rs index 6294725eb5..ef79774b71 100644 --- a/rusk/tests/services/contract_deployment.rs +++ b/rusk/tests/services/contract_deployment.rs @@ -12,6 +12,7 @@ use execution_core::bytecode::Bytecode; use execution_core::transfer::{ContractDeploy, ContractExec}; use rand::prelude::*; use rand::rngs::StdRng; +use rusk::gen_id::gen_contract_id; use rusk::{Result, Rusk}; use rusk_abi::{ContractData, ContractId, PiecrustError}; use rusk_recovery_tools::state; @@ -77,7 +78,12 @@ fn initial_state>(dir: P, deploy_bob: bool) -> Result { bob_bytecode, ContractData::builder() .owner(OWNER) - .constructor_arg(&BOB_INIT_VALUE), + .constructor_arg(&BOB_INIT_VALUE) + .contract_id(gen_contract_id( + &bob_bytecode, + 0u64, + &OWNER, + )), POINT_LIMIT, ) .expect("Deploying the alice contract should succeed"); @@ -121,6 +127,7 @@ fn make_and_execute_transaction_deploy( }, owner: OWNER.to_vec(), constructor_args, + nonce: 0, }), SENDER_INDEX, gas_limit, @@ -191,7 +198,7 @@ impl Fixture { "../../../target/dusk/wasm32-unknown-unknown/release/bob.wasm" ) .to_vec(); - let contract_id = bytecode_hash(bob_bytecode.as_slice()); + let contract_id = gen_contract_id(&bob_bytecode, 0u64, &OWNER); let path = tmp.into_path(); Self {