diff --git a/prdoc/pr_6088.prdoc b/prdoc/pr_6088.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..93e435bbd458aa0caad21d5c3ac7a08412350dbe
--- /dev/null
+++ b/prdoc/pr_6088.prdoc
@@ -0,0 +1,14 @@
+title: "[pallet-revive] EXTCODEHASH to match EIP-1052"
+
+doc:
+  - audience: Runtime Dev
+    description: |
+      Update `ext_code_hash` to match [EIP-1052](https://eips.ethereum.org/EIPS/eip-1052) specs.
+
+crates:
+  - name: pallet-revive
+    bump: major
+  - name: pallet-revive-fixtures
+    bump: patch
+  - name: pallet-revive-uapi
+    bump: major
diff --git a/substrate/frame/revive/fixtures/contracts/code_hash.rs b/substrate/frame/revive/fixtures/contracts/code_hash.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b598a485a8c72a6805c844b2563001dccee964ed
--- /dev/null
+++ b/substrate/frame/revive/fixtures/contracts/code_hash.rs
@@ -0,0 +1,40 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#![no_std]
+#![no_main]
+
+use common::input;
+use uapi::{HostFn, HostFnImpl as api};
+
+#[no_mangle]
+#[polkavm_derive::polkavm_export]
+pub extern "C" fn deploy() {}
+
+#[no_mangle]
+#[polkavm_derive::polkavm_export]
+pub extern "C" fn call() {
+    input!(
+        address: &[u8; 20],
+        expected_code_hash: &[u8; 32],
+    );
+
+    let mut code_hash = [0u8; 32];
+    api::code_hash(address, &mut code_hash);
+
+    assert!(&code_hash == expected_code_hash);
+}
diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs
index fffc3e4f48376efed32b97a6546f0cf3bfab2c1c..07dbd096339bf526590edca30c855b8b477f8153 100644
--- a/substrate/frame/revive/src/exec.rs
+++ b/substrate/frame/revive/src/exec.rs
@@ -66,6 +66,10 @@ type VarSizedKey = BoundedVec<u8, ConstU32<{ limits::STORAGE_KEY_BYTES }>>;
 
 const FRAME_ALWAYS_EXISTS_ON_INSTANTIATE: &str = "The return value is only `None` if no contract exists at the specified address. This cannot happen on instantiate or delegate; qed";
 
+/// Code hash of existing account without code (keccak256 hash of empty data).
+pub const EMPTY_CODE_HASH: H256 =
+	H256(sp_core::hex2array!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"));
+
 /// Combined key type for both fixed and variable sized storage keys.
 pub enum Key {
 	/// Variant for fixed sized keys.
@@ -272,9 +276,8 @@ pub trait Ext: sealing::Sealed {
 	fn is_contract(&self, address: &H160) -> bool;
 
 	/// Returns the code hash of the contract for the given `address`.
-	///
-	/// Returns `None` if the `address` does not belong to a contract.
-	fn code_hash(&self, address: &H160) -> Option<H256>;
+	/// If not a contract but account exists then `keccak_256([])` is returned, otherwise `zero`.
+	fn code_hash(&self, address: &H160) -> H256;
 
 	/// Returns the code hash of the contract being executed.
 	fn own_code_hash(&mut self) -> &H256;
@@ -1536,8 +1539,15 @@ where
 		ContractInfoOf::<T>::contains_key(&address)
 	}
 
-	fn code_hash(&self, address: &H160) -> Option<H256> {
-		<ContractInfoOf<T>>::get(&address).map(|contract| contract.code_hash)
+	fn code_hash(&self, address: &H160) -> H256 {
+		<ContractInfoOf<T>>::get(&address)
+			.map(|contract| contract.code_hash)
+			.unwrap_or_else(|| {
+				if System::<T>::account_exists(&T::AddressMapper::to_account_id(address)) {
+					return EMPTY_CODE_HASH;
+				}
+				H256::zero()
+			})
 	}
 
 	fn own_code_hash(&mut self) -> &H256 {
@@ -1817,9 +1827,10 @@ mod tests {
 	};
 	use assert_matches::assert_matches;
 	use frame_support::{assert_err, assert_ok, parameter_types};
-	use frame_system::{EventRecord, Phase};
+	use frame_system::{AccountInfo, EventRecord, Phase};
 	use pallet_revive_uapi::ReturnFlags;
 	use pretty_assertions::assert_eq;
+	use sp_io::hashing::keccak_256;
 	use sp_runtime::{traits::Hash, DispatchError};
 	use std::{cell::RefCell, collections::hash_map::HashMap, rc::Rc};
 
@@ -1870,8 +1881,8 @@ mod tests {
 			f: impl Fn(MockCtx, &MockExecutable) -> ExecResult + 'static,
 		) -> H256 {
 			Loader::mutate(|loader| {
-				// Generate code hashes as monotonically increasing values.
-				let hash = <Test as frame_system::Config>::Hash::from_low_u64_be(loader.counter);
+				// Generate code hashes from contract index value.
+				let hash = H256(keccak_256(&loader.counter.to_le_bytes()));
 				loader.counter += 1;
 				loader.map.insert(
 					hash,
@@ -2386,16 +2397,25 @@ mod tests {
 
 	#[test]
 	fn code_hash_returns_proper_values() {
-		let code_bob = MockLoader::insert(Call, |ctx, _| {
-			// ALICE is not a contract and hence they do not have a code_hash
-			assert!(ctx.ext.code_hash(&ALICE_ADDR).is_none());
-			// BOB is a contract and hence it has a code_hash
-			assert!(ctx.ext.code_hash(&BOB_ADDR).is_some());
+		let bob_code_hash = MockLoader::insert(Call, |ctx, _| {
+			// ALICE is not a contract but account exists so it returns hash of empty data
+			assert_eq!(ctx.ext.code_hash(&ALICE_ADDR), EMPTY_CODE_HASH);
+			// BOB is a contract (this function) and hence it has a code_hash.
+			// `MockLoader` uses contract index to generate the code hash.
+			assert_eq!(ctx.ext.code_hash(&BOB_ADDR), H256(keccak_256(&0u64.to_le_bytes())));
+			// [0xff;20] doesn't exist and returns hash zero
+			assert!(ctx.ext.code_hash(&H160([0xff; 20])).is_zero());
+
 			exec_success()
 		});
 
 		ExtBuilder::default().build().execute_with(|| {
-			place_contract(&BOB, code_bob);
+			// add alice account info to test case EOA code hash
+			frame_system::Account::<Test>::insert(
+				<Test as Config>::AddressMapper::to_account_id(&ALICE_ADDR),
+				AccountInfo { consumers: 1, providers: 1, ..Default::default() },
+			);
+			place_contract(&BOB, bob_code_hash);
 			let origin = Origin::from_account_id(ALICE);
 			let mut storage_meter = storage::meter::Meter::new(&origin, 0, 0).unwrap();
 			// ALICE (not contract) -> BOB (contract)
@@ -2415,7 +2435,7 @@ mod tests {
 	#[test]
 	fn own_code_hash_returns_proper_values() {
 		let bob_ch = MockLoader::insert(Call, |ctx, _| {
-			let code_hash = ctx.ext.code_hash(&BOB_ADDR).unwrap();
+			let code_hash = ctx.ext.code_hash(&BOB_ADDR);
 			assert_eq!(*ctx.ext.own_code_hash(), code_hash);
 			exec_success()
 		});
diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs
index 4816e65f8f5c3951c91775fbd1726e764ea51ec1..e637c5f991c6187a36c82898edf0c392bc4e2585 100644
--- a/substrate/frame/revive/src/tests.rs
+++ b/substrate/frame/revive/src/tests.rs
@@ -4442,4 +4442,46 @@ mod run_tests {
 			);
 		});
 	}
+
+	#[test]
+	fn code_hash_works() {
+		let (code_hash_code, self_code_hash) = compile_module("code_hash").unwrap();
+		let (dummy_code, code_hash) = compile_module("dummy").unwrap();
+
+		ExtBuilder::default().existential_deposit(1).build().execute_with(|| {
+			let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000);
+
+			let Contract { addr, .. } =
+				builder::bare_instantiate(Code::Upload(code_hash_code)).build_and_unwrap_contract();
+			let Contract { addr: dummy_addr, .. } =
+				builder::bare_instantiate(Code::Upload(dummy_code)).build_and_unwrap_contract();
+
+			// code hash of dummy contract
+			assert_ok!(builder::call(addr).data((dummy_addr, code_hash).encode()).build());
+			// code has of itself
+			assert_ok!(builder::call(addr).data((addr, self_code_hash).encode()).build());
+
+			// EOA doesn't exists
+			assert_err!(
+				builder::bare_call(addr)
+					.data((BOB_ADDR, crate::exec::EMPTY_CODE_HASH).encode())
+					.build()
+					.result,
+				Error::<Test>::ContractTrapped
+			);
+			// non-existing will return zero
+			assert_ok!(builder::call(addr).data((BOB_ADDR, H256::zero()).encode()).build());
+
+			// create EOA
+			let _ = <Test as Config>::Currency::set_balance(
+				&<Test as Config>::AddressMapper::to_account_id(&BOB_ADDR),
+				1_000_000,
+			);
+
+			// EOA returns empty code hash
+			assert_ok!(builder::call(addr)
+				.data((BOB_ADDR, crate::exec::EMPTY_CODE_HASH).encode())
+				.build());
+		});
+	}
 }
diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs
index 245c91278a7f80343f592a8f7dec152ae12094db..36cd03e9dd698d39b8081452278bd4d5c1ab86dd 100644
--- a/substrate/frame/revive/src/wasm/runtime.rs
+++ b/substrate/frame/revive/src/wasm/runtime.rs
@@ -1406,27 +1406,17 @@ pub mod env {
 	/// Retrieve the code hash for a specified contract address.
 	/// See [`pallet_revive_uapi::HostFn::code_hash`].
 	#[api_version(0)]
-	fn code_hash(
-		&mut self,
-		memory: &mut M,
-		addr_ptr: u32,
-		out_ptr: u32,
-	) -> Result<ReturnErrorCode, TrapReason> {
+	fn code_hash(&mut self, memory: &mut M, addr_ptr: u32, out_ptr: u32) -> Result<(), TrapReason> {
 		self.charge_gas(RuntimeCosts::CodeHash)?;
 		let mut address = H160::zero();
 		memory.read_into_buf(addr_ptr, address.as_bytes_mut())?;
-		if let Some(value) = self.ext.code_hash(&address) {
-			self.write_fixed_sandbox_output(
-				memory,
-				out_ptr,
-				&value.as_bytes(),
-				false,
-				already_charged,
-			)?;
-			Ok(ReturnErrorCode::Success)
-		} else {
-			Ok(ReturnErrorCode::KeyNotFound)
-		}
+		Ok(self.write_fixed_sandbox_output(
+			memory,
+			out_ptr,
+			&self.ext.code_hash(&address).as_bytes(),
+			false,
+			already_charged,
+		)?)
 	}
 
 	/// Retrieve the code hash of the currently executing contract.
diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs
index 2106b8fb49b7929975cbebba112038ced6c0bfbe..2663d7c2cf0cc2876ac623d98f9391973cfe2cf3 100644
--- a/substrate/frame/revive/uapi/src/host.rs
+++ b/substrate/frame/revive/uapi/src/host.rs
@@ -245,10 +245,12 @@ pub trait HostFn: private::Sealed {
 	/// - `addr`: The address of the contract.
 	/// - `output`: A reference to the output data buffer to write the code hash.
 	///
-	/// # Errors
+	/// # Note
 	///
-	/// - [CodeNotFound][`crate::ReturnErrorCode::CodeNotFound]
-	fn code_hash(addr: &[u8; 20], output: &mut [u8; 32]) -> Result;
+	/// If `addr` is not a contract but the account exists then the hash of empty data
+	/// `0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470` is written,
+	/// otherwise `zero`.
+	fn code_hash(addr: &[u8; 20], output: &mut [u8; 32]);
 
 	/// Checks whether there is a value stored under the given key.
 	///
diff --git a/substrate/frame/revive/uapi/src/host/riscv32.rs b/substrate/frame/revive/uapi/src/host/riscv32.rs
index 866b0ee8dd1762ad8fafcf820ecce0410a3cb038..c2508198c935855d79fd111db5ac320f0b43b175 100644
--- a/substrate/frame/revive/uapi/src/host/riscv32.rs
+++ b/substrate/frame/revive/uapi/src/host/riscv32.rs
@@ -74,7 +74,7 @@ mod sys {
 		pub fn seal_return(flags: u32, data_ptr: *const u8, data_len: u32);
 		pub fn caller(out_ptr: *mut u8);
 		pub fn is_contract(account_ptr: *const u8) -> ReturnCode;
-		pub fn code_hash(address_ptr: *const u8, out_ptr: *mut u8) -> ReturnCode;
+		pub fn code_hash(address_ptr: *const u8, out_ptr: *mut u8);
 		pub fn own_code_hash(out_ptr: *mut u8);
 		pub fn caller_is_origin() -> ReturnCode;
 		pub fn caller_is_root() -> ReturnCode;
@@ -528,9 +528,8 @@ impl HostFn for HostFnImpl {
 		ret_val.into()
 	}
 
-	fn code_hash(address: &[u8; 20], output: &mut [u8; 32]) -> Result {
-		let ret_val = unsafe { sys::code_hash(address.as_ptr(), output.as_mut_ptr()) };
-		ret_val.into()
+	fn code_hash(address: &[u8; 20], output: &mut [u8; 32]) {
+		unsafe { sys::code_hash(address.as_ptr(), output.as_mut_ptr()) }
 	}
 
 	fn own_code_hash(output: &mut [u8; 32]) {