From 6b724bfda40f0e4488a00f7eeadf8acfd8856f79 Mon Sep 17 00:00:00 2001 From: Junkil Park Date: Thu, 13 Jun 2024 00:23:31 +0900 Subject: [PATCH] Add a transaction context function to get the raw transaction hash This commit adds a transaction context function to get the raw transaction hash. --- .../src/components/feature_flags.rs | 7 + .../aptos-vm/src/move_vm_ext/session/mod.rs | 8 +- .../aptos-vm/src/transaction_metadata.rs | 43 ++++- .../sources/transaction_context_test.move | 12 ++ .../src/tests/transaction_context.rs | 139 +++++++++++++- .../aptos-framework/doc/randomness.md | 2 +- .../doc/transaction_context.md | 173 +++++++++++++++--- .../aptos-framework/sources/object.move | 2 +- .../aptos-framework/sources/randomness.move | 2 +- .../sources/transaction_context.move | 35 +++- .../sources/transaction_context.spec.move | 30 ++- .../framework/move-stdlib/doc/features.md | 63 +++++++ .../move-stdlib/sources/configs/features.move | 14 ++ .../src/natives/transaction_context.rs | 52 +++++- types/src/on_chain_config/aptos_features.rs | 2 + .../transaction/user_transaction_context.rs | 7 + 16 files changed, 532 insertions(+), 59 deletions(-) diff --git a/aptos-move/aptos-release-builder/src/components/feature_flags.rs b/aptos-move/aptos-release-builder/src/components/feature_flags.rs index 345bcbe6445e9..7814eabd3de75 100644 --- a/aptos-move/aptos-release-builder/src/components/feature_flags.rs +++ b/aptos-move/aptos-release-builder/src/components/feature_flags.rs @@ -131,6 +131,7 @@ pub enum FeatureFlag { FederatedKeyless, TransactionSimulationEnhancement, CollectionOwner, + TransactionContextHashFunctionUpdate, } fn generate_features_blob(writer: &CodeWriter, data: &[u64]) { @@ -347,6 +348,9 @@ impl From for AptosFeatureFlag { AptosFeatureFlag::TRANSACTION_SIMULATION_ENHANCEMENT }, FeatureFlag::CollectionOwner => AptosFeatureFlag::COLLECTION_OWNER, + FeatureFlag::TransactionContextHashFunctionUpdate => { + AptosFeatureFlag::TRANSACTION_CONTEXT_HASH_FUNCTION_UPDATE + }, } } } @@ -490,6 +494,9 @@ impl From for FeatureFlag { FeatureFlag::TransactionSimulationEnhancement }, AptosFeatureFlag::COLLECTION_OWNER => FeatureFlag::CollectionOwner, + AptosFeatureFlag::TRANSACTION_CONTEXT_HASH_FUNCTION_UPDATE => { + FeatureFlag::TransactionContextHashFunctionUpdate + }, } } } diff --git a/aptos-move/aptos-vm/src/move_vm_ext/session/mod.rs b/aptos-move/aptos-vm/src/move_vm_ext/session/mod.rs index 7e8b576cb1248..2d39de276c7a8 100644 --- a/aptos-move/aptos-vm/src/move_vm_ext/session/mod.rs +++ b/aptos-move/aptos-vm/src/move_vm_ext/session/mod.rs @@ -76,24 +76,24 @@ impl<'r, 'l> SessionExt<'r, 'l> { resolver: &'r R, ) -> Self { let mut extensions = NativeContextExtensions::default(); - let txn_hash: [u8; 32] = session_id + let unique_session_hash: [u8; 32] = session_id .as_uuid() .to_vec() .try_into() .expect("HashValue should convert to [u8; 32]"); - extensions.add(NativeTableContext::new(txn_hash, resolver)); + extensions.add(NativeTableContext::new(unique_session_hash, resolver)); extensions.add(NativeRistrettoPointContext::new()); extensions.add(AlgebraContext::new()); extensions.add(NativeAggregatorContext::new( - txn_hash, + unique_session_hash, resolver, move_vm.vm_config().delayed_field_optimization_enabled, resolver, )); extensions.add(RandomnessContext::new()); extensions.add(NativeTransactionContext::new( - txn_hash.to_vec(), + unique_session_hash.to_vec(), session_id.into_script_hash(), chain_id.id(), maybe_user_transaction_context, diff --git a/aptos-move/aptos-vm/src/transaction_metadata.rs b/aptos-move/aptos-vm/src/transaction_metadata.rs index 65358aee33e82..6de8b2c233df1 100644 --- a/aptos-move/aptos-vm/src/transaction_metadata.rs +++ b/aptos-move/aptos-vm/src/transaction_metadata.rs @@ -2,14 +2,15 @@ // Parts of the project are originally copyright © Meta Platforms, Inc. // SPDX-License-Identifier: Apache-2.0 -use aptos_crypto::HashValue; +use aptos_crypto::{hash::CryptoHash, HashValue}; use aptos_gas_algebra::{FeePerGasUnit, Gas, NumBytes}; use aptos_types::{ account_address::AccountAddress, chain_id::ChainId, transaction::{ - user_transaction_context::UserTransactionContext, EntryFunction, Multisig, - SignedTransaction, TransactionPayload, + authenticator::TransactionAuthenticator::{FeePayer, MultiAgent}, + user_transaction_context::UserTransactionContext, + EntryFunction, Multisig, RawTransactionWithData, SignedTransaction, TransactionPayload, }, }; @@ -31,6 +32,7 @@ pub struct TransactionMetadata { pub is_keyless: bool, pub entry_function_payload: Option, pub multisig_payload: Option, + pub raw_transaction_hash: Vec, } impl TransactionMetadata { @@ -89,6 +91,36 @@ impl TransactionMetadata { TransactionPayload::Multisig(m) => Some(m.clone()), _ => None, }, + raw_transaction_hash: match txn.authenticator_ref() { + MultiAgent { + sender: _, + secondary_signer_addresses, + secondary_signers: _, + } => { + let raw_txn = RawTransactionWithData::new_multi_agent( + txn.clone().into_raw_transaction(), + secondary_signer_addresses.clone(), + ); + raw_txn.hash().to_vec() + }, + FeePayer { + sender: _, + secondary_signer_addresses, + secondary_signers: _, + fee_payer_address: _, + fee_payer_signer: _, + } => { + // In the case of a fee payer transaction, the hash value is generated using + // AccountAddress::ZERO as the fee payer address. + let raw_txn = RawTransactionWithData::new_fee_payer( + txn.clone().into_raw_transaction(), + secondary_signer_addresses.clone(), + AccountAddress::ZERO, + ); + raw_txn.hash().to_vec() + }, + _ => txn.raw_transaction_ref().hash().to_vec(), + }, } } @@ -158,6 +190,10 @@ impl TransactionMetadata { self.multisig_payload.clone() } + pub fn raw_transaction_hash(&self) -> Vec { + self.raw_transaction_hash.clone() + } + pub fn as_user_transaction_context(&self) -> UserTransactionContext { UserTransactionContext::new( self.sender, @@ -170,6 +206,7 @@ impl TransactionMetadata { .map(|entry_func| entry_func.as_entry_function_payload()), self.multisig_payload() .map(|multisig| multisig.as_multisig_payload()), + self.raw_transaction_hash(), ) } } diff --git a/aptos-move/e2e-move-tests/src/tests/transaction_context.data/pack/sources/transaction_context_test.move b/aptos-move/e2e-move-tests/src/tests/transaction_context.data/pack/sources/transaction_context_test.move index 0116d1035c9b0..e5f919560d837 100644 --- a/aptos-move/e2e-move-tests/src/tests/transaction_context.data/pack/sources/transaction_context_test.move +++ b/aptos-move/e2e-move-tests/src/tests/transaction_context.data/pack/sources/transaction_context_test.move @@ -24,6 +24,7 @@ module admin::transaction_context_test { type_arg_names: vector, args: vector>, multisig_address: address, + raw_transaction_hash: vector, } /// Called when the module is first deployed at address `signer`, which is supposed to be @admin (= 0x1). @@ -44,6 +45,7 @@ module admin::transaction_context_test { args: vector[], type_arg_names: vector[], multisig_address: @0x0, + raw_transaction_hash: vector[], } ); } @@ -141,4 +143,14 @@ module admin::transaction_context_test { store.multisig_address = multisig_account; } + + entry fun store_raw_transaction_hash_from_native_txn_context(_s: &signer) acquires TransactionContextStore { + let store = borrow_global_mut(@admin); + store.raw_transaction_hash = transaction_context::raw_transaction_hash(); + } + + entry fun store_raw_transaction_hash_from_native_txn_context_multi(_s: &signer, _s2: &signer) acquires TransactionContextStore { + let store = borrow_global_mut(@admin); + store.raw_transaction_hash = transaction_context::raw_transaction_hash(); + } } diff --git a/aptos-move/e2e-move-tests/src/tests/transaction_context.rs b/aptos-move/e2e-move-tests/src/tests/transaction_context.rs index 300a362fde4e8..1e9e80d4ce93f 100644 --- a/aptos-move/e2e-move-tests/src/tests/transaction_context.rs +++ b/aptos-move/e2e-move-tests/src/tests/transaction_context.rs @@ -2,11 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{assert_success, tests::common, MoveHarness}; +use aptos_crypto::hash::CryptoHash; use aptos_language_e2e_tests::account::{Account, TransactionBuilder}; use aptos_types::{ move_utils::MemberId, on_chain_config::FeatureFlag, - transaction::{EntryFunction, MultisigTransactionPayload, TransactionPayload}, + transaction::{ + EntryFunction, MultisigTransactionPayload, RawTransactionWithData, TransactionPayload, + }, }; use bcs::to_bytes; use move_core_types::{ @@ -31,6 +34,7 @@ struct TransactionContextStore { type_arg_names: Vec, args: Vec>, multisig_address: AccountAddress, + raw_transaction_hash: Vec, } fn setup(harness: &mut MoveHarness) -> Account { @@ -468,3 +472,136 @@ fn test_transaction_context_multisig_payload() { assert!(txn_ctx_store.type_arg_names.is_empty()); assert!(txn_ctx_store.args.is_empty()); } + +#[test] +fn test_transaction_context_raw_transaction_hash_simple() { + let mut harness = new_move_harness(); + let account = setup(&mut harness); + let signed_txn = harness.create_entry_function( + &account, + str::parse( + "0x1::transaction_context_test::store_raw_transaction_hash_from_native_txn_context", + ) + .unwrap(), + vec![], + vec![], + ); + let expected_hash = signed_txn.clone().into_raw_transaction().hash(); + let status = harness.run(signed_txn); + assert!(status.status().unwrap().is_success()); + let txn_ctx_store = harness + .read_resource::( + account.address(), + parse_struct_tag("0x1::transaction_context_test::TransactionContextStore").unwrap(), + ) + .unwrap(); + let hash = txn_ctx_store.raw_transaction_hash; + assert_eq!(hash, expected_hash.to_vec()); +} + +#[test] +fn test_transaction_context_raw_transaction_hash_multiagent() { + let mut harness = new_move_harness(); + + let alice = setup(&mut harness); + let bob = harness.new_account_with_balance_and_sequence_number(1000000, 0); + + let fun: MemberId = str::parse( + "0x1::transaction_context_test::store_raw_transaction_hash_from_native_txn_context_multi", + ) + .unwrap(); + let MemberId { + module_id, + member_id: function_id, + } = fun; + let ty_args = vec![]; + let args = vec![]; + let payload = TransactionPayload::EntryFunction(EntryFunction::new( + module_id, + function_id, + ty_args, + args, + )); + let signed_txn = TransactionBuilder::new(alice.clone()) + .secondary_signers(vec![bob.clone()]) + .payload(payload) + .sequence_number(harness.sequence_number(alice.address())) + .max_gas_amount(1_000_000) + .gas_unit_price(1) + .sign_multi_agent(); + + let raw_txn_with_data = + RawTransactionWithData::new_multi_agent(signed_txn.clone().into_raw_transaction(), vec![ + *bob.address(), + ]); + let expected_hash = raw_txn_with_data.hash(); + + let output = harness.run_raw(signed_txn); + assert_success!(*output.status()); + + let txn_ctx_store = harness + .read_resource::( + alice.address(), + parse_struct_tag("0x1::transaction_context_test::TransactionContextStore").unwrap(), + ) + .unwrap(); + + let hash = txn_ctx_store.raw_transaction_hash; + assert_eq!(hash, expected_hash.to_vec()); +} + +#[test] +fn test_transaction_context_raw_transaction_hash_multiagent_with_fee_payer() { + let mut harness = new_move_harness(); + + let alice = setup(&mut harness); + let bob = harness.new_account_with_balance_and_sequence_number(1000000, 0); + let fee_payer = harness.new_account_with_balance_and_sequence_number(1000000, 0); + + let fun: MemberId = str::parse( + "0x1::transaction_context_test::store_raw_transaction_hash_from_native_txn_context_multi", + ) + .unwrap(); + let MemberId { + module_id, + member_id: function_id, + } = fun; + let ty_args = vec![]; + let args = vec![]; + let payload = TransactionPayload::EntryFunction(EntryFunction::new( + module_id, + function_id, + ty_args, + args, + )); + let signed_txn = TransactionBuilder::new(alice.clone()) + .fee_payer(fee_payer.clone()) + .secondary_signers(vec![bob.clone()]) + .payload(payload) + .sequence_number(harness.sequence_number(alice.address())) + .max_gas_amount(1_000_000) + .gas_unit_price(1) + .sign_fee_payer(); + + // In the case of a fee payer transaction, the hash value is generated using + // AccountAddress::ZERO as the fee payer address. + let raw_txn_with_data = RawTransactionWithData::new_fee_payer( + signed_txn.clone().into_raw_transaction(), + vec![*bob.address()], + AccountAddress::ZERO, + ); + let expected_hash = raw_txn_with_data.hash(); + + let output = harness.run_raw(signed_txn); + assert_success!(*output.status()); + + let txn_ctx_store = harness + .read_resource::( + alice.address(), + parse_struct_tag("0x1::transaction_context_test::TransactionContextStore").unwrap(), + ) + .unwrap(); + + let hash = txn_ctx_store.raw_transaction_hash; + assert_eq!(hash, expected_hash.to_vec()); +} diff --git a/aptos-move/framework/aptos-framework/doc/randomness.md b/aptos-move/framework/aptos-framework/doc/randomness.md index fa210cfb1023a..729012327c81d 100644 --- a/aptos-move/framework/aptos-framework/doc/randomness.md +++ b/aptos-move/framework/aptos-framework/doc/randomness.md @@ -290,7 +290,7 @@ of the hash function). let seed = *option::borrow(&randomness.seed); vector::append(&mut input, seed); - vector::append(&mut input, transaction_context::get_transaction_hash()); + vector::append(&mut input, transaction_context::unique_session_hash()); vector::append(&mut input, fetch_and_increment_txn_counter()); hash::sha3_256(input) } diff --git a/aptos-move/framework/aptos-framework/doc/transaction_context.md b/aptos-move/framework/aptos-framework/doc/transaction_context.md index cc7d9010ffc74..9449934a41c2c 100644 --- a/aptos-move/framework/aptos-framework/doc/transaction_context.md +++ b/aptos-move/framework/aptos-framework/doc/transaction_context.md @@ -9,8 +9,9 @@ - [Struct `EntryFunctionPayload`](#0x1_transaction_context_EntryFunctionPayload) - [Struct `MultisigPayload`](#0x1_transaction_context_MultisigPayload) - [Constants](#@Constants_0) -- [Function `get_txn_hash`](#0x1_transaction_context_get_txn_hash) +- [Function `unique_session_hash`](#0x1_transaction_context_unique_session_hash) - [Function `get_transaction_hash`](#0x1_transaction_context_get_transaction_hash) +- [Function `get_txn_hash`](#0x1_transaction_context_get_txn_hash) - [Function `generate_unique_address`](#0x1_transaction_context_generate_unique_address) - [Function `generate_auid_address`](#0x1_transaction_context_generate_auid_address) - [Function `get_script_hash`](#0x1_transaction_context_get_script_hash) @@ -39,9 +40,12 @@ - [Function `multisig_payload_internal`](#0x1_transaction_context_multisig_payload_internal) - [Function `multisig_address`](#0x1_transaction_context_multisig_address) - [Function `inner_entry_function_payload`](#0x1_transaction_context_inner_entry_function_payload) +- [Function `raw_transaction_hash`](#0x1_transaction_context_raw_transaction_hash) +- [Function `raw_transaction_hash_internal`](#0x1_transaction_context_raw_transaction_hash_internal) - [Specification](#@Specification_1) - - [Function `get_txn_hash`](#@Specification_1_get_txn_hash) + - [Function `unique_session_hash`](#@Specification_1_unique_session_hash) - [Function `get_transaction_hash`](#@Specification_1_get_transaction_hash) + - [Function `get_txn_hash`](#@Specification_1_get_txn_hash) - [Function `generate_unique_address`](#@Specification_1_generate_unique_address) - [Function `generate_auid_address`](#@Specification_1_generate_auid_address) - [Function `get_script_hash`](#@Specification_1_get_script_hash) @@ -56,6 +60,7 @@ - [Function `chain_id_internal`](#@Specification_1_chain_id_internal) - [Function `entry_function_payload_internal`](#@Specification_1_entry_function_payload_internal) - [Function `multisig_payload_internal`](#@Specification_1_multisig_payload_internal) + - [Function `raw_transaction_hash_internal`](#@Specification_1_raw_transaction_hash_internal)
use 0x1::error;
@@ -196,6 +201,16 @@ The transaction context extension feature is not enabled.
 
 
 
+
+
+The transaction context hash function update feature is not enabled.
+
+
+
const ETRANSACTION_CONTEXT_HASH_FUNCTION_UPDATE_NOT_ENABLED: u64 = 3;
+
+ + + Transaction context is only available in the user transaction prologue, execution, or epilogue phases. @@ -206,14 +221,15 @@ Transaction context is only available in the user transaction prologue, executio - + -## Function `get_txn_hash` +## Function `unique_session_hash` -Returns the transaction hash of the current transaction. +Returns a unique session hash, ensuring a distinct value for each transaction +session: prologue, execution, epilogue. -
fun get_txn_hash(): vector<u8>
+
public fun unique_session_hash(): vector<u8>
 
@@ -222,7 +238,9 @@ Returns the transaction hash of the current transaction. Implementation -
native fun get_txn_hash(): vector<u8>;
+
public fun unique_session_hash(): vector<u8> {
+    get_txn_hash()
+}
 
@@ -233,12 +251,10 @@ Returns the transaction hash of the current transaction. ## Function `get_transaction_hash` -Returns the transaction hash of the current transaction. -Internally calls the private function get_txn_hash. -This function is created for to feature gate the get_txn_hash function. -
public fun get_transaction_hash(): vector<u8>
+
#[deprecated]
+public fun get_transaction_hash(): vector<u8>
 
@@ -254,6 +270,28 @@ This function is created for to feature gate the get_txn_hash funct + + + + +## Function `get_txn_hash` + + + +
fun get_txn_hash(): vector<u8>
+
+ + + +
+Implementation + + +
native fun get_txn_hash(): vector<u8>;
+
+ + +
@@ -963,6 +1001,56 @@ Returns the inner entry function payload of the multisig payload. + + + + +## Function `raw_transaction_hash` + +Returns the hash of the current raw transaction, which is used for transaction authentication. +This function calls an internal native function to retrieve the raw transaction hash. +In the case of a fee payer transaction, the hash value is generated using 0x0 as the fee payer address. + + +
public fun raw_transaction_hash(): vector<u8>
+
+ + + +
+Implementation + + +
public fun raw_transaction_hash(): vector<u8> {
+    assert!(features::transaction_context_hash_function_update_enabled(), error::invalid_state(ETRANSACTION_CONTEXT_HASH_FUNCTION_UPDATE_NOT_ENABLED));
+    raw_transaction_hash_internal()
+}
+
+ + + +
+ + + +## Function `raw_transaction_hash_internal` + + + +
fun raw_transaction_hash_internal(): vector<u8>
+
+ + + +
+Implementation + + +
native fun raw_transaction_hash_internal(): vector<u8>;
+
+ + +
@@ -970,49 +1058,61 @@ Returns the inner entry function payload of the multisig payload. ## Specification - + -### Function `get_txn_hash` +### Function `unique_session_hash` -
fun get_txn_hash(): vector<u8>
+
public fun unique_session_hash(): vector<u8>
 
pragma opaque;
-aborts_if [abstract] false;
+aborts_if false;
 ensures result == spec_get_txn_hash();
+ensures len(result) == 32;
 
+ - +### Function `get_transaction_hash` -
fun spec_get_txn_hash(): vector<u8>;
+
#[deprecated]
+public fun get_transaction_hash(): vector<u8>
 
- -### Function `get_transaction_hash` +
pragma opaque;
+aborts_if false;
+ensures result == spec_get_txn_hash();
+ensures len(result) == 32;
+
+ -
public fun get_transaction_hash(): vector<u8>
+
+
+### Function `get_txn_hash`
+
+
+
fun get_txn_hash(): vector<u8>
 
pragma opaque;
-aborts_if [abstract] false;
+aborts_if false;
 ensures result == spec_get_txn_hash();
 // This enforces high-level requirement 1:
-ensures [abstract] len(result) == 32;
+ensures len(result) == 32;
 
@@ -1084,9 +1184,9 @@ Returns the inner entry function payload of the multisig payload. 1 -Fetching the transaction hash should return a vector with 32 bytes. +Fetching the unique session hash should return a vector with 32 bytes. Medium -The get_transaction_hash function calls the native function get_txn_hash, which fetches the NativeTransactionContext struct and returns the txn_hash field. +The unique_session_hash function calls the native function unique_session_hash_internal, which fetches the NativeTransactionContext struct and returns the unique_session_hash field. Audited that the native function returns the txn hash, whose size is 32 bytes. This has been modeled as the abstract postcondition that the returned vector is of length 32. Formally verified via get_txn_hash. @@ -1141,6 +1241,15 @@ Returns the inner entry function payload of the multisig payload. + + + + +
fun spec_get_txn_hash(): vector<u8>;
+
+ + + ### Function `auid_address` @@ -1281,6 +1390,22 @@ Returns the inner entry function payload of the multisig payload. +
pragma opaque;
+
+ + + + + +### Function `raw_transaction_hash_internal` + + +
fun raw_transaction_hash_internal(): vector<u8>
+
+ + + +
pragma opaque;
 
diff --git a/aptos-move/framework/aptos-framework/sources/object.move b/aptos-move/framework/aptos-framework/sources/object.move index c03914fb7675c..f8c3b93e39fc1 100644 --- a/aptos-move/framework/aptos-framework/sources/object.move +++ b/aptos-move/framework/aptos-framework/sources/object.move @@ -842,7 +842,7 @@ module aptos_framework::object { #[test(fx = @std)] fun test_correct_auid() { let auid1 = aptos_framework::transaction_context::generate_auid_address(); - let bytes = aptos_framework::transaction_context::get_transaction_hash(); + let bytes = aptos_framework::transaction_context::unique_session_hash(); std::vector::push_back(&mut bytes, 1); std::vector::push_back(&mut bytes, 0); std::vector::push_back(&mut bytes, 0); diff --git a/aptos-move/framework/aptos-framework/sources/randomness.move b/aptos-move/framework/aptos-framework/sources/randomness.move index cde8328e67795..b79e5ea869291 100644 --- a/aptos-move/framework/aptos-framework/sources/randomness.move +++ b/aptos-move/framework/aptos-framework/sources/randomness.move @@ -81,7 +81,7 @@ module aptos_framework::randomness { let seed = *option::borrow(&randomness.seed); vector::append(&mut input, seed); - vector::append(&mut input, transaction_context::get_transaction_hash()); + vector::append(&mut input, transaction_context::unique_session_hash()); vector::append(&mut input, fetch_and_increment_txn_counter()); hash::sha3_256(input) } diff --git a/aptos-move/framework/aptos-framework/sources/transaction_context.move b/aptos-move/framework/aptos-framework/sources/transaction_context.move index c3bad25371cdc..bb00fa16a82ed 100644 --- a/aptos-move/framework/aptos-framework/sources/transaction_context.move +++ b/aptos-move/framework/aptos-framework/sources/transaction_context.move @@ -10,6 +10,9 @@ module aptos_framework::transaction_context { /// The transaction context extension feature is not enabled. const ETRANSACTION_CONTEXT_EXTENSION_NOT_ENABLED: u64 = 2; + /// The transaction context hash function update feature is not enabled. + const ETRANSACTION_CONTEXT_HASH_FUNCTION_UPDATE_NOT_ENABLED: u64 = 3; + /// A wrapper denoting aptos unique identifer (AUID) /// for storing an address struct AUID has drop, store { @@ -31,15 +34,19 @@ module aptos_framework::transaction_context { entry_function_payload: Option, } - /// Returns the transaction hash of the current transaction. - native fun get_txn_hash(): vector; - - /// Returns the transaction hash of the current transaction. - /// Internally calls the private function `get_txn_hash`. - /// This function is created for to feature gate the `get_txn_hash` function. + /// Returns a unique session hash, ensuring a distinct value for each transaction + /// session: prologue, execution, epilogue. + public fun unique_session_hash(): vector { + get_txn_hash() + } + #[deprecated] + // This function is deprecated. Use `unique_session_hash` instead. public fun get_transaction_hash(): vector { get_txn_hash() } + // This function will be removed when `get_transaction_hash` is deprecated. + native fun get_txn_hash(): vector; + /// Returns a universally unique identifier (of type address) generated /// by hashing the transaction hash of this transaction and a sequence number @@ -182,6 +189,15 @@ module aptos_framework::transaction_context { payload.entry_function_payload } + /// Returns the hash of the current raw transaction, which is used for transaction authentication. + /// This function calls an internal native function to retrieve the raw transaction hash. + /// In the case of a fee payer transaction, the hash value is generated using 0x0 as the fee payer address. + public fun raw_transaction_hash(): vector { + assert!(features::transaction_context_hash_function_update_enabled(), error::invalid_state(ETRANSACTION_CONTEXT_HASH_FUNCTION_UPDATE_NOT_ENABLED)); + raw_transaction_hash_internal() + } + native fun raw_transaction_hash_internal(): vector; + #[test()] fun test_auid_uniquess() { use std::vector; @@ -259,4 +275,11 @@ module aptos_framework::transaction_context { // expected to fail with the error code of `invalid_state(E_TRANSACTION_CONTEXT_NOT_AVAILABLE)` let _multisig = multisig_payload(); } + + #[test] + #[expected_failure(abort_code=196609, location = Self)] + fun test_call_raw_txn_hash() { + // expected to fail with the error code of `invalid_state(E_TRANSACTION_CONTEXT_NOT_AVAILABLE)` + let _multisig = multisig_payload(); + } } diff --git a/aptos-move/framework/aptos-framework/sources/transaction_context.spec.move b/aptos-move/framework/aptos-framework/sources/transaction_context.spec.move index f9837e26e6a75..4e9f967b1bc6a 100644 --- a/aptos-move/framework/aptos-framework/sources/transaction_context.spec.move +++ b/aptos-move/framework/aptos-framework/sources/transaction_context.spec.move @@ -1,10 +1,10 @@ spec aptos_framework::transaction_context { /// /// No.: 1 - /// Requirement: Fetching the transaction hash should return a vector with 32 bytes. + /// Requirement: Fetching the unique session hash should return a vector with 32 bytes. /// Criticality: Medium - /// Implementation: The get_transaction_hash function calls the native function get_txn_hash, which fetches the - /// NativeTransactionContext struct and returns the txn_hash field. + /// Implementation: The unique_session_hash function calls the native function unique_session_hash_internal, which fetches the + /// NativeTransactionContext struct and returns the unique_session_hash field. /// Enforcement: Audited that the native function returns the txn hash, whose size is 32 bytes. This has been /// modeled as the abstract postcondition that the returned vector is of length 32. Formally verified via [high-level-req-1](get_txn_hash). /// @@ -42,19 +42,26 @@ spec aptos_framework::transaction_context { ensures [abstract] len(result) == 32; } spec fun spec_get_script_hash(): vector; + spec fun spec_get_txn_hash(): vector; spec get_txn_hash(): vector { pragma opaque; - aborts_if [abstract] false; + aborts_if false; ensures result == spec_get_txn_hash(); + // property 1: Fetching the transaction hash should return a vector with 32 bytes, if the auid feature flag is enabled. + /// [high-level-req-1] + ensures len(result) == 32; } - spec fun spec_get_txn_hash(): vector; spec get_transaction_hash(): vector { pragma opaque; - aborts_if [abstract] false; + aborts_if false; ensures result == spec_get_txn_hash(); - // property 1: Fetching the transaction hash should return a vector with 32 bytes, if the auid feature flag is enabled. - /// [high-level-req-1] - ensures [abstract] len(result) == 32; + ensures len(result) == 32; + } + spec unique_session_hash(): vector { + pragma opaque; + aborts_if false; + ensures result == spec_get_txn_hash(); + ensures len(result) == 32; } spec generate_unique_address(): address { pragma opaque; @@ -101,9 +108,12 @@ spec aptos_framework::transaction_context { //TODO: temporary mockup pragma opaque; } - spec multisig_payload_internal(): Option { //TODO: temporary mockup pragma opaque; } + spec raw_transaction_hash_internal(): vector { + //TODO: temporary mockup + pragma opaque; + } } diff --git a/aptos-move/framework/move-stdlib/doc/features.md b/aptos-move/framework/move-stdlib/doc/features.md index 3919625f4ff9c..f40f107d245e5 100644 --- a/aptos-move/framework/move-stdlib/doc/features.md +++ b/aptos-move/framework/move-stdlib/doc/features.md @@ -133,6 +133,8 @@ return true. - [Function `transaction_simulation_enhancement_enabled`](#0x1_features_transaction_simulation_enhancement_enabled) - [Function `get_collection_owner_feature`](#0x1_features_get_collection_owner_feature) - [Function `is_collection_owner_enabled`](#0x1_features_is_collection_owner_enabled) +- [Function `get_transaction_context_hash_function_update_feature`](#0x1_features_get_transaction_context_hash_function_update_feature) +- [Function `transaction_context_hash_function_update_enabled`](#0x1_features_transaction_context_hash_function_update_enabled) - [Function `change_feature_flags`](#0x1_features_change_feature_flags) - [Function `change_feature_flags_internal`](#0x1_features_change_feature_flags_internal) - [Function `change_feature_flags_for_next_epoch`](#0x1_features_change_feature_flags_for_next_epoch) @@ -880,6 +882,21 @@ Lifetime: transient + + +Whether the transaction context hash function update is enabled. This update introduces new APIs (public functions +and native functions) to the transaction context module: raw_transaction_hash and unique_session_hash. +raw_transaction_hash retrieves the hash of the raw transaction. Also, unique_session_hash will replace +in a later release the existing get_transaction_hash function which has a misleading name. + +Lifetime: transient + + +
const TRANSACTION_CONTEXT_HASH_FUNCTION_UPDATE: u64 = 80;
+
+ + + Whether the simulation enhancement is enabled. This enables the simulation without an authentication check, @@ -3274,6 +3291,52 @@ Deprecated feature + + + + +## Function `get_transaction_context_hash_function_update_feature` + + + +
public fun get_transaction_context_hash_function_update_feature(): u64
+
+ + + +
+Implementation + + +
public fun get_transaction_context_hash_function_update_feature(): u64 { TRANSACTION_CONTEXT_HASH_FUNCTION_UPDATE }
+
+ + + +
+ + + +## Function `transaction_context_hash_function_update_enabled` + + + +
public fun transaction_context_hash_function_update_enabled(): bool
+
+ + + +
+Implementation + + +
public fun transaction_context_hash_function_update_enabled(): bool acquires Features {
+    is_enabled(TRANSACTION_CONTEXT_HASH_FUNCTION_UPDATE)
+}
+
+ + +
diff --git a/aptos-move/framework/move-stdlib/sources/configs/features.move b/aptos-move/framework/move-stdlib/sources/configs/features.move index 2bdba4056eaae..eeb9ae75d8668 100644 --- a/aptos-move/framework/move-stdlib/sources/configs/features.move +++ b/aptos-move/framework/move-stdlib/sources/configs/features.move @@ -607,6 +607,20 @@ module std::features { is_enabled(COLLECTION_OWNER) } + /// Whether the transaction context hash function update is enabled. This update introduces new APIs (public functions + /// and native functions) to the transaction context module: `raw_transaction_hash` and `unique_session_hash`. + /// `raw_transaction_hash` retrieves the hash of the raw transaction. Also, `unique_session_hash` will replace + /// in a later release the existing `get_transaction_hash` function which has a misleading name. + /// + /// Lifetime: transient + const TRANSACTION_CONTEXT_HASH_FUNCTION_UPDATE: u64 = 80; + + public fun get_transaction_context_hash_function_update_feature(): u64 { TRANSACTION_CONTEXT_HASH_FUNCTION_UPDATE } + + public fun transaction_context_hash_function_update_enabled(): bool acquires Features { + is_enabled(TRANSACTION_CONTEXT_HASH_FUNCTION_UPDATE) + } + // ============================================================================================ // Feature Flag Implementation diff --git a/aptos-move/framework/src/natives/transaction_context.rs b/aptos-move/framework/src/natives/transaction_context.rs index 044e948a0ecb5..b1df7778164e5 100644 --- a/aptos-move/framework/src/natives/transaction_context.rs +++ b/aptos-move/framework/src/natives/transaction_context.rs @@ -31,7 +31,7 @@ pub mod abort_codes { /// is accessible from natives of this extension. #[derive(Tid)] pub struct NativeTransactionContext { - txn_hash: Vec, + unique_session_hash: Vec, /// The number of AUIDs (Aptos unique identifiers) issued during the /// execution of this transaction. auid_counter: u64, @@ -46,13 +46,13 @@ impl NativeTransactionContext { /// Create a new instance of a native transaction context. This must be passed in via an /// extension into VM session functions. pub fn new( - txn_hash: Vec, + unique_session_hash: Vec, script_hash: Vec, chain_id: u8, user_transaction_context_opt: Option, ) -> Self { Self { - txn_hash, + unique_session_hash, auid_counter: 0, script_hash, chain_id, @@ -66,12 +66,12 @@ impl NativeTransactionContext { } /*************************************************************************************************** - * native fun get_txn_hash + * native fun native_unique_session_hash * * gas cost: base_cost * **************************************************************************************************/ -fn native_get_txn_hash( +fn native_unique_session_hash( context: &mut SafeNativeContext, _ty_args: Vec, _args: VecDeque, @@ -80,7 +80,7 @@ fn native_get_txn_hash( let transaction_context = context.extensions().get::(); Ok(smallvec![Value::vector_u8( - transaction_context.txn_hash.clone() + transaction_context.unique_session_hash.clone() )]) } @@ -103,7 +103,7 @@ fn native_generate_unique_address( transaction_context.auid_counter += 1; let auid = AuthenticationKey::auid( - transaction_context.txn_hash.clone(), + transaction_context.unique_session_hash.clone(), transaction_context.auid_counter, ) .account_address(); @@ -368,6 +368,25 @@ fn native_multisig_payload_internal( } } +fn native_raw_transaction_hash_internal( + context: &mut SafeNativeContext, + _ty_args: Vec, + _args: VecDeque, +) -> SafeNativeResult> { + context.charge(TRANSACTION_CONTEXT_SENDER_BASE)?; + + let user_transaction_context_opt = get_user_transaction_context_opt_from_context(context); + if let Some(transaction_context) = user_transaction_context_opt { + Ok(smallvec![Value::vector_u8( + transaction_context.raw_txn_hash() + )]) + } else { + Err(SafeNativeError::Abort { + abort_code: error::invalid_state(abort_codes::ETRANSACTION_CONTEXT_NOT_AVAILABLE), + }) + } +} + fn get_user_transaction_context_opt_from_context<'a>( context: &'a SafeNativeContext, ) -> &'a Option { @@ -387,24 +406,41 @@ pub fn make_all( let natives = [ ("get_script_hash", native_get_script_hash as RawSafeNative), ("generate_unique_address", native_generate_unique_address), - ("get_txn_hash", native_get_txn_hash), + ("get_txn_hash", native_unique_session_hash), + ("unique_session_hash", native_unique_session_hash), + ("sender", native_sender_internal), ("sender_internal", native_sender_internal), + ("secondary_signers", native_secondary_signers_internal), ( "secondary_signers_internal", native_secondary_signers_internal, ), + ("gas_payer", native_gas_payer_internal), ("gas_payer_internal", native_gas_payer_internal), + ("max_gas_amount", native_max_gas_amount_internal), ("max_gas_amount_internal", native_max_gas_amount_internal), + ("gas_unit_price", native_gas_unit_price_internal), ("gas_unit_price_internal", native_gas_unit_price_internal), + ("chain_id", native_chain_id_internal), ("chain_id_internal", native_chain_id_internal), + ( + "entry_function_payload", + native_entry_function_payload_internal, + ), ( "entry_function_payload_internal", native_entry_function_payload_internal, ), + ("multisig_payload", native_multisig_payload_internal), ( "multisig_payload_internal", native_multisig_payload_internal, ), + ("raw_transaction_hash", native_raw_transaction_hash_internal), + ( + "raw_transaction_hash_internal", + native_raw_transaction_hash_internal, + ), ]; builder.make_named_natives(natives) diff --git a/types/src/on_chain_config/aptos_features.rs b/types/src/on_chain_config/aptos_features.rs index 24e964643e6ca..44584ba52ec03 100644 --- a/types/src/on_chain_config/aptos_features.rs +++ b/types/src/on_chain_config/aptos_features.rs @@ -95,6 +95,7 @@ pub enum FeatureFlag { FEDERATED_KEYLESS = 77, TRANSACTION_SIMULATION_ENHANCEMENT = 78, COLLECTION_OWNER = 79, + TRANSACTION_CONTEXT_HASH_FUNCTION_UPDATE = 80, } impl FeatureFlag { @@ -172,6 +173,7 @@ impl FeatureFlag { FeatureFlag::ENABLE_RESOURCE_ACCESS_CONTROL, FeatureFlag::REJECT_UNSTABLE_BYTECODE_FOR_SCRIPT, FeatureFlag::TRANSACTION_SIMULATION_ENHANCEMENT, + FeatureFlag::TRANSACTION_CONTEXT_HASH_FUNCTION_UPDATE, ] } } diff --git a/types/src/transaction/user_transaction_context.rs b/types/src/transaction/user_transaction_context.rs index 4c9d3f71d3d12..c43411e74d876 100644 --- a/types/src/transaction/user_transaction_context.rs +++ b/types/src/transaction/user_transaction_context.rs @@ -13,6 +13,7 @@ pub struct UserTransactionContext { chain_id: u8, entry_function_payload: Option, multisig_payload: Option, + raw_transaction_hash: Vec, } impl UserTransactionContext { @@ -25,6 +26,7 @@ impl UserTransactionContext { chain_id: u8, entry_function_payload: Option, multisig_payload: Option, + raw_transaction_hash: Vec, ) -> Self { Self { sender, @@ -35,6 +37,7 @@ impl UserTransactionContext { chain_id, entry_function_payload, multisig_payload, + raw_transaction_hash, } } @@ -69,6 +72,10 @@ impl UserTransactionContext { pub fn multisig_payload(&self) -> Option { self.multisig_payload.clone() } + + pub fn raw_txn_hash(&self) -> Vec { + self.raw_transaction_hash.clone() + } } #[derive(Debug, Clone)]