From 38377ba237fd5150f27922e2c624b97778b556ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Fri, 30 Aug 2024 11:30:26 +0100 Subject: [PATCH 01/10] rusk-abi: add chain ID as queryable data Contracts are given access to the ID of the chain being operated. This will allow contracts to check if a call was made originally on this chain, - by mixing the chain ID into a signature for example - effectively allowing them to prevent replay of calls performed on other chains. --- contracts/host_fn/src/lib.rs | 9 +++++++++ rusk-abi/src/abi.rs | 5 +++++ rusk-abi/src/host.rs | 6 +++++- rusk-abi/src/lib.rs | 3 ++- rusk-abi/tests/lib.rs | 21 +++++++++++++++++++-- 5 files changed, 40 insertions(+), 4 deletions(-) diff --git a/contracts/host_fn/src/lib.rs b/contracts/host_fn/src/lib.rs index bece500144..5b5a7c5f70 100644 --- a/contracts/host_fn/src/lib.rs +++ b/contracts/host_fn/src/lib.rs @@ -64,6 +64,10 @@ impl HostFnTest { rusk_abi::verify_bls(msg, pk, sig) } + pub fn chain_id(&self) -> u8 { + rusk_abi::chain_id() + } + pub fn block_height(&self) -> u64 { rusk_abi::block_height() } @@ -108,6 +112,11 @@ unsafe fn verify_bls(arg_len: u32) -> u32 { }) } +#[no_mangle] +unsafe fn chain_id(arg_len: u32) -> u32 { + rusk_abi::wrap_call(arg_len, |_: ()| STATE.chain_id()) +} + #[no_mangle] unsafe fn block_height(arg_len: u32) -> u32 { rusk_abi::wrap_call(arg_len, |_: ()| STATE.block_height()) diff --git a/rusk-abi/src/abi.rs b/rusk-abi/src/abi.rs index 24aceaaae9..15942c8880 100644 --- a/rusk-abi/src/abi.rs +++ b/rusk-abi/src/abi.rs @@ -55,6 +55,11 @@ pub fn verify_bls(msg: Vec, pk: BlsPublicKey, sig: BlsSignature) -> bool { host_query(Query::VERIFY_BLS, (msg, pk, sig)) } +/// Get the chain ID. +pub fn chain_id() -> u8 { + meta_data(Metadata::CHAIN_ID).unwrap() +} + /// Get the current block height. pub fn block_height() -> u64 { meta_data(Metadata::BLOCK_HEIGHT).unwrap() diff --git a/rusk-abi/src/host.rs b/rusk-abi/src/host.rs index a6fb2b4ff4..13cefec46d 100644 --- a/rusk-abi/src/host.rs +++ b/rusk-abi/src/host.rs @@ -32,20 +32,24 @@ use crate::{Metadata, Query}; pub fn new_session( vm: &VM, base: [u8; 32], + chain_id: u8, block_height: u64, ) -> Result { vm.session( SessionData::builder() .base(base) + .insert(Metadata::CHAIN_ID, chain_id)? .insert(Metadata::BLOCK_HEIGHT, block_height)?, ) } /// Create a new genesis session based on the given `vm`. The vm *must* have /// been created using [`new_vm`] or [`new_ephemeral_vm`]. -pub fn new_genesis_session(vm: &VM) -> Session { +pub fn new_genesis_session(vm: &VM, chain_id: u8) -> Session { vm.session( SessionData::builder() + .insert(Metadata::CHAIN_ID, chain_id) + .expect("Inserting chain ID in metadata should succeed") .insert(Metadata::BLOCK_HEIGHT, 0) .expect("Inserting block height in metadata should succeed"), ) diff --git a/rusk-abi/src/lib.rs b/rusk-abi/src/lib.rs index 6724ceb2a6..ba8342c5c6 100644 --- a/rusk-abi/src/lib.rs +++ b/rusk-abi/src/lib.rs @@ -28,7 +28,7 @@ pub use piecrust_uplink::debug as piecrust_debug; mod abi; #[cfg(feature = "abi")] pub use abi::{ - block_height, hash, owner, owner_raw, poseidon_hash, self_owner, + block_height, chain_id, hash, owner, owner_raw, poseidon_hash, self_owner, self_owner_raw, verify_bls, verify_proof, verify_schnorr, }; @@ -65,6 +65,7 @@ enum Metadata {} #[allow(dead_code)] impl Metadata { + pub const CHAIN_ID: &'static str = "chain_id"; pub const BLOCK_HEIGHT: &'static str = "block_height"; } diff --git a/rusk-abi/tests/lib.rs b/rusk-abi/tests/lib.rs index 61bf1a15fa..932885a96f 100644 --- a/rusk-abi/tests/lib.rs +++ b/rusk-abi/tests/lib.rs @@ -32,6 +32,7 @@ use ff::Field; use rusk_abi::{ContractData, Session, VM}; const POINT_LIMIT: u64 = 0x1000000; +const CHAIN_ID: u8 = 0xFA; #[test] fn hash_host() { @@ -62,7 +63,7 @@ fn instantiate(vm: &VM, height: u64) -> (Session, ContractId) { "../../target/dusk/wasm32-unknown-unknown/release/host_fn.wasm" ); - let mut session = rusk_abi::new_genesis_session(vm); + let mut session = rusk_abi::new_genesis_session(vm, CHAIN_ID); let contract_id = session .deploy( @@ -74,7 +75,7 @@ fn instantiate(vm: &VM, height: u64) -> (Session, ContractId) { let base = session.commit().expect("Committing should succeed"); - let session = rusk_abi::new_session(vm, base, height) + let session = rusk_abi::new_session(vm, base, CHAIN_ID, height) .expect("Instantiating new session should succeed"); (session, contract_id) @@ -336,6 +337,22 @@ fn plonk_proof() { assert!(!valid, "The proof should be invalid"); } +#[test] +fn chain_id() { + const HEIGHT: u64 = 123; + + let vm = + rusk_abi::new_ephemeral_vm().expect("Instantiating VM should succeed"); + let (mut session, contract_id) = instantiate(&vm, HEIGHT); + + let chain_id: u8 = session + .call(contract_id, "chain_id", &(), POINT_LIMIT) + .expect("Query should succeed") + .data; + + assert_eq!(chain_id, CHAIN_ID); +} + #[test] fn block_height() { const HEIGHT: u64 = 123; From de1292735b808dd155f1a213591d6130df8b48cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Fri, 30 Aug 2024 14:05:28 +0100 Subject: [PATCH 02/10] execution-core: add chain ID to TXs and calls Adding the chain ID to both transaction models and to relevant calls allows contracts downstream to check if they are being executed on the intended chain. The cost is minimal - 1 byte in the payload - and the benefits are cross-chain replay attack protection. --- execution-core/src/stake.rs | 20 +++++++++++++++++--- execution-core/src/transfer.rs | 5 ++++- execution-core/src/transfer/moonlight.rs | 17 +++++++++++++++-- execution-core/src/transfer/phoenix.rs | 19 +++++++++++++++++-- execution-core/tests/serialization.rs | 6 +++++- 5 files changed, 58 insertions(+), 9 deletions(-) diff --git a/execution-core/src/stake.rs b/execution-core/src/stake.rs index 2fcabc2694..d51a93e290 100644 --- a/execution-core/src/stake.rs +++ b/execution-core/src/stake.rs @@ -43,6 +43,7 @@ pub const fn next_epoch(block_height: u64) -> u64 { #[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)] #[archive_attr(derive(CheckBytes))] pub struct Stake { + chain_id: u8, account: BlsPublicKey, value: u64, nonce: u64, @@ -50,12 +51,18 @@ pub struct Stake { } impl Stake { - const MESSAGE_SIZE: usize = BlsPublicKey::SIZE + u64::SIZE + u64::SIZE; + const MESSAGE_SIZE: usize = 1 + BlsPublicKey::SIZE + u64::SIZE + u64::SIZE; /// Create a new stake. #[must_use] - pub fn new(sk: &BlsSecretKey, value: u64, nonce: u64) -> Self { + pub fn new( + sk: &BlsSecretKey, + value: u64, + nonce: u64, + chain_id: u8, + ) -> Self { let mut stake = Stake { + chain_id, account: BlsPublicKey::from(sk), value, nonce, @@ -91,6 +98,12 @@ impl Stake { self.nonce } + /// Returns the chain ID of the stake. + #[must_use] + pub fn chain_id(&self) -> u8 { + self.chain_id + } + /// Signature of the stake. #[must_use] pub fn signature(&self) -> &BlsSignature { @@ -102,7 +115,8 @@ impl Stake { pub fn signature_message(&self) -> [u8; Self::MESSAGE_SIZE] { let mut bytes = [0u8; Self::MESSAGE_SIZE]; - let mut offset = 0; + bytes[0] = self.chain_id; + let mut offset = 1; bytes[offset..offset + BlsPublicKey::SIZE] .copy_from_slice(&self.account.to_bytes()); diff --git a/execution-core/src/transfer.rs b/execution-core/src/transfer.rs index 910457e4d8..cf0abd05bc 100644 --- a/execution-core/src/transfer.rs +++ b/execution-core/src/transfer.rs @@ -73,6 +73,7 @@ impl Transaction { deposit: u64, gas_limit: u64, gas_price: u64, + chain_id: u8, exec: Option>, ) -> Result { Ok(Self::Phoenix(PhoenixTransaction::new::( @@ -87,6 +88,7 @@ impl Transaction { deposit, gas_limit, gas_price, + chain_id, exec, )?)) } @@ -102,11 +104,12 @@ impl Transaction { gas_limit: u64, gas_price: u64, nonce: u64, + chain_id: u8, exec: Option>, ) -> Self { Self::Moonlight(MoonlightTransaction::new( from_sk, to_account, value, deposit, gas_limit, gas_price, nonce, - exec, + chain_id, exec, )) } diff --git a/execution-core/src/transfer/moonlight.rs b/execution-core/src/transfer/moonlight.rs index a480d64b04..cc8b7e424a 100644 --- a/execution-core/src/transfer/moonlight.rs +++ b/execution-core/src/transfer/moonlight.rs @@ -54,9 +54,11 @@ impl Transaction { gas_limit: u64, gas_price: u64, nonce: u64, + chain_id: u8, exec: Option>, ) -> Self { let payload = Payload { + chain_id, from_account: AccountPublicKey::from(from_sk), to_account, value, @@ -121,6 +123,12 @@ impl Transaction { self.payload.nonce } + /// Returns the chain ID of the transaction. + #[must_use] + pub fn chain_id(&self) -> u8 { + self.payload.chain_id + } + /// Return the contract call data, if there is any. #[must_use] pub fn call(&self) -> Option<&ContractCall> { @@ -239,6 +247,8 @@ impl Transaction { #[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)] #[archive_attr(derive(CheckBytes))] struct Payload { + /// ID of the chain for this transaction to execute on. + pub chain_id: u8, /// Key of the sender of this transaction. pub from_account: AccountPublicKey, /// Key of the receiver of the funds. @@ -265,7 +275,7 @@ impl Payload { /// Serialize the payload into a byte buffer. #[must_use] pub fn to_var_bytes(&self) -> Vec { - let mut bytes = Vec::new(); + let mut bytes = Vec::from([self.chain_id]); bytes.extend(self.from_account.to_bytes()); @@ -309,6 +319,8 @@ impl Payload { pub fn from_slice(buf: &[u8]) -> Result { let mut buf = buf; + let chain_id = u8::from_reader(&mut buf)?; + let from_account = AccountPublicKey::from_reader(&mut buf)?; // deserialize recipient @@ -337,6 +349,7 @@ impl Payload { }; Ok(Self { + chain_id, from_account, to_account, value, @@ -354,7 +367,7 @@ impl Payload { /// for hashing and *cannot* be used to deserialize the payload again. #[must_use] pub fn signature_message(&self) -> Vec { - let mut bytes = Vec::new(); + let mut bytes = Vec::from([self.chain_id]); bytes.extend(self.from_account.to_bytes()); if let Some(to) = &self.to_account { diff --git a/execution-core/src/transfer/phoenix.rs b/execution-core/src/transfer/phoenix.rs index ebd2cafbc1..eeb92ee00b 100644 --- a/execution-core/src/transfer/phoenix.rs +++ b/execution-core/src/transfer/phoenix.rs @@ -86,6 +86,7 @@ impl Transaction { deposit: u64, gas_limit: u64, gas_price: u64, + chain_id: u8, exec: Option>, ) -> Result { let sender_pk = PublicKey::from(sender_sk); @@ -175,6 +176,7 @@ impl Transaction { deposit, }; let payload = Payload { + chain_id, tx_skeleton, fee, exec: exec.map(Into::into), @@ -356,6 +358,12 @@ impl Transaction { self.payload.fee.gas_price } + /// Returns the chain ID of the transaction. + #[must_use] + pub fn chain_id(&self) -> u8 { + self.payload.chain_id + } + /// Returns the max fee to be spend by the transaction. #[must_use] pub fn max_fee(&self) -> u64 { @@ -556,6 +564,8 @@ impl Transaction { #[derive(Debug, Clone, Archive, Serialize, Deserialize)] #[archive_attr(derive(CheckBytes))] pub struct Payload { + /// ID of the chain for this transaction to execute on. + pub chain_id: u8, /// Transaction skeleton used for the phoenix transaction. pub tx_skeleton: TxSkeleton, /// Data used to calculate the transaction fee. @@ -576,7 +586,7 @@ impl Payload { /// Serialize the `Payload` into a variable length byte buffer. #[must_use] pub fn to_var_bytes(&self) -> Vec { - let mut bytes = Vec::new(); + let mut bytes = Vec::from([self.chain_id]); // serialize the tx-skeleton let skeleton_bytes = self.tx_skeleton.to_var_bytes(); @@ -609,6 +619,8 @@ impl Payload { pub fn from_slice(buf: &[u8]) -> Result { let mut buf = buf; + let chain_id = u8::from_reader(&mut buf)?; + // deserialize the tx-skeleton #[allow(clippy::cast_possible_truncation)] let skeleton_len = usize::try_from(u64::from_reader(&mut buf)?) @@ -630,6 +642,7 @@ impl Payload { }; Ok(Self { + chain_id, tx_skeleton, fee, exec, @@ -642,7 +655,9 @@ impl Payload { /// for hashing and *cannot* be used to deserialize the `Payload` again. #[must_use] pub fn to_hash_input_bytes(&self) -> Vec { - let mut bytes = self.tx_skeleton.to_hash_input_bytes(); + let mut bytes = Vec::from([self.chain_id]); + + bytes.extend(self.tx_skeleton.to_hash_input_bytes()); match &self.exec { Some(ContractExec::Deploy(d)) => { diff --git a/execution-core/tests/serialization.rs b/execution-core/tests/serialization.rs index 22827c652e..a60e8d91c9 100644 --- a/execution-core/tests/serialization.rs +++ b/execution-core/tests/serialization.rs @@ -25,6 +25,8 @@ use poseidon_merkle::{Item, Tree}; use rand::rngs::StdRng; use rand::{CryptoRng, Rng, RngCore, SeedableRng}; +const CHAIN_ID: u8 = 0xFA; + struct TxCircuitVecProver(); // use the serialized TxCircuitVec as proof. This way that serialization is also @@ -121,6 +123,7 @@ fn new_phoenix_tx( deposit, gas_limit, gas_price, + CHAIN_ID, exec, ) .expect("transcaction generation should work") @@ -141,7 +144,8 @@ fn new_moonlight_tx( let nonce: u64 = rng.gen(); Transaction::moonlight( - &from_sk, to_account, value, deposit, gas_limit, gas_price, nonce, exec, + &from_sk, to_account, value, deposit, gas_limit, gas_price, nonce, + CHAIN_ID, exec, ) } From 070e3f5eb406c1ee17c7f6fde3d2e767b2bf647c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Fri, 30 Aug 2024 16:08:40 +0100 Subject: [PATCH 03/10] transfer-contract: add chain ID checks --- contracts/transfer/src/lib.rs | 5 +++++ contracts/transfer/src/state.rs | 12 ++++++++++++ contracts/transfer/tests/common/utils.rs | 10 ++++++++++ contracts/transfer/tests/transfer.rs | 23 ++++++++++++++++++++--- 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/contracts/transfer/src/lib.rs b/contracts/transfer/src/lib.rs index dc2a13d24f..58ff9f7c69 100644 --- a/contracts/transfer/src/lib.rs +++ b/contracts/transfer/src/lib.rs @@ -77,6 +77,11 @@ unsafe fn num_notes(arg_len: u32) -> u32 { rusk_abi::wrap_call(arg_len, |_: ()| STATE.num_notes()) } +#[no_mangle] +unsafe fn chain_id(arg_len: u32) -> u32 { + rusk_abi::wrap_call(arg_len, |_: ()| STATE.chain_id()) +} + // "Feeder" queries #[no_mangle] diff --git a/contracts/transfer/src/state.rs b/contracts/transfer/src/state.rs index 521edc7002..d0b7e8881f 100644 --- a/contracts/transfer/src/state.rs +++ b/contracts/transfer/src/state.rs @@ -342,6 +342,10 @@ impl TransferState { transitory::put_transaction(tx); let phoenix_tx = transitory::unwrap_phoenix_tx(); + if phoenix_tx.chain_id() != self.chain_id() { + panic!("The tx must target the correct chain"); + } + // panic if the root is invalid if !self.root_exists(phoenix_tx.root()) { panic!("Root not found in the state!"); @@ -398,6 +402,10 @@ impl TransferState { transitory::put_transaction(tx); let moonlight_tx = transitory::unwrap_moonlight_tx(); + if moonlight_tx.chain_id() != self.chain_id() { + panic!("The tx must target the correct chain"); + } + // check the signature is valid and made by `from` if !rusk_abi::verify_bls( moonlight_tx.signature_message(), @@ -676,6 +684,10 @@ impl TransferState { let block_height = rusk_abi::block_height(); self.push_note(block_height, note) } + + pub fn chain_id(&self) -> u8 { + rusk_abi::chain_id() + } } fn verify_tx_proof(tx: &PhoenixTransaction) -> bool { diff --git a/contracts/transfer/tests/common/utils.rs b/contracts/transfer/tests/common/utils.rs index 79393f502f..3d87b35a0c 100644 --- a/contracts/transfer/tests/common/utils.rs +++ b/contracts/transfer/tests/common/utils.rs @@ -112,6 +112,12 @@ pub fn opening( .map(|r| r.data) } +pub fn chain_id(session: &mut Session) -> Result { + session + .call(TRANSFER_CONTRACT, "chain_id", &(), GAS_LIMIT) + .map(|r| r.data) +} + /// Executes a transaction. /// Returns result containing gas spent. pub fn execute( @@ -214,6 +220,9 @@ pub fn create_phoenix_transaction( inputs.push((note.clone(), opening)); } + let chain_id = + chain_id(session).expect("Getting the chain ID should succeed"); + PhoenixTransaction::new::( rng, sender_sk, @@ -226,6 +235,7 @@ pub fn create_phoenix_transaction( deposit, gas_limit, gas_price, + chain_id, exec.map(Into::into), ) .expect("creating the creation shouldn't fail") diff --git a/contracts/transfer/tests/transfer.rs b/contracts/transfer/tests/transfer.rs index ed5f8619fb..af40fe58da 100644 --- a/contracts/transfer/tests/transfer.rs +++ b/contracts/transfer/tests/transfer.rs @@ -7,7 +7,7 @@ pub mod common; use crate::common::utils::{ - account, contract_balance, create_phoenix_transaction, execute, + account, chain_id, contract_balance, create_phoenix_transaction, execute, filter_notes_owned_by, leaves_from_height, leaves_from_pos, num_notes, owned_notes_value, update_root, }; @@ -53,6 +53,7 @@ const BOB_ID: ContractId = { }; const OWNER: [u8; 32] = [0; 32]; +const CHAIN_ID: u8 = 0xFA; /// Instantiate the virtual machine with the transfer contract deployed, with a /// single note carrying the `GENESIS_VALUE` owned by the given public key. @@ -72,7 +73,7 @@ fn instantiate( "../../../target/dusk/wasm32-unknown-unknown/release/alice.wasm" ); - let mut session = rusk_abi::new_genesis_session(vm); + let mut session = rusk_abi::new_genesis_session(vm, CHAIN_ID); session .deploy( @@ -137,7 +138,7 @@ fn instantiate( // sets the block height for all subsequent operations to 1 let base = session.commit().expect("Committing should succeed"); - rusk_abi::new_session(vm, base, 1) + rusk_abi::new_session(vm, base, CHAIN_ID, 1) .expect("Instantiating new session should succeed") } @@ -265,6 +266,9 @@ fn moonlight_transfer() { "The receiver account should be empty" ); + let chain_id = + chain_id(session).expect("Getting the chain ID should succeed"); + let transaction = MoonlightTransaction::new( &moonlight_sender_sk, Some(moonlight_receiver_pk), @@ -273,6 +277,7 @@ fn moonlight_transfer() { GAS_LIMIT, LUX, sender_account.nonce + 1, + chain_id, None::, ); @@ -396,6 +401,9 @@ fn moonlight_alice_ping() { "The account should have the genesis value" ); + let chain_id = + chain_id(session).expect("Getting the chain ID should succeed"); + let transaction = MoonlightTransaction::new( &moonlight_sk, None, @@ -404,6 +412,7 @@ fn moonlight_alice_ping() { GAS_LIMIT, LUX, acc.nonce + 1, + chain_id, contract_call, ); @@ -750,6 +759,9 @@ fn moonlight_to_phoenix_swap() { .to_vec(), }; + let chain_id = + chain_id(session).expect("Getting the chain ID should succeed"); + let tx = MoonlightTransaction::new( &moonlight_sk, None, @@ -758,6 +770,7 @@ fn moonlight_to_phoenix_swap() { GAS_LIMIT, LUX, nonce, + chain_id, Some(contract_call), ); @@ -849,6 +862,9 @@ fn swap_wrong_contract_targeted() { .to_vec(), }; + let chain_id = + chain_id(session).expect("Getting the chain ID should succeed"); + let tx = MoonlightTransaction::new( &moonlight_sk, None, @@ -857,6 +873,7 @@ fn swap_wrong_contract_targeted() { GAS_LIMIT, LUX, nonce, + chain_id, Some(contract_call), ); From 71af198c7b65ed9b57f5c822d95d25303ece39dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Fri, 30 Aug 2024 16:09:34 +0100 Subject: [PATCH 04/10] stake-contract: add chain ID checks --- contracts/stake/src/state.rs | 8 ++++++++ contracts/stake/tests/common/init.rs | 5 +++-- contracts/stake/tests/common/utils.rs | 10 ++++++++++ contracts/stake/tests/stake.rs | 11 +++++++---- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/contracts/stake/src/state.rs b/contracts/stake/src/state.rs index 62c61f4a3c..4a239373f2 100644 --- a/contracts/stake/src/state.rs +++ b/contracts/stake/src/state.rs @@ -72,6 +72,10 @@ impl StakeState { let nonce = stake.nonce(); let signature = *stake.signature(); + if stake.chain_id() != self.chain_id() { + panic!("The stake must target the correct chain"); + } + let loaded_stake = self.load_or_create_stake_mut(&account); // ensure the stake is at least the minimum and that there isn't an @@ -456,6 +460,10 @@ impl StakeState { } } + fn chain_id(&self) -> u8 { + rusk_abi::chain_id() + } + fn deduct_contract_balance(amount: u64) { // Update the module balance to reflect the change in the amount // withdrawable from the contract diff --git a/contracts/stake/tests/common/init.rs b/contracts/stake/tests/common/init.rs index e129149b29..b26a05bf19 100644 --- a/contracts/stake/tests/common/init.rs +++ b/contracts/stake/tests/common/init.rs @@ -20,6 +20,7 @@ use rusk_abi::{ContractData, Session, VM}; use crate::common::utils::update_root; const OWNER: [u8; 32] = [0; 32]; +pub const CHAIN_ID: u8 = 0xFA; const POINT_LIMIT: u64 = 0x100_000_000; /// Instantiate the virtual machine with the transfer contract deployed, with a @@ -37,7 +38,7 @@ pub fn instantiate( "../../../../target/dusk/wasm32-unknown-unknown/release/stake_contract.wasm" ); - let mut session = rusk_abi::new_genesis_session(vm); + let mut session = rusk_abi::new_genesis_session(vm, CHAIN_ID); session .deploy( @@ -81,6 +82,6 @@ pub fn instantiate( // sets the block height for all subsequent operations to 1 let base = session.commit().expect("Committing should succeed"); - rusk_abi::new_session(vm, base, 1) + rusk_abi::new_session(vm, base, CHAIN_ID, 1) .expect("Instantiating new session should succeed") } diff --git a/contracts/stake/tests/common/utils.rs b/contracts/stake/tests/common/utils.rs index d77de40c84..a794bc07e3 100644 --- a/contracts/stake/tests/common/utils.rs +++ b/contracts/stake/tests/common/utils.rs @@ -87,6 +87,12 @@ pub fn opening( .map(|r| r.data) } +pub fn chain_id(session: &mut Session) -> Result { + session + .call(TRANSFER_CONTRACT, "chain_id", &(), POINT_LIMIT) + .map(|r| r.data) +} + pub fn filter_notes_owned_by>( vk: PhoenixViewKey, iter: I, @@ -179,6 +185,9 @@ pub fn create_transaction( inputs.push((note.clone(), opening)); } + let chain_id = + chain_id(session).expect("Getting the chain ID should succeed"); + PhoenixTransaction::new::( rng, sender_sk, @@ -191,6 +200,7 @@ pub fn create_transaction( deposit, gas_limit, gas_price, + chain_id, exec.map(Into::into), ) .expect("creating the creation shouldn't fail") diff --git a/contracts/stake/tests/stake.rs b/contracts/stake/tests/stake.rs index 7501bb2f94..598c18bb6e 100644 --- a/contracts/stake/tests/stake.rs +++ b/contracts/stake/tests/stake.rs @@ -26,7 +26,7 @@ use execution_core::{ }; use crate::common::assert::assert_event; -use crate::common::init::instantiate; +use crate::common::init::{instantiate, CHAIN_ID}; use crate::common::utils::*; const GENESIS_VALUE: u64 = dusk(1_000_000.0); @@ -71,8 +71,11 @@ fn stake_withdraw_unstake() { let input_note_pos = 0; let deposit = INITIAL_STAKE; + let chain_id = + chain_id(&mut session).expect("Getting the chain ID should succeed"); + // Fashion a Stake struct - let stake = Stake::new(&stake_sk, deposit, 1); + let stake = Stake::new(&stake_sk, deposit, 1, chain_id); let stake_bytes = rkyv::to_bytes::<_, 1024>(&stake) .expect("Should serialize Stake correctly") .to_vec(); @@ -230,7 +233,7 @@ fn stake_withdraw_unstake() { // set different block height so that the new notes are easily located and // filtered let base = session.commit().expect("Committing should succeed"); - let mut session = rusk_abi::new_session(vm, base, 2) + let mut session = rusk_abi::new_session(vm, base, CHAIN_ID, 2) .expect("Instantiating new session should succeed"); let receipt = @@ -344,7 +347,7 @@ fn stake_withdraw_unstake() { // filtered // sets the block height for all subsequent operations to 1 let base = session.commit().expect("Committing should succeed"); - let mut session = rusk_abi::new_session(vm, base, 3) + let mut session = rusk_abi::new_session(vm, base, CHAIN_ID, 3) .expect("Instantiating new session should succeed"); let receipt = From 287eea6b05068e826cd1b5c10f936b0c4432d36e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Fri, 30 Aug 2024 16:10:08 +0100 Subject: [PATCH 05/10] license-contract: adapt to chain ID --- contracts/license/tests/license.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/license/tests/license.rs b/contracts/license/tests/license.rs index 2a37fe02a4..64b2f412f3 100644 --- a/contracts/license/tests/license.rs +++ b/contracts/license/tests/license.rs @@ -41,6 +41,7 @@ const LICENSE_CONTRACT_ID: ContractId = { const POINT_LIMIT: u64 = 0x10000000; const TEST_OWNER: [u8; 32] = [0; 32]; +const CHAIN_ID: u8 = 0xFA; const USER_ATTRIBUTES: u64 = 545072475273; static LABEL: &[u8] = b"dusk-network"; @@ -66,7 +67,7 @@ fn initialize() -> Session { "../../../target/dusk/wasm32-unknown-unknown/release/license_contract.wasm" ); - let mut session = rusk_abi::new_genesis_session(&vm); + let mut session = rusk_abi::new_genesis_session(&vm, CHAIN_ID); session .deploy( From b285d739ce0165230ab75ba2feca4277a083812b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Fri, 30 Aug 2024 16:11:18 +0100 Subject: [PATCH 06/10] wallet-core: use chain ID in transaction creation --- wallet-core/src/transaction.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/wallet-core/src/transaction.rs b/wallet-core/src/transaction.rs index bc21c64aae..923f6e7f6c 100644 --- a/wallet-core/src/transaction.rs +++ b/wallet-core/src/transaction.rs @@ -61,6 +61,7 @@ pub fn phoenix( deposit: u64, gas_limit: u64, gas_price: u64, + chain_id: u8, exec: Option>, ) -> Result { Ok(PhoenixTransaction::new::( @@ -75,6 +76,7 @@ pub fn phoenix( deposit, gas_limit, gas_price, + chain_id, exec, )? .into()) @@ -99,6 +101,7 @@ pub fn phoenix_stake( root: BlsScalar, gas_limit: u64, gas_price: u64, + chain_id: u8, stake_value: u64, current_nonce: u64, ) -> Result { @@ -109,7 +112,7 @@ pub fn phoenix_stake( let obfuscated_transaction = false; let deposit = stake_value; - let stake = Stake::new(stake_sk, stake_value, current_nonce + 1); + let stake = Stake::new(stake_sk, stake_value, current_nonce + 1, chain_id); let contract_call = ContractCall::new(STAKE_CONTRACT, "stake", &stake)?; @@ -125,6 +128,7 @@ pub fn phoenix_stake( deposit, gas_limit, gas_price, + chain_id, Some(contract_call), ) } @@ -150,6 +154,7 @@ pub fn phoenix_stake_reward( reward_amount: u64, gas_limit: u64, gas_price: u64, + chain_id: u8, ) -> Result { let receiver_pk = PhoenixPublicKey::from(phoenix_sender_sk); let change_pk = receiver_pk; @@ -190,6 +195,7 @@ pub fn phoenix_stake_reward( deposit, gas_limit, gas_price, + chain_id, Some(contract_call), ) } @@ -214,6 +220,7 @@ pub fn phoenix_unstake( unstake_value: u64, gas_limit: u64, gas_price: u64, + chain_id: u8, ) -> Result { let receiver_pk = PhoenixPublicKey::from(phoenix_sender_sk); let change_pk = receiver_pk; @@ -254,6 +261,7 @@ pub fn phoenix_unstake( deposit, gas_limit, gas_price, + chain_id, Some(contract_call), ) } From 8008290515d09f5d7a75fbe0dbee8029ecd2fdba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Fri, 30 Aug 2024 16:12:14 +0100 Subject: [PATCH 07/10] test-wallet: add chain ID in transaction creation --- test-wallet/src/imp.rs | 25 ++++++++++++++++++++++++- test-wallet/src/lib.rs | 3 +++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/test-wallet/src/imp.rs b/test-wallet/src/imp.rs index 6297bce503..fb0583b7e1 100644 --- a/test-wallet/src/imp.rs +++ b/test-wallet/src/imp.rs @@ -370,6 +370,9 @@ where let transfer_value = 0; let obfuscated_transaction = false; + let chain_id = + self.state.fetch_chain_id().map_err(Error::from_state_err)?; + let tx = phoenix_transaction::( rng, &sender_sk, @@ -382,6 +385,7 @@ where deposit, gas_limit, gas_price, + chain_id, Some(exec), )?; @@ -416,6 +420,9 @@ where let exec: Option = None; + let chain_id = + self.state.fetch_chain_id().map_err(Error::from_state_err)?; + let tx = phoenix_transaction::( rng, &sender_sk, @@ -428,6 +435,7 @@ where deposit, gas_limit, gas_price, + chain_id, exec, )?; @@ -465,6 +473,9 @@ where .map_err(Error::from_state_err)? .nonce; + let chain_id = + self.state.fetch_chain_id().map_err(Error::from_state_err)?; + let tx = phoenix_stake::( rng, &phoenix_sender_sk, @@ -473,6 +484,7 @@ where root, gas_limit, gas_price, + chain_id, stake_value, current_nonce, )?; @@ -517,6 +529,9 @@ where })? .value; + let chain_id = + self.state.fetch_chain_id().map_err(Error::from_state_err)?; + let tx = phoenix_unstake::( rng, &phoenix_sender_sk, @@ -526,6 +541,7 @@ where staked_amount, gas_limit, gas_price, + chain_id, )?; stake_sk.zeroize(); @@ -562,6 +578,9 @@ where .map_err(Error::from_state_err)? .reward; + let chain_id = + self.state.fetch_chain_id().map_err(Error::from_state_err)?; + let tx = phoenix_stake_reward::( rng, &phoenix_sender_sk, @@ -571,6 +590,7 @@ where stake_reward, gas_limit, gas_price, + chain_id, )?; stake_sk.zeroize(); @@ -631,9 +651,12 @@ where } let nonce = account.nonce + 1; + let chain_id = + self.state.fetch_chain_id().map_err(Error::from_state_err)?; + let tx = MoonlightTransaction::new( &from_sk, to_account, value, deposit, gas_limit, gas_price, nonce, - exec, + chain_id, exec, ); seed.zeroize(); diff --git a/test-wallet/src/lib.rs b/test-wallet/src/lib.rs index 32d4fbe7bd..773deb0ad1 100644 --- a/test-wallet/src/lib.rs +++ b/test-wallet/src/lib.rs @@ -143,4 +143,7 @@ pub trait StateClient { &self, pk: &BlsPublicKey, ) -> Result; + + /// Queries for the chain ID. + fn fetch_chain_id(&self) -> Result; } From 776ee8308584241150160b5fb2bfe67845a48e95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Fri, 30 Aug 2024 16:12:41 +0100 Subject: [PATCH 08/10] rusk-recovery: use dummy chain ID to instantiate state --- rusk-recovery/src/state.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/rusk-recovery/src/state.rs b/rusk-recovery/src/state.rs index fa57461e82..3d24c518f9 100644 --- a/rusk-recovery/src/state.rs +++ b/rusk-recovery/src/state.rs @@ -40,6 +40,7 @@ pub const DEFAULT_SNAPSHOT: &str = include_str!("../config/testnet_remote.toml"); const GENESIS_BLOCK_HEIGHT: u64 = 0; +const GENESIS_CHAIN_ID: u8 = 0xFA; pub static DUSK_KEY: Lazy = Lazy::new(|| { let addr = include_str!("../assets/dusk.address"); @@ -171,7 +172,7 @@ fn generate_empty_state>( let state_dir = state_dir.as_ref(); let vm = rusk_abi::new_vm(state_dir)?; - let mut session = rusk_abi::new_genesis_session(&vm); + let mut session = rusk_abi::new_genesis_session(&vm, GENESIS_CHAIN_ID); let transfer_code = include_bytes!( "../../target/dusk/wasm64-unknown-unknown/release/transfer_contract.wasm" @@ -259,8 +260,12 @@ where None => generate_empty_state(state_dir, snapshot), }?; - let mut session = - rusk_abi::new_session(&vm, old_commit_id, GENESIS_BLOCK_HEIGHT)?; + let mut session = rusk_abi::new_session( + &vm, + old_commit_id, + GENESIS_CHAIN_ID, + GENESIS_BLOCK_HEIGHT, + )?; generate_transfer_state(&mut session, snapshot)?; generate_stake_state(&mut session, snapshot)?; From fb480702c121b4a1cf3dc0089f74e8a24f7f4027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Fri, 30 Aug 2024 16:13:33 +0100 Subject: [PATCH 09/10] rusk: pass configurable chain ID to host data --- rusk/src/bin/config/kadcast.rs | 4 ++++ rusk/src/bin/main.rs | 1 + rusk/src/lib/node.rs | 1 + rusk/src/lib/node/rusk.rs | 14 +++++++++++++- rusk/tests/common/state.rs | 7 +++++-- rusk/tests/common/wallet.rs | 5 +++++ rusk/tests/rusk-state.rs | 3 ++- rusk/tests/services/contract_deployment.rs | 10 ++++++---- 8 files changed, 37 insertions(+), 8 deletions(-) diff --git a/rusk/src/bin/config/kadcast.rs b/rusk/src/bin/config/kadcast.rs index a95a58ee35..62d9cf09fc 100644 --- a/rusk/src/bin/config/kadcast.rs +++ b/rusk/src/bin/config/kadcast.rs @@ -33,4 +33,8 @@ impl KadcastConfig { self.0.kadcast_id = Some(network_id) }; } + + pub fn chain_id(&self) -> u8 { + self.0.kadcast_id.unwrap_or_default() + } } diff --git a/rusk/src/bin/main.rs b/rusk/src/bin/main.rs index b798d78306..9a913b7408 100644 --- a/rusk/src/bin/main.rs +++ b/rusk/src/bin/main.rs @@ -64,6 +64,7 @@ async fn main() -> Result<(), Box> { let rusk = Rusk::new( state_dir, + config.kadcast.chain_id(), config.chain.generation_timeout(), config.chain.gas_per_deploy_byte(), config.chain.block_gas_limit(), diff --git a/rusk/src/lib/node.rs b/rusk/src/lib/node.rs index 4e33f6e8c4..ec6957a43f 100644 --- a/rusk/src/lib/node.rs +++ b/rusk/src/lib/node.rs @@ -42,6 +42,7 @@ pub struct Rusk { pub(crate) tip: Arc>, pub(crate) vm: Arc, dir: PathBuf, + pub(crate) chain_id: u8, pub(crate) generation_timeout: Option, pub(crate) gas_per_deploy_byte: Option, pub(crate) feeder_gas_limit: u64, diff --git a/rusk/src/lib/node/rusk.rs b/rusk/src/lib/node/rusk.rs index 1dba4d2978..df65da6347 100644 --- a/rusk/src/lib/node/rusk.rs +++ b/rusk/src/lib/node/rusk.rs @@ -53,6 +53,7 @@ const DEFAULT_GAS_PER_DEPLOY_BYTE: u64 = 100; impl Rusk { pub fn new>( dir: P, + chain_id: u8, generation_timeout: Option, gas_per_deploy_byte: Option, block_gas_limit: u64, @@ -87,6 +88,7 @@ impl Rusk { tip, vm, dir: dir.into(), + chain_id, generation_timeout, gas_per_deploy_byte, feeder_gas_limit, @@ -347,6 +349,11 @@ impl Rusk { self.query(TRANSFER_CONTRACT, "account", pk) } + /// Returns an account's information. + pub fn chain_id(&self) -> Result { + self.query(TRANSFER_CONTRACT, "chain_id", &()) + } + /// Fetches the previous state data for stake changes in the contract. /// /// Communicates with the stake contract to obtain information about the @@ -396,7 +403,12 @@ impl Rusk { tip.current }); - let session = rusk_abi::new_session(&self.vm, commit, block_height)?; + let session = rusk_abi::new_session( + &self.vm, + commit, + self.chain_id, + block_height, + )?; Ok(session) } diff --git a/rusk/tests/common/state.rs b/rusk/tests/common/state.rs index ee97c9c8a6..153be2ae5f 100644 --- a/rusk/tests/common/state.rs +++ b/rusk/tests/common/state.rs @@ -29,6 +29,8 @@ use tracing::info; use crate::common::keys::STAKE_SK; +const CHAIN_ID: u8 = 0xFA; + // Creates a Rusk initial state in the given directory pub fn new_state>( dir: P, @@ -42,8 +44,9 @@ pub fn new_state>( let (sender, _) = broadcast::channel(10); - let rusk = Rusk::new(dir, None, None, block_gas_limit, u64::MAX, sender) - .expect("Instantiating rusk should succeed"); + let rusk = + Rusk::new(dir, CHAIN_ID, None, None, block_gas_limit, u64::MAX, sender) + .expect("Instantiating rusk should succeed"); assert_eq!( commit_id, diff --git a/rusk/tests/common/wallet.rs b/rusk/tests/common/wallet.rs index 7b164edf88..612792f50c 100644 --- a/rusk/tests/common/wallet.rs +++ b/rusk/tests/common/wallet.rs @@ -121,6 +121,11 @@ impl wallet::StateClient for TestStateClient { let account = self.rusk.account(pk)?; Ok(account) } + + fn fetch_chain_id(&self) -> Result { + let chain_id = self.rusk.chain_id()?; + Ok(chain_id) + } } #[derive(Default, Debug, Clone)] diff --git a/rusk/tests/rusk-state.rs b/rusk/tests/rusk-state.rs index 813ca14eb4..4ef736a130 100644 --- a/rusk/tests/rusk-state.rs +++ b/rusk/tests/rusk-state.rs @@ -35,6 +35,7 @@ use tracing::info; use crate::common::state::new_state; const BLOCK_HEIGHT: u64 = 1; +const CHAIN_ID: u8 = 0xFA; const BLOCK_GAS_LIMIT: u64 = 100_000_000_000; const INITIAL_BALANCE: u64 = 10_000_000_000; @@ -83,7 +84,7 @@ where rusk.with_tip(|mut tip, vm| { let current_commit = tip.current; let mut session = - rusk_abi::new_session(vm, current_commit, BLOCK_HEIGHT) + rusk_abi::new_session(vm, current_commit, CHAIN_ID, BLOCK_HEIGHT) .expect("current commit should exist"); session diff --git a/rusk/tests/services/contract_deployment.rs b/rusk/tests/services/contract_deployment.rs index 1761dd6a54..efb6a439cc 100644 --- a/rusk/tests/services/contract_deployment.rs +++ b/rusk/tests/services/contract_deployment.rs @@ -44,6 +44,7 @@ const ALICE_CONTRACT_ID: ContractId = { }; const OWNER: [u8; 32] = [1; 32]; +const CHAIN_ID: u8 = 0xFA; const BOB_ECHO_VALUE: u64 = 775; const BOB_INIT_VALUE: u8 = 5; @@ -95,8 +96,9 @@ fn initial_state>(dir: P, deploy_bob: bool) -> Result { let (sender, _) = broadcast::channel(10); - let rusk = Rusk::new(dir, None, None, BLOCK_GAS_LIMIT, u64::MAX, sender) - .expect("Instantiating rusk should succeed"); + let rusk = + Rusk::new(dir, CHAIN_ID, None, None, BLOCK_GAS_LIMIT, u64::MAX, sender) + .expect("Instantiating rusk should succeed"); Ok(rusk) } @@ -215,7 +217,7 @@ impl Fixture { let commit = self.rusk.state_root(); let vm = rusk_abi::new_vm(self.path.as_path()) .expect("VM creation should succeed"); - let mut session = rusk_abi::new_session(&vm, commit, 0) + let mut session = rusk_abi::new_session(&vm, commit, CHAIN_ID, 0) .expect("Session creation should succeed"); let result = session.call::<_, u64>( self.contract_id, @@ -233,7 +235,7 @@ impl Fixture { let commit = self.rusk.state_root(); let vm = rusk_abi::new_vm(self.path.as_path()) .expect("VM creation should succeed"); - let mut session = rusk_abi::new_session(&vm, commit, 0) + let mut session = rusk_abi::new_session(&vm, commit, CHAIN_ID, 0) .expect("Session creation should succeed"); let result = session.call::<_, u64>( self.contract_id, From b394cd6b2bc68843d0e7a0c924d2d993a8f30d77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Fri, 30 Aug 2024 16:15:23 +0100 Subject: [PATCH 10/10] node-data: adapt to chain ID in phoenix payload --- node-data/src/ledger/transaction.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/node-data/src/ledger/transaction.rs b/node-data/src/ledger/transaction.rs index bfcd2844da..1ea38619dc 100644 --- a/node-data/src/ledger/transaction.rs +++ b/node-data/src/ledger/transaction.rs @@ -156,6 +156,7 @@ pub mod faker { ContractCall::new([21; 32], "some_method", &()).unwrap(); let payload = PhoenixPayload { + chain_id: 0xFA, tx_skeleton, fee, exec: Some(ContractExec::Call(contract_call)),