diff --git a/crates/sp-domains-fraud-proof/src/verification.rs b/crates/sp-domains-fraud-proof/src/verification.rs index f3beb14bcb..b9458d75c0 100644 --- a/crates/sp-domains-fraud-proof/src/verification.rs +++ b/crates/sp-domains-fraud-proof/src/verification.rs @@ -178,7 +178,7 @@ where let state_root = bad_receipt.final_state_root; let digest_storage_key = StorageKey(sp_domains::fraud_proof::system_digest_final_key()); - let digest = StorageProofVerifier::::verify_and_get_value::( + let digest = StorageProofVerifier::::get_decoded_value::( &state_root, digest_storage_proof, digest_storage_key, diff --git a/crates/sp-domains/src/fraud_proof.rs b/crates/sp-domains/src/fraud_proof.rs index 9b1e6f3519..39d222494d 100644 --- a/crates/sp-domains/src/fraud_proof.rs +++ b/crates/sp-domains/src/fraud_proof.rs @@ -1,4 +1,5 @@ -use crate::{valued_trie_root, DomainId, ExecutionReceipt, ReceiptHash, SealedBundleHeader}; +use crate::verification::StorageProofVerifier; +use crate::{DomainId, ExecutionReceipt, ReceiptHash, SealedBundleHeader}; use hash_db::Hasher; use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; @@ -10,7 +11,7 @@ use sp_runtime::traits::{ }; use sp_runtime::{Digest, DigestItem}; use sp_std::vec::Vec; -use sp_trie::{LayoutV1, StorageProof}; +use sp_trie::StorageProof; use subspace_runtime_primitives::{AccountId, Balance}; use trie_db::TrieLayout; @@ -30,7 +31,7 @@ pub enum ExecutionPhase { InitializeBlock, /// Executes some extrinsic. ApplyExtrinsic { - proof_of_inclusion: Vec>, + proof_of_inclusion: StorageProof, mismatch_index: u32, extrinsic: Vec, }, @@ -155,11 +156,15 @@ impl ExecutionPhase { mismatch_index, extrinsic, } => { - if !valued_trie_root::verify_proof::>( - proof_of_inclusion, + let storage_key = + StorageProofVerifier::::enumerated_storage_key( + *mismatch_index, + ); + if !StorageProofVerifier::::verify_storage_proof( + proof_of_inclusion.clone(), &bad_receipt.domain_block_extrinsic_root.into(), extrinsic.clone(), - *mismatch_index, + storage_key, ) { return Err(VerificationError::InvalidApplyExtrinsicCallData); } diff --git a/crates/sp-domains/src/valued_trie_root.rs b/crates/sp-domains/src/valued_trie_root.rs index f427074763..770ded37c0 100644 --- a/crates/sp-domains/src/valued_trie_root.rs +++ b/crates/sp-domains/src/valued_trie_root.rs @@ -1,7 +1,12 @@ use hash_db::Hasher; -use parity_scale_codec::{Compact, Encode}; +use parity_scale_codec::{Codec, Compact, Encode}; +#[cfg(feature = "std")] +use sp_state_machine::prove_read; +use sp_state_machine::TrieBackendBuilder; use sp_std::cmp::max; +use sp_std::marker::PhantomData; use sp_std::vec::Vec; +use sp_trie::StorageProof; use trie_db::node::Value; use trie_db::{ nibble_ops, ChildReference, DBValue, NibbleSlice, NodeCodec, ProcessEncodedNode, @@ -251,59 +256,58 @@ type MemoryDB = memory_db::MemoryDB< DBValue, >; -/// Gnenerate a proof-of-inclusion of the value at the given `input[index]` -/// -/// Return `None` if the given `index` out of range or fail to generate the proof -pub fn generate_proof(input: &[Vec], index: u32) -> Option>> { - if input.len() <= index as usize { - return None; - } - - let input: Vec<_> = input - .iter() - .enumerate() - .map(|(i, v)| (Compact(i as u32).encode(), v)) - .collect(); +/// Type that provides utilities to generate the storage proof. +pub struct StorageProofProvider(PhantomData); - let (db, root) = { - let mut db = >::default(); - let mut root = Default::default(); - { - let mut trie = >::new(&mut db, &mut root).build(); - for (key, value) in input.iter() { - trie.insert(key, value).ok()?; - } +impl StorageProofProvider +where + Layout: TrieLayout, + ::Out: Codec, +{ + /// Generate storage proof for given index from the trie constructed from `input`. + /// + /// Returns `None` if the given `index` out of range or fail to generate the proof. + #[cfg(feature = "std")] + pub fn generate_enumerated_proof_of_inclusion( + input: &[Vec], + index: u32, + ) -> Option { + if input.len() <= index as usize { + return None; } - (db, root) - }; - let key = Compact(index).encode(); - let proof = trie_db::proof::generate_proof::<_, Layout, _, _>(&db, &root, &[key]).ok()?; + let input: Vec<_> = input + .iter() + .enumerate() + .map(|(i, v)| (Compact(i as u32).encode(), v)) + .collect(); - Some(proof) -} + let (db, root) = { + let mut db = >::default(); + let mut root = Default::default(); + { + let mut trie = >::new(&mut db, &mut root).build(); + for (key, value) in input { + trie.insert(&key, value).ok()?; + } + } + (db, root) + }; -// TODO: try using `StorageProofVerifier` to verify the proof-of-inclusion -/// Verify the proof-of-inclusion of the `data` at the `index` of the merkle root `root` -pub fn verify_proof( - proof: &[Vec], - root: &<::Hash as Hasher>::Out, - data: Vec, - index: u32, -) -> bool -where - Layout: TrieLayout, -{ - let key = Compact(index).encode(); - trie_db::proof::verify_proof::(root, proof, sp_std::vec![&(key, Some(data))]) - .is_ok() + let backend = TrieBackendBuilder::new(db, root).build(); + let key = Compact(index).encode(); + prove_read(backend, &[key]).ok() + } } #[cfg(test)] mod test { - use crate::valued_trie_root::{generate_proof, valued_ordered_trie_root, verify_proof}; + use crate::valued_trie_root::{valued_ordered_trie_root, StorageProofProvider}; + use crate::verification::StorageProofVerifier; + use parity_scale_codec::{Compact, Encode}; use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; + use sp_core::storage::StorageKey; use sp_core::H256; use sp_runtime::traits::{BlakeTwo256, Hash}; use sp_trie::LayoutV1; @@ -345,34 +349,52 @@ mod test { assert_eq!(root, got_root); for (i, ext) in exts.clone().into_iter().enumerate() { - // Gnenerate a proof-of-inclusion and verify it with the above `root` - let proof = generate_proof::>(&exts, i as u32).unwrap(); - assert!(verify_proof::>( - &proof, + // Generate a proof-of-inclusion and verify it with the above `root` + let storage_key = StorageKey(Compact(i as u32).encode()); + let storage_proof = + StorageProofProvider::>::generate_enumerated_proof_of_inclusion( + &exts, + i as u32, + ) + .unwrap(); + + assert!(StorageProofVerifier::::verify_storage_proof( + storage_proof.clone(), &root, ext.clone(), - i as u32, + storage_key.clone(), )); // Verifying the proof with a wrong root/ext/index will fail - assert!(!verify_proof::>( - &proof, + assert!(!StorageProofVerifier::::verify_storage_proof( + storage_proof.clone(), &H256::random(), ext.clone(), - i as u32, + storage_key.clone(), )); - assert!(!verify_proof::>( - &proof, + + assert!(!StorageProofVerifier::::verify_storage_proof( + storage_proof.clone(), &root, vec![i as u8; ext.len()], - i as u32, + storage_key, )); - assert!(!verify_proof::>( - &proof, + + let storage_key = StorageKey(Compact(i as u32 + 1).encode()); + assert!(!StorageProofVerifier::::verify_storage_proof( + storage_proof, &root, ext, - i as u32 + 1, + storage_key, )); } + + // fails to generate storage key for unknown index + assert!( + StorageProofProvider::>::generate_enumerated_proof_of_inclusion( + &exts, 100, + ) + .is_none() + ); } } diff --git a/crates/sp-domains/src/verification.rs b/crates/sp-domains/src/verification.rs index 24366a8e72..4959575171 100644 --- a/crates/sp-domains/src/verification.rs +++ b/crates/sp-domains/src/verification.rs @@ -2,7 +2,7 @@ use crate::{ExecutionReceipt, DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT}; use domain_runtime_primitives::opaque::AccountId; use frame_support::PalletError; use hash_db::Hasher; -use parity_scale_codec::{Decode, Encode}; +use parity_scale_codec::{Compact, Decode, Encode}; use rand::seq::SliceRandom; use rand::SeedableRng; use rand_chacha::ChaCha8Rng; @@ -31,22 +31,56 @@ pub enum VerificationError { InvalidBundleDigest, } +/// Type that provides utilities to verify the storage proof. pub struct StorageProofVerifier(PhantomData); impl StorageProofVerifier { - pub fn verify_and_get_value( + /// Extracts the value against a given key and returns a decoded value. + pub fn get_decoded_value( state_root: &H::Out, proof: StorageProof, key: StorageKey, ) -> Result { + let val = Self::get_bare_value(state_root, proof, key)?; + let decoded = V::decode(&mut &val[..]).map_err(|_| VerificationError::FailedToDecode)?; + + Ok(decoded) + } + + /// Returns the value against a given key. + pub fn get_bare_value( + state_root: &H::Out, + proof: StorageProof, + key: StorageKey, + ) -> Result, VerificationError> { let db = proof.into_memory_db::(); let val = read_trie_value::, _>(&db, state_root, key.as_ref(), None, None) .map_err(|_| VerificationError::InvalidProof)? .ok_or(VerificationError::MissingValue)?; - let decoded = V::decode(&mut &val[..]).map_err(|_| VerificationError::FailedToDecode)?; + Ok(val) + } - Ok(decoded) + /// Verifies the given storage proof and checks the expected_value matches the extracted value from the proof. + pub fn verify_storage_proof( + proof: StorageProof, + root: &H::Out, + expected_value: Vec, + storage_key: StorageKey, + ) -> bool + where + H: Hasher, + { + if let Ok(got_data) = StorageProofVerifier::::get_bare_value(root, proof, storage_key) { + expected_value == got_data + } else { + false + } + } + + /// Constructs the storage key from a given enumerated index. + pub fn enumerated_storage_key(index: u32) -> StorageKey { + StorageKey(Compact(index).encode()) } } @@ -78,7 +112,7 @@ where let storage_key = StorageKey(crate::fraud_proof::operator_block_rewards_final_key()); let storage_proof = storage_proof.clone(); - let total_rewards = StorageProofVerifier::::verify_and_get_value::( + let total_rewards = StorageProofVerifier::::get_decoded_value::( &state_root, storage_proof, storage_key, diff --git a/crates/subspace-fraud-proof/src/tests.rs b/crates/subspace-fraud-proof/src/tests.rs index f89bac070e..fa78e11465 100644 --- a/crates/subspace-fraud-proof/src/tests.rs +++ b/crates/subspace-fraud-proof/src/tests.rs @@ -19,6 +19,7 @@ use sp_domain_digests::AsPredigest; use sp_domains::fraud_proof::{ ExecutionPhase, FraudProof, InvalidStateTransitionProof, VerificationError, }; +use sp_domains::valued_trie_root::StorageProofProvider; use sp_domains::{DomainId, DomainsApi}; use sp_runtime::generic::{Digest, DigestItem}; use sp_runtime::traits::{BlakeTwo256, Header as HeaderT}; @@ -492,12 +493,11 @@ async fn execution_proof_creation_and_verification_should_work() { let post_delta_root = storage_changes.transaction_storage_root; let execution_phase = { - let proof_of_inclusion = sp_domains::valued_trie_root::generate_proof::< + let proof_of_inclusion = StorageProofProvider::< LayoutV1, - >( + >::generate_enumerated_proof_of_inclusion( encoded_test_txs.as_slice(), target_extrinsic_index as u32 - ) - .unwrap(); + ).unwrap(); ExecutionPhase::ApplyExtrinsic { proof_of_inclusion, mismatch_index: target_extrinsic_index as u32, @@ -702,12 +702,11 @@ async fn invalid_execution_proof_should_not_work() { let post_delta_root = storage_changes.transaction_storage_root; let execution_phase = { - let proof_of_inclusion = sp_domains::valued_trie_root::generate_proof::< + let proof_of_inclusion = StorageProofProvider::< LayoutV1, - >( + >::generate_enumerated_proof_of_inclusion( encoded_test_txs.as_slice(), extrinsic_index as u32 - ) - .unwrap(); + ).unwrap(); ExecutionPhase::ApplyExtrinsic { proof_of_inclusion, mismatch_index: extrinsic_index as u32, diff --git a/domains/client/domain-operator/src/fraud_proof.rs b/domains/client/domain-operator/src/fraud_proof.rs index acb8e1b438..08cdbc9468 100644 --- a/domains/client/domain-operator/src/fraud_proof.rs +++ b/domains/client/domain-operator/src/fraud_proof.rs @@ -16,6 +16,7 @@ use sp_domains::fraud_proof::{ InvalidTotalRewardsProof, MissingInvalidBundleEntryFraudProof, ValidAsInvalidBundleEntryFraudProof, ValidBundleDigest, }; +use sp_domains::valued_trie_root::StorageProofProvider; use sp_domains::{DomainId, DomainsApi}; use sp_runtime::traits::{BlakeTwo256, Block as BlockT, HashingFor, Header as HeaderT, NumberFor}; use sp_runtime::{Digest, DigestItem}; @@ -385,10 +386,11 @@ where )?; let execution_phase = { - let proof_of_inclusion = sp_domains::valued_trie_root::generate_proof::< - LayoutV1, - >( - encoded_extrinsics.as_slice(), extrinsic_index as u32 + let proof_of_inclusion = StorageProofProvider::< + LayoutV1<::Hashing>, + >::generate_enumerated_proof_of_inclusion( + encoded_extrinsics.as_slice(), + extrinsic_index as u32, ) .ok_or(FraudProofError::FailToGenerateProofOfInclusion)?; ExecutionPhase::ApplyExtrinsic { diff --git a/domains/pallets/messenger/src/lib.rs b/domains/pallets/messenger/src/lib.rs index 2fbfa41864..e13e12cc60 100644 --- a/domains/pallets/messenger/src/lib.rs +++ b/domains/pallets/messenger/src/lib.rs @@ -865,17 +865,20 @@ mod pallet { .unwrap_or(extracted_state_roots.consensus_chain_state_root); // verify and decode the message - let msg = StorageProofVerifier::::verify_and_get_value::< - Message>, - >(&state_root, xdm.proof.message_proof.clone(), storage_key) - .map_err(|err| { - log::error!( - target: "runtime::messenger", - "Failed to verify storage proof: {:?}", - err - ); - TransactionValidityError::Invalid(InvalidTransaction::BadProof) - })?; + let msg = + StorageProofVerifier::::get_decoded_value::>>( + &state_root, + xdm.proof.message_proof.clone(), + storage_key, + ) + .map_err(|err| { + log::error!( + target: "runtime::messenger", + "Failed to verify storage proof: {:?}", + err + ); + TransactionValidityError::Invalid(InvalidTransaction::BadProof) + })?; Ok(msg) } diff --git a/domains/pallets/messenger/src/tests.rs b/domains/pallets/messenger/src/tests.rs index 4dbb6d3ed9..8da4a76f63 100644 --- a/domains/pallets/messenger/src/tests.rs +++ b/domains/pallets/messenger/src/tests.rs @@ -191,7 +191,7 @@ fn test_storage_proof_verification_invalid() { message_proof: storage_proof, }; let res: Result, VerificationError> = - StorageProofVerifier::::verify_and_get_value( + StorageProofVerifier::::get_decoded_value( &proof.consensus_chain_state_root, proof.message_proof, StorageKey(vec![]), @@ -218,7 +218,7 @@ fn test_storage_proof_verification_missing_value() { message_proof: storage_proof, }; let res: Result, VerificationError> = - StorageProofVerifier::::verify_and_get_value( + StorageProofVerifier::::get_decoded_value( &proof.consensus_chain_state_root, proof.message_proof, storage_key, @@ -247,7 +247,7 @@ fn test_storage_proof_verification() { message_proof: storage_proof, }; let res: Result, VerificationError> = - StorageProofVerifier::::verify_and_get_value( + StorageProofVerifier::::get_decoded_value( &proof.consensus_chain_state_root, proof.message_proof, storage_key, diff --git a/domains/primitives/messenger/src/messages.rs b/domains/primitives/messenger/src/messages.rs index 265bdd92ac..3a968c69b9 100644 --- a/domains/primitives/messenger/src/messages.rs +++ b/domains/primitives/messenger/src/messages.rs @@ -295,7 +295,7 @@ impl CrossDomainMessage::verify_and_get_value::( + match StorageProofVerifier::::get_decoded_value::( &consensus_chain_state_root.into(), domain_state_root_proof, domain_state_root_key,